diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4ca2f124b..54bf5e55f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -9,7 +9,7 @@ PODS: - connectivity_plus (0.0.1): - Flutter - ReachabilitySwift - - cs_monero_flutter_libs (0.0.1): + - cs_monero_flutter_libs_ios (0.0.1): - Flutter - device_info_plus (0.0.1): - Flutter @@ -87,8 +87,6 @@ PODS: - "sqlite3 (3.46.0+1)": - "sqlite3/common (= 3.46.0+1)" - "sqlite3/common (3.46.0+1)" - - "sqlite3/dbstatvtab (3.46.0+1)": - - sqlite3/common - "sqlite3/fts5 (3.46.0+1)": - sqlite3/common - "sqlite3/perf-threadsafe (3.46.0+1)": @@ -97,8 +95,7 @@ PODS: - sqlite3/common - sqlite3_flutter_libs (0.0.1): - Flutter - - "sqlite3 (~> 3.46.0+1)" - - sqlite3/dbstatvtab + - sqlite3 (~> 3.46.0) - sqlite3/fts5 - sqlite3/perf-threadsafe - sqlite3/rtree @@ -117,7 +114,7 @@ DEPENDENCIES: - barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`) - coinlib_flutter (from `.symlinks/plugins/coinlib_flutter/darwin`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - - cs_monero_flutter_libs (from `.symlinks/plugins/cs_monero_flutter_libs/ios`) + - cs_monero_flutter_libs_ios (from `.symlinks/plugins/cs_monero_flutter_libs_ios/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - devicelocale (from `.symlinks/plugins/devicelocale/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) @@ -160,8 +157,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/coinlib_flutter/darwin" connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" - cs_monero_flutter_libs: - :path: ".symlinks/plugins/cs_monero_flutter_libs/ios" + cs_monero_flutter_libs_ios: + :path: ".symlinks/plugins/cs_monero_flutter_libs_ios/ios" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" devicelocale: @@ -213,7 +210,7 @@ SPEC CHECKSUMS: barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0 coinlib_flutter: 9275e8255ef67d3da33beb6e117d09ced4f46eb5 connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a - cs_monero_flutter_libs: 43cda3474c2bc907f2b2b5bb26fd89cb864fcfc6 + cs_monero_flutter_libs_ios: fd353631682247f72a36493ff060d4328d6f720d device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926 DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac @@ -231,14 +228,14 @@ SPEC CHECKSUMS: lelantus: 417f0221260013dfc052cae9cf4b741b6479edba local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb - package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866 share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 sqlite3: 292c3e1bfe89f64e51ea7fc7dab9182a017c8630 - sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b + sqlite3_flutter_libs: 0d611efdf6d1c9297d5ab03dab21b75aeebdae31 stack_wallet_backup: 5b8563aba5d8ffbf2ce1944331ff7294a0ec7c03 SwiftProtobuf: 6ef3f0e422ef90d6605ca20b21a94f6c1324d6b3 SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart index 58e51b34e..eb01ea9c7 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart @@ -114,6 +114,8 @@ class _RestoreWalletViewState extends ConsumerState { late final TextSelectionControls textSelectionControls; + bool _hideSeedWords = false; + Future onControlsPaste(TextSelectionDelegate delegate) async { final data = await widget.clipboard.getData(Clipboard.kTextPlain); if (data?.text == null) { @@ -212,6 +214,8 @@ class _RestoreWalletViewState extends ConsumerState { Future attemptRestore() async { if (_formKey.currentState!.validate()) { + if (mounted) setState(() => _hideSeedWords = true); + String mnemonic = ""; for (final element in _controllers) { mnemonic += " ${element.text.trim().toLowerCase()}"; @@ -282,6 +286,7 @@ class _RestoreWalletViewState extends ConsumerState { // TODO: do actual check to make sure it is a valid mnemonic for monero if (bip39.validateMnemonic(mnemonic) == false && !(widget.coin is Monero || widget.coin is Wownero)) { + if (mounted) setState(() => _hideSeedWords = false); unawaited( showFloatingFlushBar( type: FlushBarType.warning, @@ -313,6 +318,8 @@ class _RestoreWalletViewState extends ConsumerState { onCancel: () async { isRestoring = false; + if (mounted) setState(() => _hideSeedWords = false); + await ref.read(pWallets).deleteWallet( info, ref.read(secureStoreProvider), @@ -471,6 +478,8 @@ class _RestoreWalletViewState extends ConsumerState { ); }, ); + + if (mounted) setState(() => _hideSeedWords = false); } } @@ -868,6 +877,7 @@ class _RestoreWalletViewState extends ConsumerState { child: Column( children: [ TextFormField( + obscureText: _hideSeedWords, autocorrect: !isDesktop, enableSuggestions: !isDesktop, textCapitalization: @@ -1001,6 +1011,7 @@ class _RestoreWalletViewState extends ConsumerState { child: Column( children: [ TextFormField( + obscureText: _hideSeedWords, autocorrect: !isDesktop, enableSuggestions: !isDesktop, textCapitalization: @@ -1135,6 +1146,7 @@ class _RestoreWalletViewState extends ConsumerState { padding: const EdgeInsets.symmetric(vertical: 4), child: TextFormField( + obscureText: _hideSeedWords, autocorrect: !isDesktop, enableSuggestions: !isDesktop, textCapitalization: TextCapitalization.none, diff --git a/lib/pages/settings_views/global_settings_view/hidden_settings.dart b/lib/pages/settings_views/global_settings_view/hidden_settings.dart index b389f31a3..c0ed42019 100644 --- a/lib/pages/settings_views/global_settings_view/hidden_settings.dart +++ b/lib/pages/settings_views/global_settings_view/hidden_settings.dart @@ -75,6 +75,39 @@ class HiddenSettings extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + Consumer( + builder: (_, ref, __) { + return GestureDetector( + onTap: () async { + ref + .read(prefsChangeNotifierProvider) + .advancedFiroFeatures = + !ref + .read(prefsChangeNotifierProvider) + .advancedFiroFeatures; + }, + child: RoundedWhiteContainer( + child: Text( + ref.watch( + prefsChangeNotifierProvider.select( + (s) => s.advancedFiroFeatures, + ), + ) + ? "Hide advanced Firo features" + : "Show advanced Firo features", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + ); + }, + ), + const SizedBox( + height: 12, + ), Consumer( builder: (_, ref, __) { return GestureDetector( diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart index 56e726731..f0ebcfbf0 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart @@ -8,17 +8,27 @@ * */ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; import '../../../../providers/db/main_db_provider.dart'; +import '../../../../providers/global/wallets_provider.dart'; import '../../../../themes/stack_colors.dart'; +import '../../../../utilities/logger.dart'; +import '../../../../utilities/show_loading.dart'; import '../../../../utilities/text_styles.dart'; +import '../../../../utilities/util.dart'; import '../../../../wallets/isar/models/wallet_info.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../widgets/background.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/custom_buttons/draggable_switch_button.dart'; +import '../../../../widgets/desktop/primary_button.dart'; +import '../../../../widgets/desktop/secondary_button.dart'; +import '../../../../widgets/stack_dialog.dart'; class LelantusSettingsView extends ConsumerStatefulWidget { const LelantusSettingsView({ @@ -50,12 +60,91 @@ class _LelantusSettingsViewState extends ConsumerState { }, isar: ref.read(mainDBProvider).isar, ); + if (newValue) { + await _doRescanMaybe(); + } } finally { // ensure _isUpdatingLelantusScanning is set to false no matter what _isUpdatingLelantusScanning = false; } } + Future _doRescanMaybe() async { + final shouldRescan = await showDialog( + context: context, + builder: (context) { + return StackDialog( + title: "Rescan may be required", + message: "A blockchain rescan may be required to fully recover all " + "lelantus history. This may take a while.", + leftButton: SecondaryButton( + label: "Rescan now", + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + rightButton: PrimaryButton( + label: "Later", + onPressed: () => Navigator.of(context).pop(false), + ), + ); + }, + ); + + if (mounted && shouldRescan == true) { + try { + if (!Platform.isLinux) await WakelockPlus.enable(); + + Exception? e; + if (mounted) { + await showLoading( + whileFuture: ref.read(pWallets).getWallet(widget.walletId).recover( + isRescan: true, + ), + context: context, + message: "Rescanning blockchain", + subMessage: "This may take a while." + "\nPlease do not exit this screen.", + rootNavigator: Util.isDesktop, + onException: (ex) => e = ex, + ); + + if (e != null) { + throw e!; + } + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + if (mounted) { + // show error + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) => StackDialog( + title: "Rescan failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + Navigator.of(context, rootNavigator: Util.isDesktop).pop(); + }, + ), + ), + ); + } + } finally { + if (!Platform.isLinux) await WakelockPlus.disable(); + } + } + } + @override Widget build(BuildContext context) { return Background( diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index d3a98ef16..52d6e05a4 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -22,6 +22,8 @@ import '../../app_config.dart'; import '../../frost_route_generator.dart'; import '../../models/isar/exchange_cache/currency.dart'; import '../../notifications/show_flush_bar.dart'; +import '../../pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart'; +import '../../pages_desktop_specific/spark_coins/spark_coins_view.dart'; import '../../providers/global/active_wallet_provider.dart'; import '../../providers/global/auto_swb_service_provider.dart'; import '../../providers/global/paynym_api_provider.dart'; @@ -1138,6 +1140,38 @@ class _WalletViewState extends ConsumerState { ); }, ), + if (wallet is FiroWallet && + ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.advancedFiroFeatures, + ), + )) + WalletNavigationBarItemData( + label: "Lelantus coins", + icon: const CoinControlNavIcon(), + onTap: () { + Navigator.of(context).pushNamed( + LelantusCoinsView.routeName, + arguments: widget.walletId, + ); + }, + ), + if (wallet is FiroWallet && + ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.advancedFiroFeatures, + ), + )) + WalletNavigationBarItemData( + label: "Spark coins", + icon: const CoinControlNavIcon(), + onTap: () { + Navigator.of(context).pushNamed( + SparkCoinsView.routeName, + arguments: widget.walletId, + ); + }, + ), if (!viewOnly && wallet is PaynymInterface) WalletNavigationBarItemData( label: "PayNym", diff --git a/lib/pages/wallets_view/wallets_overview.dart b/lib/pages/wallets_view/wallets_overview.dart index 2567a83cf..95e38b07d 100644 --- a/lib/pages/wallets_view/wallets_overview.dart +++ b/lib/pages/wallets_view/wallets_overview.dart @@ -187,7 +187,7 @@ class _EthWalletsOverviewState extends ConsumerState { updateWallets(); if (AppConfig.isSingleCoinApp) { - GlobalEventBus.instance.on().listen((_) { + GlobalEventBus.instance.on().listen((_) { updateWallets(); WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { diff --git a/lib/pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart b/lib/pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart index 61624a272..c08891aef 100644 --- a/lib/pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart +++ b/lib/pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart @@ -11,224 +11,122 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:isar/isar.dart'; + import '../../models/isar/models/isar_models.dart'; import '../../providers/db/main_db_provider.dart'; import '../../themes/stack_colors.dart'; import '../../utilities/assets.dart'; import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/desktop/desktop_app_bar.dart'; import '../../widgets/desktop/desktop_scaffold.dart'; -import '../../widgets/rounded_white_container.dart'; +import '../../widgets/isar_collection_watcher_list.dart'; -class LelantusCoinsView extends ConsumerStatefulWidget { +class LelantusCoinsView extends ConsumerWidget { const LelantusCoinsView({ super.key, required this.walletId, }); + static const title = "Lelantus coins"; static const String routeName = "/lelantusCoinsView"; final String walletId; @override - ConsumerState createState() => _LelantusCoinsViewState(); -} - -class _LelantusCoinsViewState extends ConsumerState { - List _coins = []; - - Stream>? lelantusCoinsCollectionWatcher; - - void _onLelantusCoinsCollectionWatcherEvent(List coins) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - setState(() { - _coins = coins; - }); - } - }); - } - - @override - void initState() { - lelantusCoinsCollectionWatcher = ref - .read(mainDBProvider) - .isar - .lelantusCoins - .where() - .walletIdEqualTo(widget.walletId) - .sortByMintIndexDesc() - .watch(fireImmediately: true); - lelantusCoinsCollectionWatcher! - .listen((data) => _onLelantusCoinsCollectionWatcherEvent(data)); - - super.initState(); - } - - @override - void dispose() { - lelantusCoinsCollectionWatcher = null; - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return DesktopScaffold( - appBar: DesktopAppBar( - background: Theme.of(context).extension()!.popupBG, - leading: Expanded( - child: Row( - children: [ - const SizedBox( - width: 32, - ), - AppBarIconButton( - size: 32, - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.arrowLeft, - width: 18, - height: 18, - color: Theme.of(context) - .extension()! - .topNavIconPrimary, - ), - onPressed: Navigator.of(context).pop, - ), - const SizedBox( - width: 12, - ), - Text( - "Lelantus Coins", - style: STextStyles.desktopH3(context), - ), - const Spacer(), - ], - ), - ), - useSpacers: false, - isCompactHeight: true, - ), - body: Padding( - padding: const EdgeInsets.all(24), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(4), - child: RoundedWhiteContainer( - child: Row( - children: [ - Expanded( - flex: 9, - child: Text( - "TXID", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.left, - ), - ), - Expanded( - flex: 3, - child: Text( - "Value (sats)", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Index", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Is JMint", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Used", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), + Widget build(BuildContext context, WidgetRef ref) { + return ConditionalParent( + condition: Util.isDesktop, + builder: (child) { + return DesktopScaffold( + appBar: DesktopAppBar( + background: Theme.of(context).extension()!.popupBG, + leading: Expanded( + child: Row( + children: [ + const SizedBox( + width: 32, + ), + AppBarIconButton( + size: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, ), - ], - ), + onPressed: Navigator.of(context).pop, + ), + const SizedBox( + width: 12, + ), + Text( + title, + style: STextStyles.desktopH3(context), + ), + const Spacer(), + ], ), ), - Expanded( - child: ListView.separated( - shrinkWrap: true, - itemCount: _coins.length, - separatorBuilder: (_, __) => Container( - height: 1, - color: Theme.of(context) - .extension()! - .backgroundAppBar, + useSpacers: false, + isCompactHeight: true, + ), + body: Padding( + padding: const EdgeInsets.all(24), + child: child, + ), + ); + }, + child: ConditionalParent( + condition: !Util.isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + automaticallyImplyLeading: false, + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), ), - itemBuilder: (_, index) => Padding( - padding: const EdgeInsets.all(4), - child: RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - flex: 9, - child: SelectableText( - _coins[index].txid, - style: STextStyles.itemSubtitle12(context), - ), - ), - Expanded( - flex: 3, - child: SelectableText( - _coins[index].value, - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].mintIndex.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].isJMint.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].isUsed.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - ], - ), - ), + title: Text( + title, + style: STextStyles.navBarTitle(context), ), ), + body: SafeArea( + child: child, + ), ), - ], + ); + }, + child: IsarCollectionWatcherList( + itemName: title, + queryBuilder: () => ref + .read(mainDBProvider) + .isar + .lelantusCoins + .where() + .walletIdEqualTo(walletId) + .sortByMintIndexDesc(), + itemBuilder: (LelantusCoin? coin) { + return [ + ("TXID", coin?.txid ?? "", 9), + ("Value (sats)", coin?.value ?? "", 3), + ("Index", coin?.mintIndex.toString() ?? "", 2), + ("Is JMint", coin?.isJMint.toString() ?? "", 2), + ("Used", coin?.isUsed.toString() ?? "", 2), + ]; + }, ), ), ); diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart index abe41055c..659f5710a 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart @@ -10,11 +10,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/flutter_svg.dart'; + import '../../../../providers/wallet/public_private_balance_state_provider.dart'; import '../../../../providers/wallet/wallet_balance_toggle_state_provider.dart'; import '../../../../themes/stack_colors.dart'; -import '../../../../utilities/assets.dart'; import '../../../../utilities/constants.dart'; import '../../../../utilities/enums/wallet_balance_toggle_state.dart'; import '../../../../utilities/text_styles.dart'; @@ -86,7 +85,7 @@ class DesktopPrivateBalanceToggleButton extends ConsumerWidget { return SizedBox( height: 22, - width: 22, + width: 80, child: MaterialButton( color: Theme.of(context).extension()!.buttonBackSecondary, splashColor: Theme.of(context).extension()!.highlight, @@ -120,22 +119,12 @@ class DesktopPrivateBalanceToggleButton extends ConsumerWidget { ), ), child: Center( - child: currentType == FiroType.spark - ? SvgPicture.asset( - Assets.svg.spark, - width: 16, - // color: Theme.of(context) - // .extension()! - // .accentColorYellow, - ) - : Image( - image: AssetImage( - currentType == FiroType.public - ? Assets.png.glasses - : Assets.png.glassesHidden, - ), - width: 16, - ), + child: FittedBox( + child: Text( + currentType.name.toUpperCase(), + style: STextStyles.w500_10(context), + ), + ), ), ), ); diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart index 2499d4754..be0f080b1 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart @@ -49,7 +49,9 @@ import '../../../cashfusion/desktop_cashfusion_view.dart'; import '../../../churning/desktop_churning_view.dart'; import '../../../coin_control/desktop_coin_control_view.dart'; import '../../../desktop_menu.dart'; +import '../../../lelantus_coins/lelantus_coins_view.dart'; import '../../../ordinals/desktop_ordinals_view.dart'; +import '../../../spark_coins/spark_coins_view.dart'; import '../desktop_wallet_view.dart'; import 'more_features/more_features_dialog.dart'; @@ -89,6 +91,8 @@ class _DesktopWalletFeaturesState extends ConsumerState { walletId: widget.walletId, onPaynymPressed: _onPaynymPressed, onCoinControlPressed: _onCoinControlPressed, + onLelantusCoinsPressed: _onLelantusCoinsPressed, + onSparkCoinsPressedPressed: _onSparkCoinsPressed, onAnonymizeAllPressed: _onAnonymizeAllPressed, onWhirlpoolPressed: _onWhirlpoolPressed, onOrdinalsPressed: _onOrdinalsPressed, @@ -112,6 +116,24 @@ class _DesktopWalletFeaturesState extends ConsumerState { ); } + void _onLelantusCoinsPressed() { + Navigator.of(context, rootNavigator: true).pop(); + + Navigator.of(context).pushNamed( + LelantusCoinsView.routeName, + arguments: widget.walletId, + ); + } + + void _onSparkCoinsPressed() { + Navigator.of(context, rootNavigator: true).pop(); + + Navigator.of(context).pushNamed( + SparkCoinsView.routeName, + arguments: widget.walletId, + ); + } + Future _onAnonymizeAllPressed() async { Navigator.of(context, rootNavigator: true).pop(); await showDialog( diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart index 5f0e0f69b..c4a719510 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart @@ -151,6 +151,16 @@ class _WDesktopWalletSummaryState extends ConsumerState { .textSubtitle1, ), ), + if (coin is Firo) + const Row( + children: [ + DesktopPrivateBalanceToggleButton(), + SizedBox( + width: 8, + ), + DesktopBalanceToggleButton(), + ], + ) ], ), const SizedBox( @@ -163,15 +173,11 @@ class _WDesktopWalletSummaryState extends ConsumerState { ? ref.watch(pCurrentTokenWallet)!.tokenContract.address : null, ), - if (coin is Firo) + if (coin is! Firo) const SizedBox( width: 8, ), - if (coin is Firo) const DesktopPrivateBalanceToggleButton(), - const SizedBox( - width: 8, - ), - const DesktopBalanceToggleButton(), + if (coin is! Firo) const DesktopBalanceToggleButton(), ], ); }, diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart index 0869b0b50..5559e45bc 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart @@ -8,9 +8,12 @@ * */ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; import '../../../../../app_config.dart'; import '../../../../../db/sqlite/firo_cache.dart'; @@ -20,10 +23,14 @@ import '../../../../../providers/global/prefs_provider.dart'; import '../../../../../providers/global/wallets_provider.dart'; import '../../../../../themes/stack_colors.dart'; import '../../../../../utilities/assets.dart'; +import '../../../../../utilities/logger.dart'; +import '../../../../../utilities/show_loading.dart'; import '../../../../../utilities/text_styles.dart'; +import '../../../../../utilities/util.dart'; import '../../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../../wallets/isar/models/wallet_info.dart'; import '../../../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../../../wallets/wallet/impl/firo_wallet.dart'; import '../../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart'; import '../../../../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart'; @@ -39,6 +46,7 @@ import '../../../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../../../widgets/desktop/primary_button.dart'; import '../../../../../widgets/desktop/secondary_button.dart'; import '../../../../../widgets/rounded_container.dart'; +import '../../../../../widgets/stack_dialog.dart'; class MoreFeaturesDialog extends ConsumerStatefulWidget { const MoreFeaturesDialog({ @@ -46,6 +54,8 @@ class MoreFeaturesDialog extends ConsumerStatefulWidget { required this.walletId, required this.onPaynymPressed, required this.onCoinControlPressed, + required this.onLelantusCoinsPressed, + required this.onSparkCoinsPressedPressed, required this.onAnonymizeAllPressed, required this.onWhirlpoolPressed, required this.onOrdinalsPressed, @@ -57,6 +67,8 @@ class MoreFeaturesDialog extends ConsumerStatefulWidget { final String walletId; final VoidCallback? onPaynymPressed; final VoidCallback? onCoinControlPressed; + final VoidCallback? onLelantusCoinsPressed; + final VoidCallback? onSparkCoinsPressedPressed; final VoidCallback? onAnonymizeAllPressed; final VoidCallback? onWhirlpoolPressed; final VoidCallback? onOrdinalsPressed; @@ -83,6 +95,10 @@ class _MoreFeaturesDialogState extends ConsumerState { }, isar: ref.read(mainDBProvider).isar, ); + + if (newValue) { + await _doRescanMaybe(); + } } finally { // ensure _isUpdatingLelantusScanning is set to false no matter what _isUpdatingLelantusScanning = false; @@ -110,6 +126,124 @@ class _MoreFeaturesDialogState extends ConsumerState { } } + Future _doRescanMaybe() async { + final shouldRescan = await showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxWidth: 700, + child: Column( + children: [ + const DesktopDialogCloseButton(), + const SizedBox( + height: 5, + ), + Text( + "Rescan may be required", + style: STextStyles.desktopH2(context), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 16, + ), + const Spacer(), + Text( + "A blockchain rescan may be required to fully recover all lelantus history." + "\nThis may take a while.", + style: STextStyles.desktopTextMedium(context).copyWith( + color: Theme.of(context).extension()!.textDark3, + ), + textAlign: TextAlign.center, + ), + const Spacer(), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Rescan now", + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Later", + onPressed: () => Navigator.of(context).pop(false), + ), + ), + ], + ), + ), + ], + ), + ); + }, + ); + + if (mounted && shouldRescan == true) { + try { + if (!Platform.isLinux) await WakelockPlus.enable(); + + Exception? e; + if (mounted) { + await showLoading( + whileFuture: ref.read(pWallets).getWallet(widget.walletId).recover( + isRescan: true, + ), + context: context, + message: "Rescanning blockchain", + subMessage: + "This may take a while.\nPlease do not exit this screen.", + rootNavigator: Util.isDesktop, + onException: (ex) => e = ex, + ); + + if (e != null) { + throw e!; + } + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + if (mounted) { + // show error + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) => StackDialog( + title: "Rescan failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + Navigator.of(context, rootNavigator: Util.isDesktop).pop(); + }, + ), + ), + ); + } + } finally { + if (!Platform.isLinux) await WakelockPlus.disable(); + } + } + } + late final DSBController _switchController; bool _switchReuseAddressToggledLock = false; // Mutex. @@ -287,6 +421,24 @@ class _MoreFeaturesDialogState extends ConsumerState { iconAsset: Assets.svg.coinControl.gamePad, onPressed: () async => widget.onCoinControlPressed?.call(), ), + if (wallet is FiroWallet && + ref.watch(prefsChangeNotifierProvider + .select((s) => s.advancedFiroFeatures))) + _MoreFeaturesItem( + label: "Lelantus Coins", + detail: "View wallet lelantus coins", + iconAsset: Assets.svg.coinControl.gamePad, + onPressed: () async => widget.onLelantusCoinsPressed?.call(), + ), + if (wallet is FiroWallet && + ref.watch(prefsChangeNotifierProvider + .select((s) => s.advancedFiroFeatures))) + _MoreFeaturesItem( + label: "Spark Coins", + detail: "View wallet spark coins", + iconAsset: Assets.svg.coinControl.gamePad, + onPressed: () async => widget.onSparkCoinsPressedPressed?.call(), + ), if (!isViewOnly && wallet is PaynymInterface) _MoreFeaturesItem( label: "PayNym", diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart index f0756370f..47b590a61 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart @@ -10,7 +10,6 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; @@ -27,7 +26,6 @@ import '../../../../utilities/constants.dart'; import '../../../../utilities/show_loading.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; -import '../../../../wallets/crypto_currency/coins/firo.dart'; import '../../../../wallets/crypto_currency/intermediate/frost_currency.dart'; import '../../../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; @@ -35,8 +33,6 @@ import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; import '../../../addresses/desktop_wallet_addresses_view.dart'; -import '../../../lelantus_coins/lelantus_coins_view.dart'; -import '../../../spark_coins/spark_coins_view.dart'; import 'desktop_delete_wallet_dialog.dart'; enum _WalletOptions { @@ -44,8 +40,6 @@ enum _WalletOptions { deleteWallet, changeRepresentative, showXpub, - lelantusCoins, - sparkCoins, frostOptions, refreshFromHeight; @@ -59,10 +53,6 @@ enum _WalletOptions { return "Change representative"; case _WalletOptions.showXpub: return "Show xPub"; - case _WalletOptions.lelantusCoins: - return "Lelantus Coins"; - case _WalletOptions.sparkCoins: - return "Spark Coins"; case _WalletOptions.frostOptions: return "FROST settings"; case _WalletOptions.refreshFromHeight: @@ -107,12 +97,6 @@ class WalletOptionsButton extends ConsumerWidget { onShowXpubPressed: () async { Navigator.of(context).pop(_WalletOptions.showXpub); }, - onFiroShowLelantusCoins: () async { - Navigator.of(context).pop(_WalletOptions.lelantusCoins); - }, - onFiroShowSparkCoins: () async { - Navigator.of(context).pop(_WalletOptions.sparkCoins); - }, onFrostMSWalletOptionsPressed: () async { Navigator.of(context).pop(_WalletOptions.frostOptions); }, @@ -225,24 +209,6 @@ class WalletOptionsButton extends ConsumerWidget { } break; - case _WalletOptions.lelantusCoins: - unawaited( - Navigator.of(context).pushNamed( - LelantusCoinsView.routeName, - arguments: walletId, - ), - ); - break; - - case _WalletOptions.sparkCoins: - unawaited( - Navigator.of(context).pushNamed( - SparkCoinsView.routeName, - arguments: walletId, - ), - ); - break; - case _WalletOptions.frostOptions: unawaited( Navigator.of(context).pushNamed( @@ -303,8 +269,6 @@ class WalletOptionsPopupMenu extends ConsumerWidget { required this.onAddressListPressed, required this.onShowXpubPressed, required this.onChangeRepPressed, - required this.onFiroShowLelantusCoins, - required this.onFiroShowSparkCoins, required this.onFrostMSWalletOptionsPressed, required this.onRefreshHeightPressed, required this.walletId, @@ -314,8 +278,6 @@ class WalletOptionsPopupMenu extends ConsumerWidget { final VoidCallback onAddressListPressed; final VoidCallback onShowXpubPressed; final VoidCallback onChangeRepPressed; - final VoidCallback onFiroShowLelantusCoins; - final VoidCallback onFiroShowSparkCoins; final VoidCallback onFrostMSWalletOptionsPressed; final VoidCallback onRefreshHeightPressed; final String walletId; @@ -324,14 +286,11 @@ class WalletOptionsPopupMenu extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final coin = ref.watch(pWalletCoin(walletId)); - bool firoDebug = kDebugMode && (coin is Firo); - final wallet = ref.watch(pWallets).getWallet(walletId); bool xpubEnabled = wallet is ExtendedKeysInterface; if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) { xpubEnabled = false; - firoDebug = false; } final bool canChangeRep = coin is NanoCurrency; @@ -429,80 +388,6 @@ class WalletOptionsPopupMenu extends ConsumerWidget { ), ), ), - if (firoDebug) - const SizedBox( - height: 8, - ), - if (firoDebug) - TransparentButton( - onPressed: onFiroShowLelantusCoins, - child: Padding( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SvgPicture.asset( - Assets.svg.eye, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconLeft, - ), - const SizedBox(width: 14), - Expanded( - child: Text( - _WalletOptions.lelantusCoins.prettyName, - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - ), - ], - ), - ), - ), - if (firoDebug) - const SizedBox( - height: 8, - ), - if (firoDebug) - TransparentButton( - onPressed: onFiroShowSparkCoins, - child: Padding( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SvgPicture.asset( - Assets.svg.eye, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconLeft, - ), - const SizedBox(width: 14), - Expanded( - child: Text( - _WalletOptions.sparkCoins.prettyName, - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - ), - ], - ), - ), - ), if (isFrost) const SizedBox( height: 8, diff --git a/lib/pages_desktop_specific/spark_coins/spark_coins_view.dart b/lib/pages_desktop_specific/spark_coins/spark_coins_view.dart index 43ba62a6a..8a937fab3 100644 --- a/lib/pages_desktop_specific/spark_coins/spark_coins_view.dart +++ b/lib/pages_desktop_specific/spark_coins/spark_coins_view.dart @@ -11,285 +11,126 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:isar/isar.dart'; + import '../../providers/db/main_db_provider.dart'; import '../../themes/stack_colors.dart'; import '../../utilities/assets.dart'; import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; import '../../wallets/isar/models/spark_coin.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/desktop/desktop_app_bar.dart'; import '../../widgets/desktop/desktop_scaffold.dart'; -import '../../widgets/rounded_white_container.dart'; +import '../../widgets/isar_collection_watcher_list.dart'; -class SparkCoinsView extends ConsumerStatefulWidget { +class SparkCoinsView extends ConsumerWidget { const SparkCoinsView({ super.key, required this.walletId, }); + static const title = "Spark coins"; static const String routeName = "/sparkCoinsView"; final String walletId; @override - ConsumerState createState() => _SparkCoinsViewState(); -} - -class _SparkCoinsViewState extends ConsumerState { - List _coins = []; - - Stream>? sparkCoinsCollectionWatcher; - - void _onSparkCoinsCollectionWatcherEvent(List coins) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - setState(() { - _coins = coins; - }); - } - }); - } - - @override - void initState() { - sparkCoinsCollectionWatcher = ref - .read(mainDBProvider) - .isar - .sparkCoins - .where() - .walletIdEqualToAnyLTagHash(widget.walletId) - .sortByHeightDesc() - .watch(fireImmediately: true); - sparkCoinsCollectionWatcher! - .listen((data) => _onSparkCoinsCollectionWatcherEvent(data)); - - super.initState(); - } - - @override - void dispose() { - sparkCoinsCollectionWatcher = null; - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return DesktopScaffold( - appBar: DesktopAppBar( - background: Theme.of(context).extension()!.popupBG, - leading: Expanded( - child: Row( - children: [ - const SizedBox( - width: 32, - ), - AppBarIconButton( - size: 32, - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.arrowLeft, - width: 18, - height: 18, - color: Theme.of(context) - .extension()! - .topNavIconPrimary, - ), - onPressed: Navigator.of(context).pop, - ), - const SizedBox( - width: 12, - ), - Text( - "Spark Coins", - style: STextStyles.desktopH3(context), - ), - const Spacer(), - ], - ), - ), - useSpacers: false, - isCompactHeight: true, - ), - body: Padding( - padding: const EdgeInsets.all(24), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(4), - child: RoundedWhiteContainer( - child: Row( - children: [ - Expanded( - flex: 9, - child: Text( - "TXID", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.left, - ), - ), - Expanded( - flex: 9, - child: Text( - "LTag Hash", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.left, - ), - ), - Expanded( - flex: 9, - child: Text( - "Address", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.left, - ), - ), - Expanded( - flex: 4, - child: Text( - "Memo", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.left, - ), - ), - Expanded( - flex: 3, - child: Text( - "Value (sats)", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Height", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Group Id", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Type", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Used", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), + Widget build(BuildContext context, WidgetRef ref) { + return ConditionalParent( + condition: Util.isDesktop, + builder: (child) { + return DesktopScaffold( + appBar: DesktopAppBar( + background: Theme.of(context).extension()!.popupBG, + leading: Expanded( + child: Row( + children: [ + const SizedBox( + width: 32, + ), + AppBarIconButton( + size: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, ), - ], - ), + onPressed: Navigator.of(context).pop, + ), + const SizedBox( + width: 12, + ), + Text( + title, + style: STextStyles.desktopH3(context), + ), + const Spacer(), + ], ), ), - Expanded( - child: ListView.separated( - shrinkWrap: true, - itemCount: _coins.length, - separatorBuilder: (_, __) => Container( - height: 1, - color: Theme.of(context) - .extension()! - .backgroundAppBar, + useSpacers: false, + isCompactHeight: true, + ), + body: Padding( + padding: const EdgeInsets.all(24), + child: child, + ), + ); + }, + child: ConditionalParent( + condition: !Util.isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + automaticallyImplyLeading: false, + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), ), - itemBuilder: (_, index) => Padding( - padding: const EdgeInsets.all(4), - child: RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - flex: 9, - child: SelectableText( - _coins[index].txHash, - style: STextStyles.itemSubtitle12(context), - ), - ), - Expanded( - flex: 9, - child: SelectableText( - _coins[index].lTagHash, - style: STextStyles.itemSubtitle12(context), - ), - ), - Expanded( - flex: 9, - child: SelectableText( - _coins[index].address, - style: STextStyles.itemSubtitle12(context), - ), - ), - Expanded( - flex: 4, - child: SelectableText( - _coins[index].memo ?? "", - style: STextStyles.itemSubtitle12(context), - ), - ), - Expanded( - flex: 3, - child: SelectableText( - _coins[index].value.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].height.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].groupId.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].type.name, - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].isUsed.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - ], - ), - ), + title: Text( + title, + style: STextStyles.navBarTitle(context), ), ), + body: SafeArea( + child: child, + ), ), - ], + ); + }, + child: IsarCollectionWatcherList( + itemName: title, + queryBuilder: () => ref + .read(mainDBProvider) + .isar + .sparkCoins + .where() + .walletIdEqualToAnyLTagHash(walletId) + .sortByHeightDesc(), + itemBuilder: (SparkCoin? coin) { + return [ + ("TXID", coin?.txHash ?? "", 9), + ("LTag Hash", coin?.lTagHash ?? "", 9), + ("Address", coin?.address ?? "", 9), + ("Memo", coin?.memo ?? "", 4), + ("Value (sats)", coin?.value.toString() ?? "", 3), + ("Height", coin?.height.toString() ?? "", 2), + ("Group ID", coin?.groupId.toString() ?? "", 2), + ("Type", coin?.type.name ?? "", 2), + ("Used", coin?.isUsed.toString() ?? "", 2), + ]; + }, ), ), ); diff --git a/lib/services/event_bus/events/wallet_added_event.dart b/lib/services/event_bus/events/wallet_added_event.dart index 0407946fc..97b334169 100644 --- a/lib/services/event_bus/events/wallet_added_event.dart +++ b/lib/services/event_bus/events/wallet_added_event.dart @@ -1 +1 @@ -class WalletAddedEvent {} +class WalletsChangedEvent {} diff --git a/lib/services/exchange/trocador/trocador_api.dart b/lib/services/exchange/trocador/trocador_api.dart index 9b497a685..fa791d7d4 100644 --- a/lib/services/exchange/trocador/trocador_api.dart +++ b/lib/services/exchange/trocador/trocador_api.dart @@ -28,7 +28,7 @@ const kTrocadorApiKey = "8rFqf7QLxX1mUBiNPEMaLUpV2biz6n"; const kTrocadorRefCode = "9eHm9BkQfS"; abstract class TrocadorAPI { - static const String authority = "trocador.app"; + static const String authority = "api.trocador.app"; static const String onionAuthority = "trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion"; @@ -42,8 +42,8 @@ abstract class TrocadorAPI { Map? params, }) { return isOnion - ? Uri.http(onionAuthority, "api/$method", params) - : Uri.https(authority, "api/$method", params); + ? Uri.http(onionAuthority, method, params) + : Uri.https(authority, method, params); } static Future _makeGetRequest(Uri uri) async { @@ -52,7 +52,10 @@ abstract class TrocadorAPI { debugPrint("URI: $uri"); final response = await client.get( url: uri, - headers: {'Content-Type': 'application/json'}, + headers: { + "Content-Type": "application/json", + "API-KEY": kTrocadorApiKey, + }, proxyInfo: Prefs.instance.useTor ? TorService.sharedInstance.getProxyInfo() : null, @@ -83,7 +86,6 @@ abstract class TrocadorAPI { isOnion: isOnion, method: "coins", params: { - "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, }, ); @@ -123,7 +125,6 @@ abstract class TrocadorAPI { isOnion: isOnion, method: "trade", params: { - "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, "id": tradeId, }, @@ -155,7 +156,6 @@ abstract class TrocadorAPI { required String fromAmount, }) async { final params = { - "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, "ticker_from": fromTicker.toLowerCase(), "network_from": fromNetwork, @@ -180,7 +180,6 @@ abstract class TrocadorAPI { required String toAmount, }) async { final params = { - "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, "ticker_from": fromTicker.toLowerCase(), "network_from": fromNetwork, @@ -239,7 +238,6 @@ abstract class TrocadorAPI { required bool isFixedRate, }) async { final Map params = { - "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, "ticker_from": fromTicker.toLowerCase(), "network_from": fromNetwork, @@ -280,7 +278,6 @@ abstract class TrocadorAPI { required bool isFixedRate, }) async { final params = { - "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, "ticker_from": fromTicker.toLowerCase(), "network_from": fromNetwork, diff --git a/lib/services/wallets.dart b/lib/services/wallets.dart index c606a75cf..6defcf012 100644 --- a/lib/services/wallets.dart +++ b/lib/services/wallets.dart @@ -62,7 +62,9 @@ class Wallets { ); } _wallets[wallet.walletId] = wallet; - GlobalEventBus.instance.fire(WalletAddedEvent()); + if (AppConfig.isSingleCoinApp) { + GlobalEventBus.instance.fire(WalletsChangedEvent()); + } } Future deleteWallet( @@ -150,6 +152,10 @@ class Wallets { await mainDB.isar.writeTxn(() async { await mainDB.isar.walletInfo.deleteByWalletId(walletId); }); + + if (AppConfig.isSingleCoinApp) { + GlobalEventBus.instance.fire(WalletsChangedEvent()); + } } Future load(Prefs prefs, MainDB mainDB) async { diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index 5ffc7a059..0bc2dcc43 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -72,6 +72,7 @@ class Prefs extends ChangeNotifier { _fusionServerInfo = await _getFusionServerInfo(); _autoPin = await _getAutoPin(); _enableExchange = await _getEnableExchange(); + _advancedFiroFeatures = await _getAdvancedFiroFeatures(); _initialized = true; } @@ -1158,4 +1159,27 @@ class Prefs extends ChangeNotifier { ) as bool? ?? true; } + + // Show/hide lelantus and spark coins. Defaults to false + bool _advancedFiroFeatures = false; + bool get advancedFiroFeatures => _advancedFiroFeatures; + set advancedFiroFeatures(bool advancedFiroFeatures) { + if (_advancedFiroFeatures != advancedFiroFeatures) { + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "advancedFiroFeatures", + value: advancedFiroFeatures, + ); + _advancedFiroFeatures = advancedFiroFeatures; + notifyListeners(); + } + } + + Future _getAdvancedFiroFeatures() async { + return await DB.instance.get( + boxName: DB.boxNamePrefs, + key: "advancedFiroFeatures", + ) as bool? ?? + false; + } } diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart index 186eb5663..8de5bbbab 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart @@ -1014,31 +1014,35 @@ mixin SparkInterface } // check and update coins if required - final List updatedCoins = []; + final List checkedCoins = []; for (final coin in coinsToCheck) { - SparkCoin updated = coin; + final SparkCoin checked; - if (updated.height == null) { + if (coin.height == null) { final tx = await electrumXCachedClient.getTransaction( - txHash: updated.txHash, + txHash: coin.txHash, cryptoCurrency: info.coin, ); if (tx["height"] is int) { - updated = updated.copyWith(height: tx["height"] as int); + checked = coin.copyWith( + height: tx["height"] as int, + isUsed: spentCoinTags!.contains(coin.lTagHash), + ); + } else { + checked = coin; } + } else { + checked = spentCoinTags!.contains(coin.lTagHash) + ? coin.copyWith(isUsed: true) + : coin; } - if (updated.height != null && - spentCoinTags!.contains(updated.lTagHash)) { - updated = coin.copyWith(isUsed: true); - } - - updatedCoins.add(updated); + checkedCoins.add(checked); } - // update in db if any have changed - if (updatedCoins.isNotEmpty) { + // add/update in db + if (checkedCoins.isNotEmpty) { await mainDB.isar.writeTxn(() async { - await mainDB.isar.sparkCoins.putAll(updatedCoins); + await mainDB.isar.sparkCoins.putAll(checkedCoins); }); } diff --git a/lib/widgets/isar_collection_watcher_list.dart b/lib/widgets/isar_collection_watcher_list.dart new file mode 100644 index 000000000..bd4c2596e --- /dev/null +++ b/lib/widgets/isar_collection_watcher_list.dart @@ -0,0 +1,182 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:isar/isar.dart'; + +import '../../themes/stack_colors.dart'; +import '../../utilities/text_styles.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../utilities/util.dart'; +import 'detail_item.dart'; + +class IsarCollectionWatcherList extends StatefulWidget { + const IsarCollectionWatcherList({ + super.key, + required this.queryBuilder, + required this.itemBuilder, + required this.itemName, + }); + + final String itemName; + final QueryBuilder Function() queryBuilder; + final List<(String title, String value, int flex)> Function(T?) itemBuilder; + + @override + State> createState() => + _IsarCollectionWatcherListState(); +} + +class _IsarCollectionWatcherListState + extends State> { + List _items = []; + + Stream>? sparkCoinsCollectionWatcher; + + late final StreamSubscription> _streamSubscription; + + void _onSparkCoinsCollectionWatcherEvent(List items) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() { + _items = items; + }); + } + }); + } + + @override + void initState() { + super.initState(); + + sparkCoinsCollectionWatcher = + widget.queryBuilder().watch(fireImmediately: true); + _streamSubscription = sparkCoinsCollectionWatcher! + .listen((data) => _onSparkCoinsCollectionWatcherEvent(data)); + } + + @override + void dispose() { + _streamSubscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (Util.isDesktop) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: RoundedWhiteContainer( + child: Row( + children: [ + Text( + "Total ${widget.itemName}: ${_items.length}", + style: STextStyles.itemSubtitle(context), + textAlign: TextAlign.left, + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(4), + child: RoundedWhiteContainer( + child: Row( + children: [ + ...widget.itemBuilder(null).map( + (e) => Expanded( + flex: e.$3, + child: Text( + e.$1, + style: STextStyles.itemSubtitle(context), + textAlign: TextAlign.left, + ), + ), + ), + ], + ), + ), + ), + Expanded( + child: ListView.separated( + shrinkWrap: true, + itemCount: _items.length, + separatorBuilder: (_, __) => Container( + height: 1, + color: Theme.of(context) + .extension()! + .backgroundAppBar, + ), + itemBuilder: (_, index) => Padding( + padding: const EdgeInsets.all(4), + child: RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ...widget.itemBuilder(_items[index]).map( + (e) => Expanded( + flex: e.$3, + child: SelectableText( + e.$2, + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.left, + ), + ), + ), + ], + ), + ), + ), + ), + ), + ], + ); + } else { + return ListView.builder( + itemCount: _items.length + 1, + itemBuilder: (ctx, index) { + return Padding( + padding: const EdgeInsets.only( + bottom: 16, + left: 16, + right: 16, + ), + child: RoundedWhiteContainer( + child: index == 0 + ? Row( + children: [ + Text( + "Total ${widget.itemName}: ${_items.length}", + style: STextStyles.itemSubtitle(context), + ), + ], + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + ...widget.itemBuilder(_items[index - 1]).map( + (e) => DetailItem( + title: e.$1, + detail: e.$2, + ), + ), + ], + ), + ), + ); + }, + ); + } + } +} diff --git a/macos/Podfile.lock b/macos/Podfile.lock index b9159cd55..fa0068398 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -7,7 +7,7 @@ PODS: - connectivity_plus (0.0.1): - FlutterMacOS - ReachabilitySwift - - cs_monero_flutter_libs (0.0.1): + - cs_monero_flutter_libs_macos (0.0.1): - FlutterMacOS - desktop_drop (0.0.1): - FlutterMacOS @@ -44,8 +44,6 @@ PODS: - "sqlite3 (3.46.0+1)": - "sqlite3/common (= 3.46.0+1)" - "sqlite3/common (3.46.0+1)" - - "sqlite3/dbstatvtab (3.46.0+1)": - - sqlite3/common - "sqlite3/fts5 (3.46.0+1)": - sqlite3/common - "sqlite3/perf-threadsafe (3.46.0+1)": @@ -54,8 +52,7 @@ PODS: - sqlite3/common - sqlite3_flutter_libs (0.0.1): - FlutterMacOS - - "sqlite3 (~> 3.46.0+1)" - - sqlite3/dbstatvtab + - sqlite3 (~> 3.46.0) - sqlite3/fts5 - sqlite3/perf-threadsafe - sqlite3/rtree @@ -73,7 +70,7 @@ DEPENDENCIES: - camera_macos (from `Flutter/ephemeral/.symlinks/plugins/camera_macos/macos`) - coinlib_flutter (from `Flutter/ephemeral/.symlinks/plugins/coinlib_flutter/darwin`) - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`) - - cs_monero_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/cs_monero_flutter_libs/macos`) + - cs_monero_flutter_libs_macos (from `Flutter/ephemeral/.symlinks/plugins/cs_monero_flutter_libs_macos/macos`) - desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - devicelocale (from `Flutter/ephemeral/.symlinks/plugins/devicelocale/macos`) @@ -108,8 +105,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/coinlib_flutter/darwin connectivity_plus: :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos - cs_monero_flutter_libs: - :path: Flutter/ephemeral/.symlinks/plugins/cs_monero_flutter_libs/macos + cs_monero_flutter_libs_macos: + :path: Flutter/ephemeral/.symlinks/plugins/cs_monero_flutter_libs_macos/macos desktop_drop: :path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos device_info_plus: @@ -157,28 +154,28 @@ SPEC CHECKSUMS: camera_macos: c2603f5eed16f05076cf17e12030d2ce55a77839 coinlib_flutter: 9275e8255ef67d3da33beb6e117d09ced4f46eb5 connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747 - cs_monero_flutter_libs: e91a436103857259f5855cad4971301a5a29b38d + cs_monero_flutter_libs_macos: b901f94d39d1338f706312b026aba928d23582d4 desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720 devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225 flutter_libepiccash: be1560a04150c5cc85bcf08d236ec2b3d1f5d8da flutter_libsparkmobile: df2d36af1691379c81249e7be7b68be3c81d388b - flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4 + flutter_local_notifications: 4b427ffabf278fc6ea9484c97505e231166927a5 flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 frostdart: e6bf3119527ccfbcec1b8767da6ede5bb4c4f716 isar_flutter_libs: 43385c99864c168fadba7c9adeddc5d38838ca6a lelantus: 308e42c5a648598936a07a234471dd8cf8e687a0 local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3 - package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c + package_info_plus: f5790acc797bf17c3e959e9d6cf162cc68ff7523 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 sqlite3: 292c3e1bfe89f64e51ea7fc7dab9182a017c8630 - sqlite3_flutter_libs: 5ca46c1a04eddfbeeb5b16566164aa7ad1616e7b + sqlite3_flutter_libs: 1be4459672f8168ded2d8667599b8e3ca5e72b83 stack_wallet_backup: 6ebc60b1bdcf11cf1f1cbad9aa78332e1e15778c tor_ffi_plugin: 2566c1ed174688cca560fa0c64b7a799c66f07cb - url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 + url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 diff --git a/pubspec.lock b/pubspec.lock index 6a4e5316b..bb163c0e0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: "direct dev" description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" another_flushbar: dependency: "direct main" description: @@ -363,10 +363,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" compat: dependency: "direct main" description: @@ -1197,18 +1197,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -1300,10 +1300,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -1756,7 +1756,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" socks5_proxy: dependency: "direct main" description: @@ -1843,10 +1843,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stack_wallet_backup: dependency: "direct main" description: @@ -1892,10 +1892,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" string_validator: dependency: "direct main" description: @@ -1924,26 +1924,26 @@ packages: dependency: transitive description: name: test - sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" + sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" url: "https://pub.dev" source: hosted - version: "1.25.7" + version: "1.25.8" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" test_core: dependency: transitive description: name: test_core - sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" + sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" tezart: dependency: "direct main" description: @@ -2142,10 +2142,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.0" wakelock_platform_interface: dependency: transitive description: @@ -2231,10 +2231,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" webkit_inspection_protocol: dependency: transitive description: @@ -2317,5 +2317,5 @@ packages: source: hosted version: "0.2.3" sdks: - dart: ">=3.5.3 <4.0.0" - flutter: ">=3.24.3" + dart: ">=3.6.1 <4.0.0" + flutter: ">=3.27.3" diff --git a/scripts/app_config/templates/pubspec.template b/scripts/app_config/templates/pubspec.template index 46c98e8ef..bdb0df341 100644 --- a/scripts/app_config/templates/pubspec.template +++ b/scripts/app_config/templates/pubspec.template @@ -14,8 +14,8 @@ description: PLACEHOLDER version: PLACEHOLDER_V+PLACEHOLDER_B environment: - sdk: ">=3.5.3 <4.0.0" - flutter: ^3.24.3 + sdk: ">=3.6.1 <4.0.0" + flutter: ^3.27.3 dependencies: flutter: