diff --git a/packages/espressocash_app/lib/features/profile/data/profile_repository.dart b/packages/espressocash_app/lib/features/profile/data/profile_repository.dart index b055e9d8c7..2e1e28ac49 100644 --- a/packages/espressocash_app/lib/features/profile/data/profile_repository.dart +++ b/packages/espressocash_app/lib/features/profile/data/profile_repository.dart @@ -1,3 +1,4 @@ +import 'package:dfunc/dfunc.dart'; import 'package:flutter/foundation.dart'; import 'package:injectable/injectable.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -11,12 +12,27 @@ class ProfileRepository extends ChangeNotifier { final SharedPreferences _sharedPreferences; bool get hasAllRequiredFields => - firstName.isNotEmpty && email.isNotEmpty && country != null; + firstName.isNotEmpty && + lastName.isNotEmpty && + email.isNotEmpty && + country != null; - String get firstName => _sharedPreferences.getString(nameKey) ?? ''; + String get fullName => '$firstName $lastName'; + + String get initials => + (substring(firstName, 0, 1) + substring(lastName, 0, 1)).toUpperCase(); + + String get firstName => _sharedPreferences.getString(firstNameKey) ?? ''; set firstName(String value) { - _sharedPreferences.setString(nameKey, value); + _sharedPreferences.setString(firstNameKey, value); + notifyListeners(); + } + + String get lastName => _sharedPreferences.getString(lastNameKey) ?? ''; + + set lastName(String value) { + _sharedPreferences.setString(lastNameKey, value); notifyListeners(); } @@ -53,7 +69,8 @@ class ProfileRepository extends ChangeNotifier { @disposeMethod void dispose() { _sharedPreferences - ..remove(nameKey) + ..remove(firstNameKey) + ..remove(lastNameKey) ..remove(photoKey) ..remove(countryKey) ..remove(emailKey); @@ -61,7 +78,8 @@ class ProfileRepository extends ChangeNotifier { } } -const nameKey = 'name'; +const firstNameKey = 'name'; +const lastNameKey = 'lastName'; const photoKey = 'photo'; const countryKey = 'country'; const emailKey = 'email'; diff --git a/packages/espressocash_app/lib/features/profile/screens/manage_profile_screen.dart b/packages/espressocash_app/lib/features/profile/screens/manage_profile_screen.dart index 11fb00cb8b..7688700fae 100644 --- a/packages/espressocash_app/lib/features/profile/screens/manage_profile_screen.dart +++ b/packages/espressocash_app/lib/features/profile/screens/manage_profile_screen.dart @@ -29,18 +29,21 @@ class ManageProfileScreen extends StatefulWidget { const ManageProfileScreen({ super.key, required this.onSubmitted, + this.hasBackButton = true, }); static const route = ManageProfileRoute.new; final VoidCallback onSubmitted; + final bool hasBackButton; @override State createState() => _ManageProfileScreenState(); } class _ManageProfileScreenState extends State { - final _nameController = TextEditingController(); + final _firstNameController = TextEditingController(); + final _lastNameController = TextEditingController(); final _emailController = TextEditingController(); Country? _country; File? _photo; @@ -51,7 +54,8 @@ class _ManageProfileScreenState extends State { final repository = sl(); - _nameController.text = repository.firstName; + _firstNameController.text = repository.firstName; + _lastNameController.text = repository.lastName; _emailController.text = repository.email; _photo = repository.photoPath?.let(File.new); @@ -64,7 +68,8 @@ class _ManageProfileScreenState extends State { @override void dispose() { - _nameController.dispose(); + _firstNameController.dispose(); + _lastNameController.dispose(); _emailController.dispose(); super.dispose(); } @@ -86,7 +91,8 @@ class _ManageProfileScreenState extends State { } sl() - ..firstName = _nameController.text + ..firstName = _firstNameController.text + ..lastName = _lastNameController.text ..country = _country?.code ..photoPath = photo?.path ..email = _emailController.text; @@ -97,7 +103,8 @@ class _ManageProfileScreenState extends State { ); bool get _isValid => - _nameController.text.isNotEmpty && + _firstNameController.text.isNotEmpty && + _lastNameController.text.isNotEmpty && _emailController.text.isValidEmail && _country != null; @@ -105,14 +112,18 @@ class _ManageProfileScreenState extends State { Widget build(BuildContext context) => CpTheme.black( child: Scaffold( appBar: CpAppBar( - leading: CpBackButton( - onPressed: () => context.router.pop(), - ), + leading: widget.hasBackButton + ? CpBackButton(onPressed: () => context.router.pop()) + : null, ), extendBodyBehindAppBar: true, body: OnboardingScreen( footer: ListenableBuilder( - listenable: Listenable.merge([_nameController, _emailController]), + listenable: Listenable.merge([ + _firstNameController, + _lastNameController, + _emailController, + ]), builder: (context, child) => OnboardingFooterButton( text: context.l10n.save, onPressed: _isValid ? _handleSubmitted : null, @@ -122,7 +133,6 @@ class _ManageProfileScreenState extends State { SizedBox(height: MediaQuery.paddingOf(context).top + 24), ProfileImagePicker( photo: _photo, - label: context.l10n.uploadPhoto, onChanged: (File? value) => setState(() => _photo = value), ), const SizedBox(height: 32), @@ -135,11 +145,30 @@ class _ManageProfileScreenState extends State { vertical: 16, ), placeholder: context.l10n.yourFirstNamePlaceholder, - controller: _nameController, + controller: _firstNameController, + textColor: Colors.white, + placeholderColor: _placeholderTextColor, + backgroundColor: CpColors.blackTextFieldBackgroundColor, + fontSize: 16, + inputType: TextInputType.name, + textCapitalization: TextCapitalization.words, + ), + ), + OnboardingPadding( + child: CpTextField( + margin: const EdgeInsets.only(top: 16), + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 16, + ), + placeholder: context.l10n.yourLastNamePlaceholder, + controller: _lastNameController, textColor: Colors.white, placeholderColor: _placeholderTextColor, backgroundColor: CpColors.blackTextFieldBackgroundColor, fontSize: 16, + inputType: TextInputType.name, + textCapitalization: TextCapitalization.words, ), ), OnboardingPadding( @@ -155,6 +184,7 @@ class _ManageProfileScreenState extends State { placeholderColor: _placeholderTextColor, backgroundColor: CpColors.blackTextFieldBackgroundColor, fontSize: 16, + inputType: TextInputType.emailAddress, ), ), const SizedBox(height: 12), diff --git a/packages/espressocash_app/lib/features/profile/screens/profile_screen.dart b/packages/espressocash_app/lib/features/profile/screens/profile_screen.dart index 4fba8e6752..19f60d7967 100644 --- a/packages/espressocash_app/lib/features/profile/screens/profile_screen.dart +++ b/packages/espressocash_app/lib/features/profile/screens/profile_screen.dart @@ -65,8 +65,8 @@ class ProfileScreen extends StatelessWidget { (it) => FileImage(File(it)), ), userName: sl() - .firstName - .orDefault, + .initials + .ifEmpty(() => 'MW'), ), ), ), @@ -86,7 +86,9 @@ class ProfileScreen extends StatelessWidget { child: ListenableBuilder( listenable: sl(), builder: (context, child) => Text( - sl().firstName.orDefault, + sl() + .fullName + .ifEmpty(() => 'My Wallet'), style: Theme.of(context).textTheme.displaySmall, ), ), @@ -124,10 +126,6 @@ class ProfileScreen extends StatelessWidget { } } -extension on String { - String get orDefault => ifEmpty(() => 'My Wallet'); -} - const double _buttonSpacing = 22; const double _imageSize = 88; diff --git a/packages/espressocash_app/lib/features/profile/widgets/pick_profile_picture.dart b/packages/espressocash_app/lib/features/profile/widgets/pick_profile_picture.dart index 9c22db0e9b..e8c45e8284 100644 --- a/packages/espressocash_app/lib/features/profile/widgets/pick_profile_picture.dart +++ b/packages/espressocash_app/lib/features/profile/widgets/pick_profile_picture.dart @@ -10,17 +10,11 @@ class ProfileImagePicker extends StatefulWidget { const ProfileImagePicker({ super.key, required this.onChanged, - required this.label, - this.onLabelClicked, - this.labelStyle, this.photo, }); - final TextStyle? labelStyle; final ValueSetter onChanged; - final VoidCallback? onLabelClicked; final File? photo; - final String label; @override State createState() => _ProfileImagePickerState(); @@ -98,8 +92,5 @@ class _ProfileImagePickerState extends State { Widget build(BuildContext context) => PickImageContainer( image: widget.photo, pickImageClicked: () => _showPicker(context), - labelStyle: widget.labelStyle, - label: widget.label, - onLabelClicked: widget.onLabelClicked, ); } diff --git a/packages/espressocash_app/lib/l10n/intl_en.arb b/packages/espressocash_app/lib/l10n/intl_en.arb index f620ac1d06..c6e3f40a89 100644 --- a/packages/espressocash_app/lib/l10n/intl_en.arb +++ b/packages/espressocash_app/lib/l10n/intl_en.arb @@ -787,8 +787,6 @@ } } }, - "uploadPhoto": "Upload A Photo", - "@uploadPhoto": {}, "usdcExplanation": "The USD Coin (USDC) is a digital stablecoin that should be fixed to the United States dollar.", "@usdcExplanation": {}, "usdcInfo": "The US Dollar Coin (USDC) is a digital stablecoin that should be fixed to the United States dollar. ($1 = 1USDC)", @@ -838,8 +836,10 @@ "@yourEmailDisclaimer": {}, "yourEmailPlaceholder": "Email Address", "@yourEmailPlaceholder": {}, - "yourFirstNamePlaceholder": "Your Name", + "yourFirstNamePlaceholder": "First Name", "@yourFirstNamePlaceholder": {}, + "yourLastNamePlaceholder": "Last Name", + "@yourLastNamePlaceholder": {}, "yourLinkIsReady": "Your link is ready", "@yourLinkIsReady": {}, "yourRecoveryPhrase": "Your Secret Recovery Phrase", diff --git a/packages/espressocash_app/lib/ui/pick_image_container.dart b/packages/espressocash_app/lib/ui/pick_image_container.dart index 0c83575710..f458ef3268 100644 --- a/packages/espressocash_app/lib/ui/pick_image_container.dart +++ b/packages/espressocash_app/lib/ui/pick_image_container.dart @@ -4,23 +4,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg_provider/flutter_svg_provider.dart'; import '../gen/assets.gen.dart'; -import 'button.dart'; class PickImageContainer extends StatelessWidget { const PickImageContainer({ super.key, this.image, this.pickImageClicked, - this.labelStyle, - required this.label, - required this.onLabelClicked, }); final File? image; final VoidCallback? pickImageClicked; - final TextStyle? labelStyle; - final String label; - final VoidCallback? onLabelClicked; @override Widget build(BuildContext context) { @@ -28,33 +21,21 @@ class PickImageContainer extends StatelessWidget { return GestureDetector( onTap: pickImageClicked, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 114, - height: 114, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.black, - image: DecorationImage( - fit: BoxFit.cover, - image: image == null - // ignore: avoid-unnecessary-type-casts, needed here - ? Svg(Assets.images.imagePickerIcon.path) - as ImageProvider - : FileImage(image), - ), - ), + child: Container( + width: 114, + height: 114, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.black, + image: DecorationImage( + fit: BoxFit.cover, + image: image == null + // ignore: avoid-unnecessary-type-casts, needed here + ? Svg(Assets.images.imagePickerIcon.path) + as ImageProvider + : FileImage(image), ), - const SizedBox(height: 12), - CpButton( - text: label, - onPressed: pickImageClicked, - variant: CpButtonVariant.black, - size: CpButtonSize.small, - ), - ], + ), ), ); } diff --git a/packages/espressocash_app/lib/ui/text_field.dart b/packages/espressocash_app/lib/ui/text_field.dart index e21f55142a..684f07333d 100644 --- a/packages/espressocash_app/lib/ui/text_field.dart +++ b/packages/espressocash_app/lib/ui/text_field.dart @@ -24,6 +24,7 @@ class CpTextField extends StatelessWidget { this.textColor = CpColors.primaryTextColor, this.textInputAction, this.multiLine = false, + this.textCapitalization = TextCapitalization.none, }); final TextEditingController? controller; @@ -42,6 +43,7 @@ class CpTextField extends StatelessWidget { final Color? textColor; final TextInputAction? textInputAction; final bool? multiLine; + final TextCapitalization textCapitalization; @override Widget build(BuildContext context) { @@ -77,6 +79,7 @@ class CpTextField extends StatelessWidget { ), placeholder: placeholder, keyboardType: inputType, + textCapitalization: textCapitalization, keyboardAppearance: Theme.of(context).brightness, placeholderStyle: TextStyle(color: placeholderColor), textInputAction: textInputAction, diff --git a/packages/espressocash_app/lib/ui/user_avatar.dart b/packages/espressocash_app/lib/ui/user_avatar.dart index dc340ccdd8..a02f52a516 100644 --- a/packages/espressocash_app/lib/ui/user_avatar.dart +++ b/packages/espressocash_app/lib/ui/user_avatar.dart @@ -18,7 +18,7 @@ class CpUserAvatar extends StatelessWidget { @override Widget build(BuildContext context) { final double fontSize = (_fontSizeFactor * radius).roundToDouble(); - final String text = substring(userName, 0, 1).toUpperCase(); + final String text = substring(userName, 0, 2).toUpperCase(); return CircleAvatar( radius: radius, @@ -40,4 +40,4 @@ class CpUserAvatar extends StatelessWidget { } } -const _fontSizeFactor = 1.1667; +const _fontSizeFactor = 1.0;