From deced1f3933fa1ed17cc78a70da1504a7574964a Mon Sep 17 00:00:00 2001 From: kumulynja Date: Mon, 13 Jan 2025 19:03:02 +0100 Subject: [PATCH 1/2] use streams to get updates in wallet services and WalletServiceData --- lib/_repository/app_wallets_repository.dart | 9 ++ lib/home/bloc/home_bloc.dart | 54 +++++++++- lib/home/bloc/home_event.dart | 10 +- lib/home/bloc/home_state.dart | 104 ++++++++++---------- lib/home/listeners.dart | 39 +++----- 5 files changed, 130 insertions(+), 86 deletions(-) diff --git a/lib/_repository/app_wallets_repository.dart b/lib/_repository/app_wallets_repository.dart index 25cacb6a..01defd9c 100644 --- a/lib/_repository/app_wallets_repository.dart +++ b/lib/_repository/app_wallets_repository.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:bb_mobile/_model/swap.dart'; import 'package:bb_mobile/_model/transaction.dart'; import 'package:bb_mobile/_model/wallet.dart'; @@ -11,6 +13,11 @@ class AppWalletsRepository { final WalletsStorageRepository _walletsStorageRepository; final List _walletServices = []; + final StreamController> _walletsController = + StreamController.broadcast(); + + Stream> get wallets => + _walletsController.stream.asBroadcastStream(); Future getWalletsFromStorage() async { final (wallets, err) = await _walletsStorageRepository.readAllWallets(); @@ -28,6 +35,7 @@ class AppWalletsRepository { .map((_) => createWalletService(wallet: _, fromStorage: true)) .toList(), ); + _walletsController.add(_walletServices); } List get allWallets => _walletServices.map((_) => _.wallet).toList(); @@ -46,6 +54,7 @@ class AppWalletsRepository { void deleteWallet(String id) { _walletServices.removeWhere((_) => _.wallet.id == id); + _walletsController.add(_walletServices); } List walletServiceFromNetwork(BBNetwork network) => diff --git a/lib/home/bloc/home_bloc.dart b/lib/home/bloc/home_bloc.dart index 8b865b4b..5275c045 100644 --- a/lib/home/bloc/home_bloc.dart +++ b/lib/home/bloc/home_bloc.dart @@ -2,8 +2,10 @@ import 'dart:async'; import 'package:bb_mobile/_repository/app_wallets_repository.dart'; import 'package:bb_mobile/_repository/network_repository.dart'; +import 'package:bb_mobile/_repository/wallet_service.dart'; import 'package:bb_mobile/home/bloc/home_event.dart'; import 'package:bb_mobile/home/bloc/home_state.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class HomeBloc extends Bloc { @@ -19,6 +21,11 @@ class HomeBloc extends Bloc { on(_onUpdatedNotifier); on(_onLoadWalletsForNetwork); on(_onWalletUpdated); + + on(_onWalletServicesUpdated); + + _walletServicesSubscription = _appWalletsRepository.wallets + .listen((walletServices) => add(WalletServicesUpdated(walletServices))); // on( // (event, emit) async { // print('wallets updated'); @@ -38,6 +45,43 @@ class HomeBloc extends Bloc { final AppWalletsRepository _appWalletsRepository; final NetworkRepository _networkRepository; + late StreamSubscription _walletServicesSubscription; + final Map _walletServiceDataUpdateSubscriptions = + {}; + + @override + Future close() { + _walletServicesSubscription.cancel(); + for (final sub in _walletServiceDataUpdateSubscriptions.values) { + sub.cancel(); + } + return super.close(); + } + + Future _onWalletServicesUpdated( + WalletServicesUpdated event, + Emitter emit, + ) async { + debugPrint('wallet services updated: ${event.walletServices.length}'); + final walletServicesData = event.walletServices + .map((_) => WalletServiceData(wallet: _.wallet)) + .toList(); + emit(state.copyWith(wallets: walletServicesData)); + + // Listen to wallet data updates + for (final ws in event.walletServices) { + if (_walletServiceDataUpdateSubscriptions.containsKey(ws.wallet.id)) { + _walletServiceDataUpdateSubscriptions[ws.wallet.id]!.cancel(); + } + _walletServiceDataUpdateSubscriptions[ws.wallet.id] = + ws.dataStream.listen( + (data) { + debugPrint('wallet data updated from stream: ${data.wallet.id}'); + add(WalletUpdated(data)); + }, + ); + } + } Future _onLoadWalletsFromStorage( LoadWalletsFromStorage event, @@ -46,7 +90,8 @@ class HomeBloc extends Bloc { emit(state.copyWith(loadingWallets: true)); await _appWalletsRepository.getWalletsFromStorage(); final wallets = _appWalletsRepository.allWallets; - emit(state.copyWith(wallets: wallets)); + emit(state.copyWith( + wallets: wallets.map((_) => WalletServiceData(wallet: _)).toList())); await _appWalletsRepository .loadAllInNetwork(_networkRepository.getBBNetwork); _appWalletsRepository.syncAllInNetwork(_networkRepository.getBBNetwork); @@ -97,11 +142,12 @@ class HomeBloc extends Bloc { WalletUpdated event, Emitter emit, ) { - final wallet = event.wallet; + debugPrint('wallet updated: ${event.walletData.wallet.id}'); + final walletData = event.walletData; final wallets = state.wallets.toList(); - final idx = wallets.indexWhere((w) => w.id == wallet.id); + final idx = wallets.indexWhere((w) => w.wallet.id == walletData.wallet.id); if (idx == -1) return; - wallets[idx] = wallet; + wallets[idx] = walletData; emit(state.copyWith(wallets: wallets)); } } diff --git a/lib/home/bloc/home_event.dart b/lib/home/bloc/home_event.dart index fce8252c..410c39dd 100644 --- a/lib/home/bloc/home_event.dart +++ b/lib/home/bloc/home_event.dart @@ -1,4 +1,5 @@ import 'package:bb_mobile/_model/wallet.dart'; +import 'package:bb_mobile/_repository/wallet_service.dart'; class HomeEvent {} @@ -23,8 +24,13 @@ class LoadWalletsForNetwork extends HomeEvent { } class WalletUpdated extends HomeEvent { - WalletUpdated(this.wallet); - final Wallet wallet; + WalletUpdated(this.walletData); + final WalletServiceData walletData; +} + +class WalletServicesUpdated extends HomeEvent { + WalletServicesUpdated(this.walletServices); + final List walletServices; } class WalletsSubscribe extends HomeEvent {} diff --git a/lib/home/bloc/home_state.dart b/lib/home/bloc/home_state.dart index 75054621..6f21a992 100644 --- a/lib/home/bloc/home_state.dart +++ b/lib/home/bloc/home_state.dart @@ -1,6 +1,7 @@ import 'package:bb_mobile/_model/swap.dart'; import 'package:bb_mobile/_model/transaction.dart'; import 'package:bb_mobile/_model/wallet.dart'; +import 'package:bb_mobile/_repository/wallet_service.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'home_state.freezed.dart'; @@ -8,7 +9,7 @@ part 'home_state.freezed.dart'; @freezed class HomeState with _$HomeState { const factory HomeState({ - @Default([]) List wallets, + @Default([]) List wallets, // List? tempwallets, // List? wallets, @Default(true) bool loadingWallets, @@ -23,49 +24,44 @@ class HomeState with _$HomeState { }) = _HomeState; const HomeState._(); - bool hasWallets() => - !loadingWallets && wallets != null && wallets!.isNotEmpty; + bool hasWallets() => !loadingWallets && wallets.isNotEmpty; // List walletsFromNetwork(BBNetwork network) => // wallets?.where((wallet) => wallet.network == network).toList().reversed.toList() ?? []; - bool hasMainWallets() => wallets?.any((_) => _.mainWallet) ?? false; + bool hasMainWallets() => wallets.any((_) => _.wallet.mainWallet); List walletsFromNetwork(BBNetwork network) { - final blocs = wallets - ?.where((_) => _.network == network) - //.toList() - //.reversed - .toList() ?? - []; + final walletsData = wallets + .where((_) => _.wallet.network == network) + //.toList() + //.reversed + .toList(); - return blocs; + return walletsData.map((e) => e.wallet).toList(); } List walletsFromNetworkExcludeWatchOnly(BBNetwork network) { - final blocs = wallets - ?.where( - (walletBloc) => - walletBloc.network == network && - walletBloc.watchOnly() == false, - ) - .toList() ?? - []; - - return blocs; + final data = wallets + .where( + (d) => d.wallet.network == network && d.wallet.watchOnly() == false, + ) + .toList(); + + return data.map((e) => e.wallet).toList(); } List walletsNotMainFromNetwork(BBNetwork network) { final blocs = wallets - ?.where( - (wallet) => wallet.network == network && !wallet.mainWallet, - ) - .toList() - .reversed - .toList() ?? - []; - - return blocs; + .where( + (wallet) => + wallet.wallet.network == network && !wallet.wallet.mainWallet, + ) + .toList() + .reversed + .toList(); + + return blocs.map((e) => e.wallet).toList(); } int lenWalletsFromNetwork(BBNetwork network) => @@ -113,12 +109,11 @@ class HomeState with _$HomeState { } Wallet? getWalletFromTx(Transaction tx) { - if (wallets == null) return null; - - for (final walletBloc in wallets!) { + for (final walletBloc in wallets) { final wallet = walletBloc; - if (wallet.transactions.indexWhere((t) => t.txid == tx.txid) != -1) { - return walletBloc; + if (wallet.wallet.transactions.indexWhere((t) => t.txid == tx.txid) != + -1) { + return walletBloc.wallet; } } @@ -126,15 +121,13 @@ class HomeState with _$HomeState { } Wallet? getWalletFromSwapTx(SwapTx swaptx) { - if (wallets == null) return null; - - for (final walletBloc in wallets!) { + for (final walletBloc in wallets) { final wallet = walletBloc; - if (wallet.transactions.indexWhere( + if (wallet.wallet.transactions.indexWhere( (t) => t.swapTx?.id == swaptx.id, ) != -1) { - return walletBloc; + return walletBloc.wallet; } } @@ -162,9 +155,9 @@ class HomeState with _$HomeState { // if (walletIdx == -1) return null; // final wallet = wallets![walletIdx]; // final wallets = walletsFromNetwork(wallet.network); - final idx = wallets?.indexWhere((w) => id == w.id); - if (idx == -1 || idx == null) return null; - return wallets![idx]; + final idx = wallets.indexWhere((w) => id == w.wallet.id); + if (idx == -1) return null; + return wallets[idx].wallet; } Wallet? getFirstWithSpendableAndBalance(BBNetwork network, {int amt = 0}) { @@ -182,18 +175,19 @@ class HomeState with _$HomeState { } SwapTx? getSwapTxById(String id) { - for (final walletBloc in wallets!) { + for (final walletBloc in wallets) { final wallet = walletBloc; - if (wallet.swaps.isEmpty) continue; - final idx = wallet.swaps.indexWhere((_) => _.id == id); - if (idx != -1) return wallet.swaps[idx]; + if (wallet.wallet.swaps.isEmpty) continue; + final idx = wallet.wallet.swaps.indexWhere((_) => _.id == id); + if (idx != -1) return wallet.wallet.swaps[idx]; } - for (final walletBloc in wallets!) { + for (final walletBloc in wallets) { final wallet = walletBloc; - if (wallet.transactions.isEmpty) continue; - final idx = wallet.transactions.indexWhere((_) => _.swapTx?.id == id); - if (idx != -1) return wallet.transactions[idx].swapTx; + if (wallet.wallet.transactions.isEmpty) continue; + final idx = + wallet.wallet.transactions.indexWhere((_) => _.swapTx?.id == id); + if (idx != -1) return wallet.wallet.transactions[idx].swapTx; } return null; @@ -440,10 +434,12 @@ class HomeState with _$HomeState { } Wallet? findWalletWithSameFngr(Wallet wallet) { - for (final wb in wallets!) { + for (final wb in wallets) { final w = wb; - if (w.id == wallet.id) continue; - if (w.sourceFingerprint == wallet.sourceFingerprint) return wb; + if (w.wallet.id == wallet.id) continue; + if (w.wallet.sourceFingerprint == wallet.sourceFingerprint) { + return wb.wallet; + } } return null; } diff --git a/lib/home/listeners.dart b/lib/home/listeners.dart index 77c9539c..66570a2c 100644 --- a/lib/home/listeners.dart +++ b/lib/home/listeners.dart @@ -2,7 +2,6 @@ import 'package:bb_mobile/_pkg/logger.dart'; import 'package:bb_mobile/_pkg/payjoin/listeners.dart'; import 'package:bb_mobile/_pkg/payjoin/manager.dart'; import 'package:bb_mobile/home/bloc/home_bloc.dart'; -import 'package:bb_mobile/home/bloc/home_event.dart'; import 'package:bb_mobile/home/bloc/home_state.dart'; import 'package:bb_mobile/home/home_page.dart'; import 'package:bb_mobile/locator.dart'; @@ -51,7 +50,7 @@ class _HomeWalletsSetupListenerState extends State { final wallets = context.read().state.wallets; if (wallets.isNotEmpty) { context.read().updateWalletBlocs([ - for (final w in wallets) createOrRetreiveWalletBloc(w.id), + for (final w in wallets) createOrRetreiveWalletBloc(w.wallet.id), ]); } super.initState(); @@ -67,7 +66,8 @@ class _HomeWalletsSetupListenerState extends State { if (state.wallets.isEmpty) return; context.read().updateWalletBlocs([ - for (final w in state.wallets) createOrRetreiveWalletBloc(w.id), + for (final w in state.wallets) + createOrRetreiveWalletBloc(w.wallet.id), ]); // final walletBlocs = createWalletBlocs(state.tempwallets!); // context.read().updateWalletBlocs( // createWalletBlocs(state.tempwallets!), @@ -97,23 +97,23 @@ class WalletBlocListeners extends StatelessWidget { } final mainWalletBloc = wallets.firstWhere( (bloc) => - bloc.mainWallet == true && - !bloc.watchOnly() && - bloc.isSecure() && - bloc.isActive(), + bloc.wallet.mainWallet == true && + !bloc.wallet.watchOnly() && + bloc.wallet.isSecure() && + bloc.wallet.isActive(), orElse: () => wallets.first, // Return first wallet if no main wallet found ); var walletChild = child; - if (mainWalletBloc.mainWallet == true && - !mainWalletBloc.watchOnly() && - mainWalletBloc.isSecure()) { + if (mainWalletBloc.wallet.mainWallet == true && + !mainWalletBloc.wallet.watchOnly() && + mainWalletBloc.wallet.isSecure()) { // print( // 'mainWalletBloc.state.wallet!.mainWallet: ${mainWalletBloc.id}', // ); walletChild = PayjoinLifecycleManager( - wallet: mainWalletBloc, + wallet: mainWalletBloc.wallet, payjoinManager: locator(), child: child, ); @@ -121,21 +121,7 @@ class WalletBlocListeners extends StatelessWidget { if (blocs.isEmpty) return walletChild; - return MultiBlocListener( - listeners: [ - for (final bloc in context.read().state) - BlocListener( - bloc: bloc, - // bloc: createWalletBloc(w), - listenWhen: (previous, current) => - previous.wallet != current.wallet, - listener: (context, state) { - context.read().add(WalletUpdated(state.wallet)); - }, - ), - ], - child: walletChild, - ); + return walletChild; } } @@ -196,6 +182,7 @@ class _HomeWalletLoadingListenersState if (walletBlocs.isEmpty) return widget.child; _setup(); + // TODO: cleanup return MultiBlocListener( listeners: [ for (final walletBloc in walletBlocs) From 38c2ca2c11af59b6b3b90064363c1ca97830f09a Mon Sep 17 00:00:00 2001 From: kumulynja Date: Tue, 14 Jan 2025 10:57:36 +0100 Subject: [PATCH 2/2] replace home loading cubit and listeners with home state getter --- lib/home/bloc/home_state.dart | 2 + lib/home/home_page.dart | 16 ++----- lib/home/listeners.dart | 85 ----------------------------------- 3 files changed, 5 insertions(+), 98 deletions(-) diff --git a/lib/home/bloc/home_state.dart b/lib/home/bloc/home_state.dart index 6f21a992..e620631f 100644 --- a/lib/home/bloc/home_state.dart +++ b/lib/home/bloc/home_state.dart @@ -24,6 +24,8 @@ class HomeState with _$HomeState { }) = _HomeState; const HomeState._(); + bool syncingAny() => wallets.any((_) => _.syncing); + bool hasWallets() => !loadingWallets && wallets.isNotEmpty; // List walletsFromNetwork(BBNetwork network) => diff --git a/lib/home/home_page.dart b/lib/home/home_page.dart index d12dc2df..fbb5ce40 100644 --- a/lib/home/home_page.dart +++ b/lib/home/home_page.dart @@ -48,12 +48,7 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider.value( - value: HomeLoadingCubit(), - child: const HomeWalletLoadingListeners( - child: _Screen(), - ), - ); + return const _Screen(); } } @@ -879,13 +874,8 @@ class HomeLoadingTxsIndicator extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder>( - buildWhen: (previous, current) => previous.values != current.values, - builder: (context, state) { - final isLoading = state.values.contains(true); - return _Loading(loading: isLoading); - }, - ); + final isSyncing = context.select((HomeBloc x) => x.state.syncingAny()); + return _Loading(loading: isSyncing); } } diff --git a/lib/home/listeners.dart b/lib/home/listeners.dart index 66570a2c..e86e8da4 100644 --- a/lib/home/listeners.dart +++ b/lib/home/listeners.dart @@ -5,9 +5,7 @@ import 'package:bb_mobile/home/bloc/home_bloc.dart'; import 'package:bb_mobile/home/bloc/home_state.dart'; import 'package:bb_mobile/home/home_page.dart'; import 'package:bb_mobile/locator.dart'; -import 'package:bb_mobile/wallet/bloc/state.dart'; import 'package:bb_mobile/wallet/bloc/wallet_bloc.dart'; -import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -125,89 +123,6 @@ class WalletBlocListeners extends StatelessWidget { } } -class HomeLoadingEvent {} - -class SetLoading extends HomeLoadingEvent { - SetLoading(this.id, this.loading); - final String id; - final bool loading; -} - -class HomeLoadingCubit extends Bloc> { - HomeLoadingCubit() : super({}) { - on( - (event, emit) { - final map = state; - map[event.id] = event.loading; - emit({}); - emit(map); - }, - transformer: droppable(), - ); - } -} - -class HomeWalletLoadingListeners extends StatefulWidget { - const HomeWalletLoadingListeners({super.key, required this.child}); - - final Widget child; - - @override - State createState() => - _HomeWalletLoadingListenersState(); -} - -class _HomeWalletLoadingListenersState - extends State { - @override - void initState() { - super.initState(); - } - - void _setup() { - if (!context.mounted) return; - final blocs = context.read().state; - - for (final bloc in blocs) { - context - .read() - .add(SetLoading(bloc.state.wallet.id, bloc.state.syncing)); - } - } - - @override - Widget build(BuildContext context) { - final walletBlocs = context.select((AppWalletBlocs x) => x.state); - - if (walletBlocs.isEmpty) return widget.child; - - _setup(); - // TODO: cleanup - return MultiBlocListener( - listeners: [ - for (final walletBloc in walletBlocs) - BlocListener( - bloc: walletBloc, - listenWhen: (previous, current) => - previous.syncing != current.syncing, - listener: (context, state) { - if (state.syncing) { - context - .read() - .add(SetLoading(state.wallet.id, true)); - } else { - context - .read() - .add(SetLoading(state.wallet.id, false)); - } - }, - ), - ], - child: widget.child, - ); - } -} - class BBlocObserver extends BlocObserver { @override void onError(BlocBase bloc, Object error, StackTrace stackTrace) {