From 0f5218db4ff9be93c71aba153cb6490e1417df03 Mon Sep 17 00:00:00 2001 From: Francois Date: Tue, 2 Sep 2025 16:00:32 +0200 Subject: [PATCH 1/4] chore(deps): bump sdk to 3f503d2 and fix breaking sparkline change --- lib/main.dart | 7 +++--- .../initializer/app_bootstrapper.dart | 23 +++++++++++++------ sdk | 2 +- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index d103a0bab8..682d817040 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -59,7 +59,10 @@ Future main() async { // is the only/primary API/repository for KDF final KomodoDefiSdk komodoDefiSdk = await mm2.initialize(); final mm2Api = Mm2Api(mm2: mm2, sdk: komodoDefiSdk); - await AppBootstrapper.instance.ensureInitialized(komodoDefiSdk, mm2Api); + // Sparkline is dependent on Hive initialization, so we pass it on to the + // bootstrapper here + final sparklineRepository = SparklineRepository.defaultInstance(); + await AppBootstrapper.instance.ensureInitialized(komodoDefiSdk, mm2Api, sparklineRepository); final coinsRepo = CoinsRepo(kdfSdk: komodoDefiSdk, mm2: mm2); final walletsRepository = WalletsRepository( @@ -81,8 +84,6 @@ Future main() async { RepositoryProvider(create: (_) => mm2Api), RepositoryProvider(create: (_) => coinsRepo), RepositoryProvider(create: (_) => walletsRepository), - // TODO: Refactor in SDK to avoid use of this global variable. - // This is necessary for now for CoinSparkline. RepositoryProvider(create: (_) => sparklineRepository), ], child: const MyApp(), diff --git a/lib/services/initializer/app_bootstrapper.dart b/lib/services/initializer/app_bootstrapper.dart index 1d79aa6c27..b1a5e13453 100644 --- a/lib/services/initializer/app_bootstrapper.dart +++ b/lib/services/initializer/app_bootstrapper.dart @@ -9,7 +9,11 @@ final class AppBootstrapper { bool _isInitialized = false; - Future ensureInitialized(KomodoDefiSdk kdfSdk, Mm2Api mm2Api) async { + Future ensureInitialized( + KomodoDefiSdk kdfSdk, + Mm2Api mm2Api, + SparklineRepository sparklineRepository, + ) async { if (_isInitialized) return; // Register core services with GetIt @@ -22,8 +26,10 @@ final class AppBootstrapper { log('AppBootstrapper: Log initialized in ${timer.elapsedMilliseconds}ms'); timer.reset(); - await _warmUpInitializers().awaitAll(); - log('AppBootstrapper: Warm-up initializers completed in ${timer.elapsedMilliseconds}ms'); + await _warmUpInitializers(sparklineRepository).awaitAll(); + log( + 'AppBootstrapper: Warm-up initializers completed in ${timer.elapsedMilliseconds}ms', + ); timer.stop(); _isInitialized = true; @@ -38,7 +44,9 @@ final class AppBootstrapper { /// A list of futures that should be completed before the app starts /// ([runApp]) which do not depend on each other. - List> _warmUpInitializers() { + List> _warmUpInitializers( + SparklineRepository sparklineRepository, + ) { return [ app_bloc_root.loadLibrary(), packageInformation.init(), @@ -46,9 +54,10 @@ final class AppBootstrapper { CexMarketData.ensureInitialized(), PlatformTuner.setWindowTitleAndSize(), _initializeSettings(), - _initHive(isWeb: kIsWeb || kIsWasm, appFolder: appFolder).then( - (_) => sparklineRepository.init(), - ), + _initHive( + isWeb: kIsWeb || kIsWasm, + appFolder: appFolder, + ).then((_) => sparklineRepository.init()), ]; } diff --git a/sdk b/sdk index f22ca69547..3f503d2368 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit f22ca695471a52f3409726d01116fc811cee6751 +Subproject commit 3f503d2368c6ddd7a03bb15b587c3f27efa845c5 From c3d1c687ad46c0d5e53e2cc545e936b81a782030 Mon Sep 17 00:00:00 2001 From: Francois Date: Tue, 2 Sep 2025 16:03:00 +0200 Subject: [PATCH 2/4] perf(fiat-onramp): add debouncer to fiat amount input field reduce API requests sent on user input. This was previously managed via `bloc_concurrency` debounce transformer, which was changed to `restartable` after incorrect state updates were reported in testing --- lib/views/fiat/fiat_inputs.dart | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/views/fiat/fiat_inputs.dart b/lib/views/fiat/fiat_inputs.dart index 2d4f0c38b5..675f7c16ba 100644 --- a/lib/views/fiat/fiat_inputs.dart +++ b/lib/views/fiat/fiat_inputs.dart @@ -58,17 +58,20 @@ class FiatInputs extends StatefulWidget { class FiatInputsState extends State { TextEditingController fiatController = TextEditingController(); + late final Debouncer _debouncer; + bool _hasUserInput = false; @override void dispose() { fiatController.dispose(); - + _debouncer.dispose(); super.dispose(); } @override void initState() { super.initState(); + _debouncer = Debouncer(duration: const Duration(milliseconds: 300)); fiatController.text = widget.initialFiatAmount?.toString() ?? ''; } @@ -82,8 +85,8 @@ class FiatInputsState extends State { final Decimal currentFiatAmount = Decimal.tryParse(fiatController.text) ?? Decimal.zero; - // Compare using Decimal values - if (newFiatAmount != currentFiatAmount) { + // Only update if user hasn't made changes or if amounts are different + if (!_hasUserInput && newFiatAmount != currentFiatAmount) { final newFiatAmountText = newFiatAmount?.toString() ?? ''; fiatController ..text = newFiatAmountText @@ -106,7 +109,14 @@ class FiatInputsState extends State { } void fiatAmountChanged(String? newValue) { - widget.onFiatAmountUpdate(newValue); + // track if user has made inputs to avoid overwriting them + // with stale bloc state updates (e.g. race condition) + _hasUserInput = true; + _debouncer.run(() { + if (mounted) { + widget.onFiatAmountUpdate(newValue); + } + }); } @override From 1fb871a34fcd02865e1159e2699116f258b2e373 Mon Sep 17 00:00:00 2001 From: Francois Date: Tue, 2 Sep 2025 17:39:18 +0200 Subject: [PATCH 3/4] refactor(header): use SDK balance for header balance value continuation of migration away from portfolio balance in previous PR --- .../common/header/actions/header_actions.dart | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/lib/views/common/header/actions/header_actions.dart b/lib/views/common/header/actions/header_actions.dart index 11edff5779..2f69965e39 100644 --- a/lib/views/common/header/actions/header_actions.dart +++ b/lib/views/common/header/actions/header_actions.dart @@ -6,7 +6,6 @@ import 'package:komodo_ui_kit/komodo_ui_kit.dart'; import 'package:web_dex/app_config/app_config.dart'; import 'package:web_dex/bloc/coins_bloc/asset_coin_extension.dart'; import 'package:web_dex/bloc/coins_bloc/coins_bloc.dart'; -import 'package:web_dex/bloc/cex_market_data/portfolio_growth/portfolio_growth_bloc.dart'; import 'package:web_dex/common/screen.dart'; import 'package:web_dex/generated/codegen_loader.g.dart'; import 'package:web_dex/model/coin.dart'; @@ -19,9 +18,7 @@ const EdgeInsets headerActionsPadding = EdgeInsets.fromLTRB(38, 18, 0, 0); final _languageCodes = localeList.map((e) => e.languageCode).toList(); final _langCode2flags = { for (var loc in _languageCodes) - loc: SvgPicture.asset( - '$assetsPath/flags/$loc.svg', - ), + loc: SvgPicture.asset('$assetsPath/flags/$loc.svg'), }; List? getHeaderActions(BuildContext context) { return [ @@ -36,14 +33,12 @@ List? getHeaderActions(BuildContext context) { ), Padding( padding: headerActionsPadding, - child: BlocBuilder( - builder: (context, pgState) { - final coins = context.select>( - (bloc) => bloc.state.walletCoins.values, + child: BlocBuilder( + builder: (context, state) { + final totalBalance = _getTotalBalance( + state.walletCoins.values, + context, ); - final totalBalance = pgState is PortfolioGrowthChartLoadSuccess - ? pgState.totalBalance - : _getTotalBalance(coins, context); return ActionTextButton( text: LocaleKeys.balance.tr(), @@ -53,17 +48,16 @@ List? getHeaderActions(BuildContext context) { }, ), ), - const Padding( - padding: headerActionsPadding, - child: AccountSwitcher(), - ), + const Padding(padding: headerActionsPadding, child: AccountSwitcher()), if (!isWideScreen) const SizedBox(width: mainLayoutPadding), ]; } double _getTotalBalance(Iterable coins, BuildContext context) { - double total = - coins.fold(0, (prev, coin) => prev + (coin.usdBalance(context.sdk) ?? 0)); + double total = coins.fold( + 0, + (prev, coin) => prev + (coin.usdBalance(context.sdk) ?? 0), + ); if (total > 0.01) { return total; From 90217c682c6ae8c325a9503d74fa84245e532348 Mon Sep 17 00:00:00 2001 From: Francois Date: Tue, 2 Sep 2025 18:10:30 +0200 Subject: [PATCH 4/4] refactor(review): explicit imports, clear flag, and use `.value` factory --- lib/main.dart | 16 ++++++++++------ lib/views/fiat/fiat_inputs.dart | 11 ++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 682d817040..df6533b8d5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -62,7 +62,11 @@ Future main() async { // Sparkline is dependent on Hive initialization, so we pass it on to the // bootstrapper here final sparklineRepository = SparklineRepository.defaultInstance(); - await AppBootstrapper.instance.ensureInitialized(komodoDefiSdk, mm2Api, sparklineRepository); + await AppBootstrapper.instance.ensureInitialized( + komodoDefiSdk, + mm2Api, + sparklineRepository, + ); final coinsRepo = CoinsRepo(kdfSdk: komodoDefiSdk, mm2: mm2); final walletsRepository = WalletsRepository( @@ -80,11 +84,11 @@ Future main() async { path: '$assetsPath/translations', child: MultiRepositoryProvider( providers: [ - RepositoryProvider(create: (_) => komodoDefiSdk), - RepositoryProvider(create: (_) => mm2Api), - RepositoryProvider(create: (_) => coinsRepo), - RepositoryProvider(create: (_) => walletsRepository), - RepositoryProvider(create: (_) => sparklineRepository), + RepositoryProvider.value(value: komodoDefiSdk), + RepositoryProvider.value(value: mm2Api), + RepositoryProvider.value(value: coinsRepo), + RepositoryProvider.value(value: walletsRepository), + RepositoryProvider.value(value: sparklineRepository), ], child: const MyApp(), ), diff --git a/lib/views/fiat/fiat_inputs.dart b/lib/views/fiat/fiat_inputs.dart index 675f7c16ba..8ed83e5f4b 100644 --- a/lib/views/fiat/fiat_inputs.dart +++ b/lib/views/fiat/fiat_inputs.dart @@ -1,10 +1,11 @@ +import 'package:decimal/decimal.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:komodo_defi_sdk/komodo_defi_sdk.dart' show KomodoDefiSdk; import 'package:komodo_defi_types/komodo_defi_types.dart' show PubkeyInfo, AssetPubkeys; -import 'package:komodo_ui/komodo_ui.dart'; +import 'package:komodo_ui/komodo_ui.dart' show Debouncer, SourceAddressField; import 'package:komodo_ui_kit/komodo_ui_kit.dart'; import 'package:web_dex/bloc/fiat/models/fiat_price_info.dart'; import 'package:web_dex/bloc/fiat/models/i_currency.dart'; @@ -79,6 +80,12 @@ class FiatInputsState extends State { void didUpdateWidget(FiatInputs oldWidget) { super.didUpdateWidget(oldWidget); + // Reset _hasUserInput flag when asset or fiat currency changes + if (oldWidget.selectedAsset != widget.selectedAsset || + oldWidget.initialFiat != widget.initialFiat) { + _hasUserInput = false; + } + final Decimal? newFiatAmount = widget.initialFiatAmount; // Convert the current text to Decimal for comparison @@ -115,6 +122,8 @@ class FiatInputsState extends State { _debouncer.run(() { if (mounted) { widget.onFiatAmountUpdate(newValue); + // Reset flag after API call to allow future bloc state updates + _hasUserInput = false; } }); }