mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
Cw 769 fix transaction notes not showing (#1718)
* use focusNode instead of onTapOutside for TextFieldListRow * add a transaction description box to the backup * fix --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
This commit is contained in:
parent
6dba73a1d5
commit
dfccedddb2
5 changed files with 117 additions and 49 deletions
|
@ -2,14 +2,13 @@ import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:cake_wallet/core/secure_storage.dart';
|
import 'package:cake_wallet/core/secure_storage.dart';
|
||||||
|
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||||
import 'package:cake_wallet/themes/theme_list.dart';
|
import 'package:cake_wallet/themes/theme_list.dart';
|
||||||
import 'package:cw_core/root_dir.dart';
|
import 'package:cw_core/root_dir.dart';
|
||||||
import 'package:cake_wallet/utils/device_info.dart';
|
import 'package:cake_wallet/utils/device_info.dart';
|
||||||
import 'package:cw_core/root_dir.dart';
|
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:cryptography/cryptography.dart';
|
import 'package:cryptography/cryptography.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:archive/archive_io.dart';
|
import 'package:archive/archive_io.dart';
|
||||||
|
@ -24,8 +23,8 @@ import 'package:cake_wallet/wallet_types.g.dart';
|
||||||
import 'package:cake_backup/backup.dart' as cake_backup;
|
import 'package:cake_backup/backup.dart' as cake_backup;
|
||||||
|
|
||||||
class BackupService {
|
class BackupService {
|
||||||
BackupService(
|
BackupService(this._secureStorage, this._walletInfoSource, this._transactionDescriptionBox,
|
||||||
this._secureStorage, this._walletInfoSource, this._keyService, this._sharedPreferences)
|
this._keyService, this._sharedPreferences)
|
||||||
: _cipher = Cryptography.instance.chacha20Poly1305Aead(),
|
: _cipher = Cryptography.instance.chacha20Poly1305Aead(),
|
||||||
_correctWallets = <WalletInfo>[];
|
_correctWallets = <WalletInfo>[];
|
||||||
|
|
||||||
|
@ -38,6 +37,7 @@ class BackupService {
|
||||||
final SecureStorage _secureStorage;
|
final SecureStorage _secureStorage;
|
||||||
final SharedPreferences _sharedPreferences;
|
final SharedPreferences _sharedPreferences;
|
||||||
final Box<WalletInfo> _walletInfoSource;
|
final Box<WalletInfo> _walletInfoSource;
|
||||||
|
final Box<TransactionDescription> _transactionDescriptionBox;
|
||||||
final KeyService _keyService;
|
final KeyService _keyService;
|
||||||
List<WalletInfo> _correctWallets;
|
List<WalletInfo> _correctWallets;
|
||||||
|
|
||||||
|
@ -86,6 +86,13 @@ class BackupService {
|
||||||
final preferencesDump = await _exportPreferencesJSON();
|
final preferencesDump = await _exportPreferencesJSON();
|
||||||
final preferencesDumpFile = File('${tmpDir.path}/~_preferences_dump_TMP');
|
final preferencesDumpFile = File('${tmpDir.path}/~_preferences_dump_TMP');
|
||||||
final keychainDumpFile = File('${tmpDir.path}/~_keychain_dump_TMP');
|
final keychainDumpFile = File('${tmpDir.path}/~_keychain_dump_TMP');
|
||||||
|
final transactionDescriptionDumpFile =
|
||||||
|
File('${tmpDir.path}/~_transaction_descriptions_dump_TMP');
|
||||||
|
|
||||||
|
final transactionDescriptionData = _transactionDescriptionBox
|
||||||
|
.toMap()
|
||||||
|
.map((key, value) => MapEntry(key.toString(), value.toJson()));
|
||||||
|
final transactionDescriptionDump = jsonEncode(transactionDescriptionData);
|
||||||
|
|
||||||
if (tmpDir.existsSync()) {
|
if (tmpDir.existsSync()) {
|
||||||
tmpDir.deleteSync(recursive: true);
|
tmpDir.deleteSync(recursive: true);
|
||||||
|
@ -107,8 +114,10 @@ class BackupService {
|
||||||
});
|
});
|
||||||
await keychainDumpFile.writeAsBytes(keychainDump.toList());
|
await keychainDumpFile.writeAsBytes(keychainDump.toList());
|
||||||
await preferencesDumpFile.writeAsString(preferencesDump);
|
await preferencesDumpFile.writeAsString(preferencesDump);
|
||||||
|
await transactionDescriptionDumpFile.writeAsString(transactionDescriptionDump);
|
||||||
await zipEncoder.addFile(preferencesDumpFile, '~_preferences_dump');
|
await zipEncoder.addFile(preferencesDumpFile, '~_preferences_dump');
|
||||||
await zipEncoder.addFile(keychainDumpFile, '~_keychain_dump');
|
await zipEncoder.addFile(keychainDumpFile, '~_keychain_dump');
|
||||||
|
await zipEncoder.addFile(transactionDescriptionDumpFile, '~_transaction_descriptions_dump');
|
||||||
zipEncoder.close();
|
zipEncoder.close();
|
||||||
|
|
||||||
final content = File(archivePath).readAsBytesSync();
|
final content = File(archivePath).readAsBytesSync();
|
||||||
|
@ -160,6 +169,7 @@ class BackupService {
|
||||||
await _verifyWallets();
|
await _verifyWallets();
|
||||||
await _importKeychainDumpV2(password);
|
await _importKeychainDumpV2(password);
|
||||||
await _importPreferencesDump();
|
await _importPreferencesDump();
|
||||||
|
await _importTransactionDescriptionDump();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _verifyWallets() async {
|
Future<void> _verifyWallets() async {
|
||||||
|
@ -184,6 +194,21 @@ class BackupService {
|
||||||
return await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);
|
return await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _importTransactionDescriptionDump() async {
|
||||||
|
final appDir = await getAppDir();
|
||||||
|
final transactionDescriptionFile = File('${appDir.path}/~_transaction_descriptions_dump');
|
||||||
|
|
||||||
|
if (!transactionDescriptionFile.existsSync()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final jsonData =
|
||||||
|
json.decode(transactionDescriptionFile.readAsStringSync()) as Map<String, dynamic>;
|
||||||
|
final descriptionsMap = jsonData.map((key, value) =>
|
||||||
|
MapEntry(key, TransactionDescription.fromJson(value as Map<String, dynamic>)));
|
||||||
|
await _transactionDescriptionBox.putAll(descriptionsMap);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _importPreferencesDump() async {
|
Future<void> _importPreferencesDump() async {
|
||||||
final appDir = await getAppDir();
|
final appDir = await getAppDir();
|
||||||
final preferencesFile = File('${appDir.path}/~_preferences_dump');
|
final preferencesFile = File('${appDir.path}/~_preferences_dump');
|
||||||
|
|
|
@ -1149,6 +1149,7 @@ Future<void> setup({
|
||||||
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<CakePayService>()));
|
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<CakePayService>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => BackupService(getIt.get<SecureStorage>(), _walletInfoSource,
|
getIt.registerFactory(() => BackupService(getIt.get<SecureStorage>(), _walletInfoSource,
|
||||||
|
_transactionDescriptionBox,
|
||||||
getIt.get<KeyService>(), getIt.get<SharedPreferences>()));
|
getIt.get<KeyService>(), getIt.get<SharedPreferences>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => BackupViewModel(
|
getIt.registerFactory(() => BackupViewModel(
|
||||||
|
|
|
@ -21,4 +21,18 @@ class TransactionDescription extends HiveObject {
|
||||||
String? transactionNote;
|
String? transactionNote;
|
||||||
|
|
||||||
String get note => transactionNote ?? '';
|
String get note => transactionNote ?? '';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'id': id,
|
||||||
|
'recipientAddress': recipientAddress,
|
||||||
|
'transactionNote': transactionNote,
|
||||||
|
};
|
||||||
|
|
||||||
|
factory TransactionDescription.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TransactionDescription(
|
||||||
|
id: json['id'] as String,
|
||||||
|
recipientAddress: json['recipientAddress'] as String?,
|
||||||
|
transactionNote: json['transactionNote'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,49 @@
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class TextFieldListRow extends StatelessWidget {
|
class TextFieldListRow extends StatefulWidget {
|
||||||
TextFieldListRow(
|
TextFieldListRow(
|
||||||
{required this.title,
|
{required this.title,
|
||||||
required this.value,
|
required this.value,
|
||||||
this.titleFontSize = 14,
|
this.titleFontSize = 14,
|
||||||
this.valueFontSize = 16,
|
this.valueFontSize = 16,
|
||||||
this.onSubmitted,
|
this.onSubmitted});
|
||||||
this.onTapOutside})
|
|
||||||
: _textController = TextEditingController() {
|
|
||||||
_textController.text = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final String value;
|
final String value;
|
||||||
final double titleFontSize;
|
final double titleFontSize;
|
||||||
final double valueFontSize;
|
final double valueFontSize;
|
||||||
final Function(String value)? onSubmitted;
|
final Function(String value)? onSubmitted;
|
||||||
final Function(String value)? onTapOutside;
|
|
||||||
final TextEditingController _textController;
|
@override
|
||||||
|
_TextFieldListRowState createState() => _TextFieldListRowState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TextFieldListRowState extends State<TextFieldListRow> {
|
||||||
|
late TextEditingController _textController;
|
||||||
|
late FocusNode _focusNode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_textController = TextEditingController(text: widget.value);
|
||||||
|
_focusNode = FocusNode();
|
||||||
|
|
||||||
|
_focusNode.addListener(() {
|
||||||
|
if (!_focusNode.hasFocus) {
|
||||||
|
widget.onSubmitted?.call(_textController.text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_textController.dispose();
|
||||||
|
_focusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -29,41 +51,48 @@ class TextFieldListRow extends StatelessWidget {
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding:
|
padding: const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
|
||||||
const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(title,
|
Text(
|
||||||
style: TextStyle(
|
widget.title,
|
||||||
fontSize: titleFontSize,
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w500,
|
fontSize: widget.titleFontSize,
|
||||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor),
|
fontWeight: FontWeight.w500,
|
||||||
textAlign: TextAlign.left),
|
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||||
TextField(
|
),
|
||||||
controller: _textController,
|
textAlign: TextAlign.left,
|
||||||
keyboardType: TextInputType.multiline,
|
),
|
||||||
textInputAction: TextInputAction.done,
|
TextField(
|
||||||
maxLines: null,
|
controller: _textController,
|
||||||
textAlign: TextAlign.start,
|
focusNode: _focusNode,
|
||||||
style: TextStyle(
|
keyboardType: TextInputType.multiline,
|
||||||
fontSize: valueFontSize,
|
textInputAction: TextInputAction.done,
|
||||||
fontWeight: FontWeight.w500,
|
maxLines: null,
|
||||||
color:
|
textAlign: TextAlign.start,
|
||||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
style: TextStyle(
|
||||||
decoration: InputDecoration(
|
fontSize: widget.valueFontSize,
|
||||||
isDense: true,
|
fontWeight: FontWeight.w500,
|
||||||
contentPadding: EdgeInsets.only(top: 12, bottom: 0),
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
hintText: S.of(context).enter_your_note,
|
),
|
||||||
hintStyle: TextStyle(
|
decoration: InputDecoration(
|
||||||
fontSize: valueFontSize,
|
isDense: true,
|
||||||
fontWeight: FontWeight.w500,
|
contentPadding: EdgeInsets.only(top: 12, bottom: 0),
|
||||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor),
|
hintText: S.of(context).enter_your_note,
|
||||||
border: InputBorder.none),
|
hintStyle: TextStyle(
|
||||||
onTapOutside: (_) => onTapOutside?.call(_textController.text),
|
fontSize: widget.valueFontSize,
|
||||||
onSubmitted: (value) => onSubmitted?.call(value),
|
fontWeight: FontWeight.w500,
|
||||||
)
|
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||||
]),
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
onSubmitted: (value) {
|
||||||
|
widget.onSubmitted?.call(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,6 @@ class UnspentCoinsDetailsPage extends BasePage {
|
||||||
return TextFieldListRow(
|
return TextFieldListRow(
|
||||||
title: item.title,
|
title: item.title,
|
||||||
value: item.value,
|
value: item.value,
|
||||||
onTapOutside: item.onSubmitted,
|
|
||||||
onSubmitted: item.onSubmitted,
|
onSubmitted: item.onSubmitted,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue