import 'dart:async'; import 'package:async_tools/async_tools.dart'; 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_translate/flutter_translate.dart'; import 'package:go_router/go_router.dart'; import 'package:veilid_support/veilid_support.dart'; import '../../layout/default_app_bar.dart'; import '../../notifications/notifications.dart'; import '../../theme/theme.dart'; import '../../tools/tools.dart'; import '../../veilid_processor/veilid_processor.dart'; import '../account_manager.dart'; import 'edit_profile_form.dart'; const _kDoBackArrow = 'doBackArrow'; class EditAccountPage extends StatefulWidget { const EditAccountPage( {required this.superIdentityRecordKey, required this.initialValue, required this.accountRecord, super.key}); @override State createState() => _EditAccountPageState(); final TypedKey superIdentityRecordKey; final AccountSpec initialValue; final OwnedDHTRecordPointer accountRecord; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties ..add(DiagnosticsProperty( 'superIdentityRecordKey', superIdentityRecordKey)) ..add(DiagnosticsProperty('initialValue', initialValue)) ..add(DiagnosticsProperty( 'accountRecord', accountRecord)); } } class _EditAccountPageState extends WindowSetupState { _EditAccountPageState() : super( titleBarStyle: TitleBarStyle.normal, orientationCapability: OrientationCapability.portraitOnly); EditProfileForm _editAccountForm(BuildContext context) => EditProfileForm( header: translate('edit_account_page.header'), instructions: translate('edit_account_page.instructions'), submitText: translate('button.update'), submitDisabledText: translate('button.waiting_for_network'), onSubmit: _onSubmit, onModifiedState: _onModifiedState, initialValue: widget.initialValue, ); Future _onRemoveAccount() async { final confirmed = await StyledDialog.show( context: context, title: translate('edit_account_page.remove_account_confirm'), child: Column(mainAxisSize: MainAxisSize.min, children: [ Text(translate('edit_account_page.remove_account_confirm_message')) .paddingLTRB(24, 24, 24, 0), Text(translate('confirmation.are_you_sure')).paddingAll(8), Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: () { Navigator.of(context).pop(false); }, child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.cancel, size: 16).paddingLTRB(0, 0, 4, 0), Text(translate('button.no')).paddingLTRB(0, 0, 4, 0) ])), ElevatedButton( onPressed: () { Navigator.of(context).pop(true); }, child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), Text(translate('button.yes')).paddingLTRB(0, 0, 4, 0) ])) ]).paddingAll(24) ])); if (confirmed != null && confirmed && mounted) { // dismiss the keyboard by unfocusing the textfield FocusScope.of(context).unfocus(); try { setState(() { _isInAsyncCall = true; }); try { final success = await AccountRepository.instance.deleteLocalAccount( widget.superIdentityRecordKey, widget.accountRecord); if (mounted) { if (success) { context .read() .info(text: translate('edit_account_page.account_removed')); GoRouterHelper(context).pop(); } else { context.read().error( title: translate('edit_account_page.failed_to_remove_title'), text: translate('edit_account_page.try_again_network')); } } } finally { setState(() { _isInAsyncCall = false; }); } } on Exception catch (e, st) { if (mounted) { await showErrorStacktraceModal( context: context, error: e, stackTrace: st); } } } } Future _onDestroyAccount() async { final confirmed = await StyledDialog.show( context: context, title: translate('edit_account_page.destroy_account_confirm'), child: Column(mainAxisSize: MainAxisSize.min, children: [ Text(translate('edit_account_page.destroy_account_confirm_message')) .paddingLTRB(24, 24, 24, 0), Text(translate( 'edit_account_page.destroy_account_confirm_message_details')) .paddingLTRB(24, 24, 24, 0), Text(translate('confirmation.are_you_sure')).paddingAll(8), Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: () { Navigator.of(context).pop(false); }, child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.cancel, size: 16).paddingLTRB(0, 0, 4, 0), Text(translate('button.no')).paddingLTRB(0, 0, 4, 0) ])), ElevatedButton( onPressed: () { Navigator.of(context).pop(true); }, child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), Text(translate('button.yes')).paddingLTRB(0, 0, 4, 0) ])) ]).paddingAll(24) ])); if (confirmed != null && confirmed && mounted) { // dismiss the keyboard by unfocusing the textfield FocusScope.of(context).unfocus(); try { setState(() { _isInAsyncCall = true; }); try { final success = await AccountRepository.instance.destroyAccount( widget.superIdentityRecordKey, widget.accountRecord); if (mounted) { if (success) { context .read() .info(text: translate('edit_account_page.account_destroyed')); GoRouterHelper(context).pop(); } else { context.read().error( title: translate('edit_account_page.failed_to_destroy_title'), text: translate('edit_account_page.try_again_network')); } } } finally { setState(() { _isInAsyncCall = false; }); } } on Exception catch (e, st) { if (mounted) { await showErrorStacktraceModal( context: context, error: e, stackTrace: st); } } } } void _onModifiedState(bool isModified) { setState(() { _isModified = isModified; }); } Future _onSubmit(AccountSpec accountSpec) async { try { setState(() { _isInAsyncCall = true; }); try { // Look up account cubit for this specific account final perAccountCollectionBlocMapCubit = context.read(); final accountRecordCubit = await perAccountCollectionBlocMapCubit .operate(widget.superIdentityRecordKey, closure: (c) async => c.accountRecordCubit); if (accountRecordCubit == null) { return false; } // Update account profile DHT record // This triggers ConversationCubits to update accountRecordCubit.updateAccount(accountSpec, () async { // Update local account profile await AccountRepository.instance .updateLocalAccount(widget.superIdentityRecordKey, accountSpec); }); return true; } finally { setState(() { _isInAsyncCall = false; }); } } on Exception catch (e, st) { if (mounted) { await showErrorStacktraceModal( context: context, error: e, stackTrace: st); } } return false; } @override Widget build(BuildContext context) { final displayModalHUD = _isInAsyncCall; return StyledScaffold( // resizeToAvoidBottomInset: false, appBar: DefaultAppBar( title: Text(translate('edit_account_page.titlebar')), leading: Navigator.canPop(context) ? IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { singleFuture((this, _kDoBackArrow), () async { if (_isModified) { final ok = await showConfirmModal( context: context, title: translate('confirmation.discard_changes'), text: translate( 'confirmation.are_you_sure_discard')); if (!ok) { return; } } if (context.mounted) { Navigator.pop(context); } }); }) : null, actions: [ const SignalStrengthMeterWidget(), IconButton( icon: const Icon(Icons.settings), tooltip: translate('menu.settings_tooltip'), onPressed: () async { await GoRouterHelper(context).push('/settings'); }) ]), body: SingleChildScrollView( child: Column(children: [ _editAccountForm(context).paddingLTRB(0, 0, 0, 32), OptionBox( instructions: translate('edit_account_page.remove_account_description'), buttonIcon: Icons.person_remove_alt_1, buttonText: translate('edit_account_page.remove_account'), onClick: _onRemoveAccount, ), OptionBox( instructions: translate('edit_account_page.destroy_account_description'), buttonIcon: Icons.person_off, buttonText: translate('edit_account_page.destroy_account'), onClick: _onDestroyAccount, ) ]).paddingSymmetric(horizontal: 24, vertical: 8))) .withModalHUD(context, displayModalHUD); } //////////////////////////////////////////////////////////////////////////// bool _isInAsyncCall = false; bool _isModified = false; }