Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class _RestoreFromFileViewState extends ConsumerState<CreateBackupView> {
await showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => !Util.isDesktop
builder: (dialogContext) => !Util.isDesktop
? StackOkDialog(title: "Backup saved to:", message: savedPath)
: DesktopDialog(
maxHeight: double.infinity,
Expand Down Expand Up @@ -154,7 +154,9 @@ class _RestoreFromFileViewState extends ConsumerState<CreateBackupView> {
child: PrimaryButton(
label: "Ok",
buttonHeight: ButtonHeight.l,
onPressed: Navigator.of(context).pop,
onPressed: () {
Navigator.of(dialogContext).pop();
},
),
),
],
Expand All @@ -176,6 +178,7 @@ class _RestoreFromFileViewState extends ConsumerState<CreateBackupView> {
builder: (_) => StackOkDialog(
title: "Backup creation failed",
message: ex?.toString() ?? "Unexpected error",
desktopPopRootNavigator: true,
),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,32 @@ abstract class SWB {
backupWallet['isFavorite'] = wallet.info.isFavourite;
backupWallet['otherDataJsonString'] = wallet.info.otherDataJsonString;

if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) {
backupWallet['viewOnlyWalletDataKey'] =
(await wallet.getViewOnlyWalletData()).toJsonEncodedString();
// Check secure storage for view-only data even if flag is missing.
String? rawViewOnlyData;
if (wallet is ViewOnlyOptionInterface) {
rawViewOnlyData = await _secureStore.read(
key: Wallet.getViewOnlyWalletDataSecStoreKey(
walletId: wallet.walletId,
),
);
}

if (rawViewOnlyData != null) {
backupWallet['viewOnlyWalletDataKey'] = rawViewOnlyData;
// Patch missing isViewOnlyKey flag in otherDataJsonString.
if (wallet.info.otherData[WalletInfoKeys.isViewOnlyKey] != true) {
final patchedOtherData = Map<String, dynamic>.from(
wallet.info.otherData,
);
patchedOtherData[WalletInfoKeys.isViewOnlyKey] = true;
final parsed = ViewOnlyWalletData.fromJsonEncodedString(
rawViewOnlyData,
walletId: wallet.walletId,
);
patchedOtherData[WalletInfoKeys.viewOnlyTypeIndexKey] =
parsed.type.index;
backupWallet['otherDataJsonString'] = jsonEncode(patchedOtherData);
}
} else if (wallet is MnemonicInterface) {
backupWallet['mnemonic'] = await wallet.getMnemonic();
backupWallet['mnemonicPassphrase'] = await wallet
Expand Down Expand Up @@ -378,7 +401,7 @@ abstract class SWB {
String? mnemonic, mnemonicPassphrase, privateKey;

ViewOnlyWalletData? viewOnlyData;
if (info.isViewOnly) {
if (info.isViewOnly || walletbackup['viewOnlyWalletDataKey'] is String) {
final viewOnlyDataEncoded =
walletbackup['viewOnlyWalletDataKey'] as String;

Expand Down Expand Up @@ -775,6 +798,28 @@ abstract class SWB {
);
}

// Patch missing isViewOnlyKey if backup has view-only data.
if (walletbackup['viewOnlyWalletDataKey'] is String &&
otherData?[WalletInfoKeys.isViewOnlyKey] != true) {
otherData ??= {};
otherData[WalletInfoKeys.isViewOnlyKey] = true;
if (otherData[WalletInfoKeys.viewOnlyTypeIndexKey] == null) {
try {
final parsed = ViewOnlyWalletData.fromJsonEncodedString(
walletbackup['viewOnlyWalletDataKey'] as String,
walletId: walletId,
);
otherData[WalletInfoKeys.viewOnlyTypeIndexKey] = parsed.type.index;
} catch (e, s) {
Logging.instance.e(
"SWB restore: failed to recover viewOnlyTypeIndexKey",
error: e,
stackTrace: s,
);
}
}
}

final info = WalletInfo(
coinName: coin.identifier,
walletId: walletId,
Expand Down
1 change: 1 addition & 0 deletions lib/utilities/show_loading.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Future<T?> showLoading<T>({
unawaited(
showDialog<void>(
context: context,
useRootNavigator: rootNavigator,
barrierDismissible: false,
builder: (_) => WillPopScope(
onWillPop: () async => false,
Expand Down
165 changes: 83 additions & 82 deletions lib/wallets/isar/models/wallet_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -187,78 +187,79 @@ class WalletInfo implements IsarId {
required Balance newBalance,
required Isar isar,
}) async {
// try to get latest instance of this from db
final thisInfo = await isar.walletInfo.get(id) ?? this;

final newEncoded = newBalance.toJsonIgnoreCoin();

// only update if there were changes to the balance
if (thisInfo.cachedBalanceString != newEncoded) {
await isar.writeTxn(() async {
// Read inside the tx so concurrent updates don't create a race condition.
await isar.writeTxn(() async {
final thisInfo = await isar.walletInfo
.where()
.walletIdEqualTo(walletId)
.findFirst();
if (thisInfo != null && thisInfo.cachedBalanceString != newEncoded) {
await isar.walletInfo.delete(thisInfo.id);
await isar.walletInfo.put(
thisInfo.copyWith(cachedBalanceString: newEncoded),
);
});
}
}
});
}

Future<void> updateBalanceSecondary({
required Balance newBalance,
required Isar isar,
}) async {
// try to get latest instance of this from db
final thisInfo = await isar.walletInfo.get(id) ?? this;

final newEncoded = newBalance.toJsonIgnoreCoin();

// only update if there were changes to the balance
if (thisInfo.cachedBalanceSecondaryString != newEncoded) {
await isar.writeTxn(() async {
await isar.writeTxn(() async {
final thisInfo = await isar.walletInfo
.where()
.walletIdEqualTo(walletId)
.findFirst();
if (thisInfo != null &&
thisInfo.cachedBalanceSecondaryString != newEncoded) {
await isar.walletInfo.delete(thisInfo.id);
await isar.walletInfo.put(
thisInfo.copyWith(cachedBalanceSecondaryString: newEncoded),
);
});
}
}
});
}

Future<void> updateBalanceTertiary({
required Balance newBalance,
required Isar isar,
}) async {
// try to get latest instance of this from db
final thisInfo = await isar.walletInfo.get(id) ?? this;

final newEncoded = newBalance.toJsonIgnoreCoin();

// only update if there were changes to the balance
if (thisInfo.cachedBalanceTertiaryString != newEncoded) {
await isar.writeTxn(() async {
await isar.writeTxn(() async {
final thisInfo = await isar.walletInfo
.where()
.walletIdEqualTo(walletId)
.findFirst();
if (thisInfo != null &&
thisInfo.cachedBalanceTertiaryString != newEncoded) {
await isar.walletInfo.delete(thisInfo.id);
await isar.walletInfo.put(
thisInfo.copyWith(cachedBalanceTertiaryString: newEncoded),
);
});
}
}
});
}

/// copies this with a new chain height and updates the db
Future<void> updateCachedChainHeight({
required int newHeight,
required Isar isar,
}) async {
// try to get latest instance of this from db
final thisInfo = await isar.walletInfo.get(id) ?? this;
// only update if there were changes to the height
if (thisInfo.cachedChainHeight != newHeight) {
await isar.writeTxn(() async {
await isar.writeTxn(() async {
final thisInfo = await isar.walletInfo
.where()
.walletIdEqualTo(walletId)
.findFirst();
if (thisInfo != null && thisInfo.cachedChainHeight != newHeight) {
await isar.walletInfo.delete(thisInfo.id);
await isar.walletInfo.put(
thisInfo.copyWith(cachedChainHeight: newHeight),
);
});
}
}
});
}

/// update favourite wallet and its index it the ui list.
Expand All @@ -283,18 +284,18 @@ class WalletInfo implements IsarId {
index = -1;
}

// try to get latest instance of this from db
final thisInfo = await isar.walletInfo.get(id) ?? this;

// only update if there were changes to the height
if (thisInfo.favouriteOrderIndex != index) {
await isar.writeTxn(() async {
await isar.writeTxn(() async {
final thisInfo = await isar.walletInfo
.where()
.walletIdEqualTo(walletId)
.findFirst();
if (thisInfo != null && thisInfo.favouriteOrderIndex != index) {
await isar.walletInfo.delete(thisInfo.id);
await isar.walletInfo.put(
thisInfo.copyWith(favouriteOrderIndex: index),
);
});
}
}
});
}

/// copies this with a new name and updates the db
Expand All @@ -303,59 +304,60 @@ class WalletInfo implements IsarId {
if (newName.isEmpty) {
throw Exception("Empty wallet name not allowed!");
}

// try to get latest instance of this from db
final thisInfo = await isar.walletInfo.get(id) ?? this;

// only update if there were changes to the name
if (thisInfo.name != newName) {
await isar.writeTxn(() async {
await isar.writeTxn(() async {
final thisInfo = await isar.walletInfo
.where()
.walletIdEqualTo(walletId)
.findFirst();
if (thisInfo != null && thisInfo.name != newName) {
await isar.walletInfo.delete(thisInfo.id);
await isar.walletInfo.put(thisInfo.copyWith(name: newName));
});
}
}
});
}

/// copies this with a new name and updates the db
/// copies this with a new receiving address and updates the db
Future<void> updateReceivingAddress({
required String newAddress,
required Isar isar,
}) async {
// try to get latest instance of this from db
final thisInfo = await isar.walletInfo.get(id) ?? this;
// only update if there were changes to the name
if (thisInfo.cachedReceivingAddress != newAddress) {
await isar.writeTxn(() async {
await isar.writeTxn(() async {
final thisInfo = await isar.walletInfo
.where()
.walletIdEqualTo(walletId)
.findFirst();
if (thisInfo != null && thisInfo.cachedReceivingAddress != newAddress) {
await isar.walletInfo.delete(thisInfo.id);
await isar.walletInfo.put(
thisInfo.copyWith(cachedReceivingAddress: newAddress),
);
});
}
}
});
}

/// update [otherData] with the map entries in [newEntries]
Future<void> updateOtherData({
required Map<String, dynamic> newEntries,
required Isar isar,
}) async {
// try to get latest instance of this from db
final thisInfo = await isar.walletInfo.get(id) ?? this;
await isar.writeTxn(() async {
final thisInfo = await isar.walletInfo
.where()
.walletIdEqualTo(walletId)
.findFirst();
if (thisInfo == null) return;

final Map<String, dynamic> newMap = {};
newMap.addAll(thisInfo.otherData);
newMap.addAll(newEntries);
final encodedNew = jsonEncode(newMap);
final newMap = Map<String, dynamic>.from(thisInfo.otherData)
..addAll(newEntries);
final encodedNew = jsonEncode(newMap);

// only update if there were changes
if (thisInfo.otherDataJsonString != encodedNew) {
await isar.writeTxn(() async {
if (thisInfo.otherDataJsonString != encodedNew) {
await isar.walletInfo.delete(thisInfo.id);
await isar.walletInfo.put(
thisInfo.copyWith(otherDataJsonString: encodedNew),
);
});
}
}
});
}

/// Can be dangerous. Don't use unless you know the consequences
Expand Down Expand Up @@ -385,28 +387,26 @@ class WalletInfo implements IsarId {
}
}

/// copies this with a new name and updates the db
/// copies this with a new restore height and updates the db
Future<void> updateRestoreHeight({
required int newRestoreHeight,
required Isar isar,
}) async {
// don't allow empty names
if (newRestoreHeight < 0) {
throw Exception("Negative restore height not allowed!");
}

// try to get latest instance of this from db
final thisInfo = await isar.walletInfo.get(id) ?? this;

// only update if there were changes to the name
if (thisInfo.restoreHeight != newRestoreHeight) {
await isar.writeTxn(() async {
await isar.writeTxn(() async {
final thisInfo = await isar.walletInfo
.where()
.walletIdEqualTo(walletId)
.findFirst();
if (thisInfo != null && thisInfo.restoreHeight != newRestoreHeight) {
await isar.walletInfo.delete(thisInfo.id);
await isar.walletInfo.put(
thisInfo.copyWith(restoreHeight: newRestoreHeight),
);
});
}
}
});
}

/// copies this with a new name and updates the db
Expand Down Expand Up @@ -442,7 +442,8 @@ class WalletInfo implements IsarId {
}) async {
await updateOtherData(
newEntries: {
WalletInfoKeys.solanaCustomTokenMintAddresses: newMintAddresses.toList(),
WalletInfoKeys.solanaCustomTokenMintAddresses: newMintAddresses
.toList(),
},
isar: isar,
);
Expand Down
Loading