removed column

This commit is contained in:
Joons van Stuijvenberg 2022-12-02 10:40:16 +01:00
parent 3dca4489ba
commit 5b2757e19b
8 changed files with 243 additions and 258 deletions

View file

@ -24,4 +24,8 @@
## 1.0.1 ## 1.0.1
* Added a default wrap instead of column * Added a default wrap instead of column
## 1.0.2
* Added form key and more customizability for web

View file

@ -35,8 +35,8 @@ class ProfileExample extends StatefulWidget {
class _ProfileExampleState extends State<ProfileExample> { class _ProfileExampleState extends State<ProfileExample> {
late User _user; late User _user;
ProfileData profileData = ProfileData profileData = ExampleProfileData().fromMap(
ExampleProfileData().fromMap({'email': 'example@email.com'}); {'email': 'example@email.com', 'about': 'about', 'remarks': 'remarks'});
@override @override
void initState() { void initState() {
@ -53,55 +53,62 @@ class _ProfileExampleState extends State<ProfileExample> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
//get width and height
var width = MediaQuery.of(context).size.width;
return Scaffold( return Scaffold(
body: Center( body: Center(
child: ProfilePage( child: Column(
wrapViewOptions: children: [
WrapViewOptions(direction: Axis.vertical, spacing: 16), SizedBox(
bottomActionText: 'Log out', height: 400,
itemBuilderOptions: ItemBuilderOptions( width: 800,
inputDecorationField: { child: ProfilePage(
'first_name': const InputDecoration( showItems: false,
constraints: BoxConstraints(maxHeight: 70, maxWidth: 200), prioritizedItems: const ['remarks', 'about'],
label: Text('First name'), wrapViewOptions: WrapViewOptions(
direction: Axis.horizontal,
spacing: 16,
),
bottomActionText: 'Log out',
itemBuilderOptions: ItemBuilderOptions(
//no label for email
validators: {
'first_name': (String? value) {
if (value == null || value.isEmpty) {
return 'Field empty';
}
return null;
},
'last_name': (String? value) {
if (value == null || value.isEmpty) {
return 'Field empty';
}
return null;
},
'email': (String? value) {
if (value == null || value.isEmpty) {
return 'Field empty';
}
return null;
},
},
),
user: _user,
service: ExampleProfileService(),
style: ProfileStyle(
avatarTextStyle: const TextStyle(fontSize: 20),
pagePadding: EdgeInsets.only(
top: 50,
bottom: 50,
left: width * 0.1,
right: width * 0.1,
),
),
), ),
'last_name': const InputDecoration(
constraints: BoxConstraints(maxHeight: 70, maxWidth: 150),
label: Text('First name'),
),
},
validators: {
'first_name': (String? value) {
if (value == null || value.isEmpty) {
return 'Field empty';
}
return null;
},
'last_name': (String? value) {
if (value == null || value.isEmpty) {
return 'Field empty';
}
return null;
},
'email': (String? value) {
if (value == null || value.isEmpty) {
return 'Field empty';
}
return null;
},
},
),
user: _user,
service: ExampleProfileService(),
style: ProfileStyle(
avatarTextStyle: const TextStyle(fontSize: 20),
pagePadding: EdgeInsets.only(
top: 50,
bottom: 50,
left: MediaQuery.of(context).size.width * 0.1,
right: MediaQuery.of(context).size.width * 0.1,
), ),
), const Text('test')
],
), ),
), ),
); );
@ -127,7 +134,7 @@ class CustomItemBuilderExample extends ItemBuilder {
options.inputDecorationField?[key] ?? options.inputDecoration; options.inputDecorationField?[key] ?? options.inputDecoration;
var formFieldKey = GlobalKey<FormFieldState>(); var formFieldKey = GlobalKey<FormFieldState>();
return SizedBox( return SizedBox(
width: 300, width: 400,
child: TextFormField( child: TextFormField(
keyboardType: options.keyboardType?[key], keyboardType: options.keyboardType?[key],
key: formFieldKey, key: formFieldKey,

View file

@ -8,9 +8,13 @@ import 'package:flutter_profile/flutter_profile.dart';
class ExampleProfileData extends ProfileData { class ExampleProfileData extends ProfileData {
ExampleProfileData({ ExampleProfileData({
this.email, this.email,
this.about,
this.remarks,
}); });
String? email; String? email;
String? about;
String? remarks;
@override @override
Map<String, dynamic> mapWidget( Map<String, dynamic> mapWidget(
@ -19,6 +23,8 @@ class ExampleProfileData extends ProfileData {
) { ) {
return { return {
'email': null, 'email': null,
'about': null,
'remarks': null,
}; };
} }
@ -26,14 +32,14 @@ class ExampleProfileData extends ProfileData {
ProfileData fromMap(Map<String, dynamic> data) { ProfileData fromMap(Map<String, dynamic> data) {
return ExampleProfileData( return ExampleProfileData(
email: data['email'], email: data['email'],
about: data['about'],
remarks: data['remarks'],
); );
} }
@override @override
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {'email': email, 'about': about, 'remarks': remarks};
'email': email,
};
} }
@override @override

View file

@ -124,7 +124,7 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "1.0.1" version: "1.0.2"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter

View file

@ -6,81 +6,57 @@ import 'package:flutter/material.dart';
import 'package:flutter_profile/src/widgets/item_builder/item_builder.dart'; import 'package:flutter_profile/src/widgets/item_builder/item_builder.dart';
import 'package:flutter_profile/src/widgets/item_builder/item_builder_options.dart'; import 'package:flutter_profile/src/widgets/item_builder/item_builder_options.dart';
class ItemList extends StatefulWidget { class ItemList {
const ItemList( ItemList(
this.items, this.items,
this.typeMap, this.typeMap,
this.spacing,
this.updateProfile, this.updateProfile,
this.saveProfile, { this.saveProfile, {
required this.formKey, required this.formKey,
this.itemBuilder, this.itemBuilder,
this.itemBuilderOptions, this.itemBuilderOptions,
super.key, }) {
}); for (var item in items.entries) {
widgets.addAll({
item.key: itemBuilder == null
? builder.build(
item.key,
item.value,
typeMap[item.key],
(value) {
saveProfile();
},
(value) {
updateProfile(item.key, value);
},
)
: itemBuilder!.build(
item.key,
item.value,
typeMap[item.key],
(value) {
saveProfile();
},
(value) {
updateProfile(item.key, value);
},
)
});
}
}
Map<String, Widget> getItemList() => widgets;
final Map<String, dynamic> items; final Map<String, dynamic> items;
final Map<String, dynamic> typeMap; final Map<String, dynamic> typeMap;
final double spacing;
final Function(String, String?) updateProfile; final Function(String, String?) updateProfile;
final Function() saveProfile; final Function() saveProfile;
final ItemBuilder? itemBuilder; final ItemBuilder? itemBuilder;
final ItemBuilderOptions? itemBuilderOptions; final ItemBuilderOptions? itemBuilderOptions;
final GlobalKey<FormState> formKey; final GlobalKey<FormState> formKey;
@override Map<String, Widget> widgets = {};
State<ItemList> createState() => _ItemListState();
}
class _ItemListState extends State<ItemList> {
var widgets = <Widget>[];
late ItemBuilder builder = ItemBuilder( late ItemBuilder builder = ItemBuilder(
options: widget.itemBuilderOptions ?? ItemBuilderOptions(), options: itemBuilderOptions ?? ItemBuilderOptions(),
); );
@override
void initState() {
super.initState();
for (var item in widget.items.entries) {
widget.itemBuilder == null
? widgets.add(
builder.build(
item.key,
item.value,
widget.typeMap[item.key],
(value) {
widget.saveProfile();
},
(value) {
widget.updateProfile(item.key, value);
},
),
)
: widgets.add(
widget.itemBuilder!.build(
item.key,
item.value,
widget.typeMap[item.key],
(value) {
widget.saveProfile();
},
(value) {
widget.updateProfile(item.key, value);
},
),
);
widgets.add(SizedBox(
height: widget.spacing,
));
}
}
@override
Widget build(BuildContext context) {
return Column(
children: widgets,
);
}
} }

View file

@ -35,6 +35,7 @@ class ProfilePage extends StatefulWidget {
this.style = const ProfileStyle(), this.style = const ProfileStyle(),
this.customAvatar, this.customAvatar,
this.showAvatar = true, this.showAvatar = true,
this.showItems = true,
this.itemBuilder, this.itemBuilder,
this.itemBuilderOptions, this.itemBuilderOptions,
this.bottomActionText, this.bottomActionText,
@ -42,6 +43,8 @@ class ProfilePage extends StatefulWidget {
this.showDefaultItems = true, this.showDefaultItems = true,
this.wrapItemsBuilder, this.wrapItemsBuilder,
this.wrapViewOptions, this.wrapViewOptions,
this.extraWidgets,
this.formKey,
}) : super(key: key); }) : super(key: key);
/// User containing all the user data. /// User containing all the user data.
@ -59,6 +62,9 @@ class ProfilePage extends StatefulWidget {
/// Whether to show the users avatar. /// Whether to show the users avatar.
final bool showAvatar; final bool showAvatar;
/// Whether you want to show the input fields, sometimes you just want to edit the avatar.
final bool showItems;
/// Sets the text for the [InkWell] at the bottom of the profile page. The [InkWell] is disabled when null. /// Sets the text for the [InkWell] at the bottom of the profile page. The [InkWell] is disabled when null.
final String? bottomActionText; final String? bottomActionText;
@ -80,6 +86,12 @@ class ProfilePage extends StatefulWidget {
/// Edit the direction and spacing between every item /// Edit the direction and spacing between every item
final WrapViewOptions? wrapViewOptions; final WrapViewOptions? wrapViewOptions;
/// The map of extra widgets that might want to be added like empty SizedBoxes for styling.
final Map<String, Widget>? extraWidgets;
/// Use the form key to save on any custom callback
final GlobalKey<FormState>? formKey;
@override @override
State<ProfilePage> createState() => _ProfilePageState(); State<ProfilePage> createState() => _ProfilePageState();
} }
@ -96,6 +108,7 @@ class _ProfilePageState extends State<ProfilePage> {
style: widget.style, style: widget.style,
customAvatar: widget.customAvatar, customAvatar: widget.customAvatar,
showAvatar: widget.showAvatar, showAvatar: widget.showAvatar,
showItems: widget.showItems,
bottomActionText: widget.bottomActionText, bottomActionText: widget.bottomActionText,
itemBuilder: widget.itemBuilder, itemBuilder: widget.itemBuilder,
itemBuilderOptions: widget.itemBuilderOptions, itemBuilderOptions: widget.itemBuilderOptions,
@ -103,15 +116,22 @@ class _ProfilePageState extends State<ProfilePage> {
showDefaultItems: widget.showDefaultItems, showDefaultItems: widget.showDefaultItems,
wrapItemsBuilder: widget.wrapItemsBuilder, wrapItemsBuilder: widget.wrapItemsBuilder,
wrapViewOptions: widget.wrapViewOptions, wrapViewOptions: widget.wrapViewOptions,
extraWidgets: widget.extraWidgets,
formKey: widget.formKey,
); );
} }
} }
class WrapViewOptions { class WrapViewOptions {
WrapViewOptions( WrapViewOptions(
{this.direction, this.spacing, this.runSpacing, this.clipBehavior}); {this.direction,
this.spacing,
this.wrapAlignment,
this.runSpacing,
this.clipBehavior});
Axis? direction; Axis? direction;
double? spacing; double? spacing;
double? runSpacing; double? runSpacing;
Clip? clipBehavior; Clip? clipBehavior;
WrapAlignment? wrapAlignment;
} }

View file

@ -26,7 +26,10 @@ class ProfileWrapper extends StatefulWidget {
this.bottomActionText, this.bottomActionText,
this.prioritizedItems = const [], this.prioritizedItems = const [],
this.showDefaultItems = true, this.showDefaultItems = true,
this.showItems = true,
this.wrapItemsBuilder, this.wrapItemsBuilder,
this.formKey,
this.extraWidgets,
super.key, super.key,
}); });
@ -41,7 +44,10 @@ class ProfileWrapper extends StatefulWidget {
final Function rebuild; final Function rebuild;
final ItemBuilderOptions? itemBuilderOptions; final ItemBuilderOptions? itemBuilderOptions;
final bool showDefaultItems; final bool showDefaultItems;
final bool showItems;
final Widget Function(BuildContext context, Widget child)? wrapItemsBuilder; final Widget Function(BuildContext context, Widget child)? wrapItemsBuilder;
final Map<String, Widget>? extraWidgets;
final GlobalKey<FormState>? formKey;
/// Map keys of items that should be shown first before the default items and the rest of the items. /// Map keys of items that should be shown first before the default items and the rest of the items.
final List<String> prioritizedItems; final List<String> prioritizedItems;
@ -51,161 +57,128 @@ class ProfileWrapper extends StatefulWidget {
} }
class _ProfileWrapperState extends State<ProfileWrapper> { class _ProfileWrapperState extends State<ProfileWrapper> {
List<Widget> defaultItems = []; late Map<String, Widget> widgets = {};
GlobalKey<FormState> formKey = GlobalKey<FormState>(); Map<String, Widget> defaultItems = {};
late GlobalKey<FormState> _formKey;
Map<String, dynamic> formValues = {}; Map<String, dynamic> formValues = {};
bool _isUploadingImage = false; bool _isUploadingImage = false;
late final Widget child; late final Widget child;
@override @override
void initState() { void initState() {
_formKey = widget.formKey ?? GlobalKey<FormState>();
super.initState(); super.initState();
if (widget.itemBuilder == null) { if (widget.showDefaultItems) {
ItemBuilder builder = ItemBuilder( if (widget.itemBuilder == null) {
options: widget.itemBuilderOptions ?? ItemBuilderOptions(), ItemBuilder builder = ItemBuilder(
); options: widget.itemBuilderOptions ?? ItemBuilderOptions(),
defaultItems.add( );
builder.build( defaultItems.addAll({
'first_name', 'first_name': builder.build(
widget.user.firstName, 'first_name',
null, widget.user.firstName,
(value) { null,
submitAllChangedFields(); (value) {
}, submitAllChangedFields();
(v) { },
if (widget.user.firstName != v) { (v) {
widget.user.firstName = v; if (widget.user.firstName != v) {
widget.service.editProfile(widget.user, 'first_name', v); widget.user.firstName = v;
} widget.service.editProfile(widget.user, 'first_name', v);
}, }
), },
); ),
defaultItems.add( 'last_name': builder.build(
SizedBox( 'last_name',
height: widget.style.betweenDefaultItemPadding, widget.user.lastName,
), null,
); (value) {
defaultItems.add( submitAllChangedFields();
builder.build( },
'last_name', (v) {
widget.user.lastName, if (widget.user.lastName != v) {
null, widget.user.lastName = v;
(value) { widget.service.editProfile(widget.user, 'last_name', v);
submitAllChangedFields(); }
}, },
(v) { ),
if (widget.user.lastName != v) { });
widget.user.lastName = v; } else {
widget.service.editProfile(widget.user, 'last_name', v); defaultItems.addAll({
} 'first_name': widget.itemBuilder!.build(
}, 'first_name',
), widget.user.firstName,
); null,
defaultItems.add( (value) {
SizedBox( submitAllChangedFields();
height: widget.style.betweenDefaultItemPadding, },
), (v) {
); if (widget.user.firstName != v) {
} else { widget.user.firstName = v;
defaultItems.add( widget.service.editProfile(widget.user, 'first_name', v);
widget.itemBuilder!.build( }
'first_name', },
widget.user.firstName, ),
null, 'last_name': widget.itemBuilder!.build(
(value) { 'last_name',
submitAllChangedFields(); widget.user.lastName,
}, null,
(v) { (value) {
if (widget.user.firstName != v) { submitAllChangedFields();
widget.user.firstName = v; },
widget.service.editProfile(widget.user, 'first_name', v); (v) {
} if (widget.user.lastName != v) {
}, widget.user.lastName = v;
), widget.service.editProfile(widget.user, 'last_name', v);
); }
defaultItems.add( },
SizedBox( ),
height: widget.style.betweenDefaultItemPadding, });
), }
);
defaultItems.add(
widget.itemBuilder!.build(
'last_name',
widget.user.lastName,
null,
(value) {
submitAllChangedFields();
},
(v) {
if (widget.user.lastName != v) {
widget.user.lastName = v;
widget.service.editProfile(widget.user, 'last_name', v);
}
},
),
);
defaultItems.add(
SizedBox(
height: widget.style.betweenDefaultItemPadding,
),
);
} }
widgets.addAll(widget.extraWidgets ?? {});
widgets.addAll(defaultItems);
widgets.addAll(ItemList(
Map.fromEntries(widget.user.profileData!.toMap().entries),
widget.user.profileData!.mapWidget(
() {
widget.rebuild();
},
context,
),
(key, value) {
if (widget.user.toMap()['profile_data'][key] == null) {
widget.service.editProfile(widget.user, key, value);
} else if (widget.user.toMap()['profile_data'][key] != value) {
widget.service.editProfile(widget.user, key, value);
}
},
() {
submitAllChangedFields();
},
itemBuilder: widget.itemBuilder,
itemBuilderOptions: widget.itemBuilderOptions,
formKey: _formKey,
).getItemList());
var items = Wrap( var items = Wrap(
alignment: widget.wrapViewOptions?.wrapAlignment ?? WrapAlignment.start,
direction: widget.wrapViewOptions?.direction ?? Axis.vertical, direction: widget.wrapViewOptions?.direction ?? Axis.vertical,
spacing: widget.wrapViewOptions?.spacing ?? 0, spacing: widget.wrapViewOptions?.spacing ?? 0,
runSpacing: widget.wrapViewOptions?.runSpacing ?? 0, runSpacing: widget.wrapViewOptions?.runSpacing ?? 0,
clipBehavior: widget.wrapViewOptions?.clipBehavior ?? Clip.none, clipBehavior: widget.wrapViewOptions?.clipBehavior ?? Clip.none,
children: [ children: [
ItemList( //add all items with prio then those without
Map.fromEntries(widget.user.profileData!.toMap().entries.where( for (var key in widget.prioritizedItems)
(element) => widget.prioritizedItems.contains(element.key))), // get values from widgets with this key
widget.user.profileData!.mapWidget( ...widgets.entries
() { .where((element) => element.key == key)
widget.rebuild(); .map((e) => e.value),
},
context, ...widgets.entries
), .where((element) => !widget.prioritizedItems.contains(element.key))
widget.style.betweenDefaultItemPadding, .map((e) => e.value),
(key, value) {
if (widget.user.toMap()['profile_data'][key] == null) {
widget.service.editProfile(widget.user, key, value);
} else if (widget.user.toMap()['profile_data'][key] != value) {
widget.service.editProfile(widget.user, key, value);
}
},
() {
submitAllChangedFields();
},
itemBuilder: widget.itemBuilder,
itemBuilderOptions: widget.itemBuilderOptions,
formKey: formKey,
),
if (widget.showDefaultItems) ...defaultItems,
// remove all the items that have priority from the widget.user.profileData!.toMap()
ItemList(
Map.fromEntries(widget.user.profileData!.toMap().entries.where(
(element) => !widget.prioritizedItems.contains(element.key))),
widget.user.profileData!.mapWidget(
() {
widget.rebuild();
},
context,
),
widget.style.betweenDefaultItemPadding,
(key, value) {
if (widget.user.toMap()['profile_data'][key] == null) {
widget.service.editProfile(widget.user, key, value);
} else if (widget.user.toMap()['profile_data'][key] != value) {
widget.service.editProfile(widget.user, key, value);
}
},
() {
submitAllChangedFields();
},
itemBuilder: widget.itemBuilder,
itemBuilderOptions: widget.itemBuilderOptions,
formKey: formKey,
),
], ],
); );
@ -250,8 +223,7 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
height: widget.style.betweenDefaultItemPadding, height: widget.style.betweenDefaultItemPadding,
), ),
], ],
// all the items that have priority above the default items if (widget.showItems) Form(key: _formKey, child: child),
Form(key: formKey, child: child),
if (widget.bottomActionText != null) ...[ if (widget.bottomActionText != null) ...[
SizedBox( SizedBox(
height: widget.style.betweenDefaultItemPadding, height: widget.style.betweenDefaultItemPadding,
@ -280,8 +252,8 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
/// This calls onSaved on all the fiels which check if they have a new value /// This calls onSaved on all the fiels which check if they have a new value
void submitAllChangedFields() { void submitAllChangedFields() {
if (formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
formKey.currentState!.save(); _formKey.currentState!.save();
} }
} }
} }

View file

@ -1,6 +1,6 @@
name: flutter_profile name: flutter_profile
description: Flutter profile package description: Flutter profile package
version: 1.0.1 version: 1.0.2
repository: https://github.com/Iconica-Development/flutter_profile repository: https://github.com/Iconica-Development/flutter_profile
environment: environment: