first test version

Functionality has been added and is working. No test or documentation has been written yet.
This commit is contained in:
Jacques Doeleman 2022-09-19 12:11:58 +02:00
parent ce4054f478
commit 38b076416f
9 changed files with 129 additions and 94 deletions

View file

@ -58,8 +58,8 @@ class MyProfileService extends ProfileService {
} }
@override @override
uploadImage() { uploadImage(context) {
return super.uploadImage(); return super.uploadImage(context);
} }
} }
@ -73,40 +73,38 @@ class MyProfileData extends ProfileData {
int justMyString; int justMyString;
@override @override
Map<String, dynamic> mapWidget(Function update) { Map<String, dynamic> mapWidget(Function update, BuildContext context) {
return { return {
'justMyString': Container( 'justMyString': SizedBox(
height: 100, height: 100,
width: 300, width: 300,
child: Row( child: Row(
children: [ children: [
ElevatedButton( ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
primary: justMyString == 1 ? Colors.green : Colors.blue, backgroundColor: justMyString == 1 ? Colors.green : Colors.blue,
), ),
onPressed: () { onPressed: () {
justMyString = 1; justMyString = 1;
update(); update();
print(justMyString);
}, },
child: const Text('1'), child: const Text('1'),
), ),
const Spacer(), const Spacer(),
ElevatedButton( ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
primary: justMyString == 2 ? Colors.green : Colors.blue, backgroundColor: justMyString == 2 ? Colors.green : Colors.blue,
), ),
onPressed: () { onPressed: () {
justMyString = 2; justMyString = 2;
update(); update();
print(justMyString);
}, },
child: const Text('2'), child: const Text('2'),
), ),
const Spacer(), const Spacer(),
ElevatedButton( ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
primary: justMyString == 3 ? Colors.green : Colors.blue, backgroundColor: justMyString == 3 ? Colors.green : Colors.blue,
), ),
onPressed: () { onPressed: () {
justMyString = 3; justMyString = 3;
@ -117,7 +115,7 @@ class MyProfileData extends ProfileData {
const Spacer(), const Spacer(),
ElevatedButton( ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
primary: justMyString == 4 ? Colors.green : Colors.blue, backgroundColor: justMyString == 4 ? Colors.green : Colors.blue,
), ),
onPressed: () { onPressed: () {
justMyString = 4; justMyString = 4;

View file

@ -7,7 +7,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.2" version: "2.9.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -21,21 +21,14 @@ packages:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -56,7 +49,7 @@ packages:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.3.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -87,28 +80,28 @@ packages:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.11" version: "0.12.12"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.4" version: "0.1.5"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.0" version: "1.8.0"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.8.2"
profile: profile:
dependency: "direct main" dependency: "direct main"
description: description:
@ -127,7 +120,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.2" version: "1.9.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -148,21 +141,21 @@ packages:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.9" version: "0.4.12"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:

View file

@ -1,6 +1,9 @@
library profile; library profile;
export 'src/widgets/profile/profile_page.dart'; 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/services/profile_service.dart';
export 'src/widgets/item_builder/item_builder.dart'; export 'src/widgets/item_builder/item_builder.dart';
export 'src/models/user.dart'; export 'src/models/user.dart';
export 'src/widgets/item_builder/item_builder_options.dart';

View file

@ -2,8 +2,6 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:profile/profile.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<T extends ProfileData> { class User<T extends ProfileData> {
String? firstName; String? firstName;
@ -44,7 +42,7 @@ abstract class ProfileData {
Map<String, dynamic> toMap(); Map<String, dynamic> toMap();
Map<String, dynamic> mapWidget(Function update); Map<String, dynamic> mapWidget(VoidCallback update, BuildContext context);
ProfileData create(); ProfileData create();
@ -58,12 +56,13 @@ abstract class ProfileData {
}) { }) {
var widgets = <Widget>[]; var widgets = <Widget>[];
ItemBuilder builder = ItemBuilder( ItemBuilder builder = ItemBuilder(
options: itemBuilderOptions ?? const ItemBuilderOptions(), options: itemBuilderOptions ?? ItemBuilderOptions(),
); );
for (var item in items.entries) { for (var item in items.entries) {
itemBuilder == null itemBuilder == null
? widgets.add( ? widgets.add(
builder.build( builder.build(
item.key,
item.value, item.value,
typeMap[item.key], typeMap[item.key],
(value) { (value) {
@ -73,6 +72,7 @@ abstract class ProfileData {
) )
: widgets.add( : widgets.add(
itemBuilder.build( itemBuilder.build(
item.key,
item.value, item.value,
typeMap[item.key], typeMap[item.key],
(value) { (value) {

View file

@ -1,12 +1,10 @@
import 'package:flutter/material.dart';
import 'package:profile/profile.dart'; import 'package:profile/profile.dart';
abstract class ProfileService { abstract class ProfileService {
const ProfileService(); const ProfileService();
deleteProfile() { deleteProfile() {}
print("Request to delete profile");
// TODO(anyone) project specific
}
editProfile<T extends ProfileData>(User user, String key, String value) { editProfile<T extends ProfileData>(User user, String key, String value) {
if (user.profileData != null) { if (user.profileData != null) {
@ -19,8 +17,5 @@ abstract class ProfileService {
} }
} }
uploadImage() { uploadImage(BuildContext context) async {}
print('Request to change picture');
// TODO(anyone) open image picker and update profile
}
} }

View file

@ -7,13 +7,15 @@ class Avatar extends StatelessWidget {
const Avatar({ const Avatar({
Key? key, Key? key,
this.image, this.image,
this.name, this.firstName,
this.lastName,
this.avatar, this.avatar,
this.style = const AvatarStyle(), this.style = const AvatarStyle(),
}) : super(key: key); }) : super(key: key);
final Uint8List? image; final Uint8List? image;
final String? name; final String? firstName;
final String? lastName;
final Widget? avatar; final Widget? avatar;
final AvatarStyle style; final AvatarStyle style;
@ -22,9 +24,12 @@ class Avatar extends StatelessWidget {
return Column( return Column(
children: [ children: [
_avatar(), _avatar(),
if (name != null) const SizedBox(
height: 16,
),
if (firstName != null || lastName != null)
Text( Text(
name!, '${firstName ?? ''} ${lastName ?? ''}',
style: style.displayNameStyle, style: style.displayNameStyle,
) )
], ],
@ -39,22 +44,26 @@ class Avatar extends StatelessWidget {
return Container( return Container(
width: style.width, width: style.width,
height: style.height, height: style.height,
decoration: const BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, 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( return Container(
width: style.width, width: style.width,
height: style.height, height: style.height,
decoration: BoxDecoration( decoration: BoxDecoration(
color: _generateColorWithIntials(name!), color: _generateColorWithIntials(firstName, lastName),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Center( child: Center(
child: Text( child: Text(
_getInitials(name!), style: const TextStyle(fontSize: 40),
_getInitials(firstName, lastName),
), ),
), ),
); );
@ -67,15 +76,13 @@ class Avatar extends StatelessWidget {
} }
} }
String _getInitials(String name) { String _getInitials(String? firstName, String? lastName) {
var nameList = name.split(' '); return (firstName?[0] ?? '') + (lastName?[0] ?? '');
return nameList.first[0] + nameList.last[0];
} }
Color _generateColorWithIntials(String name) { Color _generateColorWithIntials(String? firstName, String? lastName) {
var nameList = name.split(' '); var uniqueInitialId = (firstName?.toLowerCase().codeUnitAt(0) ?? 0) +
var uniqueInitialId = nameList.first.toLowerCase().codeUnitAt(0) + (lastName?.toLowerCase().codeUnitAt(0) ?? 0);
nameList.last.toLowerCase().codeUnitAt(0);
return Colors.primaries[uniqueInitialId % Colors.primaries.length]; return Colors.primaries[uniqueInitialId % Colors.primaries.length];
} }

View file

@ -8,19 +8,42 @@ class ItemBuilder {
final ItemBuilderOptions options; 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) { if (widget == null) {
var controller = TextEditingController( var controller = TextEditingController(
text: '$value', text: '${value ?? ''}',
); );
return TextField( late InputDecoration inputDecoration;
controller: controller, if (options.inputDecorationField != null &&
decoration: options.inputDecoration, options.inputDecorationField![key] != null) {
readOnly: options.readOnly, inputDecoration = options.inputDecorationField![key]!;
onSubmitted: (s) { } else {
updateItem(s); inputDecoration = options.inputDecoration;
}, }
final formKey = GlobalKey<FormState>();
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; return widget;

View file

@ -1,11 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ItemBuilderOptions { class ItemBuilderOptions {
const ItemBuilderOptions({ ItemBuilderOptions({
this.inputDecoration = const InputDecoration(), this.inputDecoration = const InputDecoration(),
this.inputDecorationField,
this.readOnly = false, this.readOnly = false,
this.validators,
}); });
final InputDecoration inputDecoration; final InputDecoration inputDecoration;
final Map<String, InputDecoration>? inputDecorationField;
final bool readOnly; final bool readOnly;
final Map<String, String? Function(String?)>? validators;
} }

View file

@ -1,9 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:profile/profile.dart'; import 'package:profile/profile.dart';
import 'package:profile/src/widgets/avatar/avatar.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 { class ProfilePage extends StatefulWidget {
const ProfilePage({ const ProfilePage({
@ -81,52 +78,50 @@ class ProfileWrapper extends StatefulWidget {
} }
class _ProfileWrapperState extends State<ProfileWrapper> { class _ProfileWrapperState extends State<ProfileWrapper> {
late List<Widget> profileItems;
List<Widget> defaultItems = []; List<Widget> defaultItems = [];
@override @override
void initState() { void initState() {
super.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) { if (widget.itemBuilder == null) {
ItemBuilder builder = ItemBuilder( 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.user.firstName = v;
widget.service.editProfile(widget.user, 'firstName', v);
})); }));
defaultItems.add( defaultItems.add(
SizedBox( SizedBox(
height: widget.style.betweenDefaultItemPadding, 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.user.lastName = v;
widget.service.editProfile(widget.user, 'lastName', v);
})); }));
} else { } else {
defaultItems defaultItems.add(widget.itemBuilder!
.add(widget.itemBuilder!.build(widget.user.firstName, null, (v) { .build('firstName', widget.user.firstName, null, (v) {
widget.user.firstName = v; widget.user.firstName = v;
widget.service.editProfile(widget.user, 'firstname', v);
})); }));
defaultItems.add( defaultItems.add(
SizedBox( SizedBox(
height: widget.style.betweenDefaultItemPadding, height: widget.style.betweenDefaultItemPadding,
), ),
); );
defaultItems defaultItems.add(widget.itemBuilder!
.add(widget.itemBuilder!.build(widget.user.lastName, null, (v) { .build('lastName', widget.user.lastName, null, (v) {
widget.user.lastName = v; widget.user.lastName = v;
widget.service.editProfile(widget.user, 'lastName', v);
})); }));
} }
} }
@ -134,17 +129,19 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return Material(
color: Colors.transparent,
child: Padding( child: Padding(
padding: widget.style.pagePadding, padding: widget.style.pagePadding,
child: Column( child: Column(
children: [ children: [
if (widget.showAvatar) if (widget.showAvatar)
InkWell( InkWell(
onTap: () { onTap: () async {
widget.service.uploadImage(); await widget.service.uploadImage(context);
}, },
child: Avatar( child: Avatar(
name: '${widget.user.firstName} ${widget.user.lastName}', firstName: widget.user.firstName,
lastName: widget.user.lastName,
style: widget.style.avatarStyle, style: widget.style.avatarStyle,
avatar: widget.customAvatar, avatar: widget.customAvatar,
image: widget.user.image, image: widget.user.image,
@ -155,11 +152,26 @@ class _ProfileWrapperState extends State<ProfileWrapper> {
height: widget.style.betweenDefaultItemPadding, height: widget.style.betweenDefaultItemPadding,
), ),
...defaultItems, ...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) if (widget.showDeleteProfile)
SizedBox( SizedBox(
height: widget.style.betweenDefaultItemPadding, height: widget.style.betweenDefaultItemPadding,
), ),
const Spacer(),
if (widget.showDeleteProfile) if (widget.showDeleteProfile)
InkWell( InkWell(
onTap: () { onTap: () {