From a6e4ba11e99b681d2387ed36c1b28905c1418b4c Mon Sep 17 00:00:00 2001 From: brenzi Date: Wed, 18 Sep 2024 07:57:45 +0200 Subject: [PATCH] support system.remark on account manage page (#1698) * support system.remark on account manage page * minor mod * fix review comments * make account manage page scrollable * prevent listView scrolling within scrollable page * fix CI * add AI translations * add AI translations * fix lints * [send_tx] fix: use correct notification text and remove needless onFinish callback --------- Co-authored-by: clangenb <37865735+clangenb@users.noreply.github.com> --- app/lib/l10n/arb/app_de.arb | 7 + app/lib/l10n/arb/app_en.arb | 7 + app/lib/l10n/arb/app_fr.arb | 7 + app/lib/l10n/arb/app_ru.arb | 7 + app/lib/l10n/arb/app_sw.arb | 7 + .../profile/account/account_manage_page.dart | 202 ++++++++++-------- app/lib/page/profile/account/benefits.dart | 1 + app/lib/page/profile/account/remark.dart | 87 ++++++++ .../tx/lib/src/submit_tx_wrappers.dart | 25 +++ .../service/tx/lib/src/tx_notification.dart | 5 + 10 files changed, 262 insertions(+), 93 deletions(-) create mode 100644 app/lib/page/profile/account/remark.dart diff --git a/app/lib/l10n/arb/app_de.arb b/app/lib/l10n/arb/app_de.arb index 61c1241fc..d41ea8bea 100644 --- a/app/lib/l10n/arb/app_de.arb +++ b/app/lib/l10n/arb/app_de.arb @@ -231,6 +231,13 @@ "registerUntil": "Registriere dich vor dem", "remainingNewbieTicketsAsBootStrapper": "Verbleibende Newbie Tickets als Bootstrapper:", "remainingNewbieTicketsAsReputable": "Verbleibende Newbie Tickets als Reputable:", + "remarkNotificationTitle": "Notiz eingereicht", + "remarkNotificationBody": "Sie haben eine Notiz eingereicht.", + "remarks": "Onchain Notizen", + "remarksButton": "öffentliche Notiz einreichen", + "remarksExplain": "Sie können eine Notiz an das Netzwerk senden. Diese Notiz wird öffentlich und unveränderlich sein. Sie kann von jemensch gelesen und authentifiziert werden, da sie digital von Dir signiert wird.", + "remarksNote": "Notiz", + "remarksSubmit": "Notiz einreichen", "reputableContent": "Du hast deine Reputation genutzt um einen garantierten Platz zu erhalten. Achtung: Solltest Du dich anmelden, aber nicht zur Versammlung erscheinen, wirst du wieder ein Newbie.", "reputableTitle": "Als Reputable registriert. Dein Platz ist garantiert", "reputationAlreadyCommittedTitle": "Reputation bereits benutzt", diff --git a/app/lib/l10n/arb/app_en.arb b/app/lib/l10n/arb/app_en.arb index 0098cc6b4..54eabc695 100644 --- a/app/lib/l10n/arb/app_en.arb +++ b/app/lib/l10n/arb/app_en.arb @@ -231,6 +231,13 @@ "registerUntil": "Register before", "remainingNewbieTicketsAsBootStrapper": "Remaining newbie tickets as bootsrapper:", "remainingNewbieTicketsAsReputable": "Remaining newbie tickets as reputable:", + "remarkNotificationBody": "You have submitted a note.", + "remarkNotificationTitle": "note submitted", + "remarks": "Onchain Remarks", + "remarksButton": "submit public note", + "remarksExplain": "You can submit a note to the network. This note will be public and immutable. It can be read and authenticated by everyone as it will be digitally signed by you.", + "remarksNote": "Note", + "remarksSubmit": "Submit note", "reputableContent": "You used your reputation to get a guaranteed seat. Caution: Should you register, but not show up at the cycle, you become a newbie again.", "reputableTitle": "Registered as reputable - your seat is guaranteed", "reputationAlreadyCommittedTitle": "Reputation already used", diff --git a/app/lib/l10n/arb/app_fr.arb b/app/lib/l10n/arb/app_fr.arb index d6c3f9ecd..fa3c6eb14 100644 --- a/app/lib/l10n/arb/app_fr.arb +++ b/app/lib/l10n/arb/app_fr.arb @@ -231,6 +231,13 @@ "registerUntil": "Inscrive-toi avant le", "remainingNewbieTicketsAsBootStrapper": "Novice-Tickets restants en tant que bootstrapper", "remainingNewbieTicketsAsReputable": "Comme Reputable les billets pour Novice restants:", + "remarkNotificationTitle": "Note soumise", + "remarkNotificationBody": "Vous avez soumis une note.", + "remarks": "Remarques en chaine", + "remarksButton": "soumettre une note publique", + "remarksExplain": "Vous pouvez soumettre une note au réseau. Cette note sera publique et immuable. Elle peut être lue et authentifiée par tout le monde car elle sera signée numériquement par toi.", + "remarksNote": "Note", + "remarksSubmit": "Soumettre la note", "reputableContent": "\"Tu as utilisé ta réputation pour obtenir une place garantie. Attention : si tu t'enregistres, mais que tu ne te présentes pas à la réunion, tu redeviens un Novice\".", "reputableTitle": "Enregistré en tant que Reputable. Ta place est garantie", "reputationAlreadyCommittedTitle": "Réputation déjà utilisée", diff --git a/app/lib/l10n/arb/app_ru.arb b/app/lib/l10n/arb/app_ru.arb index fc3f28799..9e688e68f 100644 --- a/app/lib/l10n/arb/app_ru.arb +++ b/app/lib/l10n/arb/app_ru.arb @@ -231,6 +231,13 @@ "registerUntil": "Зарегистрируйтесь до", "remainingNewbieTicketsAsBootStrapper": "Оставшиеся билеты для новичков Бутстреппера:", "remainingNewbieTicketsAsReputable": "Оставшиеся билеты для новичков Уважаемого:", + "remarkNotificationBody": "Вы отправили заметку.", + "remarkNotificationTitle": "заметка отправлена", + "remarks": "Замечания в блокчейне", + "remarksButton": "отправить публичную заметку", + "remarksExplain": "Вы можете отправить заметку в сеть. Эта заметка будет публичной и неизменяемой. Её могут прочитать и аутентифицировать все, так как она будет цифрово подписана вами.", + "remarksNote": "Заметка", + "remarksSubmit": "Отправить заметку", "reputableContent": "Вы воспользовались своей репутаций для получения гарантированного места. Внимание: Если вы зарегистрируетесь, но не явитесь на цикл, вы снова станете новичком.", "reputableTitle": "Зарегистрирован в качестве Уважаемого - ваше место гарантировано.", "reputationAlreadyCommittedTitle": "Репутация уже использована", diff --git a/app/lib/l10n/arb/app_sw.arb b/app/lib/l10n/arb/app_sw.arb index 3bf0fd567..ba7a0dfca 100644 --- a/app/lib/l10n/arb/app_sw.arb +++ b/app/lib/l10n/arb/app_sw.arb @@ -232,6 +232,13 @@ "registerUntil": "Jisajili kabla", "remainingNewbieTicketsAsBootStrapper": "Tiketi za wanachama wapya/wageni zilizobaki kama mwanachama anzilishi:", "remainingNewbieTicketsAsReputable": "Tiketi za wanachama wageni zilizobaki kama mwanachama hai:", + "remarkNotificationBody": "Umetuma kumbuka.", + "remarkNotificationTitle": "kumbuka imetumwa", + "remarks": "Maoni ya Onchain", + "remarksButton": "tuma kumbuka ya umma", + "remarksExplain": "Unaweza kutuma kumbuka kwenye mtandao. Kumbuka hii itakuwa ya umma na isiyobadilika. Inaweza kusomwa na kuthibitishwa na kila mtu kwani itasainiwa kidijitali na wewe.", + "remarksNote": "Kumbuka", + "remarksSubmit": "Tuma kumbuka", "reputableContent": "Umetumia sifa yako kupata kiti kilicho hakikishiwa. Tahadhari: Ikiwa utajiandikisha lakini usijitokeze katika mzunguko, utarudi kuwa mwanachama mpya/mgeni tena..", "reputableTitle": "Umejisajili kama mwenye mwanachama hai - kiti chako kimehakikishiwa", "reputationAlreadyCommittedTitle": "Hadhi yako imetumika", diff --git a/app/lib/page/profile/account/account_manage_page.dart b/app/lib/page/profile/account/account_manage_page.dart index e2cee3cf4..3df4225c2 100644 --- a/app/lib/page/profile/account/account_manage_page.dart +++ b/app/lib/page/profile/account/account_manage_page.dart @@ -1,3 +1,4 @@ +import 'package:encointer_wallet/page/profile/account/remark.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -169,7 +170,6 @@ class _AccountManagePageState extends State { _nameCtrl = TextEditingController(text: accountToBeEdited.name); _nameCtrl!.selection = TextSelection.fromPosition(TextPosition(offset: _nameCtrl!.text.length)); - // Not an ideal practice, but we only release a dev-version of the faucet, and cleanup can be later. Widget benefits() { if (faucets == null) { return appConfig.isIntegrationTest ? const SizedBox.shrink() : const CupertinoActivityIndicator(); @@ -192,6 +192,16 @@ class _AccountManagePageState extends State { ); } + Widget remarks() { + return Remarks( + store, + userAddress: Address( + pubkey: AddressUtils.pubKeyHexToPubKey(accountToBeEditedPubKey), + prefix: store.settings.currentNetwork.ss58(), + ), + ); + } + return Observer( builder: (_) => Scaffold( appBar: AppBar( @@ -232,54 +242,63 @@ class _AccountManagePageState extends State { ], ), body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - const SizedBox(height: 20), - if (!isKeyboard) - AddressIcon( - addressSS58, - accountToBeEditedPubKey, - size: 130, - ), - Text( - addressSS58, - key: const Key(EWTestKeys.accountPublicKey), - // Text only read `addressSS58` for integration test - style: const TextStyle(fontSize: 2, color: Colors.transparent), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - Fmt.address(addressSS58)!, - style: const TextStyle(fontSize: 20), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - IconButton( - icon: const Icon(Iconsax.copy), - color: context.colorScheme.secondary, - onPressed: () => UI.copyAndNotify(context, addressSS58), - ), - ], - ), - Text(l10n.communities, style: h3Grey, textAlign: TextAlign.left), - ListView.builder( - shrinkWrap: true, - itemCount: store.encointer.accountStores!.containsKey(addressSS58) - ? store.encointer.accountStores![addressSS58]?.balanceEntries.length ?? 0 - : 0, - itemBuilder: (BuildContext context, int index) => _getBalanceEntryListTile( - index, - addressSS58, + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + const SizedBox(height: 20), + if (!isKeyboard) + AddressIcon( + addressSS58, + accountToBeEditedPubKey, + size: 130, + ), + Text( + addressSS58, + key: const Key(EWTestKeys.accountPublicKey), + style: const TextStyle(fontSize: 2, color: Colors.transparent), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + Fmt.address(addressSS58)!, + style: const TextStyle(fontSize: 20), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + IconButton( + icon: const Icon(Iconsax.copy), + color: context.colorScheme.secondary, + onPressed: () => UI.copyAndNotify(context, addressSS58), + ), + ], + ), + Text(l10n.communities, style: h3Grey, textAlign: TextAlign.left), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: store.encointer.accountStores!.containsKey(addressSS58) + ? store.encointer.accountStores![addressSS58]?.balanceEntries.length ?? 0 + : 0, + itemBuilder: (BuildContext context, int index) => _getBalanceEntryListTile( + index, + addressSS58, + ), + ), + benefits(), + remarks(), + const SizedBox(height: 20), + ], ), ), - benefits(), - const Spacer(), - DecoratedBox( - // width: double.infinity, + ), + Padding( + padding: const EdgeInsets.all(16), + child: DecoratedBox( decoration: BoxDecoration( gradient: AppColors.primaryGradient(context), borderRadius: BorderRadius.circular(20), @@ -289,7 +308,7 @@ class _AccountManagePageState extends State { ElevatedButton( key: const Key(EWTestKeys.goToAccountShare), style: ElevatedButton.styleFrom( - padding: const EdgeInsets.all(16), // make splash animation as high as the container + padding: const EdgeInsets.all(16), backgroundColor: Colors.transparent, foregroundColor: Colors.white, shadowColor: Colors.transparent, @@ -310,55 +329,52 @@ class _AccountManagePageState extends State { ), const Spacer(), PopupMenuButton( - offset: const Offset(-10, -150), - icon: const Icon( - Iconsax.more, - key: Key(EWTestKeys.popupMenuAccountTrashExport), - color: Colors.white, - ), - color: context.colorScheme.background, - padding: const EdgeInsets.all(20), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - onSelected: (AccountAction accountAction) { - return switch (accountAction) { - AccountAction.delete => _onDeleteAccount(context, accountToBeEdited), - AccountAction.export => _showPasswordDialog(context, accountToBeEdited), - }; - }, - itemBuilder: (BuildContext context) => [ - AccountActionItemData( - accountAction: AccountAction.delete, - icon: Iconsax.trash, - title: l10n.deleteAccount, - ), - AccountActionItemData( - accountAction: AccountAction.export, - icon: Iconsax.export, - title: l10n.exportAccount), - ] - .map((AccountActionItemData data) => PopupMenuItem( - key: Key(data.accountAction.name), - value: data.accountAction, - // https://github.com/flutter/flutter/issues/31247 as soon as we use a newer flutter version we might be able to add this to our theme.dart - child: ListTileTheme( - textColor: context.colorScheme.secondary, - iconColor: context.colorScheme.secondary, - child: ListTile( - minLeadingWidth: 0, - title: Text(data.title), - leading: Icon(data.icon), - ), - ), - )) - .toList() //>, + offset: const Offset(-10, -150), + icon: const Icon( + Iconsax.more, + key: Key(EWTestKeys.popupMenuAccountTrashExport), + color: Colors.white, + ), + color: context.colorScheme.background, + padding: const EdgeInsets.all(20), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + onSelected: (AccountAction accountAction) { + return switch (accountAction) { + AccountAction.delete => _onDeleteAccount(context, accountToBeEdited), + AccountAction.export => _showPasswordDialog(context, accountToBeEdited), + }; + }, + itemBuilder: (BuildContext context) => [ + AccountActionItemData( + accountAction: AccountAction.delete, + icon: Iconsax.trash, + title: l10n.deleteAccount, ), + AccountActionItemData( + accountAction: AccountAction.export, icon: Iconsax.export, title: l10n.exportAccount), + ] + .map((AccountActionItemData data) => PopupMenuItem( + key: Key(data.accountAction.name), + value: data.accountAction, + child: ListTileTheme( + textColor: context.colorScheme.secondary, + iconColor: context.colorScheme.secondary, + child: ListTile( + minLeadingWidth: 0, + title: Text(data.title), + leading: Icon(data.icon), + ), + ), + )) + .toList(), + ), ], ), ), - ], - ), + ), + ], ), ), ), diff --git a/app/lib/page/profile/account/benefits.dart b/app/lib/page/profile/account/benefits.dart index 6219975f1..58042448b 100644 --- a/app/lib/page/profile/account/benefits.dart +++ b/app/lib/page/profile/account/benefits.dart @@ -52,6 +52,7 @@ class Benefits extends StatelessWidget { ), ListView.builder( shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), itemCount: faucets.length, itemBuilder: (BuildContext context, int index) { final faucetPubKeyHex = faucets.keys.elementAt(index); diff --git a/app/lib/page/profile/account/remark.dart b/app/lib/page/profile/account/remark.dart new file mode 100644 index 000000000..e9123f3ef --- /dev/null +++ b/app/lib/page/profile/account/remark.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; + +import 'package:encointer_wallet/store/app.dart'; +import 'package:encointer_wallet/l10n/l10.dart'; +import 'package:encointer_wallet/theme/theme.dart'; +import 'package:ew_keyring/ew_keyring.dart'; + +import 'package:encointer_wallet/service/substrate_api/api.dart'; +import 'package:encointer_wallet/service/tx/lib/tx.dart'; + +class Remarks extends StatelessWidget { + const Remarks( + this.store, { + required this.userAddress, + super.key, + }); + + final AppStore store; + + final Address userAddress; + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final titleLarge = context.titleLarge.copyWith(fontSize: 19, color: AppColors.encointerGrey); + + return Column( + children: [ + Text(l10n.remarks, style: titleLarge, textAlign: TextAlign.left), + Text( + l10n.remarksExplain, + textAlign: TextAlign.left, + ), + ElevatedButton( + onPressed: () => _showRemarkDialog(context), + child: Text(l10n.remarksButton), + ), + ], + ); + } + + void _showRemarkDialog(BuildContext context) { + final remarkController = TextEditingController(); + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(context.l10n.remarksSubmit), + content: TextField( + controller: remarkController, + decoration: InputDecoration(hintText: context.l10n.remarksNote), + ), + actions: [ + TextButton( + child: Text(context.l10n.cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text(context.l10n.remarksSubmit), + onPressed: () async { + final remark = remarkController.text; + if (remark.isNotEmpty) { + await _submitRemarkTx(context, remark); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + Future _submitRemarkTx(BuildContext context, String remark) async { + return submitRemark( + context, + store, + webApi, + store.account.getKeyringAccount(store.account.currentAccountPubKey!), + remark, + txPaymentAsset: store.encointer.getTxPaymentAsset(store.encointer.chosenCid), + ); + } +} diff --git a/app/lib/service/tx/lib/src/submit_tx_wrappers.dart b/app/lib/service/tx/lib/src/submit_tx_wrappers.dart index 6445aa41d..ddc526145 100644 --- a/app/lib/service/tx/lib/src/submit_tx_wrappers.dart +++ b/app/lib/service/tx/lib/src/submit_tx_wrappers.dart @@ -89,6 +89,31 @@ Future submitClaimRewards( ); } +Future submitRemark( + BuildContext context, + AppStore store, + Api api, + KeyringAccount signer, + String remark, { + required CommunityIdentifier? txPaymentAsset, +}) async { + final remarkList = remark.codeUnits; + final call = api.encointer.encointerKusama.tx.system.remarkWithEvent(remark: remarkList); + final xt = await TxBuilder(api.provider).createSignedExtrinsic( + signer.pair, + call, + paymentAsset: txPaymentAsset?.toPolkadart(), + ); + + return submitTx( + context, + store, + api, + OpaqueExtrinsic(xt), + TxNotification.remark(context.l10n), + ); +} + Future submitEndorseNewcomer( BuildContext context, AppStore store, diff --git a/app/lib/service/tx/lib/src/tx_notification.dart b/app/lib/service/tx/lib/src/tx_notification.dart index a32abe50b..6720719d9 100644 --- a/app/lib/service/tx/lib/src/tx_notification.dart +++ b/app/lib/service/tx/lib/src/tx_notification.dart @@ -17,6 +17,11 @@ class TxNotification { body: l10n.claimRewardsNotificationBody, ); + factory TxNotification.remark(AppLocalizations l10n) => TxNotification( + title: l10n.remarkNotificationTitle, + body: l10n.remarkNotificationBody, + ); + factory TxNotification.democracyVote(AppLocalizations l10n) => TxNotification( title: l10n.democracyVotedNotificationTitle, body: l10n.democracyVotedNotificationTitle,