Added Last Name display and made it editable
This commit is contained in:
@@ -20,6 +20,14 @@ class AccountBase implements StorableDescribable {
|
|||||||
DateTime get updatedAt => storable.updatedAt;
|
DateTime get updatedAt => storable.updatedAt;
|
||||||
@override
|
@override
|
||||||
String get name => describable.name;
|
String get name => describable.name;
|
||||||
|
String get fullName {
|
||||||
|
final first = describable.name.trim();
|
||||||
|
final last = lastName.trim();
|
||||||
|
|
||||||
|
if (last.isEmpty) return first;
|
||||||
|
if (first.isEmpty) return last;
|
||||||
|
return '$first $last';
|
||||||
|
}
|
||||||
@override
|
@override
|
||||||
String? get description => describable.description;
|
String? get description => describable.description;
|
||||||
|
|
||||||
@@ -32,7 +40,7 @@ class AccountBase implements StorableDescribable {
|
|||||||
required this.lastName,
|
required this.lastName,
|
||||||
});
|
});
|
||||||
|
|
||||||
String get nameInitials => getNameInitials(describable.name);
|
String get nameInitials => getNameInitials(fullName);
|
||||||
|
|
||||||
AccountBase copyWith({
|
AccountBase copyWith({
|
||||||
Describable? describable,
|
Describable? describable,
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
Future<Account?> update({
|
Future<Account?> update({
|
||||||
Describable? describable,
|
Describable? describable,
|
||||||
|
String? lastName,
|
||||||
String? locale,
|
String? locale,
|
||||||
String? avatarUrl,
|
String? avatarUrl,
|
||||||
String? notificationFrequency,
|
String? notificationFrequency,
|
||||||
@@ -213,6 +214,7 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
final updated = await AccountService.update(
|
final updated = await AccountService.update(
|
||||||
account!.copyWith(
|
account!.copyWith(
|
||||||
describable: describable,
|
describable: describable,
|
||||||
|
lastName: lastName,
|
||||||
avatarUrl: () => avatarUrl ?? account!.avatarUrl,
|
avatarUrl: () => avatarUrl ?? account!.avatarUrl,
|
||||||
locale: locale ?? account!.locale,
|
locale: locale ?? account!.locale,
|
||||||
),
|
),
|
||||||
@@ -250,10 +252,11 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Account?> resetUsername(String userName) async {
|
Future<Account?> resetUsername(String userName, {String? lastName}) async {
|
||||||
if (account == null) throw ErrorUnauthorized();
|
if (account == null) throw ErrorUnauthorized();
|
||||||
return update(
|
return update(
|
||||||
describable: account!.describable.copyWith(name: userName),
|
describable: account!.describable.copyWith(name: userName),
|
||||||
|
lastName: lastName ?? account!.lastName,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,16 +17,20 @@ class _AccountNameConstants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AccountName extends StatelessWidget {
|
class AccountName extends StatelessWidget {
|
||||||
final String name;
|
final String firstName;
|
||||||
|
final String lastName;
|
||||||
final String title;
|
final String title;
|
||||||
final String hintText;
|
final String hintText;
|
||||||
|
final String lastNameHint;
|
||||||
final String errorText;
|
final String errorText;
|
||||||
|
|
||||||
const AccountName({
|
const AccountName({
|
||||||
super.key,
|
super.key,
|
||||||
required this.name,
|
required this.firstName,
|
||||||
|
required this.lastName,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.hintText,
|
required this.hintText,
|
||||||
|
required this.lastNameHint,
|
||||||
required this.errorText,
|
required this.errorText,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,12 +38,14 @@ class AccountName extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return ChangeNotifierProvider(
|
||||||
create: (ctx) => AccountNameState(
|
create: (ctx) => AccountNameState(
|
||||||
initialName: name,
|
initialFirstName: firstName,
|
||||||
|
initialLastName: lastName,
|
||||||
errorMessage: errorText,
|
errorMessage: errorText,
|
||||||
accountProvider: ctx.read<AccountProvider>(),
|
accountProvider: ctx.read<AccountProvider>(),
|
||||||
),
|
),
|
||||||
child: _AccountNameBody(
|
child: _AccountNameBody(
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
|
lastNameHint: lastNameHint,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -48,9 +54,11 @@ class AccountName extends StatelessWidget {
|
|||||||
class _AccountNameBody extends StatelessWidget {
|
class _AccountNameBody extends StatelessWidget {
|
||||||
const _AccountNameBody({
|
const _AccountNameBody({
|
||||||
required this.hintText,
|
required this.hintText,
|
||||||
|
required this.lastNameHint,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String hintText;
|
final String hintText;
|
||||||
|
final String lastNameHint;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -58,8 +66,9 @@ class _AccountNameBody extends StatelessWidget {
|
|||||||
final provider = context.watch<AccountProvider>();
|
final provider = context.watch<AccountProvider>();
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
final currentName = provider.account?.name ?? state.initialName;
|
final currentFirstName = provider.account?.name ?? state.initialFirstName;
|
||||||
state.syncName(currentName);
|
final currentLastName = provider.account?.lastName ?? state.initialLastName;
|
||||||
|
state.syncNames(currentFirstName, currentLastName);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -69,6 +78,7 @@ class _AccountNameBody extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
AccountNameText(
|
AccountNameText(
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
|
lastNameHint: lastNameHint,
|
||||||
inputWidth: _AccountNameConstants.inputWidth,
|
inputWidth: _AccountNameConstants.inputWidth,
|
||||||
borderWidth: _AccountNameConstants.borderWidth,
|
borderWidth: _AccountNameConstants.borderWidth,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ class AccountNameText extends StatelessWidget {
|
|||||||
const AccountNameText({
|
const AccountNameText({
|
||||||
super.key,
|
super.key,
|
||||||
required this.hintText,
|
required this.hintText,
|
||||||
|
required this.lastNameHint,
|
||||||
required this.inputWidth,
|
required this.inputWidth,
|
||||||
required this.borderWidth,
|
required this.borderWidth,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String hintText;
|
final String hintText;
|
||||||
|
final String lastNameHint;
|
||||||
final double inputWidth;
|
final double inputWidth;
|
||||||
final double borderWidth;
|
final double borderWidth;
|
||||||
|
|
||||||
@@ -25,8 +27,11 @@ class AccountNameText extends StatelessWidget {
|
|||||||
if (state.isEditing) {
|
if (state.isEditing) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: inputWidth,
|
width: inputWidth,
|
||||||
child: TextFormField(
|
child: Column(
|
||||||
controller: state.controller,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: state.firstNameController,
|
||||||
style: theme.textTheme.headlineMedium?.copyWith(
|
style: theme.textTheme.headlineMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@@ -34,6 +39,7 @@ class AccountNameText extends StatelessWidget {
|
|||||||
enabled: !state.isBusy,
|
enabled: !state.isBusy,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
|
labelText: hintText,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
border: UnderlineInputBorder(
|
border: UnderlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
@@ -43,11 +49,33 @@ class AccountNameText extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextFormField(
|
||||||
|
controller: state.lastNameController,
|
||||||
|
style: theme.textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
enabled: !state.isBusy,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: lastNameHint,
|
||||||
|
labelText: lastNameHint,
|
||||||
|
isDense: true,
|
||||||
|
border: UnderlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
width: borderWidth,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final displayName = state.currentFullName.isNotEmpty ? state.currentFullName : hintText;
|
||||||
return Text(
|
return Text(
|
||||||
state.currentName,
|
displayName,
|
||||||
style: theme.textTheme.headlineMedium?.copyWith(
|
style: theme.textTheme.headlineMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -23,8 +23,11 @@ class ProfileSettingsPage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final accountName = context.select<AccountProvider, String?>(
|
final accountFirstName = context.select<AccountProvider, String?>(
|
||||||
(provider) => provider.account?.describable.name,
|
(provider) => provider.account?.name,
|
||||||
|
);
|
||||||
|
final accountLastName = context.select<AccountProvider, String?>(
|
||||||
|
(provider) => provider.account?.lastName,
|
||||||
);
|
);
|
||||||
final accountAvatarUrl = context.select<AccountProvider, String?>(
|
final accountAvatarUrl = context.select<AccountProvider, String?>(
|
||||||
(provider) => provider.account?.avatarUrl,
|
(provider) => provider.account?.avatarUrl,
|
||||||
@@ -49,9 +52,11 @@ class ProfileSettingsPage extends StatelessWidget {
|
|||||||
errorText: loc.avatarUpdateError,
|
errorText: loc.avatarUpdateError,
|
||||||
),
|
),
|
||||||
AccountName(
|
AccountName(
|
||||||
name: accountName ?? loc.userNamePlaceholder,
|
firstName: accountFirstName ?? '',
|
||||||
|
lastName: accountLastName ?? '',
|
||||||
title: loc.accountName,
|
title: loc.accountName,
|
||||||
hintText: loc.accountNameHint,
|
hintText: loc.accountNameHint,
|
||||||
|
lastNameHint: loc.lastName,
|
||||||
errorText: loc.accountNameUpdateError,
|
errorText: loc.accountNameUpdateError,
|
||||||
),
|
),
|
||||||
AccountPassword(
|
AccountPassword(
|
||||||
|
|||||||
@@ -7,50 +7,70 @@ import 'package:pweb/models/edit_state.dart';
|
|||||||
|
|
||||||
class AccountNameState extends ChangeNotifier {
|
class AccountNameState extends ChangeNotifier {
|
||||||
AccountNameState({
|
AccountNameState({
|
||||||
required this.initialName,
|
required this.initialFirstName,
|
||||||
|
required this.initialLastName,
|
||||||
required this.errorMessage,
|
required this.errorMessage,
|
||||||
required AccountProvider accountProvider,
|
required AccountProvider accountProvider,
|
||||||
}) : _accountProvider = accountProvider {
|
}) : _accountProvider = accountProvider {
|
||||||
_controller = TextEditingController(text: initialName);
|
_firstNameController = TextEditingController(text: initialFirstName);
|
||||||
|
_lastNameController = TextEditingController(text: initialLastName);
|
||||||
}
|
}
|
||||||
|
|
||||||
final AccountProvider _accountProvider;
|
final AccountProvider _accountProvider;
|
||||||
final String initialName;
|
final String initialFirstName;
|
||||||
|
final String initialLastName;
|
||||||
final String errorMessage;
|
final String errorMessage;
|
||||||
|
|
||||||
late final TextEditingController _controller;
|
late final TextEditingController _firstNameController;
|
||||||
|
late final TextEditingController _lastNameController;
|
||||||
EditState _editState = EditState.view;
|
EditState _editState = EditState.view;
|
||||||
String _errorText = '';
|
String _errorText = '';
|
||||||
bool _disposed = false;
|
bool _disposed = false;
|
||||||
|
|
||||||
TextEditingController get controller => _controller;
|
TextEditingController get firstNameController => _firstNameController;
|
||||||
|
TextEditingController get lastNameController => _lastNameController;
|
||||||
EditState get editState => _editState;
|
EditState get editState => _editState;
|
||||||
String get errorText => _errorText;
|
String get errorText => _errorText;
|
||||||
bool get isEditing => _editState != EditState.view;
|
bool get isEditing => _editState != EditState.view;
|
||||||
bool get isSaving => _editState == EditState.saving;
|
bool get isSaving => _editState == EditState.saving;
|
||||||
bool get isBusy => _accountProvider.isLoading || isSaving;
|
bool get isBusy => _accountProvider.isLoading || isSaving;
|
||||||
String get currentName => _accountProvider.account?.name ?? initialName;
|
String get currentFirstName => _accountProvider.account?.name ?? initialFirstName;
|
||||||
|
String get currentLastName => _accountProvider.account?.lastName ?? initialLastName;
|
||||||
|
String get currentFullName {
|
||||||
|
final first = currentFirstName.trim();
|
||||||
|
final last = currentLastName.trim();
|
||||||
|
if (first.isEmpty && last.isEmpty) return '';
|
||||||
|
if (first.isEmpty) return last;
|
||||||
|
if (last.isEmpty) return first;
|
||||||
|
return '$first $last';
|
||||||
|
}
|
||||||
|
|
||||||
void startEditing() => _setState(EditState.edit);
|
void startEditing() => _setState(EditState.edit);
|
||||||
|
|
||||||
void cancelEditing() {
|
void cancelEditing() {
|
||||||
_controller.text = currentName;
|
_firstNameController.text = currentFirstName;
|
||||||
|
_lastNameController.text = currentLastName;
|
||||||
_setError('');
|
_setError('');
|
||||||
_setState(EditState.view);
|
_setState(EditState.view);
|
||||||
}
|
}
|
||||||
|
|
||||||
void syncName(String latestName) {
|
void syncNames(String latestFirstName, String latestLastName) {
|
||||||
if (isEditing) return;
|
if (isEditing) return;
|
||||||
if (_controller.text != latestName) {
|
if (_firstNameController.text != latestFirstName) {
|
||||||
_controller.text = latestName;
|
_firstNameController.text = latestFirstName;
|
||||||
|
}
|
||||||
|
if (_lastNameController.text != latestLastName) {
|
||||||
|
_lastNameController.text = latestLastName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> save() async {
|
Future<bool> save() async {
|
||||||
final newName = _controller.text.trim();
|
final newFirstName = _firstNameController.text.trim();
|
||||||
final current = currentName;
|
final newLastName = _lastNameController.text.trim();
|
||||||
|
final currentFirst = currentFirstName;
|
||||||
|
final currentLast = currentLastName;
|
||||||
|
|
||||||
if (newName.isEmpty || newName == current) {
|
if (newFirstName.isEmpty || (newFirstName == currentFirst && newLastName == currentLast)) {
|
||||||
cancelEditing();
|
cancelEditing();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -59,7 +79,7 @@ class AccountNameState extends ChangeNotifier {
|
|||||||
_setState(EditState.saving);
|
_setState(EditState.saving);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _accountProvider.resetUsername(newName);
|
await _accountProvider.resetUsername(newFirstName, lastName: newLastName);
|
||||||
_setState(EditState.view);
|
_setState(EditState.view);
|
||||||
return true;
|
return true;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
@@ -88,7 +108,8 @@ class AccountNameState extends ChangeNotifier {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
_controller.dispose();
|
_firstNameController.dispose();
|
||||||
|
_lastNameController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class AccountAvatar extends StatelessWidget {
|
|||||||
|
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
return UserAccountsDrawerHeader(
|
return UserAccountsDrawerHeader(
|
||||||
accountName: Text(provider.account?.describable.name ?? loc.userNamePlaceholder),
|
accountName: Text(provider.account?.fullName ?? loc.userNamePlaceholder),
|
||||||
accountEmail: Text(provider.account?.login ?? loc.usernameHint),
|
accountEmail: Text(provider.account?.login ?? loc.usernameHint),
|
||||||
currentAccountPicture: avatar,
|
currentAccountPicture: avatar,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class PayoutSidebar extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final accountName = context.select<AccountProvider, String?>(
|
final accountName = context.select<AccountProvider, String?>(
|
||||||
(provider) => provider.account?.describable.name,
|
(provider) => provider.account?.fullName,
|
||||||
);
|
);
|
||||||
final accountAvatar = context.select<AccountProvider, String?>(
|
final accountAvatar = context.select<AccountProvider, String?>(
|
||||||
(provider) => provider.account?.avatarUrl,
|
(provider) => provider.account?.avatarUrl,
|
||||||
|
|||||||
Reference in New Issue
Block a user