Skip to content

Commit 91c4a18

Browse files
Support adding memo hash while sending (#960)
* add dropdownList for memo hash * remove unused import * fix pr comments * remove Memo from dropdown list * decrease width of dropdown && make it align with textfield * trigger validation when type changes * fix spacing issue with textfield * use listTile * make types uppercase * Fix the memo alignment (#971) --------- Co-authored-by: AhmedHanafy725 <41957921+AhmedHanafy725@users.noreply.github.com>
1 parent 4f9884b commit 91c4a18

2 files changed

Lines changed: 92 additions & 17 deletions

File tree

app/lib/screens/wallets/send.dart

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class _WalletSendScreenState extends ConsumerState<WalletSendScreen> {
4040
final FocusNode textFieldFocusNode = FocusNode();
4141
List percentages = [25, 50, 75, 100];
4242
List<Wallet> wallets = [];
43+
MemoType selectedMemoType = MemoType.TEXT;
44+
String? memoError;
4345

4446
@override
4547
void initState() {
@@ -182,6 +184,16 @@ class _WalletSendScreenState extends ConsumerState<WalletSendScreen> {
182184
return validAddress && validAmount;
183185
}
184186

187+
String? _validateMemo(String memo, MemoType type) {
188+
if (type == MemoType.HASH) {
189+
final isValidHex = RegExp(r'^[0-9a-fA-F]{1,64}$').hasMatch(memo);
190+
if (!isValidHex) return 'Invalid Hash (Hex, max 64 chars)';
191+
} else {
192+
if (memo.length > 28) return 'Invalid Text length, max length is 28';
193+
}
194+
return null;
195+
}
196+
185197
void _selectToAddress(String address) {
186198
toController.text = address;
187199
setState(() {});
@@ -324,24 +336,67 @@ class _WalletSendScreenState extends ConsumerState<WalletSendScreen> {
324336
if (chainType == ChainType.Stellar)
325337
ListTile(
326338
title: TextField(
327-
style: Theme.of(context)
328-
.textTheme
329-
.bodyMedium!
330-
.copyWith(
331-
color: Theme.of(context).colorScheme.onSurface,
339+
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
340+
color: Theme.of(context).colorScheme.onSurface,
341+
),
342+
controller: memoController,
343+
decoration: InputDecoration(
344+
labelText: 'Memo',
345+
errorText: memoError,
346+
suffixIcon: SizedBox(
347+
width: 100,
348+
child: DropdownButtonFormField<MemoType>(
349+
value: selectedMemoType,
350+
alignment: Alignment.centerRight,
351+
decoration: const InputDecoration(
352+
contentPadding: EdgeInsets.only(left: 25),
353+
border: InputBorder.none,
332354
),
333-
controller: memoController,
334-
decoration: const InputDecoration(
335-
labelText: 'Memo',
336-
)),
355+
onChanged: (MemoType? newValue) {
356+
setState(() {
357+
selectedMemoType = newValue!;
358+
if (memoController.text.isNotEmpty) {
359+
memoError = _validateMemo(
360+
memoController.text, selectedMemoType);
361+
} else {
362+
memoError = null;
363+
}
364+
});
365+
},
366+
items: MemoType.values.map((MemoType type) {
367+
return DropdownMenuItem<MemoType>(
368+
value: type,
369+
child: Text(
370+
type.name,
371+
style: Theme.of(context)
372+
.textTheme
373+
.bodyLarge!
374+
.copyWith(
375+
color: Theme.of(context)
376+
.colorScheme
377+
.onSurface,
378+
),
379+
),
380+
);
381+
}).toList(),
382+
),
383+
),
384+
),
385+
onChanged: (value) {
386+
setState(() {
387+
memoError = _validateMemo(value, selectedMemoType);
388+
});
389+
},
390+
),
391+
titleAlignment: ListTileTitleAlignment.top,
337392
),
338393
const SizedBox(height: 40),
339394
Padding(
340395
padding:
341396
const EdgeInsets.symmetric(vertical: 8, horizontal: 10),
342397
child: ElevatedButton(
343398
onPressed: () async {
344-
if (await _validate()) {
399+
if (await _validate() && memoError == null) {
345400
await _send_confirmation();
346401
}
347402
},
@@ -397,9 +452,14 @@ class _WalletSendScreenState extends ConsumerState<WalletSendScreen> {
397452
if (code.queryParameters.containsKey('amount')) {
398453
amountController.text = code.queryParameters['amount']!;
399454
}
400-
if (chainType == ChainType.Stellar &&
401-
code.queryParameters.containsKey('message')) {
402-
memoController.text = code.queryParameters['message']!;
455+
if (chainType == ChainType.Stellar) {
456+
if (code.queryParameters.containsKey('memo_hash')) {
457+
selectedMemoType = MemoType.HASH;
458+
memoController.text = code.queryParameters['memo_hash']!;
459+
} else if (code.queryParameters.containsKey('message')) {
460+
selectedMemoType = MemoType.TEXT;
461+
memoController.text = code.queryParameters['message']!;
462+
}
403463
}
404464
setState(() {});
405465
} else {
@@ -441,6 +501,9 @@ class _WalletSendScreenState extends ConsumerState<WalletSendScreen> {
441501
}
442502

443503
_send_confirmation() async {
504+
final trimmedMemo = memoController.text.trim();
505+
final memoHash = selectedMemoType == MemoType.HASH ? trimmedMemo : null;
506+
final memoText = selectedMemoType == MemoType.TEXT ? trimmedMemo : null;
444507
showModalBottomSheet(
445508
isScrollControlled: true,
446509
useSafeArea: true,
@@ -455,10 +518,13 @@ class _WalletSendScreenState extends ConsumerState<WalletSendScreen> {
455518
from: fromController.text.trim(),
456519
to: toController.text.trim(),
457520
amount: amountController.text.trim(),
458-
memo: memoController.text.trim(),
521+
memo: memoText,
522+
memoHash: memoHash,
459523
reloadBalance: chainType == ChainType.Stellar
460524
? _loadStellarBalance
461525
: _loadTFChainBalance,
462526
));
463527
}
464528
}
529+
530+
enum MemoType { TEXT, HASH }

app/lib/widgets/wallets/send_confirmation.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import 'dart:async';
2+
import 'dart:typed_data';
23

4+
import 'package:convert/convert.dart';
35
import 'package:flutter/material.dart';
46
import 'package:threebotlogin/helpers/transaction_helpers.dart';
57
import 'package:threebotlogin/models/wallet.dart';
@@ -16,6 +18,7 @@ class SendConfirmationWidget extends StatefulWidget {
1618
required this.to,
1719
required this.amount,
1820
required this.memo,
21+
required this.memoHash,
1922
required this.reloadBalance,
2023
});
2124

@@ -24,7 +27,8 @@ class SendConfirmationWidget extends StatefulWidget {
2427
final String from;
2528
final String to;
2629
final String amount;
27-
final String memo;
30+
final String? memo;
31+
final String? memoHash;
2832
final void Function() reloadBalance;
2933

3034
@override
@@ -45,7 +49,11 @@ class _SendConfirmationWidgetState extends State<SendConfirmationWidget> {
4549
toController.text = widget.to;
4650
amountController.text = widget.amount;
4751
feeController.text = widget.chainType == ChainType.Stellar ? '0.1' : '0.01';
48-
memoController.text = widget.memo;
52+
if (widget.memo != null) {
53+
memoController.text = widget.memo!;
54+
} else if (widget.memoHash != null) {
55+
memoController.text = widget.memoHash!;
56+
}
4957
super.initState();
5058
}
5159

@@ -210,8 +218,9 @@ class _SendConfirmationWidgetState extends State<SendConfirmationWidget> {
210218

211219
Future<void> _performTransfer() async {
212220
if (widget.chainType == ChainType.Stellar) {
221+
final memoHash = widget.memoHash != null ? Uint8List.fromList(hex.decode(memoController.text.trim())) : null;
213222
await Stellar.transfer(
214-
widget.secret, widget.to, widget.amount, memo: widget.memo);
223+
widget.secret, widget.to, widget.amount, memo: widget.memo, memoHash: memoHash);
215224
} else {
216225
await TFChain.transfer(widget.secret, widget.to, widget.amount);
217226
}

0 commit comments

Comments
 (0)