From 11b6e51523347d96f26590a75d4b52935d3fba5d Mon Sep 17 00:00:00 2001 From: clangenb <37865735+clangenb@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:57:10 +0900 Subject: [PATCH 1/4] [AccountManagePage] display correct community icon for non-current community and hide 0 balances (#1646) * [CommunityIconObserver] pass the particular community store in the constructor. * [AccountManagePage] hide 0 balances * fmt * [AccountManagePage] show the balance for the current community even if it is 0. * fix null error when referencing the community icon * [communityStore] add log when getting community icon * better logs --- .../components/logo/community_icon.dart | 25 ++++--------------- .../common/community_chooser_panel.dart | 16 +++++++++--- .../meetup/ceremony_step3_finish.dart | 5 +++- .../widgets/announcement_card.dart | 8 +++++- .../profile/account/account_manage_page.dart | 9 +++++-- app/lib/store/encointer/encointer.dart | 11 ++++++++ app/lib/store/encointer/encointer.g.dart | 7 ++++++ .../community_store/community_store.dart | 13 ++++++++++ .../community_store/community_store.g.dart | 8 +++++- 9 files changed, 74 insertions(+), 28 deletions(-) diff --git a/app/lib/common/components/logo/community_icon.dart b/app/lib/common/components/logo/community_icon.dart index b6f670658..58b1b905e 100644 --- a/app/lib/common/components/logo/community_icon.dart +++ b/app/lib/common/components/logo/community_icon.dart @@ -1,36 +1,21 @@ import 'package:flutter/material.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:provider/provider.dart'; -import 'package:encointer_wallet/config/consts.dart'; import 'package:encointer_wallet/theme/theme.dart'; -import 'package:encointer_wallet/store/app.dart'; -class CommunityIconObserver extends StatelessWidget { - const CommunityIconObserver({super.key, double? radius}) : radius = radius ?? 10; +class CommunityCircleAvatar extends StatelessWidget { + const CommunityCircleAvatar(this.icon, {super.key, double? radius}) : radius = radius ?? 10; + + final SvgPicture icon; final double radius; @override Widget build(BuildContext context) { - final store = context.watch(); return CircleAvatar( backgroundColor: context.colorScheme.background, radius: radius, - child: Observer( - builder: (_) { - if (store.encointer.community != null && store.encointer.community!.assetsCid != null) { - if (store.encointer.community!.communityIcon != null) { - return SvgPicture.string(store.encointer.community!.communityIcon!); - } else { - return SvgPicture.asset(fallBackCommunityIcon); - } - } else { - return SvgPicture.asset(fallBackCommunityIcon); - } - }, - ), + child: icon, ); } } diff --git a/app/lib/page-encointer/common/community_chooser_panel.dart b/app/lib/page-encointer/common/community_chooser_panel.dart index 5570eaf98..671b97aff 100644 --- a/app/lib/page-encointer/common/community_chooser_panel.dart +++ b/app/lib/page-encointer/common/community_chooser_panel.dart @@ -6,6 +6,7 @@ import 'package:encointer_wallet/common/components/address_icon.dart'; import 'package:encointer_wallet/common/components/logo/community_icon.dart'; import 'package:encointer_wallet/store/app.dart'; import 'package:encointer_wallet/utils/format.dart'; +import 'package:flutter_svg/svg.dart'; /// the CombinedCommunityAndAccountAvatar should be wrapped in an InkWell to provide the callback on a click class CombinedCommunityAndAccountAvatar extends StatefulWidget { @@ -42,7 +43,10 @@ class _CombinedCommunityAndAccountAvatarState extends State(); + final local = Localizations.localeOf(context); final cardStore = context.watch(); return Padding( @@ -39,7 +42,10 @@ class AnnouncementCard extends StatelessWidget { radius: 8, backgroundImage: Assets.images.public.app.provider(), ) - : const CommunityIconObserver(radius: 8), + : CommunityCircleAvatar( + store.encointer.communityIconOrDefault, + radius: 8, + ), ), title: Align( alignment: Alignment.centerRight, diff --git a/app/lib/page/profile/account/account_manage_page.dart b/app/lib/page/profile/account/account_manage_page.dart index 61ef018b5..27fbcabe5 100644 --- a/app/lib/page/profile/account/account_manage_page.dart +++ b/app/lib/page/profile/account/account_manage_page.dart @@ -104,12 +104,17 @@ class _AccountManagePageState extends State { final community = _appStore.encointer.communityStores?[cidFmt]; - if (community == null) { + if (community == null || community.applyDemurrage == null) { // Never happened, but we want to be defensive here to prevent a red screen. Log.e('[AccountManagePage] Communities is null, even though we have a balance entry for it. Fatal app error.'); return Container(); } + if ((community.applyDemurrage!(entry) ?? 0) <= 0.0001 && cidFmt != _appStore.encointer.chosenCid!.toFmtString()) { + Log.p("[AccountManagePage] Don't display community with 0 balance"); + return Container(); + } + final isBootstrapper = _appStore.encointer.community!.bootstrappers != null && _appStore.encointer.community!.bootstrappers!.contains(address); @@ -120,7 +125,7 @@ class _AccountManagePageState extends State { leading: CommunityIcon( store: _appStore, isBootstrapper: isBootstrapper, - icon: const CommunityIconObserver(), + icon: CommunityCircleAvatar(community.icon), ), title: Text(community.name!, style: h3), subtitle: Text(community.symbol!, style: h3), diff --git a/app/lib/store/encointer/encointer.dart b/app/lib/store/encointer/encointer.dart index 2d5fd87ac..0409e7179 100644 --- a/app/lib/store/encointer/encointer.dart +++ b/app/lib/store/encointer/encointer.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter_svg/svg.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:mobx/mobx.dart'; @@ -15,6 +16,7 @@ import 'package:encointer_wallet/store/encointer/sub_stores/bazaar_store/bazaar_ import 'package:encointer_wallet/store/encointer/sub_stores/community_store/community_account_store/community_account_store.dart'; import 'package:encointer_wallet/store/encointer/sub_stores/community_store/community_store.dart'; import 'package:encointer_wallet/store/encointer/sub_stores/encointer_account_store/encointer_account_store.dart'; +import 'package:encointer_wallet/config/consts.dart'; part 'encointer.g.dart'; @@ -176,6 +178,15 @@ abstract class _EncointerStore with Store { return applyDemurrage(communityBalanceEntry); } + @computed + SvgPicture get communityIconOrDefault { + if (community != null) { + return community!.icon; + } else { + return SvgPicture.asset(fallBackCommunityIcon); + } + } + double? applyDemurrage(BalanceEntry? entry) { if (_rootStore.chain.latestHeaderNumber != null && entry != null && diff --git a/app/lib/store/encointer/encointer.g.dart b/app/lib/store/encointer/encointer.g.dart index 608900eba..87b75c604 100644 --- a/app/lib/store/encointer/encointer.g.dart +++ b/app/lib/store/encointer/encointer.g.dart @@ -119,6 +119,12 @@ mixin _$EncointerStore on _EncointerStore, Store { double? get communityBalance => (_$communityBalanceComputed ??= Computed(() => super.communityBalance, name: '_EncointerStore.communityBalance')) .value; + Computed? _$communityIconOrDefaultComputed; + + @override + SvgPicture get communityIconOrDefault => (_$communityIconOrDefaultComputed ??= + Computed(() => super.communityIconOrDefault, name: '_EncointerStore.communityIconOrDefault')) + .value; Computed? _$assigningPhaseStartComputed; @override @@ -448,6 +454,7 @@ communityAccount: ${communityAccount}, account: ${account}, communityBalanceEntry: ${communityBalanceEntry}, communityBalance: ${communityBalance}, +communityIconOrDefault: ${communityIconOrDefault}, assigningPhaseStart: ${assigningPhaseStart}, attestingPhaseStart: ${attestingPhaseStart}, nextRegisteringPhaseStart: ${nextRegisteringPhaseStart}, diff --git a/app/lib/store/encointer/sub_stores/community_store/community_store.dart b/app/lib/store/encointer/sub_stores/community_store/community_store.dart index ba2d5cfc4..2a5a65bba 100644 --- a/app/lib/store/encointer/sub_stores/community_store/community_store.dart +++ b/app/lib/store/encointer/sub_stores/community_store/community_store.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'dart:convert'; +import 'package:encointer_wallet/config/consts.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:mobx/mobx.dart'; @@ -79,13 +81,24 @@ abstract class _CommunityStore with Store { @observable String? communityIcon; + @computed + SvgPicture get icon { + if (communityIcon != null) { + return SvgPicture.string(communityIcon!); + } else { + return SvgPicture.asset(fallBackCommunityIcon); + } + } + double? Function(BalanceEntry)? get applyDemurrage => _applyDemurrage; @action Future getCommunityIcon() async { try { if (assetsCid != null) { + Log.e('[getCommunityIcon] get icon with cid: $assetsCid'); final maybeIcon = await webApi.ipfsApi.getCommunityIcon(assetsCid!); + Log.e('[getCommunityIcon]: got icon: $maybeIcon'); if (maybeIcon != null) communityIcon = maybeIcon; } } catch (e) { diff --git a/app/lib/store/encointer/sub_stores/community_store/community_store.g.dart b/app/lib/store/encointer/sub_stores/community_store/community_store.g.dart index 051ed1447..07bd7576a 100644 --- a/app/lib/store/encointer/sub_stores/community_store/community_store.g.dart +++ b/app/lib/store/encointer/sub_stores/community_store/community_store.g.dart @@ -62,6 +62,11 @@ mixin _$CommunityStore on _CommunityStore, Store { @override String? get assetsCid => (_$assetsCidComputed ??= Computed(() => super.assetsCid, name: '_CommunityStore.assetsCid')).value; + Computed? _$iconComputed; + + @override + SvgPicture get icon => + (_$iconComputed ??= Computed(() => super.icon, name: '_CommunityStore.icon')).value; late final _$metadataAtom = Atom(name: '_CommunityStore.metadata', context: context); @@ -281,7 +286,8 @@ communityAccountStores: ${communityAccountStores}, communityIcon: ${communityIcon}, name: ${name}, symbol: ${symbol}, -assetsCid: ${assetsCid} +assetsCid: ${assetsCid}, +icon: ${icon} '''; } } From 712bbd69d1f28568d2dd5bb1718333342a5551a8 Mon Sep 17 00:00:00 2001 From: clangenb <37865735+clangenb@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:59:32 +0900 Subject: [PATCH 2/4] v1.13.1 --- app/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pubspec.yaml b/app/pubspec.yaml index fdc3f3b6f..4cac34e15 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -14,7 +14,7 @@ description: EncointerWallet made with Flutter. # bump version already while working on new release, bumping build number at the same time # bump build number even more, if needed to clarify what's deployed -version: 1.13.0+880 +version: 1.13.1+881 publish_to: none From 120a5c3271c4e21829f4e104ced335f7e267fc40 Mon Sep 17 00:00:00 2001 From: clangenb <37865735+clangenb@users.noreply.github.com> Date: Wed, 6 Mar 2024 09:41:50 +0800 Subject: [PATCH 3/4] Fix: Refresh balance immediately after starting the app for the first time and when adding a new account (#1654) * remove balanceEntry watchdog overkill * [assets] extract `addBalanceEntryAndReturnDelta` method to encointer account store * [assets] log balance delta * [assets] remove balance watchdog that was very slow and kind of unreliable and update encointer balance with the rest of the state periodically --- app/lib/page/assets/index.dart | 106 ++---------------- app/lib/store/encointer/encointer.dart | 12 ++ .../encointer_account_store.dart | 19 ++++ .../encointer_account_store.g.dart | 12 ++ 4 files changed, 52 insertions(+), 97 deletions(-) diff --git a/app/lib/page/assets/index.dart b/app/lib/page/assets/index.dart index 8fe26d9b4..c3553cfd8 100644 --- a/app/lib/page/assets/index.dart +++ b/app/lib/page/assets/index.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:math'; import 'package:ew_test_keys/ew_test_keys.dart'; @@ -5,9 +6,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:focus_detector/focus_detector.dart'; import 'package:iconsax/iconsax.dart'; -import 'package:pausable_timer/pausable_timer.dart'; import 'package:provider/provider.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart'; import 'package:upgrader/upgrader.dart'; @@ -26,7 +25,6 @@ import 'package:encointer_wallet/utils/repository_provider.dart'; import 'package:encointer_wallet/config/consts.dart'; import 'package:encointer_wallet/models/index.dart'; import 'package:encointer_wallet/modules/modules.dart'; -import 'package:encointer_wallet/models/encointer_balance_data/balance_entry.dart'; import 'package:encointer_wallet/page-encointer/ceremony_box/ceremony_box.dart'; import 'package:encointer_wallet/page-encointer/common/community_chooser_on_map.dart'; import 'package:encointer_wallet/page-encointer/common/community_chooser_panel.dart'; @@ -34,8 +32,6 @@ import 'package:encointer_wallet/page/assets/account_or_community/account_or_com import 'package:encointer_wallet/page/assets/account_or_community/switch_account_or_community.dart'; import 'package:encointer_wallet/page/assets/receive/receive_page.dart'; import 'package:encointer_wallet/page/assets/transfer/transfer_page.dart'; -import 'package:encointer_wallet/service/log/log_service.dart'; -import 'package:encointer_wallet/service/notification/lib/notification.dart'; import 'package:encointer_wallet/service/substrate_api/api.dart'; import 'package:encointer_wallet/service/tx/lib/tx.dart'; import 'package:encointer_wallet/store/account/types/account_data.dart'; @@ -58,7 +54,6 @@ class _AssetsViewState extends State { static const double fractionOfScreenHeight = .7; static const double avatarSize = 70; late PanelController _panelController; - late PausableTimer _balanceWatchdog; late AppSettings _appSettingsStore; late double _panelHeightOpen; final double _panelHeightClosed = 0; @@ -76,7 +71,6 @@ class _AssetsViewState extends State { @override void didChangeDependencies() { _appSettingsStore = context.read(); - _startBalanceWatchdog(); l10n = context.l10n; // Should typically not be higher than panelHeight, but on really small devices // it should not exceed fractionOfScreenHeight x the screen height. @@ -89,32 +83,16 @@ class _AssetsViewState extends State { @override void dispose() { - _balanceWatchdog.cancel(); super.dispose(); } @override Widget build(BuildContext context) { - return FocusDetector( - onFocusLost: () { - Log.d('[home:FocusDetector] Focus Lost.'); - _balanceWatchdog.pause(); - }, - onFocusGained: () { - Log.d('[home:FocusDetector] Focus Gained.'); - if (!widget.store.settings.loading) { - _refreshBalanceAndNotify(); - } - _balanceWatchdog - ..reset() - ..start(); - }, - child: Scaffold( - appBar: _appBar(), - body: RepositoryProvider.of(context).isIntegrationTest - ? _slidingUpPanel(_appBar()) - : _upgradeAlert(_appBar()), - ), + return Scaffold( + appBar: _appBar(), + body: RepositoryProvider.of(context).isIntegrationTest + ? _slidingUpPanel(_appBar()) + : _upgradeAlert(_appBar()), ); } @@ -338,7 +316,7 @@ class _AssetsViewState extends State { }, onAddIconPressed: () { Navigator.pushNamed(context, CommunityChooserOnMap.route).then((_) { - _refreshBalanceAndNotify(); + _refreshEncointerState(); }); }, addIconButtonKey: const Key(EWTestKeys.addCommunity), @@ -350,7 +328,7 @@ class _AssetsViewState extends State { accountOrCommunityData: initAllAccounts(), onTap: (int index) async { await switchAccount(widget.store.account.accountListAll[index]); - _refreshBalanceAndNotify(); + unawaited(_refreshEncointerState()); }, onAddIconPressed: () { Navigator.of(context).pushNamed(AddAccountView.route); @@ -435,76 +413,10 @@ class _AssetsViewState extends State { }); } - void _refreshBalanceAndNotify() { - webApi.encointer.getAllBalances(widget.store.account.currentAddress).then((balances) { - Log.d('[home:refreshBalanceAndNotify] get all balances', 'Assets'); - if (widget.store.encointer.chosenCid == null) { - Log.d('[home:refreshBalanceAndNotify] no community selected', 'Assets'); - return; - } - var activeAccountHasBalance = false; - balances.forEach((cid, balanceEntry) { - final cidStr = cid.toFmtString(); - if (widget.store.encointer.communityStores!.containsKey(cidStr)) { - final community = widget.store.encointer.communityStores![cidStr]!; - final oldBalanceEntry = - widget.store.encointer.accountStores?[widget.store.account.currentAddress]?.balanceEntries[cidStr]; - final demurrageRate = community.demurrage!; - final newBalance = community.applyDemurrage != null ? community.applyDemurrage!(balanceEntry) ?? 0 : 0; - final oldBalance = (community.applyDemurrage != null && oldBalanceEntry != null) - ? community.applyDemurrage!(oldBalanceEntry) ?? 0 - : 0; - - final delta = newBalance - oldBalance; - Log.d('[home:refreshBalanceAndNotify] balance for $cidStr was $oldBalance, changed by $delta', 'Assets'); - if (delta.abs() > demurrageRate) { - widget.store.encointer.accountStores![widget.store.account.currentAddress] - ?.addBalanceEntry(cid, balances[cid]!); - if (delta > demurrageRate) { - final msg = l10n.incomingConfirmed( - delta, - community.metadata!.symbol, - widget.store.account.currentAccount.name, - ); - Log.d('[home:balanceWatchdog] $msg', 'Assets'); - NotificationPlugin.showNotification(45, l10n.fundsReceived, msg, cid: cidStr); - } - } - if (cid == widget.store.encointer.chosenCid) { - activeAccountHasBalance = true; - } - } - }); - if (!activeAccountHasBalance) { - Log.d( - "[home:refreshBalanceAndNotify] didn't get any balance for active account. initialize store balance to zero", - 'Assets', - ); - widget.store.encointer.accountStores![widget.store.account.currentAddress] - ?.addBalanceEntry(widget.store.encointer.chosenCid!, BalanceEntry(0, 0)); - } - }).catchError((Object? e, StackTrace? s) { - Log.e('[home:refreshBalanceAndNotify] WARNING: could not update balance: $e', 'Assets', s); - }); - } - - void _startBalanceWatchdog() { - _balanceWatchdog = PausableTimer( - const Duration(seconds: 12), - () { - Log.d('[balanceWatchdog] triggered', 'Assets'); - - _refreshBalanceAndNotify(); - _balanceWatchdog - ..reset() - ..start(); - }, - )..start(); - } - Future _refreshEncointerState() async { // getCurrentPhase is the root of all state updates. await webApi.encointer.getCurrentPhase(); + await widget.store.encointer.getEncointerBalance(); } } diff --git a/app/lib/store/encointer/encointer.dart b/app/lib/store/encointer/encointer.dart index 0409e7179..4988a0043 100644 --- a/app/lib/store/encointer/encointer.dart +++ b/app/lib/store/encointer/encointer.dart @@ -348,6 +348,7 @@ abstract class _EncointerStore with Store { webApi.encointer.getReputations(), webApi.encointer.getMeetupTime(), webApi.encointer.getMeetupTimeOverride(), + getEncointerBalance(), updateAggregatedAccountData(), ]).timeout(const Duration(seconds: 15)).catchError((Object? e, s) { Log.e('Error executing update state: $e', 'EncointerStore'); @@ -360,6 +361,17 @@ abstract class _EncointerStore with Store { await _updateStateFuture!; } + Future getEncointerBalance() async { + final currentAddress = _rootStore.account.currentAddress; + + if (currentAddress.isEmpty || chosenCid == null) { + Log.d('[getEncointerBalance] address empty or chosenCid == null', 'EncointerStore'); + } + + final balanceEntry = await webApi.encointer.getEncointerBalance(currentAddress, chosenCid!); + _rootStore.encointer.account?.addBalanceEntry(chosenCid!, balanceEntry); + } + Future updateAggregatedAccountData() async { try { final currentPubKey = _rootStore.account.currentAccountPubKey; diff --git a/app/lib/store/encointer/sub_stores/encointer_account_store/encointer_account_store.dart b/app/lib/store/encointer/sub_stores/encointer_account_store/encointer_account_store.dart index 321c35480..f811bec24 100644 --- a/app/lib/store/encointer/sub_stores/encointer_account_store/encointer_account_store.dart +++ b/app/lib/store/encointer/sub_stores/encointer_account_store/encointer_account_store.dart @@ -104,6 +104,25 @@ abstract class _EncointerAccountStore with Store { writeToCache(); } + @action + double addBalanceEntryAndReturnDelta( + CommunityIdentifier cid, + BalanceEntry balanceEntry, + double? Function(BalanceEntry) demurrageFn, + ) { + final cidStr = cid.toFmtString(); + Log.d('balanceEntry $balanceEntry added to cid $cidStr added', 'EncointerAccountStore'); + + final oldBalanceEntry = balanceEntries[cidStr]; + final oldBalance = oldBalanceEntry != null ? demurrageFn(oldBalanceEntry) ?? 0 : 0; + final newBalance = demurrageFn(balanceEntry) ?? 0; + + balanceEntries[cid.toFmtString()] = balanceEntry; + writeToCache(); + + return newBalance - oldBalance; + } + @action Future setReputations(Map reps) async { reputations = reps; diff --git a/app/lib/store/encointer/sub_stores/encointer_account_store/encointer_account_store.g.dart b/app/lib/store/encointer/sub_stores/encointer_account_store/encointer_account_store.g.dart index d27be6801..c9362d88b 100644 --- a/app/lib/store/encointer/sub_stores/encointer_account_store/encointer_account_store.g.dart +++ b/app/lib/store/encointer/sub_stores/encointer_account_store/encointer_account_store.g.dart @@ -175,6 +175,18 @@ mixin _$EncointerAccountStore on _EncointerAccountStore, Store { } } + @override + double addBalanceEntryAndReturnDelta( + CommunityIdentifier cid, BalanceEntry balanceEntry, double? Function(BalanceEntry) demurrageFn) { + final _$actionInfo = _$_EncointerAccountStoreActionController.startAction( + name: '_EncointerAccountStore.addBalanceEntryAndReturnDelta'); + try { + return super.addBalanceEntryAndReturnDelta(cid, balanceEntry, demurrageFn); + } finally { + _$_EncointerAccountStoreActionController.endAction(_$actionInfo); + } + } + @override void purgeReputations() { final _$actionInfo = From eac68f1abdb0916ebdf7e973a47a55584f953013 Mon Sep 17 00:00:00 2001 From: clangenb <37865735+clangenb@users.noreply.github.com> Date: Wed, 6 Mar 2024 09:43:14 +0800 Subject: [PATCH 4/4] v1.13.2 --- app/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 4cac34e15..f2a88dce8 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -14,7 +14,7 @@ description: EncointerWallet made with Flutter. # bump version already while working on new release, bumping build number at the same time # bump build number even more, if needed to clarify what's deployed -version: 1.13.1+881 +version: 1.13.2+882 publish_to: none