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:
Serhii 2024-10-05 00:50:36 +03:00 committed by GitHub
parent 6dba73a1d5
commit dfccedddb2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 117 additions and 49 deletions

View file

@ -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');

View file

@ -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(

View file

@ -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?,
);
}
} }

View file

@ -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);
},
),
],
),
), ),
); );
} }

View file

@ -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,
); );
} }