From 48e7e0a4cd6fd879073f0a3103343159b69b1d20 Mon Sep 17 00:00:00 2001 From: redDwarf03 Date: Fri, 20 Dec 2024 12:13:38 +0100 Subject: [PATCH] feat: :sparkles: Add change environment widget with accounts selection --- lib/application/session/providers.dart | 37 ++- lib/domain/repositories/features_flags.dart | 2 +- lib/l10n/intl_en.arb | 13 +- lib/l10n/intl_fr.arb | 13 +- lib/ui/menu/settings/main_settings_view.dart | 13 +- lib/ui/menu/settings/network_change.dart | 123 ++++++++ lib/ui/menu/settings/settings_sheet.dart | 3 +- lib/ui/util/accounts_dialog.dart | 262 ++++++++++++++++++ .../intro/layouts/intro_import_seed.dart | 116 +------- lib/ui/widgets/components/picker_item.dart | 3 + 10 files changed, 448 insertions(+), 137 deletions(-) create mode 100644 lib/ui/menu/settings/network_change.dart create mode 100644 lib/ui/util/accounts_dialog.dart diff --git a/lib/application/session/providers.dart b/lib/application/session/providers.dart index 6a32ad674..2f23845ae 100644 --- a/lib/application/session/providers.dart +++ b/lib/application/session/providers.dart @@ -126,19 +126,12 @@ class SessionNotifier extends _$SessionNotifier { ); } - Future restoreFromMnemonics({ - required List mnemonics, + Future restoreFromSeed({ + required String seed, required String languageCode, }) async { await _appWalletDatasource.clearAppWallet(); - final seed = AppMnemomics.mnemonicListToSeed( - mnemonics, - languageCode: languageCode, - ); - if (seed.isEmpty) { - return null; - } final vault = await KeychainInfoVaultDatasource.getInstance(); await vault.setSeed(seed); @@ -170,7 +163,33 @@ class SessionNotifier extends _$SessionNotifier { ), ); } catch (e) { + if (e.toString() == "Exception: Keychain doesn't exists") { + throw const ArchethicKeychainNotExistsException(); + } + return null; } } + + Future restoreFromMnemonics({ + required List mnemonics, + required String languageCode, + }) async { + await _appWalletDatasource.clearAppWallet(); + + final seed = AppMnemomics.mnemonicListToSeed( + mnemonics, + languageCode: languageCode, + ); + if (seed.isEmpty) { + return null; + } + return restoreFromSeed(seed: seed, languageCode: languageCode); + } +} + +// TODO(reddwarf03): Move to libdart +@immutable +class ArchethicKeychainNotExistsException implements ArchethicException { + const ArchethicKeychainNotExistsException(); } diff --git a/lib/domain/repositories/features_flags.dart b/lib/domain/repositories/features_flags.dart index ab9b313d9..c3778c4f9 100644 --- a/lib/domain/repositories/features_flags.dart +++ b/lib/domain/repositories/features_flags.dart @@ -2,7 +2,7 @@ import 'package:aewallet/util/universal_platform.dart'; /// Simply activate/deactivate features. class FeatureFlags { - static const forceLogout = false; + static const forceLogout = true; static const tokenFungibleCreationFeature = false; static const dappBoard = false; static final privacyMask = !UniversalPlatform diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 1cc44c998..53f37d92c 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -279,6 +279,7 @@ "searchField": "Search...", "search": "Search", "pleaseWait": "Please wait", + "pleaseWaitChangeNetwork": "Network change in progress", "appWalletInitInProgress": "Your wallet is being configured...", "sendMessageHeader": "Message", "sendMessageConfirmHeader": "Your message :", @@ -785,5 +786,15 @@ }, "dappBoardLinkHeader": "DApps Board", "dappBoardLinkDesc": "Access to decentralized applications deployed on Archethic Public Blockchain", - "dappBoardTitle": "DApps Board" + "dappBoardTitle": "DApps Board", + "networkChangeHeader": "Network change", + "networkChangeKeychainNotExists": "Your wallet doesn't yet exist in the {envName} environment. You can create it automatically.\nPlease select the accounts you wish to duplicate in this environment. You can add or remove accounts from your {envName} wallet at any time.\n\nImportant:\n- There is no automatic synchronization between environments.\n- Funds, tokens, NFTs, and UCO cannot be transferred or duplicated between environments. Accounts created in Testnet will be empty.", + "@networkChangeKeychainNotExists": { + "placeholders": { + "envName": { + "type": "String" + } + } + }, + "networkChangeCreateKeychainBtn": "Create wallet" } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 6f1ab2913..9886e0009 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -271,6 +271,7 @@ "searchField": "Recherche...", "search": "Rechercher", "pleaseWait": "Veuillez patienter", + "pleaseWaitChangeNetwork": "Changement d'environnement en cours", "appWalletInitInProgress": "Votre wallet est en cours de configuration...", "sendMessageHeader": "Message", "sendMessageConfirmHeader": "Votre message :", @@ -765,5 +766,15 @@ }, "dappBoardLinkHeader": "DApps Board", "dappBoardLinkDesc": "Accédez aux applications décentralisées sur la Blockchain Publique Archethic", - "dappBoardTitle": "DApps Board" + "dappBoardTitle": "DApps Board", + "networkChangeHeader": "Changement d'env.", + "networkChangeKeychainNotExists": "Votre wallet n'existe pas encore dans l'environnement {envName}. Nous vous proposons de le créer automatiquement.\nVeuillez sélectionner les comptes que vous souhaitez dupliquer dans cet environnement. Vous pourrez à tout moment ajouter ou retirer des comptes sur votre wallet {envName}.\n\nImportant :\n- Il n'existe aucune synchronisation automatique entre les environnements.\n- Les fonds, tokens, NFT et UCO ne peuvent pas être transférés ou dupliqués entre les environnements. Les comptes créés dans Testnet seront donc vides.", + "@networkChangeKeychainNotExists": { + "placeholders": { + "envName": { + "type": "String" + } + } + }, + "networkChangeCreateKeychainBtn": "Créer le wallet" } \ No newline at end of file diff --git a/lib/ui/menu/settings/main_settings_view.dart b/lib/ui/menu/settings/main_settings_view.dart index 9531f74fc..4ba60129f 100644 --- a/lib/ui/menu/settings/main_settings_view.dart +++ b/lib/ui/menu/settings/main_settings_view.dart @@ -16,7 +16,6 @@ class MainMenuView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final localizations = AppLocalizations.of(context)!; - final environment = ref.watch(environmentProvider); final selectedAccount = ref.watch( AccountProviders.accounts.select( (accounts) => accounts.valueOrNull?.selectedAccount, @@ -45,17 +44,7 @@ class MainMenuView extends ConsumerWidget { children: [ ListView( children: [ - Padding( - padding: const EdgeInsets.only(bottom: 10), - child: Align( - child: Text( - environment.label, - style: AppTextStyles.bodyMediumSecondaryColor( - context, - ), - ), - ), - ), + const NetworkChange(), const _SettingsListItem.spacer(), _SettingsListItem.title(text: localizations.information), const _SettingsListItem.spacer(), diff --git a/lib/ui/menu/settings/network_change.dart b/lib/ui/menu/settings/network_change.dart new file mode 100644 index 000000000..986e99291 --- /dev/null +++ b/lib/ui/menu/settings/network_change.dart @@ -0,0 +1,123 @@ +/// SPDX-License-Identifier: AGPL-3.0-or-later +import 'package:aewallet/application/account/providers.dart'; +import 'package:aewallet/application/session/session.dart'; +import 'package:aewallet/application/settings/settings.dart'; +import 'package:aewallet/modules/aeswap/application/session/provider.dart'; +import 'package:aewallet/modules/aeswap/ui/views/util/app_styles.dart'; +import 'package:aewallet/ui/themes/archethic_theme_base.dart'; +import 'package:aewallet/ui/util/accounts_dialog.dart'; +import 'package:aewallet/ui/widgets/components/dialog.dart'; +import 'package:aewallet/ui/widgets/dialogs/network_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/localizations.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:material_symbols_icons/symbols.dart'; + +class NetworkChange extends ConsumerWidget { + const NetworkChange({ + super.key, + }); + @override + Widget build(BuildContext context, WidgetRef ref) { + final environment = ref.watch(environmentProvider); + final selectedAccount = ref.watch( + AccountProviders.accounts.select( + (accounts) => accounts.valueOrNull?.selectedAccount, + ), + ); + final settings = ref.watch(SettingsProviders.settings); + final localizations = AppLocalizations.of(context)!; + + if (selectedAccount == null) return const SizedBox(); + + return InkWell( + onTap: settings.devMode + ? () async { + final _saveNetwork = settings.network; + await context.push(NetworkDialog.routerPage); + final _settings = ref.read(SettingsProviders.settings); + if (_settings.network.network != _saveNetwork.network) { + context.loadingOverlay.show( + title: AppLocalizations.of(context)!.pleaseWaitChangeNetwork, + ); + final languageSeed = ref.read( + SettingsProviders.settings.select( + (settings) => settings.languageSeed, + ), + ); + final seed = + ref.read(sessionNotifierProvider).loggedIn?.wallet.seed; + if (seed != null) { + try { + await ref + .read(sessionNotifierProvider.notifier) + .restoreFromSeed( + seed: seed, + languageCode: languageSeed, + ); + context.loadingOverlay.hide(); + } on ArchethicKeychainNotExistsException catch (_) { + context.loadingOverlay.hide(); + context.pop(); + final session = ref.read(sessionNotifierProvider); + await AccountsDialog.selectMultipleAccounts( + context: context, + accounts: session.loggedIn!.wallet.appKeychain.accounts, + confirmBtnLabel: + localizations.networkChangeCreateKeychainBtn, + dialogTitle: localizations.networkChangeHeader, + isModal: true, + header: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Column( + children: [ + Text( + localizations.networkChangeKeychainNotExists( + _saveNetwork.getDisplayName(context), + ), + style: AppTextStyles.bodySmall(context), + ), + ], + ), + ), + ); + } + } + } + } + : null, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Align( + child: Text( + environment.label, + style: AppTextStyles.bodyMediumSecondaryColor( + context, + ), + ), + ), + ), + if (settings.devMode) + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + const SizedBox( + width: 5, + ), + Icon( + Symbols.keyboard_arrow_down, + color: ArchethicThemeBase.neutral0, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/ui/menu/settings/settings_sheet.dart b/lib/ui/menu/settings/settings_sheet.dart index a9bff6f1b..8493ec401 100644 --- a/lib/ui/menu/settings/settings_sheet.dart +++ b/lib/ui/menu/settings/settings_sheet.dart @@ -15,8 +15,7 @@ import 'package:aewallet/model/device_lock_timeout.dart'; import 'package:aewallet/model/privacy_mask_option.dart'; import 'package:aewallet/model/setting_item.dart'; import 'package:aewallet/modules/aeswap/application/pool/dex_pool.dart'; -import 'package:aewallet/modules/aeswap/application/session/provider.dart'; -import 'package:aewallet/modules/aeswap/ui/views/util/app_styles.dart'; +import 'package:aewallet/ui/menu/settings/network_change.dart'; import 'package:aewallet/ui/themes/archethic_theme.dart'; import 'package:aewallet/ui/themes/archethic_theme_base.dart'; import 'package:aewallet/ui/themes/styles.dart'; diff --git a/lib/ui/util/accounts_dialog.dart b/lib/ui/util/accounts_dialog.dart new file mode 100644 index 000000000..ca7975f78 --- /dev/null +++ b/lib/ui/util/accounts_dialog.dart @@ -0,0 +1,262 @@ +/// SPDX-License-Identifier: AGPL-3.0-or-later + +import 'dart:async'; +import 'dart:ui'; + +import 'package:aewallet/application/account/providers.dart'; +import 'package:aewallet/model/data/account.dart'; +import 'package:aewallet/ui/themes/archethic_theme.dart'; +import 'package:aewallet/ui/themes/styles.dart'; +import 'package:aewallet/ui/util/dimens.dart'; +import 'package:aewallet/ui/widgets/components/app_button_tiny.dart'; +import 'package:aewallet/ui/widgets/components/picker_item.dart'; +import 'package:archethic_dapp_framework_flutter/archethic_dapp_framework_flutter.dart' + as aedappfm; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/localizations.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; + +class AccountsDialog { + static Future selectSingleAccount({ + required BuildContext context, + required WidgetRef ref, + required List accounts, + String? dialogTitle, + Widget? header, + bool? isModal = false, + }) async { + final selection = await _showAccountsDialog( + context: context, + accounts: accounts, + multipleSelectionsAllowed: false, + dialogTitle: dialogTitle, + header: header, + isModal: isModal, + ) as Account?; + if (selection != null) { + await ref + .read(AccountProviders.accounts.notifier) + .selectAccount(selection); + } + return selection; + } + + static Future?> selectMultipleAccounts({ + required BuildContext context, + required List accounts, + String? confirmBtnLabel, + String? dialogTitle, + Widget? header, + bool? isModal = false, + }) async { + return await _showAccountsDialog( + context: context, + accounts: accounts, + multipleSelectionsAllowed: true, + confirmBtnLabel: confirmBtnLabel, + dialogTitle: dialogTitle, + header: header, + isModal: isModal, + ) as List?; + } + + static Future _showAccountsDialog({ + required BuildContext context, + required List accounts, + required bool multipleSelectionsAllowed, + String? confirmBtnLabel, + String? dialogTitle, + Widget? header, + bool? isModal = false, + }) async { + final pickerItemsList = >[]; + + for (final account in accounts) { + pickerItemsList.add( + PickerItem( + account.nameDisplayed, + null, + null, + null, + account, + true, + key: Key('accountName${account.nameDisplayed}'), + ), + ); + } + + if (isModal == false) { + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + backgroundColor: ArchethicTheme.backgroundPopupColor, + elevation: 0, + contentPadding: EdgeInsets.zero, + content: AccountsDialogContent( + accounts: accounts, + multipleSelectionsAllowed: multipleSelectionsAllowed, + pickerItemsList: pickerItemsList, + confirmBtnLabel: confirmBtnLabel, + dialogTitle: dialogTitle, + header: header, + ), + ); + }, + ); + } + + await showCupertinoModalBottomSheet( + context: context, + builder: (BuildContext context) { + return FractionallySizedBox( + heightFactor: 1, + child: Scaffold( + backgroundColor: + aedappfm.AppThemeBase.sheetBackground.withOpacity(0.2), + body: AccountsDialogContent( + accounts: accounts, + multipleSelectionsAllowed: multipleSelectionsAllowed, + pickerItemsList: pickerItemsList, + confirmBtnLabel: confirmBtnLabel, + dialogTitle: dialogTitle, + header: header, + ), + ), + ); + }, + ); + return; + } +} + +class AccountsDialogContent extends ConsumerWidget { + const AccountsDialogContent({ + required this.accounts, + required this.multipleSelectionsAllowed, + required this.pickerItemsList, + this.confirmBtnLabel, + this.dialogTitle, + this.header, + super.key, + }); + + final List accounts; + final bool multipleSelectionsAllowed; + final String? confirmBtnLabel; + final String? dialogTitle; + final Widget? header; + final List> pickerItemsList; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final localizations = AppLocalizations.of(context)!; + final pickerItemsListSelected = >[]; + return Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(16), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: ArchethicTheme.sheetBackground.withOpacity(0.2), + border: Border.all( + color: ArchethicTheme.sheetBorder, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Text( + dialogTitle ?? localizations.accountsHeader, + style: ArchethicThemeStyles.textStyleSize24W700Primary, + ), + ), + header ?? const SizedBox(height: 20), + Expanded( + child: Padding( + padding: multipleSelectionsAllowed + ? const EdgeInsets.only(bottom: 130) + : EdgeInsets.zero, + child: PickerWidget( + pickerItems: pickerItemsList, + multipleSelectionsAllowed: multipleSelectionsAllowed, + scrollable: true, + onSelected: (pickerItem) { + if (!multipleSelectionsAllowed) { + Navigator.of(context).pop(pickerItem.value); + } + }, + onUnselected: (_) {}, + selectedIndexes: const [], + getSelectedIndexes: (selectedIndexes) { + if (selectedIndexes != null) { + pickerItemsListSelected + ..clear() + ..addAll( + selectedIndexes + .map((index) => pickerItemsList[index]), + ); + } + }, + ), + ), + ), + ], + ), + ), + ), + ), + if (multipleSelectionsAllowed) + Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom + 20, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Row( + children: [ + AppButtonTinyConnectivity( + confirmBtnLabel ?? localizations.ok, + Dimens.buttonTopDimens, + onPressed: () { + final _accountsList = pickerItemsListSelected + .map((item) => item.value) + .toList(); + + Navigator.of(context).pop(_accountsList); + }, + ), + ], + ), + Row( + children: [ + AppButtonTinyConnectivity( + localizations.cancel, + Dimens.buttonBottomDimens, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/ui/views/intro/layouts/intro_import_seed.dart b/lib/ui/views/intro/layouts/intro_import_seed.dart index b4e3727db..256084d28 100755 --- a/lib/ui/views/intro/layouts/intro_import_seed.dart +++ b/lib/ui/views/intro/layouts/intro_import_seed.dart @@ -1,17 +1,16 @@ /// SPDX-License-Identifier: AGPL-3.0-or-later import 'dart:async'; -import 'dart:ui'; import 'package:aewallet/application/account/providers.dart'; import 'package:aewallet/application/connectivity_status.dart'; import 'package:aewallet/application/recovery_phrase_saved.dart'; import 'package:aewallet/application/session/session.dart'; import 'package:aewallet/application/settings/settings.dart'; -import 'package:aewallet/model/data/account.dart'; import 'package:aewallet/modules/aeswap/application/pool/dex_pool.dart'; import 'package:aewallet/ui/themes/archethic_theme.dart'; import 'package:aewallet/ui/themes/styles.dart'; +import 'package:aewallet/ui/util/accounts_dialog.dart'; import 'package:aewallet/ui/util/dimens.dart'; import 'package:aewallet/ui/util/formatters.dart'; import 'package:aewallet/ui/util/ui_util.dart'; @@ -22,7 +21,6 @@ import 'package:aewallet/ui/views/main/home_page.dart'; import 'package:aewallet/ui/widgets/components/app_button_tiny.dart'; import 'package:aewallet/ui/widgets/components/dialog.dart'; import 'package:aewallet/ui/widgets/components/icon_network_warning.dart'; -import 'package:aewallet/ui/widgets/components/picker_item.dart'; import 'package:aewallet/ui/widgets/components/sheet_skeleton.dart'; import 'package:aewallet/ui/widgets/components/sheet_skeleton_interface.dart'; import 'package:aewallet/util/mnemonics.dart'; @@ -31,7 +29,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:logging/logging.dart'; import 'package:unorm_dart/unorm_dart.dart' as unorm; class IntroImportSeedPage extends ConsumerStatefulWidget { @@ -45,8 +42,6 @@ class IntroImportSeedPage extends ConsumerStatefulWidget { class _IntroImportSeedState extends ConsumerState implements SheetSkeletonInterface { - final _logger = Logger('IntroImportSeed'); - bool _mnemonicIsValid = false; String _mnemonicError = ''; bool? isPressed; @@ -174,8 +169,10 @@ class _IntroImportSeedState extends ConsumerState context.go(IntroImportSeedPage.routerPage); return; } - await _accountsDialog( - newSession.wallet.appKeychain.accounts, + await AccountsDialog.selectSingleAccount( + context: context, + ref: ref, + accounts: newSession.wallet.appKeychain.accounts, ); context.loadingOverlay.show( title: localizations.pleaseWait, @@ -534,107 +531,4 @@ class _IntroImportSeedState extends ConsumerState ], ); } - - Future _accountsDialog(List accounts) async { - final pickerItemsList = List.empty(growable: true); - for (var i = 0; i < accounts.length; i++) { - if (accounts[i].serviceType == 'archethicWallet') { - final account = accounts[i]; - pickerItemsList.add( - PickerItem( - account.nameDisplayed, - null, - null, - null, - account, - true, - key: Key('accountName${account.nameDisplayed}'), - ), - ); - _logger.info('<>'); - } - } - - final selection = await showDialog( - barrierDismissible: false, - useRootNavigator: false, - context: context, - builder: (BuildContext context) { - final localizations = AppLocalizations.of(context)!; - return AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - backgroundColor: ArchethicTheme.backgroundPopupColor, - elevation: 0, - contentPadding: EdgeInsets.zero, - content: ClipRRect( - borderRadius: BorderRadius.circular(16), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: ArchethicTheme.sheetBackground.withOpacity(0.2), - border: Border.all( - color: ArchethicTheme.sheetBorder, - ), - ), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - localizations.accountsHeader, - style: - ArchethicThemeStyles.textStyleSize24W700Primary, - ), - const SizedBox( - height: 5, - ), - if (accounts.length > 1) - Text( - localizations.selectAccountDescSeveral, - style: ArchethicThemeStyles - .textStyleSize12W100Primary, - ) - else - Text( - localizations.selectAccountDescOne, - style: ArchethicThemeStyles - .textStyleSize12W100Primary, - ), - ], - ), - ), - const SizedBox(height: 20), - Expanded( - child: SingleChildScrollView( - child: PickerWidget( - pickerItems: pickerItemsList, - selectedIndexes: const [0], - onSelected: (value) { - context.pop(value.value); - }, - ), - ), - ), - ], - ), - ), - ), - ), - ); - }, - ); - if (selection != null) { - await ref - .read(AccountProviders.accounts.notifier) - .selectAccount(selection); - } - return selection; - } } diff --git a/lib/ui/widgets/components/picker_item.dart b/lib/ui/widgets/components/picker_item.dart index bff5e8810..6b1badee8 100644 --- a/lib/ui/widgets/components/picker_item.dart +++ b/lib/ui/widgets/components/picker_item.dart @@ -40,6 +40,7 @@ class PickerWidget extends ConsumerStatefulWidget { required this.pickerItems, this.onSelected, this.onUnselected, + this.getSelectedIndexes, List? selectedIndexes, this.multipleSelectionsAllowed = false, this.height, @@ -50,6 +51,7 @@ class PickerWidget extends ConsumerStatefulWidget { final FutureOr Function(PickerItem)? onSelected; final FutureOr Function(PickerItem)? onUnselected; + final FutureOr Function(List?)? getSelectedIndexes; final List> pickerItems; late final List selectedIndexes; final bool multipleSelectionsAllowed; @@ -99,6 +101,7 @@ class _PickerWidgetState extends ConsumerState> { selectedIndexes.add(index); }); widget.onSelected?.call(widget.pickerItems[index]); + widget.getSelectedIndexes?.call(selectedIndexes); } }, key: pickerItem.key,