dev
dart 304 lines 13.7 KB
Raw
1 import 'package:cake_wallet/generated/i18n.dart';
2 import 'package:cake_wallet/new-ui/pages/bridge/bridge_history_page.dart';
3 import 'package:cake_wallet/routes.dart';
4 import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
5 import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
6 import 'package:cake_wallet/utils/request_review_handler.dart';
7 import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
8 import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
9 import 'package:cake_wallet/view_model/bridge/bridge_history_view_model.dart';
10 import 'package:cake_wallet/view_model/bridge/bridge_view_model.dart';
11 import 'package:cw_core/crypto_currency.dart';
12 import 'package:flutter/material.dart';
13 import 'package:flutter/services.dart';
14 import 'package:flutter_mobx/flutter_mobx.dart';
15 import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
16
17 class BridgeAmountPage extends StatefulWidget {
18 const BridgeAmountPage({
19 super.key,
20 required this.bridgeViewModel,
21 required this.bridgeHistoryViewModel,
22 required this.initialToken,
23 });
24
25 final BridgeViewModel bridgeViewModel;
26 final BridgeHistoryViewModel bridgeHistoryViewModel;
27
28 final CryptoCurrency initialToken;
29
30 @override
31 State<BridgeAmountPage> createState() => _BridgeAmountPageState();
32 }
33
34 class _BridgeAmountPageState extends State<BridgeAmountPage> {
35 final TextEditingController _amountController = TextEditingController();
36 final FocusNode _amountFocusNode = FocusNode();
37 bool _amountFocused = false;
38
39 BridgeViewModel get bridgeViewModel => widget.bridgeViewModel;
40
41 @override
42 void initState() {
43 super.initState();
44 bridgeViewModel.applyInitialBridgeToken(widget.initialToken);
45 bridgeViewModel.onBridgeSuccess = _showBridgeSuccessBottomSheet;
46
47 _amountFocusNode.addListener(() => setState(() => _amountFocused = _amountFocusNode.hasFocus));
48
49 WidgetsBinding.instance.addPostFrameCallback((_) {
50 bridgeViewModel.ensureFiatPriceForSelectedToken();
51 _amountFocusNode.requestFocus();
52 });
53 }
54
55 void _showBridgeSuccessBottomSheet() {
56 if (!mounted) return;
57 WidgetsBinding.instance.addPostFrameCallback((_) async {
58 if (!mounted) return;
59 final ctx = context;
60 if (!ctx.mounted) return;
61
62 await showModalBottomSheet<void>(
63 context: ctx,
64 isScrollControlled: true,
65 builder: (BuildContext bottomSheetContext) {
66 return InfoBottomSheet(
67 footerType: FooterType.doubleActionButton,
68 titleText: 'Bridge initiated!',
69 contentImage: 'assets/images/birthday_cake.png',
70 content: 'The bridging will take between 30 seconds and 3 '
71 'minutes to complete.',
72 doubleActionLeftButtonText: S.of(bottomSheetContext).close,
73 doubleActionRightButtonText: 'View history',
74 onLeftActionButtonPressed: () {
75 bridgeViewModel.clearOnBridgeSuccess();
76 Navigator.of(context, rootNavigator: true).pop();
77 RequestReviewHandler.requestReview();
78 },
79 onRightActionButtonPressed: () {
80 bridgeViewModel.clearOnBridgeSuccess();
81 Navigator.of(context).popUntil((route) => route.isFirst);
82 showMaterialModalBottomSheet(
83 context: context,
84 backgroundColor: Colors.transparent,
85 builder: (ctx) => BridgeHistoryPage(widget.bridgeHistoryViewModel),
86 );
87 },
88 );
89 },
90 );
91 });
92 }
93
94 @override
95 void dispose() {
96 bridgeViewModel.onBridgeSuccess = null;
97 bridgeViewModel.setAmount('');
98 _amountController.dispose();
99 bridgeViewModel.dispose();
100 super.dispose();
101 }
102
103 @override
104 Widget build(BuildContext context) {
105 final theme = Theme.of(context);
106
107 return Container(
108 decoration: BoxDecoration(
109 color: theme.colorScheme.surface,
110 borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
111 ),
112 child: Padding(
113 padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
114 child: Column(
115 children: [
116 ModalTopBar(
117 title: S.of(context).enter_amount,
118 leadingIcon: Icon(Icons.arrow_back_ios_new),
119 leadingSemanticLabel: S.of(context).seed_alert_back,
120 onLeadingPressed: () => Navigator.of(context, rootNavigator: true).pop(),
121 trailingIcon: Icon(Icons.history),
122 trailingSemanticLabel: S.of(context).history,
123 onTrailingPressed: () {
124 Navigator.pushNamed(context, Routes.bridgeHistoryPage,
125 arguments: widget.bridgeHistoryViewModel);
126 },
127 ),
128 Expanded(
129 child: SafeArea(
130 child: Padding(
131 padding: const EdgeInsets.symmetric(horizontal: 18),
132 child: Observer(
133 builder: (_) {
134 final canNext = bridgeViewModel.canProceedToDestinationNetwork;
135
136 return Column(
137 crossAxisAlignment: CrossAxisAlignment.stretch,
138 children: [
139 const Spacer(flex: 2),
140 Center(
141 child: Row(
142 mainAxisSize: MainAxisSize.min,
143 crossAxisAlignment: CrossAxisAlignment.baseline,
144 textBaseline: TextBaseline.alphabetic,
145 children: [
146 IntrinsicWidth(
147 child: TextFormField(
148 controller: _amountController,
149 focusNode: _amountFocusNode,
150 maxLines: 1,
151 onChanged: bridgeViewModel.setAmount,
152 autovalidateMode: AutovalidateMode.always,
153 validator: bridgeViewModel.decimalAmountValidator,
154 keyboardType: TextInputType.numberWithOptions(
155 signed: false,
156 decimal: true,
157 ),
158 inputFormatters: <TextInputFormatter>[
159 FilteringTextInputFormatter.allow(
160 RegExp(r'^\d*[.,]?\d*$'),
161 ),
162 ],
163 decoration: InputDecoration(
164 isDense: true,
165 isCollapsed: true,
166 contentPadding: EdgeInsets.zero,
167 fillColor: Colors.transparent,
168 hoverColor: Colors.transparent,
169 focusedBorder: InputBorder.none,
170 enabledBorder: InputBorder.none,
171 hintText: _amountFocused || _amountController.text.isNotEmpty
172 ? null
173 : "0.00",
174 hintStyle: theme.textTheme.displayMedium?.copyWith(
175 fontWeight: FontWeight.w400,
176 color: theme.colorScheme.onSurfaceVariant,
177 ),
178 ),
179 style: theme.textTheme.displayMedium?.copyWith(
180 fontWeight: FontWeight.w400,
181 fontSize: 45,
182 color: theme.colorScheme.onSurface,
183 ),
184 ),
185 ),
186 const SizedBox(width: 8),
187 Text(
188 bridgeViewModel.selectedToken?.title ?? '',
189 maxLines: 1,
190 overflow: TextOverflow.ellipsis,
191 style: theme.textTheme.displayMedium?.copyWith(
192 fontWeight: FontWeight.w400,
193 fontSize: 45,
194 color: theme.colorScheme.onSurfaceVariant,
195 ),
196 ),
197 ],
198 ),
199 ),
200 const SizedBox(height: 12),
201 Center(
202 child: Text(
203 bridgeViewModel.fiatAmountFormatted.isEmpty
204 ? ''
205 : '~${bridgeViewModel.fiatAmountFormatted} ${bridgeViewModel.fiatCurrencyTitle}',
206 style: theme.textTheme.headlineSmall?.copyWith(
207 color: theme.colorScheme.onSurfaceVariant,
208 fontWeight: FontWeight.w600,
209 fontSize: 22,
210 letterSpacing: -0.11,
211 ),
212 ),
213 ),
214 if (bridgeViewModel.amountError != null) ...[
215 const SizedBox(height: 10),
216 Text(
217 bridgeViewModel.amountError!,
218 textAlign: TextAlign.center,
219 style: theme.textTheme.bodySmall?.copyWith(
220 color: theme.colorScheme.error,
221 ),
222 ),
223 ],
224 const Spacer(flex: 3),
225 Row(
226 spacing: 8,
227 crossAxisAlignment: CrossAxisAlignment.center,
228 children: [
229 Expanded(
230 child: Row(
231 spacing: 8,
232 children: [
233 Text(
234 S.of(context).available,
235 style: TextStyle(
236 fontSize: 16,
237 color: Theme.of(context).colorScheme.onSurfaceVariant),
238 ),
239 Text(
240 "${bridgeViewModel.tokenBalanceFormatted} ${bridgeViewModel.selectedToken?.title ?? ""}",
241 style: TextStyle(
242 fontSize: 16,
243 color: Theme.of(context).colorScheme.onSurface),
244 )
245 ],
246 )),
247 GestureDetector(
248 onTap: () {
249 bridgeViewModel.setMaxAmount();
250 _amountController.text = bridgeViewModel.amount;
251 _amountController.selection = TextSelection.collapsed(
252 offset: _amountController.text.length,
253 );
254 },
255 child: Container(
256 height: 34,
257 decoration: BoxDecoration(
258 color: Theme.of(context).colorScheme.surfaceContainer,
259 borderRadius: BorderRadius.circular(999999)),
260 child: Center(
261 child: Padding(
262 padding: EdgeInsets.symmetric(horizontal: 16),
263 child: Text(
264 S.of(context).max,
265 style: TextStyle(
266 fontSize: 14,
267 color: Theme.of(context).colorScheme.primary),
268 ),
269 )),
270 ),
271 ),
272 IgnorePointer(
273 ignoring: !canNext,
274 child: ModernButton(
275 size: 34,
276 backgroundColor: theme.colorScheme.primary,
277 iconColor: theme.colorScheme.onPrimary,
278 icon: Icon(Icons.arrow_forward, size: 20),
279 semanticLabel: S.of(context).continue_text,
280 onPressed: () {
281 Navigator.pushNamed(
282 context,
283 Routes.bridgeDestinationNetworkPage,
284 arguments: bridgeViewModel,
285 );
286 },
287 ),
288 ),
289 ],
290 ),
291 SizedBox(height: 24)
292 ],
293 );
294 },
295 ),
296 ),
297 ),
298 ),
299 ],
300 ),
301 ),
302 );
303 }
304 }