feat: initial hive -> SQLite (#2502)

* feat: initial hive -> SQLite

Includes migration script and initial work done to
remove dependency on hive generators.

Also contains some refactoring to make sync code
async.

Features new dev screen that lists all SQLite tables

Aspects that need testing:
- backups (from this version, and from previous versions as well)
- wallet loading / wallet list
- almost everything else
- renaming
- deletion
- sorting in wallet list
- wallet groups
- wallet seed/keys
- wallet switching
- wallet creation / restore - all methods
- wallet address page
- hardware wallet connection (and prompts from mall screens)
- contacts
- advanced settings in wallet creation
- derivation paths
- some minor wownero fixes
- base, hardware wallets

* fix: build errors

* fix: Bad state: No element in backup restore

* address comments from review

* fix: HardwareWalletType
This commit is contained in:
cyan
2025-10-31 22:37:15 +01:00
committed by GitHub
parent 718525926e
commit 76876181e5
121 changed files with 2623 additions and 917 deletions
@@ -17,6 +17,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class ContactListPage extends BasePage {
@@ -150,7 +151,9 @@ class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProv
child: TabBarView(
controller: _tabController,
children: [
_buildWalletContacts(context),
Observer(
builder: (final BuildContext context) => _buildWalletContacts(context),
),
ContactListBody(
contactListViewModel: widget.contactListViewModel,
tabController: _tabController,
@@ -351,7 +351,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
_formProcessing = false;
return;
}
if (_walletNewVM.nameExists(_walletNewVM.name)) {
if (await _walletNewVM.nameExists(_walletNewVM.name)) {
await showPopUp<void>(
context: context,
builder: (_) {
@@ -258,7 +258,7 @@ class WalletRestorePage extends BasePage {
return;
}
if (walletRestoreViewModel.nameExists(name)) {
if (await walletRestoreViewModel.nameExists(name)) {
showNameExistsAlert(formContext!);
_formProcessing = false;
return;
@@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
import 'package:cake_wallet/src/screens/settings/widgets/setting_priority_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
@@ -11,6 +12,7 @@ import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/db/sqlite.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -144,6 +146,16 @@ class OtherSettingsPage extends BasePage {
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] browse sqlite db',
handler: (BuildContext context) async {
final data = await dumpDb();
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => JsonExplorerPage(data: data, title: 'sqlite db')),
);
}
),
Spacer(),
SettingsVersionCell(
title: S.of(context).version(_otherSettingsViewModel.currentVersion),
+11 -5
View File
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:another_flushbar/flushbar.dart';
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
@@ -10,6 +12,7 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
@@ -86,7 +89,7 @@ class WalletEditPage extends BasePage {
onPressed: () async {
if (_formKey.currentState?.validate() ?? false) {
if (!pageArguments.isWalletGroup &&
pageArguments.walletNewVM!
await pageArguments.walletNewVM!
.nameExists(walletEditViewModel.newName)) {
showPopUp<void>(
context: context,
@@ -217,9 +220,12 @@ class WalletEditPage extends BasePage {
}
Future<void> hideProgressText() async {
await Future.delayed(Duration(milliseconds: 50), () {
_progressBar?.dismiss();
_progressBar = null;
});
try {
await Future.delayed(Duration(milliseconds: 250));
await _progressBar?.dismiss();
} catch (e) {
printV(e);
}
_progressBar = null;
}
}
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:another_flushbar/flushbar.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/new_wallet_arguments.dart';
@@ -27,6 +29,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -40,17 +43,26 @@ class WalletListPage extends BasePage {
final WalletListViewModel walletListViewModel;
final AuthService authService;
final Function(BuildContext)? onWalletLoaded;
final Future<void> Function(BuildContext)? onWalletLoaded;
@override
String get title => S.current.wallets;
@override
Widget body(BuildContext context) => WalletListBody(
Widget body(BuildContext context) => Observer(
builder: (_) {
if (walletListViewModel.singleWalletsList.isEmpty && walletListViewModel.multiWalletGroups.isEmpty) {
return Center(
child: CircularProgressIndicator(),
);
}
return WalletListBody(
walletListViewModel: walletListViewModel,
authService: authService,
onWalletLoaded: onWalletLoaded ?? (context) => Navigator.of(context).pop(),
);
}
);
@override
Widget trailing(BuildContext context) {
@@ -459,7 +471,7 @@ class WalletListBodyState extends State<WalletListBody> {
try {
final requireHardwareWalletConnection =
widget.walletListViewModel.requireHardwareWalletConnection(wallet);
await widget.walletListViewModel.requireHardwareWalletConnection(wallet);
if (requireHardwareWalletConnection) {
bool didConnect = false;
await Navigator.of(context).pushNamed(
@@ -489,22 +501,20 @@ class WalletListBodyState extends State<WalletListBody> {
buttonAction: () => Navigator.of(context).pop()),
);
}
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
await widget.walletListViewModel.loadWallet(wallet);
await hideProgressText();
// only pop the wallets route in mobile as it will go back to dashboard page
// in desktop platforms the navigation tree is different
if (responsiveLayoutUtil.shouldRenderMobileUI) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (this.mounted) {
if (requireHardwareWalletConnection) {
Navigator.of(context).pop();
}
widget.onWalletLoaded.call(context);
}
});
// await Future.delayed(Duration(seconds: 1));
// if (!this.mounted) return;
if (!context.mounted) return;
if (requireHardwareWalletConnection) {
Navigator.of(context).pop();
}
await widget.onWalletLoaded.call(context);
}
unawaited(hideProgressText());
} catch (e) {
await ExceptionHandler.resetLastPopupDate();
final err = e.toString();
@@ -52,76 +52,79 @@ class _WalletSwitcherContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Observer(
builder: (_) {
final wallets = viewModel.getWallets(filterWalletType);
if (viewModel.isProcessing) {
return Container(
height: 200,
child: Center(
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.primary,
),
),
);
}
return Container(
height: 400,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: StandardList(
itemCount: wallets.length,
itemBuilder: (context, index) {
final wallet = wallets[index];
return InkWell(
onTap: () {
viewModel.selectWallet(wallet);
Navigator.of(context).pop();
},
borderRadius: BorderRadius.circular(12),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(16),
),
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
Image.asset(
walletTypeToCryptoCurrency(wallet.type).iconPath!,
width: 32,
height: 32,
),
const SizedBox(width: 16),
Text(
wallet.name,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
letterSpacing: 0.0,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
);
},
return FutureBuilder(
future: viewModel.getWallets(filterWalletType),
builder: (context, snapshot) => Observer(
builder: (_) {
final List<WalletInfo> wallets = (snapshot.data ?? []);
if (viewModel.isProcessing) {
return Container(
height: 200,
child: Center(
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.primary,
),
),
],
),
);
},
);
}
return Container(
height: 400,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: StandardList(
itemCount: wallets.length,
itemBuilder: (context, index) {
final wallet = wallets[index];
return InkWell(
onTap: () {
viewModel.selectWallet(wallet);
Navigator.of(context).pop();
},
borderRadius: BorderRadius.circular(12),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(16),
),
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
Image.asset(
walletTypeToCryptoCurrency(wallet.type).iconPath!,
width: 32,
height: 32,
),
const SizedBox(width: 16),
Text(
wallet.name,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
letterSpacing: 0.0,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
);
},
),
),
],
),
);
},
),
);
}
}