diff --git a/example/lib/main.dart b/example/lib/main.dart index 7df78ab..56b4fe5 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -58,8 +58,8 @@ class MyProfileService extends ProfileService { } @override - uploadImage() { - return super.uploadImage(); + uploadImage(context) { + return super.uploadImage(context); } } @@ -73,40 +73,38 @@ class MyProfileData extends ProfileData { int justMyString; @override - Map mapWidget(Function update) { + Map mapWidget(Function update, BuildContext context) { return { - 'justMyString': Container( + 'justMyString': SizedBox( height: 100, width: 300, child: Row( children: [ ElevatedButton( style: ElevatedButton.styleFrom( - primary: justMyString == 1 ? Colors.green : Colors.blue, + backgroundColor: justMyString == 1 ? Colors.green : Colors.blue, ), onPressed: () { justMyString = 1; update(); - print(justMyString); }, child: const Text('1'), ), const Spacer(), ElevatedButton( style: ElevatedButton.styleFrom( - primary: justMyString == 2 ? Colors.green : Colors.blue, + backgroundColor: justMyString == 2 ? Colors.green : Colors.blue, ), onPressed: () { justMyString = 2; update(); - print(justMyString); }, child: const Text('2'), ), const Spacer(), ElevatedButton( style: ElevatedButton.styleFrom( - primary: justMyString == 3 ? Colors.green : Colors.blue, + backgroundColor: justMyString == 3 ? Colors.green : Colors.blue, ), onPressed: () { justMyString = 3; @@ -117,7 +115,7 @@ class MyProfileData extends ProfileData { const Spacer(), ElevatedButton( style: ElevatedButton.styleFrom( - primary: justMyString == 4 ? Colors.green : Colors.blue, + backgroundColor: justMyString == 4 ? Colors.green : Colors.blue, ), onPressed: () { justMyString = 4; diff --git a/example/pubspec.lock b/example/pubspec.lock index a9c8f14..9668a4f 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -21,21 +21,14 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: @@ -56,7 +49,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -87,28 +80,28 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" profile: dependency: "direct main" description: @@ -127,7 +120,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -148,21 +141,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" vector_math: dependency: transitive description: diff --git a/lib/profile.dart b/lib/profile.dart index a5911f2..1a644a7 100644 --- a/lib/profile.dart +++ b/lib/profile.dart @@ -1,6 +1,9 @@ library profile; export 'src/widgets/profile/profile_page.dart'; +export 'src/widgets/profile/profile_style.dart'; +export 'src/widgets/avatar/avatar_style.dart'; export 'src/services/profile_service.dart'; export 'src/widgets/item_builder/item_builder.dart'; export 'src/models/user.dart'; +export 'src/widgets/item_builder/item_builder_options.dart'; diff --git a/lib/src/models/user.dart b/lib/src/models/user.dart index 76dbc09..83e62ff 100644 --- a/lib/src/models/user.dart +++ b/lib/src/models/user.dart @@ -2,8 +2,6 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:profile/profile.dart'; -import 'package:profile/src/widgets/item_builder/item_builder.dart'; -import 'package:profile/src/widgets/item_builder/item_builder_options.dart'; class User { String? firstName; @@ -44,7 +42,7 @@ abstract class ProfileData { Map toMap(); - Map mapWidget(Function update); + Map mapWidget(VoidCallback update, BuildContext context); ProfileData create(); @@ -58,12 +56,13 @@ abstract class ProfileData { }) { var widgets = []; ItemBuilder builder = ItemBuilder( - options: itemBuilderOptions ?? const ItemBuilderOptions(), + options: itemBuilderOptions ?? ItemBuilderOptions(), ); for (var item in items.entries) { itemBuilder == null ? widgets.add( builder.build( + item.key, item.value, typeMap[item.key], (value) { @@ -73,6 +72,7 @@ abstract class ProfileData { ) : widgets.add( itemBuilder.build( + item.key, item.value, typeMap[item.key], (value) { diff --git a/lib/src/services/profile_service.dart b/lib/src/services/profile_service.dart index a94a3e5..c1a35ce 100644 --- a/lib/src/services/profile_service.dart +++ b/lib/src/services/profile_service.dart @@ -1,12 +1,10 @@ +import 'package:flutter/material.dart'; import 'package:profile/profile.dart'; abstract class ProfileService { const ProfileService(); - deleteProfile() { - print("Request to delete profile"); - // TODO(anyone) project specific - } + deleteProfile() {} editProfile(User user, String key, String value) { if (user.profileData != null) { @@ -19,8 +17,5 @@ abstract class ProfileService { } } - uploadImage() { - print('Request to change picture'); - // TODO(anyone) open image picker and update profile - } + uploadImage(BuildContext context) async {} } diff --git a/lib/src/widgets/avatar/avatar.dart b/lib/src/widgets/avatar/avatar.dart index 0fb6896..8d885c6 100644 --- a/lib/src/widgets/avatar/avatar.dart +++ b/lib/src/widgets/avatar/avatar.dart @@ -7,13 +7,15 @@ class Avatar extends StatelessWidget { const Avatar({ Key? key, this.image, - this.name, + this.firstName, + this.lastName, this.avatar, this.style = const AvatarStyle(), }) : super(key: key); final Uint8List? image; - final String? name; + final String? firstName; + final String? lastName; final Widget? avatar; final AvatarStyle style; @@ -22,9 +24,12 @@ class Avatar extends StatelessWidget { return Column( children: [ _avatar(), - if (name != null) + const SizedBox( + height: 16, + ), + if (firstName != null || lastName != null) Text( - name!, + '${firstName ?? ''} ${lastName ?? ''}', style: style.displayNameStyle, ) ], @@ -39,22 +44,26 @@ class Avatar extends StatelessWidget { return Container( width: style.width, height: style.height, - decoration: const BoxDecoration( + decoration: BoxDecoration( shape: BoxShape.circle, + image: DecorationImage( + image: MemoryImage(image!), + fit: BoxFit.fill, + ), ), - child: Image.memory(image!), ); - } else if (name != null && name!.isNotEmpty) { + } else if (firstName != null || lastName != null) { return Container( width: style.width, height: style.height, decoration: BoxDecoration( - color: _generateColorWithIntials(name!), + color: _generateColorWithIntials(firstName, lastName), shape: BoxShape.circle, ), child: Center( child: Text( - _getInitials(name!), + style: const TextStyle(fontSize: 40), + _getInitials(firstName, lastName), ), ), ); @@ -67,15 +76,13 @@ class Avatar extends StatelessWidget { } } - String _getInitials(String name) { - var nameList = name.split(' '); - return nameList.first[0] + nameList.last[0]; + String _getInitials(String? firstName, String? lastName) { + return (firstName?[0] ?? '') + (lastName?[0] ?? ''); } - Color _generateColorWithIntials(String name) { - var nameList = name.split(' '); - var uniqueInitialId = nameList.first.toLowerCase().codeUnitAt(0) + - nameList.last.toLowerCase().codeUnitAt(0); + Color _generateColorWithIntials(String? firstName, String? lastName) { + var uniqueInitialId = (firstName?.toLowerCase().codeUnitAt(0) ?? 0) + + (lastName?.toLowerCase().codeUnitAt(0) ?? 0); return Colors.primaries[uniqueInitialId % Colors.primaries.length]; } diff --git a/lib/src/widgets/item_builder/item_builder.dart b/lib/src/widgets/item_builder/item_builder.dart index 10fab7c..f28bdf9 100644 --- a/lib/src/widgets/item_builder/item_builder.dart +++ b/lib/src/widgets/item_builder/item_builder.dart @@ -8,19 +8,42 @@ class ItemBuilder { final ItemBuilderOptions options; - Widget build(dynamic value, Widget? widget, Function(String) updateItem) { + Widget build( + String key, dynamic value, Widget? widget, Function(String) updateItem) { if (widget == null) { var controller = TextEditingController( - text: '$value', + text: '${value ?? ''}', ); - return TextField( - controller: controller, - decoration: options.inputDecoration, - readOnly: options.readOnly, - onSubmitted: (s) { - updateItem(s); - }, + late InputDecoration inputDecoration; + if (options.inputDecorationField != null && + options.inputDecorationField![key] != null) { + inputDecoration = options.inputDecorationField![key]!; + } else { + inputDecoration = options.inputDecoration; + } + + final formKey = GlobalKey(); + + return Form( + key: formKey, + child: TextFormField( + controller: controller, + decoration: inputDecoration, + readOnly: options.readOnly, + onFieldSubmitted: (value) { + if (formKey.currentState!.validate()) { + updateItem(value); + } + }, + validator: (value) { + if (options.validators != null && + options.validators![key] != null) { + return options.validators![key]!(value); + } + return null; + }, + ), ); } return widget; diff --git a/lib/src/widgets/item_builder/item_builder_options.dart b/lib/src/widgets/item_builder/item_builder_options.dart index 616c25d..2ab6356 100644 --- a/lib/src/widgets/item_builder/item_builder_options.dart +++ b/lib/src/widgets/item_builder/item_builder_options.dart @@ -1,11 +1,15 @@ import 'package:flutter/material.dart'; class ItemBuilderOptions { - const ItemBuilderOptions({ + ItemBuilderOptions({ this.inputDecoration = const InputDecoration(), + this.inputDecorationField, this.readOnly = false, + this.validators, }); final InputDecoration inputDecoration; + final Map? inputDecorationField; final bool readOnly; + final Map? validators; } diff --git a/lib/src/widgets/profile/profile_page.dart b/lib/src/widgets/profile/profile_page.dart index 6c20331..a87d1f1 100644 --- a/lib/src/widgets/profile/profile_page.dart +++ b/lib/src/widgets/profile/profile_page.dart @@ -1,9 +1,6 @@ import 'package:flutter/material.dart'; import 'package:profile/profile.dart'; import 'package:profile/src/widgets/avatar/avatar.dart'; -import 'package:profile/src/widgets/item_builder/item_builder_options.dart'; - -import 'package:profile/src/widgets/profile/profile_style.dart'; class ProfilePage extends StatefulWidget { const ProfilePage({ @@ -81,52 +78,50 @@ class ProfileWrapper extends StatefulWidget { } class _ProfileWrapperState extends State { - late List profileItems; List defaultItems = []; @override void initState() { super.initState(); - profileItems = widget.user.profileData!.buildItems( - widget.user.profileData!.toMap(), - widget.user.profileData!.mapWidget(() { - widget.rebuild(); - }), - widget.style.betweenDefaultItemPadding, - (key, value) { - widget.service.editProfile(widget.user, key, value); - }, - itemBuilder: widget.itemBuilder, - itemBuilderOptions: widget.itemBuilderOptions, - ); + if (widget.itemBuilder == null) { ItemBuilder builder = ItemBuilder( - options: widget.itemBuilderOptions ?? const ItemBuilderOptions(), + options: widget.itemBuilderOptions ?? ItemBuilderOptions(), ); - defaultItems.add(builder.build(widget.user.firstName, null, (v) { + defaultItems + .add(builder.build('firstName', widget.user.firstName, null, (v) { widget.user.firstName = v; + + widget.service.editProfile(widget.user, 'firstName', v); })); defaultItems.add( SizedBox( height: widget.style.betweenDefaultItemPadding, ), ); - defaultItems.add(builder.build(widget.user.lastName, null, (v) { + defaultItems + .add(builder.build('lastName', widget.user.lastName, null, (v) { widget.user.lastName = v; + + widget.service.editProfile(widget.user, 'lastName', v); })); } else { - defaultItems - .add(widget.itemBuilder!.build(widget.user.firstName, null, (v) { + defaultItems.add(widget.itemBuilder! + .build('firstName', widget.user.firstName, null, (v) { widget.user.firstName = v; + + widget.service.editProfile(widget.user, 'firstname', v); })); defaultItems.add( SizedBox( height: widget.style.betweenDefaultItemPadding, ), ); - defaultItems - .add(widget.itemBuilder!.build(widget.user.lastName, null, (v) { + defaultItems.add(widget.itemBuilder! + .build('lastName', widget.user.lastName, null, (v) { widget.user.lastName = v; + + widget.service.editProfile(widget.user, 'lastName', v); })); } } @@ -134,17 +129,19 @@ class _ProfileWrapperState extends State { @override Widget build(BuildContext context) { return Material( + color: Colors.transparent, child: Padding( padding: widget.style.pagePadding, child: Column( children: [ if (widget.showAvatar) InkWell( - onTap: () { - widget.service.uploadImage(); + onTap: () async { + await widget.service.uploadImage(context); }, child: Avatar( - name: '${widget.user.firstName} ${widget.user.lastName}', + firstName: widget.user.firstName, + lastName: widget.user.lastName, style: widget.style.avatarStyle, avatar: widget.customAvatar, image: widget.user.image, @@ -155,11 +152,26 @@ class _ProfileWrapperState extends State { height: widget.style.betweenDefaultItemPadding, ), ...defaultItems, - ...profileItems, + ...widget.user.profileData!.buildItems( + widget.user.profileData!.toMap(), + widget.user.profileData!.mapWidget( + () { + widget.rebuild(); + }, + context, + ), + widget.style.betweenDefaultItemPadding, + (key, value) { + widget.service.editProfile(widget.user, key, value); + }, + itemBuilder: widget.itemBuilder, + itemBuilderOptions: widget.itemBuilderOptions, + ), if (widget.showDeleteProfile) SizedBox( height: widget.style.betweenDefaultItemPadding, ), + const Spacer(), if (widget.showDeleteProfile) InkWell( onTap: () {