mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-30 06:48:49 +01:00
CW-1513/CW-1610: Add account archival and UI improvements (#3600)
* add ability to hide accounts * remove import * merge * merge * fix loading accounts when styleSettings are not saved yet * merge * auto-reformat * small fixes * proper reload on card unhide * block hiding last card * fixup * revert formatting-only changes * liner fixes * fix overflows * card layout as separate class to prevent index errors * fix mini card on addresses page * block opening customizers if wallet is syncing * switch to BlocProvider.value to prevent auto dispose * imports * fix fiat balance * Improve homescreen layout and account access * Add homescreen layout regression tests * improve account management w/ education + archival flows * reorganize wallet-specific settings * preserve account archival during dismissal * fix: align account visuals with design feedback * minor account & archival visual corrections * simplify wallet settings + account UI state * generalise education carousel & dismissal tracking * refactor account UI flows + minor improvements * minor refactors and improvement * pass account balances as Money and format at display * use fresh full balance for account archival check * refactor BalanceCard w/ MoneyText + cleanup * minor card_customizer cleanup * rework card customizer flow --------- Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Robert Malikowski
Omar Hatem
parent
9f1f82fdd4
commit
20f1ceb651
@@ -1,11 +1,8 @@
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_toggle.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/coin_control_page.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/modal_header.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/modal_page_wrapper.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
|
||||
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
|
||||
@@ -75,29 +72,6 @@ class PrivacyPage extends BasePage {
|
||||
_privacySettingsViewModel.setUseLightning(val);
|
||||
}),
|
||||
],
|
||||
"": [
|
||||
if (_privacySettingsViewModel.hasSilentPaymentsScanning)
|
||||
ListItemRegularRow(
|
||||
iconPath: "assets/new-ui/settings_row_icons/silent-payments.svg",
|
||||
keyValue: "silent_payments",
|
||||
label: S.of(context).silent_payments,
|
||||
onTap: () =>
|
||||
Navigator.of(context).pushNamed(Routes.silentPaymentsSettings)),
|
||||
if (_privacySettingsViewModel.hasMWEB)
|
||||
ListItemRegularRow(
|
||||
iconPath: "assets/new-ui/settings_row_icons/mweb.svg",
|
||||
keyValue: "mweb",
|
||||
label: "MWEB",
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.mwebSettings)),
|
||||
if (_privacySettingsViewModel.hasCoinControl)
|
||||
ListItemRegularRow(
|
||||
iconPath: "assets/new-ui/settings_row_icons/coin-control.svg",
|
||||
keyValue: "coin_control",
|
||||
label: "Coin Control",
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList,
|
||||
arguments:
|
||||
CoinControlPageArgs(canEdit: false, coinTypeToSpendFrom: null))),
|
||||
],
|
||||
}),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import "package:cake_wallet/core/execution_state.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/modal_grab_handle.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/src/widgets/alert_with_one_action.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cake_wallet/view_model/wallet_account_list/account_edit_or_create_view_model.dart";
|
||||
import "package:cw_core/generate_name.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter/services.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
|
||||
class AccountCreationModal extends StatefulWidget {
|
||||
const AccountCreationModal({
|
||||
required this.viewModel,
|
||||
super.key,
|
||||
});
|
||||
const AccountCreationModal({required this.viewModel, super.key});
|
||||
|
||||
final WalletAccountEditOrCreateViewModel viewModel;
|
||||
|
||||
@@ -26,14 +22,59 @@ class _AccountCreationModalState extends State<AccountCreationModal> {
|
||||
static const int maxAccountNameLength = 25;
|
||||
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
bool _loading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.text = widget.viewModel.label;
|
||||
_controller.addListener(() {
|
||||
widget.viewModel.label = _controller.text;
|
||||
});
|
||||
bool get _canContinue =>
|
||||
_controller.text.trim().isNotEmpty && _controller.text.length <= maxAccountNameLength;
|
||||
|
||||
Future<void> _generateAccountName() async {
|
||||
final generatedName = await generateName();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() => _controller.text = generatedName.length > maxAccountNameLength
|
||||
? generatedName.substring(0, maxAccountNameLength)
|
||||
: generatedName);
|
||||
}
|
||||
|
||||
Future<void> _save() async {
|
||||
if (_loading || !_canContinue) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _loading = true);
|
||||
widget.viewModel.label = _controller.text;
|
||||
|
||||
late final String errorMessage;
|
||||
try {
|
||||
await widget.viewModel.save();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final state = widget.viewModel.state;
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
Navigator.of(context).pop(true);
|
||||
return;
|
||||
}
|
||||
errorMessage = state is FailureState ? state.error : S.of(context).error_while_processing;
|
||||
} catch (error) {
|
||||
errorMessage = error.toString();
|
||||
}
|
||||
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() => _loading = false);
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertWithOneAction(
|
||||
alertTitle: S.of(dialogContext).error,
|
||||
alertContent: errorMessage,
|
||||
buttonText: S.of(dialogContext).ok,
|
||||
buttonAction: Navigator.of(dialogContext).pop,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -42,105 +83,117 @@ class _AccountCreationModalState extends State<AccountCreationModal> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _onSubmit() async {
|
||||
if (widget.viewModel.state is IsExecutingState) return;
|
||||
if (_controller.text.trim().isEmpty || _controller.text.length > maxAccountNameLength) return;
|
||||
|
||||
await widget.viewModel.save();
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
if (widget.viewModel.state is ExecutedSuccessfullyState) {
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
Widget build(BuildContext context) => PopScope(
|
||||
canPop: !_loading,
|
||||
child: _buildContent(context),
|
||||
);
|
||||
|
||||
Widget _buildContent(BuildContext context) => Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(30))),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
|
||||
),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(context).bottom),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const ModalGrabHandle(),
|
||||
ModalTopBar(
|
||||
title: widget.viewModel.isEdit
|
||||
? S.of(context).edit_account
|
||||
: S.of(context).create_account,
|
||||
title: S.of(context).create_account,
|
||||
trailingIcon: const Icon(Icons.close),
|
||||
trailingSemanticLabel: S.of(context).close,
|
||||
onTrailingPressed: Navigator.of(context).maybePop,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
child: Column(
|
||||
spacing: 50,
|
||||
children: [
|
||||
const SizedBox(),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(18, 32, 18, 24),
|
||||
child: Column(
|
||||
spacing: 24,
|
||||
children: [
|
||||
const CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/account_education/create_account.svg",
|
||||
width: 125,
|
||||
height: 125,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
maxLength: maxAccountNameLength,
|
||||
inputFormatters: [
|
||||
LengthLimitingTextInputFormatter(maxAccountNameLength),
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
hintText: S.of(context).account_name,
|
||||
counterText: "",
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
final generated = await generateName();
|
||||
_controller.text = generated.length > maxAccountNameLength
|
||||
? generated.substring(0, maxAccountNameLength)
|
||||
: generated;
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/randomize.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
Text(
|
||||
S.of(context).account_creation_description,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
maxLength: maxAccountNameLength,
|
||||
inputFormatters: [
|
||||
LengthLimitingTextInputFormatter(maxAccountNameLength)
|
||||
],
|
||||
textInputAction: TextInputAction.done,
|
||||
onChanged: (_) => setState(() {}),
|
||||
decoration: InputDecoration(
|
||||
hintText: S.of(context).account_name,
|
||||
counterText: "",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: _controller,
|
||||
builder: (context, value, _) => Observer(
|
||||
builder: (_) => NewPrimaryButton(
|
||||
onPressed: _onSubmit,
|
||||
text: S.of(context).continue_text,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
disabled: value.text.trim().isEmpty ||
|
||||
value.text.length > maxAccountNameLength,
|
||||
isLoading: widget.viewModel.state is IsExecutingState,
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Semantics(
|
||||
button: true,
|
||||
label: S.of(context).generate_name,
|
||||
onTap: _generateAccountName,
|
||||
child: ExcludeSemantics(
|
||||
child: GestureDetector(
|
||||
onTap: _generateAccountName,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/randomize.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(18, 0, 18, 12),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: NewPrimaryButton(
|
||||
onPressed: _save,
|
||||
text: S.of(context).continue_text,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
isLoading: _loading,
|
||||
disabled: !_canContinue,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user