mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-08-15 01:15:42 -04:00
account management update
This commit is contained in:
parent
01c6490ec4
commit
5e4f47d5a1
42 changed files with 1663 additions and 831 deletions
|
@ -40,12 +40,43 @@ class AccountRecordCubit extends DefaultDHTRecordCubit<AccountRecordState> {
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
// Public Interface
|
||||
|
||||
Future<void> updateProfile(proto.Profile profile) async {
|
||||
Future<void> updateAccount(
|
||||
AccountSpec accountSpec,
|
||||
) async {
|
||||
await record.eventualUpdateProtobuf(proto.Account.fromBuffer, (old) async {
|
||||
if (old == null || old.profile == profile) {
|
||||
if (old == null) {
|
||||
return null;
|
||||
}
|
||||
return old.deepCopy()..profile = profile;
|
||||
|
||||
final newAccount = old.deepCopy()
|
||||
..profile.name = accountSpec.name
|
||||
..profile.pronouns = accountSpec.pronouns
|
||||
..profile.about = accountSpec.about
|
||||
..profile.availability = accountSpec.availability
|
||||
..profile.status = accountSpec.status
|
||||
//..profile.avatar =
|
||||
..profile.timestamp = Veilid.instance.now().toInt64()
|
||||
..invisible = accountSpec.invisible
|
||||
..autodetectAway = accountSpec.autoAway
|
||||
..autoAwayTimeoutMin = accountSpec.autoAwayTimeout
|
||||
..freeMessage = accountSpec.freeMessage
|
||||
..awayMessage = accountSpec.awayMessage
|
||||
..busyMessage = accountSpec.busyMessage;
|
||||
|
||||
var changed = false;
|
||||
if (newAccount.profile != old.profile ||
|
||||
newAccount.invisible != old.invisible ||
|
||||
newAccount.autodetectAway != old.autodetectAway ||
|
||||
newAccount.autoAwayTimeoutMin != old.autoAwayTimeoutMin ||
|
||||
newAccount.freeMessage != old.freeMessage ||
|
||||
newAccount.busyMessage != old.busyMessage ||
|
||||
newAccount.awayMessage != old.awayMessage) {
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
return newAccount;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
55
lib/account_manager/models/account_spec.dart
Normal file
55
lib/account_manager/models/account_spec.dart
Normal file
|
@ -0,0 +1,55 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../../proto/proto.dart' as proto;
|
||||
|
||||
/// Profile and Account configurable fields
|
||||
/// Some are publicly visible via the proto.Profile
|
||||
/// Some are privately held as proto.Account configurations
|
||||
class AccountSpec {
|
||||
AccountSpec(
|
||||
{required this.name,
|
||||
required this.pronouns,
|
||||
required this.about,
|
||||
required this.availability,
|
||||
required this.invisible,
|
||||
required this.freeMessage,
|
||||
required this.awayMessage,
|
||||
required this.busyMessage,
|
||||
required this.avatar,
|
||||
required this.autoAway,
|
||||
required this.autoAwayTimeout});
|
||||
|
||||
String get status {
|
||||
late final String status;
|
||||
switch (availability) {
|
||||
case proto.Availability.AVAILABILITY_AWAY:
|
||||
status = awayMessage;
|
||||
break;
|
||||
case proto.Availability.AVAILABILITY_BUSY:
|
||||
status = busyMessage;
|
||||
break;
|
||||
case proto.Availability.AVAILABILITY_FREE:
|
||||
status = freeMessage;
|
||||
break;
|
||||
case proto.Availability.AVAILABILITY_UNSPECIFIED:
|
||||
case proto.Availability.AVAILABILITY_OFFLINE:
|
||||
status = '';
|
||||
break;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
String name;
|
||||
String pronouns;
|
||||
String about;
|
||||
proto.Availability availability;
|
||||
bool invisible;
|
||||
String freeMessage;
|
||||
String awayMessage;
|
||||
String busyMessage;
|
||||
ImageProvider? avatar;
|
||||
bool autoAway;
|
||||
int autoAwayTimeout;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
export 'account_info.dart';
|
||||
export 'account_update_spec.dart';
|
||||
export 'encryption_key_type.dart';
|
||||
export 'local_account/local_account.dart';
|
||||
export 'new_profile_spec.dart';
|
||||
export 'per_account_collection_state/per_account_collection_state.dart';
|
||||
export 'user_login/user_login.dart';
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
class NewProfileSpec {
|
||||
NewProfileSpec({required this.name, required this.pronouns});
|
||||
String name;
|
||||
String pronouns;
|
||||
}
|
|
@ -133,14 +133,14 @@ class AccountRepository {
|
|||
/// with the identity instance, stores the account in the identity key and
|
||||
/// then logs into that account with no password set at this time
|
||||
Future<WritableSuperIdentity> createWithNewSuperIdentity(
|
||||
proto.Profile newProfile) async {
|
||||
AccountSpec accountSpec) async {
|
||||
log.debug('Creating super identity');
|
||||
final wsi = await WritableSuperIdentity.create();
|
||||
try {
|
||||
final localAccount = await _newLocalAccount(
|
||||
superIdentity: wsi.superIdentity,
|
||||
identitySecret: wsi.identitySecret,
|
||||
newProfile: newProfile);
|
||||
accountSpec: accountSpec);
|
||||
|
||||
// Log in the new account by default with no pin
|
||||
final ok = await login(
|
||||
|
@ -154,15 +154,13 @@ class AccountRepository {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> editAccountProfile(
|
||||
TypedKey superIdentityRecordKey, proto.Profile newProfile) async {
|
||||
log.debug('Editing profile for $superIdentityRecordKey');
|
||||
|
||||
Future<void> updateLocalAccount(
|
||||
TypedKey superIdentityRecordKey, AccountSpec accountSpec) async {
|
||||
final localAccounts = await _localAccounts.get();
|
||||
|
||||
final newLocalAccounts = localAccounts.replaceFirstWhere(
|
||||
(x) => x.superIdentity.recordKey == superIdentityRecordKey,
|
||||
(localAccount) => localAccount!.copyWith(name: newProfile.name));
|
||||
(localAccount) => localAccount!.copyWith(name: accountSpec.name));
|
||||
|
||||
await _localAccounts.set(newLocalAccounts);
|
||||
_streamController.add(AccountRepositoryChange.localAccounts);
|
||||
|
@ -248,7 +246,7 @@ class AccountRepository {
|
|||
Future<LocalAccount> _newLocalAccount(
|
||||
{required SuperIdentity superIdentity,
|
||||
required SecretKey identitySecret,
|
||||
required proto.Profile newProfile,
|
||||
required AccountSpec accountSpec,
|
||||
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
|
||||
String encryptionKey = ''}) async {
|
||||
log.debug('Creating new local account');
|
||||
|
@ -285,7 +283,10 @@ class AccountRepository {
|
|||
|
||||
// Make account object
|
||||
final account = proto.Account()
|
||||
..profile = newProfile
|
||||
..profile.name = accountSpec.name
|
||||
..profile.pronouns = accountSpec.pronouns
|
||||
..profile.about = accountSpec.about
|
||||
..profile.status = accountSpec.status
|
||||
..contactList = contactList.toProto()
|
||||
..contactInvitationRecords = contactInvitationRecords.toProto()
|
||||
..chatList = chatRecords.toProto();
|
||||
|
@ -309,7 +310,7 @@ class AccountRepository {
|
|||
encryptionKeyType: encryptionKeyType,
|
||||
biometricsEnabled: false,
|
||||
hiddenAccount: false,
|
||||
name: newProfile.name,
|
||||
name: accountSpec.name,
|
||||
);
|
||||
|
||||
// Add local account object to internal store
|
||||
|
|
|
@ -4,10 +4,8 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../layout/default_app_bar.dart';
|
||||
|
@ -17,12 +15,12 @@ import '../../theme/theme.dart';
|
|||
import '../../tools/tools.dart';
|
||||
import '../../veilid_processor/veilid_processor.dart';
|
||||
import '../account_manager.dart';
|
||||
import 'profile_edit_form.dart';
|
||||
import 'edit_profile_form.dart';
|
||||
|
||||
class EditAccountPage extends StatefulWidget {
|
||||
const EditAccountPage(
|
||||
{required this.superIdentityRecordKey,
|
||||
required this.existingProfile,
|
||||
required this.existingAccount,
|
||||
required this.accountRecord,
|
||||
super.key});
|
||||
|
||||
|
@ -30,7 +28,7 @@ class EditAccountPage extends StatefulWidget {
|
|||
State createState() => _EditAccountPageState();
|
||||
|
||||
final TypedKey superIdentityRecordKey;
|
||||
final proto.Profile existingProfile;
|
||||
final proto.Account existingAccount;
|
||||
final OwnedDHTRecordPointer accountRecord;
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
|
@ -38,8 +36,8 @@ class EditAccountPage extends StatefulWidget {
|
|||
properties
|
||||
..add(DiagnosticsProperty<TypedKey>(
|
||||
'superIdentityRecordKey', superIdentityRecordKey))
|
||||
..add(DiagnosticsProperty<proto.Profile>(
|
||||
'existingProfile', existingProfile))
|
||||
..add(DiagnosticsProperty<proto.Account>(
|
||||
'existingAccount', existingAccount))
|
||||
..add(DiagnosticsProperty<OwnedDHTRecordPointer>(
|
||||
'accountRecord', accountRecord));
|
||||
}
|
||||
|
@ -52,8 +50,7 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||
orientationCapability: OrientationCapability.portraitOnly);
|
||||
|
||||
Widget _editAccountForm(BuildContext context,
|
||||
{required Future<void> Function(GlobalKey<FormBuilderState>)
|
||||
onSubmit}) =>
|
||||
{required Future<void> Function(AccountSpec) onSubmit}) =>
|
||||
EditProfileForm(
|
||||
header: translate('edit_account_page.header'),
|
||||
instructions: translate('edit_account_page.instructions'),
|
||||
|
@ -61,8 +58,25 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||
submitDisabledText: translate('button.waiting_for_network'),
|
||||
onSubmit: onSubmit,
|
||||
initialValueCallback: (key) => switch (key) {
|
||||
EditProfileForm.formFieldName => widget.existingProfile.name,
|
||||
EditProfileForm.formFieldPronouns => widget.existingProfile.pronouns,
|
||||
EditProfileForm.formFieldName => widget.existingAccount.profile.name,
|
||||
EditProfileForm.formFieldPronouns =>
|
||||
widget.existingAccount.profile.pronouns,
|
||||
EditProfileForm.formFieldAbout =>
|
||||
widget.existingAccount.profile.about,
|
||||
EditProfileForm.formFieldAvailability =>
|
||||
widget.existingAccount.profile.availability,
|
||||
EditProfileForm.formFieldFreeMessage =>
|
||||
widget.existingAccount.freeMessage,
|
||||
EditProfileForm.formFieldAwayMessage =>
|
||||
widget.existingAccount.awayMessage,
|
||||
EditProfileForm.formFieldBusyMessage =>
|
||||
widget.existingAccount.busyMessage,
|
||||
EditProfileForm.formFieldAvatar =>
|
||||
widget.existingAccount.profile.avatar,
|
||||
EditProfileForm.formFieldAutoAway =>
|
||||
widget.existingAccount.autodetectAway,
|
||||
EditProfileForm.formFieldAutoAwayTimeout =>
|
||||
widget.existingAccount.autoAwayTimeoutMin,
|
||||
String() => throw UnimplementedError(),
|
||||
},
|
||||
);
|
||||
|
@ -200,21 +214,11 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _onSubmit(GlobalKey<FormBuilderState> formKey) async {
|
||||
Future<void> _onSubmit(AccountSpec accountSpec) async {
|
||||
// dismiss the keyboard by unfocusing the textfield
|
||||
FocusScope.of(context).unfocus();
|
||||
|
||||
try {
|
||||
final name = formKey
|
||||
.currentState!.fields[EditProfileForm.formFieldName]!.value as String;
|
||||
final pronouns = formKey.currentState!
|
||||
.fields[EditProfileForm.formFieldPronouns]!.value as String? ??
|
||||
'';
|
||||
final newProfile = widget.existingProfile.deepCopy()
|
||||
..name = name
|
||||
..pronouns = pronouns
|
||||
..timestamp = Veilid.instance.now().toInt64();
|
||||
|
||||
setState(() {
|
||||
_isInAsyncCall = true;
|
||||
});
|
||||
|
@ -231,11 +235,11 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||
|
||||
// Update account profile DHT record
|
||||
// This triggers ConversationCubits to update
|
||||
await accountRecordCubit.updateProfile(newProfile);
|
||||
await accountRecordCubit.updateAccount(accountSpec);
|
||||
|
||||
// Update local account profile
|
||||
await AccountRepository.instance
|
||||
.editAccountProfile(widget.superIdentityRecordKey, newProfile);
|
||||
.updateLocalAccount(widget.superIdentityRecordKey, accountSpec);
|
||||
|
||||
if (mounted) {
|
||||
Navigator.canPop(context)
|
||||
|
|
302
lib/account_manager/views/edit_profile_form.dart
Normal file
302
lib/account_manager/views/edit_profile_form.dart
Normal file
|
@ -0,0 +1,302 @@
|
|||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
|
||||
import '../../contacts/contacts.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../theme/theme.dart';
|
||||
import '../models/models.dart';
|
||||
|
||||
class EditProfileForm extends StatefulWidget {
|
||||
const EditProfileForm({
|
||||
required this.header,
|
||||
required this.instructions,
|
||||
required this.submitText,
|
||||
required this.submitDisabledText,
|
||||
super.key,
|
||||
this.onSubmit,
|
||||
this.initialValueCallback,
|
||||
});
|
||||
|
||||
@override
|
||||
State createState() => _EditProfileFormState();
|
||||
|
||||
final String header;
|
||||
final String instructions;
|
||||
final Future<void> Function(AccountSpec)? onSubmit;
|
||||
final String submitText;
|
||||
final String submitDisabledText;
|
||||
final Object? Function(String key)? initialValueCallback;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(StringProperty('header', header))
|
||||
..add(StringProperty('instructions', instructions))
|
||||
..add(ObjectFlagProperty<Future<void> Function(AccountSpec)?>.has(
|
||||
'onSubmit', onSubmit))
|
||||
..add(StringProperty('submitText', submitText))
|
||||
..add(StringProperty('submitDisabledText', submitDisabledText))
|
||||
..add(ObjectFlagProperty<Object? Function(String key)?>.has(
|
||||
'initialValueCallback', initialValueCallback));
|
||||
}
|
||||
|
||||
static const String formFieldName = 'name';
|
||||
static const String formFieldPronouns = 'pronouns';
|
||||
static const String formFieldAbout = 'about';
|
||||
static const String formFieldAvailability = 'availability';
|
||||
static const String formFieldFreeMessage = 'free_message';
|
||||
static const String formFieldAwayMessage = 'away_message';
|
||||
static const String formFieldBusyMessage = 'busy_message';
|
||||
static const String formFieldAvatar = 'avatar';
|
||||
static const String formFieldAutoAway = 'auto_away';
|
||||
static const String formFieldAutoAwayTimeout = 'auto_away_timeout';
|
||||
}
|
||||
|
||||
class _EditProfileFormState extends State<EditProfileForm> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
FormBuilderDropdown<proto.Availability> _availabilityDropDown(
|
||||
BuildContext context) {
|
||||
final initialValueX =
|
||||
widget.initialValueCallback?.call(EditProfileForm.formFieldAvailability)
|
||||
as proto.Availability? ??
|
||||
proto.Availability.AVAILABILITY_FREE;
|
||||
final initialValue =
|
||||
initialValueX == proto.Availability.AVAILABILITY_UNSPECIFIED
|
||||
? proto.Availability.AVAILABILITY_FREE
|
||||
: initialValueX;
|
||||
|
||||
final availabilities = [
|
||||
proto.Availability.AVAILABILITY_FREE,
|
||||
proto.Availability.AVAILABILITY_AWAY,
|
||||
proto.Availability.AVAILABILITY_BUSY,
|
||||
proto.Availability.AVAILABILITY_OFFLINE,
|
||||
];
|
||||
|
||||
return FormBuilderDropdown<proto.Availability>(
|
||||
name: EditProfileForm.formFieldAvailability,
|
||||
initialValue: initialValue,
|
||||
items: availabilities
|
||||
.map((x) => DropdownMenuItem<proto.Availability>(
|
||||
value: x,
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(AvailabilityWidget.availabilityIcon(x)),
|
||||
Text(x == proto.Availability.AVAILABILITY_OFFLINE
|
||||
? translate('availability.always_show_offline')
|
||||
: AvailabilityWidget.availabilityName(x)),
|
||||
])))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
AccountSpec _makeAccountSpec() {
|
||||
final name = _formKey
|
||||
.currentState!.fields[EditProfileForm.formFieldName]!.value as String;
|
||||
final pronouns = _formKey.currentState!
|
||||
.fields[EditProfileForm.formFieldPronouns]!.value as String? ??
|
||||
'';
|
||||
final about = _formKey.currentState!.fields[EditProfileForm.formFieldAbout]!
|
||||
.value as String? ??
|
||||
'';
|
||||
final availability = _formKey
|
||||
.currentState!
|
||||
.fields[EditProfileForm.formFieldAvailability]!
|
||||
.value as proto.Availability? ??
|
||||
proto.Availability.AVAILABILITY_FREE;
|
||||
|
||||
final invisible = availability == proto.Availability.AVAILABILITY_OFFLINE;
|
||||
|
||||
final freeMessage = _formKey.currentState!
|
||||
.fields[EditProfileForm.formFieldFreeMessage]!.value as String? ??
|
||||
'';
|
||||
final awayMessage = _formKey.currentState!
|
||||
.fields[EditProfileForm.formFieldAwayMessage]!.value as String? ??
|
||||
'';
|
||||
final busyMessage = _formKey.currentState!
|
||||
.fields[EditProfileForm.formFieldBusyMessage]!.value as String? ??
|
||||
'';
|
||||
final autoAway = _formKey.currentState!
|
||||
.fields[EditProfileForm.formFieldAutoAway]!.value as bool? ??
|
||||
false;
|
||||
final autoAwayTimeout = _formKey.currentState!
|
||||
.fields[EditProfileForm.formFieldAutoAwayTimeout]!.value as int? ??
|
||||
30;
|
||||
|
||||
return AccountSpec(
|
||||
name: name,
|
||||
pronouns: pronouns,
|
||||
about: about,
|
||||
availability: availability,
|
||||
invisible: invisible,
|
||||
freeMessage: freeMessage,
|
||||
awayMessage: awayMessage,
|
||||
busyMessage: busyMessage,
|
||||
avatar: null,
|
||||
autoAway: autoAway,
|
||||
autoAwayTimeout: autoAwayTimeout);
|
||||
}
|
||||
|
||||
Widget _editProfileForm(
|
||||
BuildContext context,
|
||||
) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||
final textTheme = theme.textTheme;
|
||||
|
||||
late final Color border;
|
||||
if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) {
|
||||
border = scale.primaryScale.elementBackground;
|
||||
} else {
|
||||
border = scale.primaryScale.border;
|
||||
}
|
||||
|
||||
return FormBuilder(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
AvatarWidget(
|
||||
name: _formKey.currentState?.value[EditProfileForm.formFieldName]
|
||||
as String? ??
|
||||
'?',
|
||||
size: 128,
|
||||
borderColor: border,
|
||||
foregroundColor: scale.primaryScale.primaryText,
|
||||
backgroundColor: scale.primaryScale.primary,
|
||||
scaleConfig: scaleConfig,
|
||||
textStyle: theme.textTheme.titleLarge!.copyWith(fontSize: 64),
|
||||
).paddingLTRB(0, 0, 0, 16),
|
||||
FormBuilderTextField(
|
||||
autofocus: true,
|
||||
name: EditProfileForm.formFieldName,
|
||||
initialValue: widget.initialValueCallback
|
||||
?.call(EditProfileForm.formFieldName) as String?,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('account.form_name'),
|
||||
hintText: translate('account.empty_name')),
|
||||
maxLength: 64,
|
||||
// The validator receives the text that the user has entered.
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(),
|
||||
]),
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
FormBuilderTextField(
|
||||
name: EditProfileForm.formFieldPronouns,
|
||||
initialValue: widget.initialValueCallback
|
||||
?.call(EditProfileForm.formFieldPronouns) as String?,
|
||||
maxLength: 64,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('account.form_pronouns'),
|
||||
hintText: translate('account.empty_pronouns')),
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
FormBuilderTextField(
|
||||
name: EditProfileForm.formFieldAbout,
|
||||
initialValue: widget.initialValueCallback
|
||||
?.call(EditProfileForm.formFieldAbout) as String?,
|
||||
maxLength: 1024,
|
||||
maxLines: 8,
|
||||
minLines: 1,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('account.form_about'),
|
||||
hintText: translate('account.empty_about')),
|
||||
textInputAction: TextInputAction.newline,
|
||||
),
|
||||
_availabilityDropDown(context),
|
||||
FormBuilderTextField(
|
||||
name: EditProfileForm.formFieldFreeMessage,
|
||||
initialValue: widget.initialValueCallback
|
||||
?.call(EditProfileForm.formFieldFreeMessage) as String?,
|
||||
maxLength: 128,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('account.form_free_message'),
|
||||
hintText: translate('account.empty_free_message')),
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
FormBuilderTextField(
|
||||
name: EditProfileForm.formFieldAwayMessage,
|
||||
initialValue: widget.initialValueCallback
|
||||
?.call(EditProfileForm.formFieldAwayMessage) as String?,
|
||||
maxLength: 128,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('account.form_away_message'),
|
||||
hintText: translate('account.empty_away_message')),
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
FormBuilderTextField(
|
||||
name: EditProfileForm.formFieldBusyMessage,
|
||||
initialValue: widget.initialValueCallback
|
||||
?.call(EditProfileForm.formFieldBusyMessage) as String?,
|
||||
maxLength: 128,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('account.form_busy_message'),
|
||||
hintText: translate('account.empty_busy_message')),
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
FormBuilderCheckbox(
|
||||
name: EditProfileForm.formFieldAutoAway,
|
||||
initialValue: widget.initialValueCallback
|
||||
?.call(EditProfileForm.formFieldAutoAway) as bool? ??
|
||||
false,
|
||||
side: BorderSide(color: scale.primaryScale.border, width: 2),
|
||||
title: Text(translate('account.form_auto_away'),
|
||||
style: textTheme.labelMedium),
|
||||
),
|
||||
FormBuilderTextField(
|
||||
name: EditProfileForm.formFieldAutoAwayTimeout,
|
||||
enabled: _formKey.currentState
|
||||
?.value[EditProfileForm.formFieldAutoAway] as bool? ??
|
||||
false,
|
||||
initialValue: widget.initialValueCallback
|
||||
?.call(EditProfileForm.formFieldAutoAwayTimeout)
|
||||
as String? ??
|
||||
'15',
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('account.form_auto_away_timeout'),
|
||||
),
|
||||
validator: FormBuilderValidators.positiveNumber(),
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
Row(children: [
|
||||
const Spacer(),
|
||||
Text(widget.instructions).toCenter().flexible(flex: 6),
|
||||
const Spacer(),
|
||||
]).paddingSymmetric(vertical: 4),
|
||||
ElevatedButton(
|
||||
onPressed: widget.onSubmit == null
|
||||
? null
|
||||
: () async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final aus = _makeAccountSpec();
|
||||
await widget.onSubmit!(aus);
|
||||
}
|
||||
},
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0),
|
||||
Text((widget.onSubmit == null)
|
||||
? widget.submitDisabledText
|
||||
: widget.submitText)
|
||||
.paddingLTRB(0, 0, 4, 0)
|
||||
]),
|
||||
).paddingSymmetric(vertical: 4).alignAtCenterRight(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _editProfileForm(
|
||||
context,
|
||||
);
|
||||
}
|
|
@ -3,17 +3,15 @@ import 'dart:async';
|
|||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../layout/default_app_bar.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../theme/theme.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import '../../veilid_processor/veilid_processor.dart';
|
||||
import '../account_manager.dart';
|
||||
import 'profile_edit_form.dart';
|
||||
import 'edit_profile_form.dart';
|
||||
|
||||
class NewAccountPage extends StatefulWidget {
|
||||
const NewAccountPage({super.key});
|
||||
|
@ -29,7 +27,7 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
|
|||
orientationCapability: OrientationCapability.portraitOnly);
|
||||
|
||||
Widget _newAccountForm(BuildContext context,
|
||||
{required Future<void> Function(GlobalKey<FormBuilderState>) onSubmit}) {
|
||||
{required Future<void> Function(AccountSpec) onSubmit}) {
|
||||
final networkReady = context
|
||||
.watch<ConnectionStateCubit>()
|
||||
.state
|
||||
|
@ -47,28 +45,19 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
|
|||
onSubmit: !canSubmit ? null : onSubmit);
|
||||
}
|
||||
|
||||
Future<void> _onSubmit(GlobalKey<FormBuilderState> formKey) async {
|
||||
Future<void> _onSubmit(AccountSpec accountSpec) async {
|
||||
// dismiss the keyboard by unfocusing the textfield
|
||||
FocusScope.of(context).unfocus();
|
||||
|
||||
try {
|
||||
final name = formKey
|
||||
.currentState!.fields[EditProfileForm.formFieldName]!.value as String;
|
||||
final pronouns = formKey.currentState!
|
||||
.fields[EditProfileForm.formFieldPronouns]!.value as String? ??
|
||||
'';
|
||||
final newProfile = proto.Profile()
|
||||
..name = name
|
||||
..pronouns = pronouns;
|
||||
|
||||
setState(() {
|
||||
_isInAsyncCall = true;
|
||||
});
|
||||
try {
|
||||
final writableSuperIdentity = await AccountRepository.instance
|
||||
.createWithNewSuperIdentity(newProfile);
|
||||
.createWithNewSuperIdentity(accountSpec);
|
||||
GoRouterHelper(context).pushReplacement('/new_account/recovery_key',
|
||||
extra: [writableSuperIdentity, newProfile.name]);
|
||||
extra: [writableSuperIdentity, accountSpec.name]);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
|
||||
class EditProfileForm extends StatefulWidget {
|
||||
const EditProfileForm({
|
||||
required this.header,
|
||||
required this.instructions,
|
||||
required this.submitText,
|
||||
required this.submitDisabledText,
|
||||
super.key,
|
||||
this.onSubmit,
|
||||
this.initialValueCallback,
|
||||
});
|
||||
|
||||
@override
|
||||
State createState() => _EditProfileFormState();
|
||||
|
||||
final String header;
|
||||
final String instructions;
|
||||
final Future<void> Function(GlobalKey<FormBuilderState>)? onSubmit;
|
||||
final String submitText;
|
||||
final String submitDisabledText;
|
||||
final Object? Function(String key)? initialValueCallback;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(StringProperty('header', header))
|
||||
..add(StringProperty('instructions', instructions))
|
||||
..add(ObjectFlagProperty<
|
||||
Future<void> Function(
|
||||
GlobalKey<FormBuilderState> p1)?>.has('onSubmit', onSubmit))
|
||||
..add(StringProperty('submitText', submitText))
|
||||
..add(StringProperty('submitDisabledText', submitDisabledText))
|
||||
..add(ObjectFlagProperty<Object? Function(String key)?>.has(
|
||||
'initialValueCallback', initialValueCallback));
|
||||
}
|
||||
|
||||
static const String formFieldName = 'name';
|
||||
static const String formFieldPronouns = 'pronouns';
|
||||
}
|
||||
|
||||
class _EditProfileFormState extends State<EditProfileForm> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Widget _editProfileForm(
|
||||
BuildContext context,
|
||||
) =>
|
||||
FormBuilder(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(widget.header)
|
||||
.textStyle(context.headlineSmall)
|
||||
.paddingSymmetric(vertical: 16),
|
||||
FormBuilderTextField(
|
||||
autofocus: true,
|
||||
name: EditProfileForm.formFieldName,
|
||||
initialValue: widget.initialValueCallback
|
||||
?.call(EditProfileForm.formFieldName) as String?,
|
||||
decoration:
|
||||
InputDecoration(labelText: translate('account.form_name')),
|
||||
maxLength: 64,
|
||||
// The validator receives the text that the user has entered.
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(),
|
||||
]),
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
FormBuilderTextField(
|
||||
name: EditProfileForm.formFieldPronouns,
|
||||
initialValue: widget.initialValueCallback
|
||||
?.call(EditProfileForm.formFieldPronouns) as String?,
|
||||
maxLength: 64,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('account.form_pronouns')),
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
Row(children: [
|
||||
const Spacer(),
|
||||
Text(widget.instructions).toCenter().flexible(flex: 6),
|
||||
const Spacer(),
|
||||
]).paddingSymmetric(vertical: 4),
|
||||
ElevatedButton(
|
||||
onPressed: widget.onSubmit == null
|
||||
? null
|
||||
: () async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
await widget.onSubmit!(_formKey);
|
||||
}
|
||||
},
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0),
|
||||
Text((widget.onSubmit == null)
|
||||
? widget.submitDisabledText
|
||||
: widget.submitText)
|
||||
.paddingLTRB(0, 0, 4, 0)
|
||||
]),
|
||||
).paddingSymmetric(vertical: 4).alignAtCenterRight(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _editProfileForm(
|
||||
context,
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue