From 3eaf0731205856b7575e82136c423ab38d85761f Mon Sep 17 00:00:00 2001 From: malik1004x Date: Sat, 13 Jun 2026 11:19:47 +0200 Subject: [PATCH 1/4] fix lnurl from scan button (#3332) --- cw_core/lib/lnurl.dart | 13 +++++++---- .../action_row/coin_action_row.dart | 22 +++++++++---------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/cw_core/lib/lnurl.dart b/cw_core/lib/lnurl.dart index c04bfc684..03be284d5 100644 --- a/cw_core/lib/lnurl.dart +++ b/cw_core/lib/lnurl.dart @@ -7,12 +7,17 @@ const _BOLT_PREFIXES = ["lnbcrt", "lntbs", "lnbc", "lntb"]; const _LUD17_PREFIXES = ['lnurlw', 'lnurlc', 'lnurlp', 'keyauth']; bool isBolt11ZeroInvoice(String invoice) { - final request = Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length); + try { + final request = Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length); - final prefix = _BOLT_PREFIXES - .firstWhere((prefix) => request.hrp.startsWith(prefix), orElse: () => ""); - return request.hrp.length == prefix.length; + final prefix = _BOLT_PREFIXES + .firstWhere((prefix) => request.hrp.startsWith(prefix), orElse: () => ""); + + return request.hrp.length == prefix.length; + } catch(e) { + return false; + } } /// Get the amount of a Bolt 11 Invoice in diff --git a/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart b/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart index db877e1e2..5613132bc 100644 --- a/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart +++ b/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart @@ -163,17 +163,17 @@ class CoinActionRow extends StatelessWidget { late final PaymentRequest req; var unspentCoinType = UnspentCoinType.any; - // if (false && SendViewModelBase.isNonZeroAmountLightningInvoice(code)) { - // unspentCoinType = UnspentCoinType.lightning; - // final amount = CryptoCurrency.btcln.formatAmount(BigInt.from(getBolt11Amount(code) ?? 0)); - // req = PaymentRequest(code, amount, "", "", ""); - // } else if (false && SendViewModelBase.isLnurlInvoice(code)) { - // unspentCoinType = UnspentCoinType.lightning; - // final amount = CryptoCurrency.btcln.formatAmount(BigInt.from(await LNURL.getPayRequestAmount(code) ?? 0)); - // req = PaymentRequest(code, amount, "", "", ""); - // } else if (false &&OpenCryptoPayService.isOpenCryptoPayQR(code)) { - // req = PaymentRequest(code, "", "", "", ""); - // } else + if (SendViewModelBase.isNonZeroAmountLightningInvoice(code)) { + unspentCoinType = UnspentCoinType.lightning; + final amount = CryptoCurrency.btcln.formatAmount(BigInt.from(getBolt11Amount(code) ?? 0)); + req = PaymentRequest(code, amount, "", "", ""); + } else if (SendViewModelBase.isLnurlInvoice(code)) { + unspentCoinType = UnspentCoinType.lightning; + final amount = CryptoCurrency.btcln.formatAmount(BigInt.from(await LNURL.getPayRequestAmount(code) ?? 0)); + req = PaymentRequest(code, amount, "", "", ""); + } else if (OpenCryptoPayService.isOpenCryptoPayQR(code)) { + req = PaymentRequest(code, "", "", "", ""); + } else if (Uri.tryParse(code)?.scheme == "wc") { if (!isWalletConnectCompatibleChain(walletType)) { showPopUp( From dbbd06aa564f5efc7de6e7d0ada818846d946e52 Mon Sep 17 00:00:00 2001 From: malik1004x Date: Sat, 13 Jun 2026 11:34:49 +0200 Subject: [PATCH 2/4] fix padding on NewWalletTypePage (#3333) --- .../new_wallet/new_wallet_type_page.dart | 122 +++++++++--------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart index 55bd16803..2c44370ed 100644 --- a/lib/src/screens/new_wallet/new_wallet_type_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart @@ -135,69 +135,73 @@ class WalletTypeFormState extends State { fontSize: 14, ), ), - Flexible( - child: Padding( - padding: const EdgeInsets.only(left: 18, right: 18, bottom: 12), - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainer, - borderRadius: BorderRadius.circular(18), - ), - child: ListView.separated( - key: const ValueKey('new_wallet_type_scrollable_key'), - itemCount: filteredTypes.length, - shrinkWrap: true, - itemBuilder: (context, index) { - final item = filteredTypes[index]; - - return GestureDetector( - key: ValueKey('new_wallet_type_${item.name}_button_key'), - behavior: HitTestBehavior.opaque, - onTap: () { - onTypeSelected(item); - }, - child: SizedBox( - height: 48, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - CakeImageWidget( - height: 24, - width: 24, - imageUrl: getCryptoCurrencyIconForWalletListItem(item), - ), - const SizedBox(width: 12), - Text(walletTypeToDisplayName(item)), - const SizedBox(width: 4), - Text( - walletTypeToDisplayTicker(item), - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only(left: 18, right: 18, bottom: 12), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(18), + ), + child: ListView.separated( + key: const ValueKey('new_wallet_type_scrollable_key'), + itemCount: filteredTypes.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemBuilder: (context, index) { + final item = filteredTypes[index]; + + return GestureDetector( + key: ValueKey('new_wallet_type_${item.name}_button_key'), + behavior: HitTestBehavior.opaque, + onTap: () { + onTypeSelected(item); + }, + child: SizedBox( + height: 48, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + CakeImageWidget( + height: 24, + width: 24, + imageUrl: getCryptoCurrencyIconForWalletListItem(item), ), - ) - ], - ), - CakeImageWidget( - imageUrl: "assets/new-ui/arrow_forward.svg", - height: 16, - colorFilter: ColorFilter.mode( - Theme.of(context).colorScheme.onSurfaceVariant, - BlendMode.srcIn, + const SizedBox(width: 12), + Text(walletTypeToDisplayName(item)), + const SizedBox(width: 4), + Text( + walletTypeToDisplayTicker(item), + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], ), - ) - ], + CakeImageWidget( + imageUrl: "assets/new-ui/arrow_forward.svg", + height: 16, + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.onSurfaceVariant, + BlendMode.srcIn, + ), + ) + ], + ), ), ), - ), - ); - }, - separatorBuilder: (context, index) => Container( - height: 1, - color: Theme.of(context).colorScheme.surfaceContainerHigh, + ); + }, + separatorBuilder: (context, index) => Container( + height: 1, + color: Theme.of(context).colorScheme.surfaceContainerHigh, + ), ), ), ), From 9face62effa3326005047351a8e7557e51ac1044 Mon Sep 17 00:00:00 2001 From: malik1004x Date: Mon, 15 Jun 2026 01:17:47 +0200 Subject: [PATCH 3/4] disable editing coin control from settings (#3284) --- lib/di.dart | 6 ++-- lib/new-ui/pages/coin_control_page.dart | 28 ++++++++++++++----- lib/new-ui/pages/send_page.dart | 2 +- .../coin_control_list_item.dart | 7 ++++- .../widgets/swap_page/swap_options_page.dart | 2 +- lib/router.dart | 9 ++---- lib/src/screens/settings/privacy_page.dart | 3 +- res/values/strings_en.arb | 1 + 8 files changed, 38 insertions(+), 20 deletions(-) diff --git a/lib/di.dart b/lib/di.dart index 0043c4e04..821f51130 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1569,10 +1569,10 @@ Future setup({ unspentCoinsListViewModel: getIt.get(param1: coinTypeToSpendFrom))); - getIt.registerFactoryParam( - (coinTypeToSpendFrom, _) => NewCoinControlPage( + getIt.registerFactoryParam( + (coinTypeToSpendFrom, canEdit) => NewCoinControlPage( unspentCoinsListViewModel: - getIt.get(param1: coinTypeToSpendFrom)) + getIt.get(param1: coinTypeToSpendFrom), canEdit: canEdit ?? true,) ); diff --git a/lib/new-ui/pages/coin_control_page.dart b/lib/new-ui/pages/coin_control_page.dart index 2cf3f7e11..656b608ad 100644 --- a/lib/new-ui/pages/coin_control_page.dart +++ b/lib/new-ui/pages/coin_control_page.dart @@ -4,15 +4,25 @@ import 'package:cake_wallet/new-ui/widgets/modal_header.dart'; import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; +import 'package:cw_core/unspent_coin_type.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import "package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart"; +class CoinControlPageArgs { + final bool canEdit; + final UnspentCoinType? coinTypeToSpendFrom; + + const CoinControlPageArgs({required this.canEdit, this.coinTypeToSpendFrom}); +} + class NewCoinControlPage extends StatefulWidget { - const NewCoinControlPage({super.key, required this.unspentCoinsListViewModel}); + const NewCoinControlPage( + {super.key, required this.unspentCoinsListViewModel, required this.canEdit}); final UnspentCoinsListViewModel unspentCoinsListViewModel; + final bool canEdit; @override State createState() => _NewCoinControlPageState(); @@ -31,9 +41,9 @@ class _NewCoinControlPageState extends State { Widget build(BuildContext context) { return PopScope( canPop: !widget.unspentCoinsListViewModel.isSavingItems, - onPopInvokedWithResult: (didPop, result) async{ + onPopInvokedWithResult: (didPop, result) async { await widget.unspentCoinsListViewModel.dispose(); - if(!didPop && mounted) Navigator.of(context).pop(); + if (!didPop && mounted) Navigator.of(context).pop(); }, child: Material( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), @@ -94,9 +104,9 @@ class _NewCoinControlPageState extends State { child: ModalHeader( iconPath: "assets/new-ui/settings_row_icons/coin-control.svg", title: "Coin Control", - message: S.of(context).coin_control_desc), + message: widget.canEdit ? S.of(context).coin_control_desc : S.of(context).coin_control_desc_no_edit), ), - if (widget.unspentCoinsListViewModel.items.isNotEmpty) + if (widget.unspentCoinsListViewModel.items.isNotEmpty && widget.canEdit) Padding( padding: const EdgeInsets.all(12.0), child: Row( @@ -124,7 +134,7 @@ class _NewCoinControlPageState extends State { ) ], ), - ), + ) else SizedBox(height: 24,), if (widget.unspentCoinsListViewModel.nonFrozenItems.isEmpty && widget.unspentCoinsListViewModel.frozenItems.isEmpty) ...[ SizedBox(height: 12), @@ -144,6 +154,7 @@ class _NewCoinControlPageState extends State { : Padding( padding: const EdgeInsets.symmetric(horizontal: 18.0), child: CoinControlListSection( + canEdit: widget.canEdit, items: widget.unspentCoinsListViewModel.nonFrozenItems, unspentCoinsListViewModel: widget.unspentCoinsListViewModel), @@ -170,6 +181,7 @@ class _NewCoinControlPageState extends State { .onSurfaceVariant), ), CoinControlListSection( + canEdit: widget.canEdit, items: widget .unspentCoinsListViewModel.frozenItems, unspentCoinsListViewModel: @@ -194,9 +206,10 @@ class _NewCoinControlPageState extends State { class CoinControlListSection extends StatelessWidget { const CoinControlListSection( - {super.key, required this.items, required this.unspentCoinsListViewModel}); + {super.key, required this.items, required this.unspentCoinsListViewModel, required this.canEdit}); final List items; + final bool canEdit; final UnspentCoinsListViewModel unspentCoinsListViewModel; @override @@ -231,6 +244,7 @@ class CoinControlListSection extends StatelessWidget { isLoading: item.isBeingSaved, isFirst: index == 0, isLast: index == items.length - 1, + hasCheckbox: canEdit, onCheckBoxTap: item.isFrozen ? null : () async { diff --git a/lib/new-ui/pages/send_page.dart b/lib/new-ui/pages/send_page.dart index fec5e06f5..fd0b69699 100644 --- a/lib/new-ui/pages/send_page.dart +++ b/lib/new-ui/pages/send_page.dart @@ -524,7 +524,7 @@ class _NewSendPageState extends State { label: "Coin Control", onTap: () { showCupertinoModalBottomSheet(enableDrag: false, useRootNavigator: true, isDismissible: false, context: context, builder: (context){ - return NewCoinControlPage(unspentCoinsListViewModel: widget.sendViewModel.unspentCoinsListViewModel,); + return NewCoinControlPage(unspentCoinsListViewModel: widget.sendViewModel.unspentCoinsListViewModel,canEdit: true); }); } ), diff --git a/lib/new-ui/widgets/coin_control_page/coin_control_list_item.dart b/lib/new-ui/widgets/coin_control_page/coin_control_list_item.dart index ab58ea240..3254498d1 100644 --- a/lib/new-ui/widgets/coin_control_page/coin_control_list_item.dart +++ b/lib/new-ui/widgets/coin_control_page/coin_control_list_item.dart @@ -17,7 +17,7 @@ class CoinControlListItem extends StatelessWidget { required this.isFirst, required this.isLast, required this.isLoading, - this.onCheckBoxTap, + this.onCheckBoxTap, required this.hasCheckbox, }); final String note; @@ -31,6 +31,7 @@ class CoinControlListItem extends StatelessWidget { final bool isFirst; final bool isLast; final bool isLoading; + final bool hasCheckbox; final Function()? onCheckBoxTap; @override @@ -123,6 +124,10 @@ class CoinControlListItem extends StatelessWidget { return CakeImageWidget(imageUrl:"assets/new-ui/frozen.svg"); } + if(!hasCheckbox) { + return SizedBox.shrink(); + } + return NewSimpleCheckbox(value: isSending, onChanged: (value){onCheckBoxTap?.call();}); } diff --git a/lib/new-ui/widgets/swap_page/swap_options_page.dart b/lib/new-ui/widgets/swap_page/swap_options_page.dart index 917537db0..6bd8a643f 100644 --- a/lib/new-ui/widgets/swap_page/swap_options_page.dart +++ b/lib/new-ui/widgets/swap_page/swap_options_page.dart @@ -102,7 +102,7 @@ class SwapOptionsPage extends StatelessWidget { label: "Coin Control", onTap: () { showCupertinoModalBottomSheet(enableDrag: false, useRootNavigator: true, isDismissible: false, context: context, builder: (context){ - return NewCoinControlPage(unspentCoinsListViewModel: exchangeViewModel.unspentCoinsListViewModel,); + return NewCoinControlPage(unspentCoinsListViewModel: exchangeViewModel.unspentCoinsListViewModel,canEdit: true,); }); }), ListItemSelector( diff --git a/lib/router.dart b/lib/router.dart index a08f78e4d..638329ae8 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -759,12 +759,9 @@ Route createRoute(RouteSettings settings) { ); case Routes.unspentCoinsList: - final coinTypeToSpendFrom = settings.arguments as UnspentCoinType?; - return handleRouteWithPlatformAwareness( - (context) => FeatureFlag.hasNewUi - ? getIt.get(param1: coinTypeToSpendFrom) - : getIt.get(param1: coinTypeToSpendFrom), - ); + final args = settings.arguments as CoinControlPageArgs?; + return handleRouteWithPlatformAwareness((context) => + getIt.get(param1: args?.coinTypeToSpendFrom, param2: args?.canEdit)); case Routes.unspentCoinsDetails: final args = settings.arguments as List; diff --git a/lib/src/screens/settings/privacy_page.dart b/lib/src/screens/settings/privacy_page.dart index 2a8c537e8..883814617 100644 --- a/lib/src/screens/settings/privacy_page.dart +++ b/lib/src/screens/settings/privacy_page.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart'; import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_toggle.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/new-ui/pages/coin_control_page.dart'; import 'package:cake_wallet/new-ui/widgets/modal_header.dart'; import 'package:cake_wallet/new-ui/widgets/modal_page_wrapper.dart'; import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; @@ -95,7 +96,7 @@ class PrivacyPage extends BasePage { keyValue: "coin_control", label: "Coin Control", onTap: () => - Navigator.of(context).pushNamed(Routes.unspentCoinsList)), + Navigator.of(context).pushNamed(Routes.unspentCoinsList, arguments: CoinControlPageArgs(canEdit: false, coinTypeToSpendFrom: null))), ], } ), diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 964255cac..b7f1832fd 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -208,6 +208,7 @@ "close": "Close", "coin_control": "Coin Control (optional)", "coin_control_desc": "Filter outputs for this transaction. The changes you make here will only reflect on the current transaction.", + "coin_control_desc_no_edit": "Freeze outputs if needed. The changes will be persistent across all transactions. You can also filter outputs for one transaction on the Send or Swap screens.", "coin_control_load_failed": "Failed to load unspent coins", "cold_or_recover_wallet": "Add an air-gapped cold wallet or recover a paper wallet.", "collection_address": "Collection Address", From 241503774f64e345ff5ec372775d0a09acf55e1a Mon Sep 17 00:00:00 2001 From: cyan Date: Mon, 15 Jun 2026 01:21:14 +0200 Subject: [PATCH 4/4] dev: add recovery screen when sqlite db doesn't open (#3179) * dev: add recovery screen when sqlite db doesn't open * Update cw_core/lib/db/sqlite_debug.dart [skip ci] --------- Co-authored-by: Omar Hatem --- cw_core/lib/db/sqlite.dart | 20 ++ cw_core/lib/db/sqlite_debug.dart | 177 ++++++++++++++++++ .../screens/settings/other_settings_page.dart | 12 ++ 3 files changed, 209 insertions(+) create mode 100644 cw_core/lib/db/sqlite_debug.dart diff --git a/cw_core/lib/db/sqlite.dart b/cw_core/lib/db/sqlite.dart index bcd1a275d..bbcbf1941 100644 --- a/cw_core/lib/db/sqlite.dart +++ b/cw_core/lib/db/sqlite.dart @@ -1,8 +1,10 @@ import 'dart:io'; +import 'package:cw_core/db/sqlite_debug.dart'; import 'package:cw_core/root_dir.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import 'package:path/path.dart' as p; Database? db; @@ -22,7 +24,25 @@ Future _addColumnIfNotExists( } } +Future sqliteDebugMarkerFile() async { + final appDir = await getAppDir(); + final dbDebugMarker = p.join(appDir.path, ".sqlite_db_debug"); + return File(dbDebugMarker); +} + Future initDb({String? pathOverride}) async { + final dbDebugMarker = await sqliteDebugMarkerFile(); + try { + if (dbDebugMarker.existsSync()) { + throw Exception("Debug marker is present"); + } + await _initDb(pathOverride: pathOverride); + } catch (e, s) { + await handleSqliteError(e, s); + } +} + +Future _initDb({String? pathOverride}) async { if (Platform.isLinux || Platform.isWindows) { databaseFactory = databaseFactoryFfi; } diff --git a/cw_core/lib/db/sqlite_debug.dart b/cw_core/lib/db/sqlite_debug.dart new file mode 100644 index 000000000..8e4a3538e --- /dev/null +++ b/cw_core/lib/db/sqlite_debug.dart @@ -0,0 +1,177 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:cw_core/db/sqlite.dart'; +import 'package:cw_core/root_dir.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +Future handleSqliteError(final dynamic error, final StackTrace stackTrace) async { + try { + final f = await sqliteDebugMarkerFile(); + f.createSync(); + } catch (_) {} + runApp( + SqliteErrorHandlerApp( + error: error, + stackTrace: stackTrace, + ), + ); + await Future.delayed(Duration(hours: 24)); +} + +class SqliteErrorHandlerApp extends StatelessWidget { + const SqliteErrorHandlerApp({ + super.key, + required this.error, + required this.stackTrace, + }); + + final dynamic error; + final StackTrace? stackTrace; + @override + Widget build(final BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Cake Wallet Crash', + themeMode: ThemeMode.light, + home: SqliteErrorHandler( + error: error, + stackTrace: stackTrace, + ), + ); + } +} + +class SqliteErrorHandler extends StatefulWidget { + SqliteErrorHandler({ + super.key, + required this.error, + required this.stackTrace, + }); + + final dynamic error; + final StackTrace? stackTrace; + + @override + State createState() => _SqliteErrorHandlerState(); +} + +class _SqliteErrorHandlerState extends State { + String log = ""; + + @override + Widget build(final BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Sqlite Recovery Screen"), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildNotice(), + ), + ), + ), + ); + } + + Widget _text(final dynamic element) { + return SelectableText( + element.toString(), + ); + } + + void _dbgRemoveDebugMarker() async { + final f = await sqliteDebugMarkerFile(); + f.delete(); + } + + void _corruptTheDb() async { + final dbFile = File("${(await getAppDir()).path}/cake.db"); + await writeBytesToFile(dbFile); + } + + void _dumpDb() async { + try { + final dbFile = File("${(await getAppDir()).path}/cake.db"); + final jsonText = JsonEncoder.withIndent(" ").convert(await dumpCustomDb(dbFile.path)); + setState(() { + log = jsonText; + }); + } catch (e, s) { + setState(() { + log = """ +e: $e + +s: $s +"""; + }); + } + } + + void _removeDb() async { + try { + File("${(await getAppDir()).path}/cake.db.old").deleteSync(); + } catch (_) {} + final dbFile = File("${(await getAppDir()).path}/cake.db"); + dbFile.renameSync("${(await getAppDir()).path}/cake.db.old"); + } + + List _debugOptions() { + return [ + _debugButton("Remove debug marker", _dbgRemoveDebugMarker), + if (kDebugMode || kProfileMode) _debugButton("[DO NOT PRESS] Corrupt the DB", _corruptTheDb), + _debugButton("Remove DB", _removeDb), + _debugButton("Dump DB", _dumpDb), + ]; + } + + Widget _debugButton(String text, VoidCallback callback) { + return ElevatedButton( + onPressed: callback, + child: Text(text), + ); + } + + List _buildNotice() { + return [ + _text("""Critical error occured in SQLite +This is very rare, usually caused by the device killing the app before it finishes writing file. +Here you can recover the database or recreate it to regain access to your wallets. +During this process some of the metadata may get lost. +Please contact support@cakewallet.com if you need any help."""), + Divider(), + ..._debugOptions(), + if (log.isNotEmpty) ...[ + Divider(), + SelectableText( + log, + style: TextStyle( + fontSize: 8, + ), + ), + ], + Divider(), + _text(widget.error), + Divider(), + _text(widget.stackTrace), + ]; + } +} + +Future writeBytesToFile(File file) async { + const int seek = 5; + const int count = 50000; + final raf = await file.open(mode: FileMode.writeOnlyAppend); + try { + await raf.setPosition(seek); + final zeros = Uint8List.fromList(List.generate(count, (_) => 0x42)); + await raf.writeFrom(zeros); + } finally { + await raf.close(); + } +} diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index 1670d6631..9593f8b6b 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart'; import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_selector.dart'; @@ -14,11 +16,13 @@ import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/share_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart'; +import 'package:cw_core/root_dir.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/db/sqlite.dart'; import 'package:flutter/material.dart'; +import 'package:path/path.dart' as p; class OtherSettingsPage extends BasePage { OtherSettingsPage(this._otherSettingsViewModel) { @@ -211,6 +215,14 @@ class OtherSettingsPage extends BasePage { ), ); }), + ListItemRegularRow( + keyValue: '[dev] fake corrupt sqlite db', + label: '[dev] fake corrupt sqlite db', + onTap: () async { + final dbDebugMarker = await sqliteDebugMarkerFile(); + dbDebugMarker.create(); + } + ), ] }), );