refactor and cleanup in prep for profile changing

This commit is contained in:
Christien Rioux 2024-06-13 14:52:34 -04:00
parent 87bb1657c7
commit 56d65442f4
49 changed files with 967 additions and 655 deletions

View file

@ -0,0 +1,163 @@
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 '../../contacts/cubits/contact_list_cubit.dart';
import '../../conversation/conversation.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';
class EditAccountPage extends StatefulWidget {
const EditAccountPage(
{required this.superIdentityRecordKey,
required this.existingProfile,
super.key});
@override
State createState() => _EditAccountPageState();
final TypedKey superIdentityRecordKey;
final proto.Profile existingProfile;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<TypedKey>(
'superIdentityRecordKey', superIdentityRecordKey))
..add(DiagnosticsProperty<proto.Profile>(
'existingProfile', existingProfile));
}
}
class _EditAccountPageState extends State<EditAccountPage> {
final _formKey = GlobalKey<FormBuilderState>();
bool _isInAsyncCall = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await changeWindowSetup(
TitleBarStyle.normal, OrientationCapability.portraitOnly);
});
}
Widget _editAccountForm(BuildContext context,
{required Future<void> Function(GlobalKey<FormBuilderState>)
onSubmit}) =>
EditProfileForm(
header: translate('edit_account_page.header'),
instructions: translate('edit_account_page.instructions'),
submitText: translate('edit_account_page.update'),
submitDisabledText: translate('button.waiting_for_network'),
onSubmit: onSubmit);
@override
Widget build(BuildContext context) {
final displayModalHUD = _isInAsyncCall;
final accountRecordCubit = context.read<AccountRecordCubit>();
final activeConversationsBlocMapCubit =
context.read<ActiveConversationsBlocMapCubit>();
final contactListCubit = context.read<ContactListCubit>();
return Scaffold(
// resizeToAvoidBottomInset: false,
appBar: DefaultAppBar(
title: Text(translate('edit_account_page.titlebar')),
actions: [
const SignalStrengthMeterWidget(),
IconButton(
icon: const Icon(Icons.settings),
tooltip: translate('menu.settings_tooltip'),
onPressed: () async {
await GoRouterHelper(context).push('/settings');
})
]),
body: _editAccountForm(
context,
onSubmit: (formKey) 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;
setState(() {
_isInAsyncCall = true;
});
try {
// Update account profile DHT record
final newValue = await accountRecordCubit.record
.tryWriteProtobuf(proto.Account.fromBuffer, newProfile);
if (newValue != null) {
if (context.mounted) {
await showErrorModal(
context,
translate('edit_account_page.error'),
'Failed to update profile online');
return;
}
}
// Update local account profile
await AccountRepository.instance.editAccountProfile(
widget.superIdentityRecordKey, newProfile);
// Update all conversations with new profile
final updates = <Future<void>>[];
for (final key in activeConversationsBlocMapCubit.state.keys) {
await activeConversationsBlocMapCubit.operateAsync(key,
closure: (cubit) async {
final newLocalConversation =
cubit.state.asData?.value.localConversation.deepCopy();
if (newLocalConversation != null) {
newLocalConversation.profile = newProfile;
updates.add(cubit.input.writeLocalConversation(
conversation: newLocalConversation));
}
});
}
// Wait for updates
await updates.wait;
// XXX: how to do this for non-chat contacts?
} finally {
if (mounted) {
setState(() {
_isInAsyncCall = false;
});
}
}
} on Exception catch (e) {
if (context.mounted) {
await showErrorModal(context,
translate('edit_account_page.error'), 'Exception: $e');
}
}
},
).paddingSymmetric(horizontal: 24, vertical: 8),
).withModalHUD(context, displayModalHUD);
}
}

View file

@ -1,30 +1,28 @@
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:form_builder_validators/form_builder_validators.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';
class NewAccountPage extends StatefulWidget {
const NewAccountPage({super.key});
@override
NewAccountPageState createState() => NewAccountPageState();
State createState() => _NewAccountPageState();
}
class NewAccountPageState extends State<NewAccountPage> {
class _NewAccountPageState extends State<NewAccountPage> {
final _formKey = GlobalKey<FormBuilderState>();
late bool isInAsyncCall = false;
static const String formFieldName = 'name';
static const String formFieldPronouns = 'pronouns';
bool _isInAsyncCall = false;
@override
void initState() {
@ -47,70 +45,17 @@ class NewAccountPageState extends State<NewAccountPage> {
false;
final canSubmit = networkReady;
return FormBuilder(
key: _formKey,
child: ListView(
children: [
Text(translate('new_account_page.header'))
.textStyle(context.headlineSmall)
.paddingSymmetric(vertical: 16),
FormBuilderTextField(
autofocus: true,
name: formFieldName,
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: formFieldPronouns,
maxLength: 64,
decoration:
InputDecoration(labelText: translate('account.form_pronouns')),
textInputAction: TextInputAction.next,
),
Row(children: [
const Spacer(),
Text(translate('new_account_page.instructions'))
.toCenter()
.flexible(flex: 6),
const Spacer(),
]).paddingSymmetric(vertical: 4),
ElevatedButton(
onPressed: !canSubmit
? null
: () async {
if (_formKey.currentState?.saveAndValidate() ?? false) {
setState(() {
isInAsyncCall = true;
});
try {
await onSubmit(_formKey);
} finally {
if (mounted) {
setState(() {
isInAsyncCall = false;
});
}
}
}
},
child: Text(translate(!networkReady
? 'button.waiting_for_network'
: 'new_account_page.create')),
).paddingSymmetric(vertical: 4).alignAtCenterRight(),
],
),
);
return EditProfileForm(
header: translate('new_account_page.header'),
instructions: translate('new_account_page.instructions'),
submitText: translate('new_account_page.create'),
submitDisabledText: translate('button.waiting_for_network'),
onSubmit: !canSubmit ? null : onSubmit);
}
@override
Widget build(BuildContext context) {
final displayModalHUD = isInAsyncCall;
final displayModalHUD = _isInAsyncCall;
return Scaffold(
// resizeToAvoidBottomInset: false,
@ -120,7 +65,7 @@ class NewAccountPageState extends State<NewAccountPage> {
const SignalStrengthMeterWidget(),
IconButton(
icon: const Icon(Icons.settings),
tooltip: translate('app_bar.settings_tooltip'),
tooltip: translate('menu.settings_tooltip'),
onPressed: () async {
await GoRouterHelper(context).push('/settings');
})
@ -132,19 +77,33 @@ class NewAccountPageState extends State<NewAccountPage> {
FocusScope.of(context).unfocus();
try {
final name =
_formKey.currentState!.fields[formFieldName]!.value as String;
final pronouns = _formKey.currentState!.fields[formFieldPronouns]!
final name = _formKey.currentState!
.fields[EditProfileForm.formFieldName]!.value as String;
final pronouns = _formKey
.currentState!
.fields[EditProfileForm.formFieldPronouns]!
.value as String? ??
'';
final newProfileSpec =
NewProfileSpec(name: name, pronouns: pronouns);
final newProfile = proto.Profile()
..name = name
..pronouns = pronouns;
final superSecret = await AccountRepository.instance
.createWithNewSuperIdentity(newProfileSpec);
GoRouterHelper(context).pushReplacement('/new_account/recovery_key',
extra: superSecret);
setState(() {
_isInAsyncCall = true;
});
try {
final superSecret = await AccountRepository.instance
.createWithNewSuperIdentity(newProfile);
GoRouterHelper(context).pushReplacement(
'/new_account/recovery_key',
extra: superSecret);
} finally {
if (mounted) {
setState(() {
_isInAsyncCall = false;
});
}
}
} on Exception catch (e) {
if (context.mounted) {
await showErrorModal(context, translate('new_account_page.error'),
@ -155,10 +114,4 @@ class NewAccountPageState extends State<NewAccountPage> {
).paddingSymmetric(horizontal: 24, vertical: 8),
).withModalHUD(context, displayModalHUD);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('isInAsyncCall', isInAsyncCall));
}
}

View file

@ -0,0 +1,106 @@
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,
});
@override
State createState() => _EditProfileFormState();
final String header;
final String instructions;
final Future<void> Function(GlobalKey<FormBuilderState>)? onSubmit;
final String submitText;
final String submitDisabledText;
@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));
}
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: ListView(
children: [
Text(widget.header)
.textStyle(context.headlineSmall)
.paddingSymmetric(vertical: 16),
FormBuilderTextField(
autofocus: true,
name: EditProfileForm.formFieldName,
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,
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: Text((widget.onSubmit == null)
? widget.submitDisabledText
: widget.submitText),
).paddingSymmetric(vertical: 4).alignAtCenterRight(),
],
),
);
@override
Widget build(BuildContext context) => _editProfileForm(
context,
);
}

View file

@ -48,7 +48,7 @@ class ShowRecoveryKeyPageState extends State<ShowRecoveryKeyPage> {
const SignalStrengthMeterWidget(),
IconButton(
icon: const Icon(Icons.settings),
tooltip: translate('app_bar.settings_tooltip'),
tooltip: translate('menu.settings_tooltip'),
onPressed: () async {
await GoRouterHelper(context).push('/settings');
})