dev
dart 288 lines 10.9 KB
Raw
1 import 'package:cake_wallet/entities/contact_base.dart';
2 import 'package:cake_wallet/entities/qr_scanner.dart';
3 import 'package:cake_wallet/generated/i18n.dart';
4 import 'package:cake_wallet/routes.dart';
5 import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
6 import 'package:cake_wallet/utils/device_info.dart';
7 import 'package:cake_wallet/utils/permission_handler.dart';
8 import 'package:cake_wallet/utils/responsive_layout_util.dart';
9 import 'package:cw_core/currency.dart';
10 import 'package:flutter/material.dart';
11 import 'package:flutter/services.dart';
12 import 'package:permission_handler/permission_handler.dart';
13
14 enum AddressTextFieldOption { paste, qrCode, addressBook, walletAddresses }
15
16 class AddressTextField<T extends Currency> extends StatelessWidget {
17 AddressTextField({
18 required this.controller,
19 this.isActive = true,
20 this.placeholder,
21 this.options = const [AddressTextFieldOption.qrCode, AddressTextFieldOption.addressBook],
22 this.onURIScanned,
23 this.focusNode,
24 this.buttonColor,
25 this.iconColor,
26 this.textStyle,
27 this.hintStyle,
28 this.validator,
29 this.onPushPasteButton,
30 this.onPushAddressBookButton,
31 this.onPushAddressPickerButton,
32 this.onEditingComplete,
33 this.onSelectedContact,
34 this.selectedCurrency,
35 this.addressKey,
36 this.fillColor,
37 this.hasUnderlineBorder = false,
38 this.borderWidth = 1.0,
39 this.contentPadding,
40 this.copyImagePath,
41 });
42
43 static const prefixIconWidth = 34.0;
44 static const prefixIconHeight = 34.0;
45 static const spaceBetweenPrefixIcons = 10.0;
46
47 final TextEditingController? controller;
48 final bool isActive;
49 final String? placeholder;
50 final Function(Uri)? onURIScanned;
51 final List<AddressTextFieldOption> options;
52 final FormFieldValidator<String>? validator;
53 final VoidCallback? onEditingComplete;
54
55 final Color? buttonColor;
56 final Color? fillColor;
57 final Color? iconColor;
58 final double borderWidth;
59 final TextStyle? textStyle;
60 final TextStyle? hintStyle;
61 final bool hasUnderlineBorder;
62 final EdgeInsetsGeometry? contentPadding;
63 final FocusNode? focusNode;
64 final T? selectedCurrency;
65 final Key? addressKey;
66 final String? copyImagePath;
67
68 final Function(BuildContext context)? onPushPasteButton;
69 final Function(BuildContext context)? onPushAddressBookButton;
70 final Function(BuildContext context)? onPushAddressPickerButton;
71 final Function(ContactBase contact)? onSelectedContact;
72
73 @override
74 Widget build(BuildContext context) {
75 return Stack(
76 children: <Widget>[
77 BaseTextFormField(
78 contentPadding: contentPadding,
79 borderWidth: borderWidth,
80 hasUnderlineBorder: hasUnderlineBorder,
81 key: addressKey,
82 onEditingComplete: onEditingComplete,
83 enableIMEPersonalizedLearning: false,
84 keyboardType: TextInputType.visiblePassword,
85 onFieldSubmitted: (_) => FocusScope.of(context).unfocus(),
86 enabled: isActive,
87 controller: controller,
88 focusNode: focusNode,
89 textStyle: textStyle ??
90 Theme.of(context).textTheme.bodyMedium!.copyWith(
91 fontSize: 16,
92 color: Theme.of(context).colorScheme.onSurfaceVariant,
93 ),
94 fillColor: fillColor ?? Theme.of(context).colorScheme.surfaceContainer,
95 suffixIcon: SizedBox(
96 width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length),
97 ),
98 placeholderTextStyle: hintStyle ??
99 Theme.of(context).textTheme.bodyMedium!.copyWith(
100 fontSize: 16,
101 color: Theme.of(context).colorScheme.onSurfaceVariant,
102 ),
103 hintText: placeholder ?? S.current.widgets_address,
104 validator: validator,
105 ),
106 Positioned(
107 top: 8,
108 right: 6,
109 child: SizedBox(
110 width: (prefixIconWidth * options.length) + (spaceBetweenPrefixIcons * options.length),
111 child: Row(
112 mainAxisAlignment: responsiveLayoutUtil.shouldRenderMobileUI
113 ? MainAxisAlignment.spaceBetween
114 : MainAxisAlignment.end,
115 children: [
116 if (this.options.contains(AddressTextFieldOption.paste)) ...[
117 SizedBox(width: 5),
118 Container(
119 width: prefixIconWidth,
120 height: prefixIconHeight,
121 padding: EdgeInsets.only(top: 0),
122 child: Semantics(
123 label: S.of(context).paste,
124 child: InkWell(
125 onTap: () async => _pasteAddress(context),
126 child: Container(
127 padding: EdgeInsets.all(8),
128 decoration: BoxDecoration(
129 color: Theme.of(context).colorScheme.surface,
130 borderRadius: BorderRadius.all(Radius.circular(6)),
131 ),
132 child: Image.asset(
133 copyImagePath ?? 'assets/images/paste_ios.png',
134 color: iconColor ?? Theme.of(context).colorScheme.onSurfaceVariant,
135 ),
136 ),
137 ),
138 ),
139 ),
140 ],
141 if (this.options.contains(AddressTextFieldOption.qrCode) &&
142 DeviceInfo.instance.isMobile) ...[
143 SizedBox(width: 5),
144 Container(
145 width: prefixIconWidth,
146 height: prefixIconHeight,
147 padding: EdgeInsets.only(top: 0),
148 child: Semantics(
149 label: S.of(context).scan_qr_code,
150 child: InkWell(
151 onTap: () async => _presentQRScanner(context),
152 child: Container(
153 padding: EdgeInsets.all(8),
154 decoration: BoxDecoration(
155 color: Theme.of(context).colorScheme.surface,
156 borderRadius: BorderRadius.all(
157 Radius.circular(6),
158 ),
159 ),
160 child: Image.asset(
161 'assets/images/qr_code_icon.png',
162 color: iconColor ?? Theme.of(context).colorScheme.onSurfaceVariant,
163 ),
164 ),
165 ),
166 ),
167 ),
168 ],
169 if (this.options.contains(AddressTextFieldOption.addressBook)) ...[
170 SizedBox(width: 5),
171 Container(
172 width: prefixIconWidth,
173 height: prefixIconHeight,
174 padding: EdgeInsets.only(top: 0),
175 child: Semantics(
176 label: S.of(context).address_book,
177 child: InkWell(
178 onTap: () async => _presetAddressBookPicker(context),
179 child: Container(
180 padding: EdgeInsets.all(8),
181 decoration: BoxDecoration(
182 color: Theme.of(context).colorScheme.surface,
183 borderRadius: BorderRadius.all(
184 Radius.circular(6),
185 ),
186 ),
187 child: Image.asset(
188 'assets/images/open_book.png',
189 color: iconColor ?? Theme.of(context).colorScheme.onSurfaceVariant,
190 ),
191 ),
192 ),
193 ),
194 ),
195 ],
196 if (this.options.contains(AddressTextFieldOption.walletAddresses)) ...[
197 SizedBox(width: 5),
198 Container(
199 width: prefixIconWidth,
200 height: prefixIconHeight,
201 padding: EdgeInsets.only(top: 0),
202 child: Semantics(
203 label: S.of(context).address_book,
204 child: InkWell(
205 onTap: () async => _presetWalletAddressPicker(context),
206 child: Container(
207 padding: EdgeInsets.all(8),
208 decoration: BoxDecoration(
209 color: buttonColor ?? Theme.of(context).colorScheme.surfaceContainerLow,
210 borderRadius: BorderRadius.all(Radius.circular(6)),
211 ),
212 child: Image.asset(
213 'assets/images/open_book.png',
214 color: iconColor ?? Theme.of(context).colorScheme.onSurfaceVariant,
215 ),
216 ),
217 ),
218 ),
219 )
220 ],
221 ],
222 ),
223 ),
224 )
225 ],
226 );
227 }
228
229 Future<void> _presentQRScanner(BuildContext context) async {
230 bool isCameraPermissionGranted =
231 await PermissionHandler.checkPermission(Permission.camera, context);
232 if (!isCameraPermissionGranted) return;
233 final code = await presentQRScanner(context);
234 if (code == null) return;
235 if (code.isEmpty) return;
236
237 try {
238 final uri = Uri.parse(code);
239 controller?.text = uri.path;
240 onURIScanned?.call(uri);
241 } catch (_) {
242 controller?.text = code;
243 }
244 }
245
246 Future<void> _presetAddressBookPicker(BuildContext context) async {
247 final contact = await Navigator.of(context)
248 .pushNamed(Routes.pickerAddressBook, arguments: [selectedCurrency, false]);
249
250 if (contact is ContactBase) {
251 controller?.text = contact.address;
252 onPushAddressBookButton?.call(context);
253 onSelectedContact?.call(contact);
254 }
255 }
256
257 Future<void> _presetWalletAddressPicker(BuildContext context) async {
258 final address = await Navigator.of(context).pushNamed(Routes.pickerWalletAddress);
259
260 if (address is String) {
261 controller?.text = address;
262 onPushAddressPickerButton?.call(context);
263 }
264 }
265
266 Future<void> _pasteAddress(BuildContext context) async {
267 final clipboard = await Clipboard.getData('text/plain');
268 final address = clipboard?.text ?? '';
269
270 if (address.isNotEmpty) {
271 // if it has query parameters then it's a valid uri
272 // added because Uri.parse(address) can parse a normal address string and would still be valid
273 if (address.contains("=")) {
274 try {
275 final uri = Uri.parse(address);
276 controller?.text = uri.path;
277 onURIScanned?.call(uri);
278 } catch (_) {
279 controller?.text = address;
280 }
281 } else {
282 controller?.text = address;
283 }
284 }
285
286 onPushPasteButton?.call(context);
287 }
288 }