dev
dart 331 lines 12.7 KB
Raw
1 import 'package:cake_wallet/core/address_validator.dart';
2 import 'package:cake_wallet/nano/nano.dart';
3 import 'package:cake_wallet/src/widgets/address_text_field.dart';
4 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
5 import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
6 import 'package:cake_wallet/store/settings_store.dart';
7 import 'package:cake_wallet/utils/payment_request.dart';
8 import 'package:cake_wallet/utils/show_pop_up.dart';
9 import 'package:cw_core/crypto_currency.dart';
10 import 'package:cw_core/wallet_base.dart';
11 import 'package:cw_core/n2_node.dart';
12 import 'package:flutter/material.dart';
13 import 'package:flutter_mobx/flutter_mobx.dart';
14 import 'package:cake_wallet/generated/i18n.dart';
15 import 'package:cake_wallet/src/widgets/primary_button.dart';
16 import 'package:cake_wallet/src/screens/base_page.dart';
17 import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
18
19 class NanoChangeRepPage extends BasePage {
20 NanoChangeRepPage({required SettingsStore settingsStore, required WalletBase wallet})
21 : _wallet = wallet,
22 _settingsStore = settingsStore,
23 _addressController = TextEditingController(),
24 _formKey = GlobalKey<FormState>() {}
25
26 final TextEditingController _addressController;
27 final WalletBase _wallet;
28 final SettingsStore _settingsStore;
29
30 final GlobalKey<FormState> _formKey;
31
32 @override
33 String get title => S.current.change_rep;
34
35 N2Node getCurrentRepNode(List<N2Node> nodes) {
36 final currentRepAccount = nano!.getRepresentative(_wallet);
37 final currentNode = nodes.firstWhere(
38 (node) => node.account == currentRepAccount,
39 orElse: () => N2Node(
40 account: currentRepAccount,
41 score: 0,
42 uptime: "???",
43 weight: 0,
44 ),
45 );
46 return currentNode;
47 }
48
49 @override
50 Widget body(BuildContext context) {
51 return Form(
52 key: _formKey,
53 child: FutureBuilder(
54 future: nano!.getN2Reps(_wallet),
55 builder: (context, snapshot) {
56 final reps = snapshot.data ?? [];
57
58 return Container(
59 padding: EdgeInsets.only(left: 24, right: 24),
60 child: ScrollableWithBottomSection(
61 topSectionPadding: EdgeInsets.only(bottom: 24),
62 topSection: Column(
63 children: [
64 Row(
65 children: <Widget>[
66 Expanded(
67 child: AddressTextField(
68 controller: _addressController,
69 onURIScanned: (uri) {
70 final paymentRequest = PaymentRequest.fromUri(uri);
71 _addressController.text = paymentRequest.address;
72 },
73 options: [
74 AddressTextFieldOption.paste,
75 AddressTextFieldOption.qrCode,
76 ],
77 buttonColor: Theme.of(context).colorScheme.surfaceContainerHighest,
78 validator: AddressValidator(type: CryptoCurrency.nano),
79 ),
80 )
81 ],
82 ),
83 Column(
84 children: [
85 Container(
86 margin: EdgeInsets.only(top: 12),
87 child: Text(
88 S.current.nano_current_rep,
89 style: Theme.of(context).textTheme.bodyMedium!.copyWith(
90 fontSize: 16,
91 fontWeight: FontWeight.w700,
92 ),
93 ),
94 ),
95 _buildSingleRepresentative(
96 context,
97 getCurrentRepNode(reps),
98 isList: false,
99 divider: false,
100 ),
101 if (reps.isNotEmpty) ...[
102 Divider(height: 20),
103 Container(
104 margin: EdgeInsets.only(top: 12),
105 child: Text(
106 S.current.nano_pick_new_rep,
107 style: Theme.of(context).textTheme.bodyMedium!.copyWith(
108 fontSize: 16,
109 fontWeight: FontWeight.w700,
110 ),
111 ),
112 ),
113 Divider(height: 20),
114 ],
115 ],
116 ),
117 ],
118 ),
119 contentPadding: EdgeInsets.only(bottom: 24),
120 content: Container(
121 child: reps.isNotEmpty
122 ? Column(
123 children: _getRepresentativeWidgets(context, reps),
124 )
125 : SizedBox(),
126 ),
127 bottomSectionPadding: EdgeInsets.only(bottom: 24),
128 bottomSection: Observer(
129 builder: (_) => Row(
130 mainAxisAlignment: MainAxisAlignment.center,
131 children: <Widget>[
132 Flexible(
133 child: Container(
134 padding: EdgeInsets.only(right: 8.0),
135 child: LoadingPrimaryButton(
136 onPressed: () => _onSubmit(context),
137 text: S.of(context).change,
138 color: Theme.of(context).colorScheme.primary,
139 textColor: Theme.of(context).colorScheme.onPrimary,
140 ),
141 )),
142 ],
143 )),
144 ),
145 );
146 },
147 ),
148 );
149 }
150
151 Future<void> _onSubmit(BuildContext context) async {
152 if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
153 return;
154 }
155
156 final confirmed = await showPopUp<bool>(
157 context: context,
158 builder: (BuildContext context) {
159 return AlertWithTwoActions(
160 alertTitle: S.of(context).change_rep,
161 alertContent: S.of(context).change_rep_message,
162 rightButtonText: S.of(context).change,
163 leftButtonText: S.of(context).cancel,
164 actionRightButton: () => Navigator.pop(context, true),
165 actionLeftButton: () => Navigator.pop(context, false));
166 }) ??
167 false;
168
169 if (confirmed) {
170 try {
171 _settingsStore.defaultNanoRep = _addressController.text;
172
173 await nano!.changeRep(_wallet, _addressController.text);
174
175 // reset this flag whenever we successfully change reps:
176 _settingsStore.shouldShowRepWarning = true;
177
178 await showPopUp<void>(
179 context: context,
180 builder: (BuildContext context) {
181 return AlertWithOneAction(
182 alertTitle: S.of(context).successful,
183 alertContent: S.of(context).change_rep_successful,
184 buttonText: S.of(context).ok,
185 buttonAction: () => Navigator.pop(context));
186 });
187 } catch (e) {
188 await showPopUp<void>(
189 context: context,
190 builder: (BuildContext context) {
191 return AlertWithOneAction(
192 alertTitle: S.of(context).error,
193 alertContent: e.toString(),
194 buttonText: S.of(context).ok,
195 buttonAction: () => Navigator.pop(context));
196 });
197 }
198 }
199 }
200
201 List<Widget> _getRepresentativeWidgets(BuildContext context, List<N2Node>? list) {
202 if (list == null) {
203 return [];
204 }
205 final List<Widget> ret = [];
206 for (final N2Node node in list) {
207 if (node.alias != null && node.alias!.trim().isNotEmpty) {
208 bool divider = node != list.first;
209 ret.add(_buildSingleRepresentative(context, node, divider: divider, isList: true));
210 }
211 }
212 return ret;
213 }
214
215 Widget _buildSingleRepresentative(
216 BuildContext context,
217 N2Node rep, {
218 bool isList = true,
219 bool divider = false,
220 }) {
221 return Column(
222 children: <Widget>[
223 if (divider) Divider(height: 2),
224 TextButton(
225 style: TextButton.styleFrom(
226 padding: EdgeInsets.zero,
227 ),
228 onPressed: () async {
229 if (!isList) {
230 return;
231 }
232 _addressController.text = rep.account!;
233 },
234 child: Container(
235 margin: const EdgeInsets.symmetric(vertical: 20),
236 child: Row(
237 mainAxisAlignment: MainAxisAlignment.spaceBetween,
238 crossAxisAlignment: CrossAxisAlignment.center,
239 children: <Widget>[
240 Container(
241 margin: const EdgeInsetsDirectional.only(start: 24),
242 width: MediaQuery.of(context).size.width * 0.50,
243 child: Column(
244 mainAxisAlignment: MainAxisAlignment.center,
245 crossAxisAlignment: CrossAxisAlignment.start,
246 children: <Widget>[
247 Text(
248 rep.alias ?? rep.account!,
249 style: Theme.of(context).textTheme.bodyMedium!.copyWith(
250 color: Theme.of(context).colorScheme.onSurface,
251 fontWeight: FontWeight.w700,
252 fontSize: rep.alias == null ? 14 : 18,
253 ),
254 ),
255 Container(
256 margin: const EdgeInsets.only(top: 7),
257 child: RichText(
258 text: TextSpan(
259 text: "${S.current.voting_weight}: ${rep.weight.toString()}%",
260 style: Theme.of(context).textTheme.bodyMedium!.copyWith(
261 color: Theme.of(context).colorScheme.onSurfaceVariant,
262 fontWeight: FontWeight.w700,
263 ),
264 ),
265 ),
266 ),
267 Container(
268 margin: const EdgeInsets.only(top: 4),
269 child: RichText(
270 text: TextSpan(
271 text: '',
272 children: [
273 TextSpan(
274 text: "${S.current.uptime}: ",
275 style: Theme.of(context).textTheme.bodyMedium!.copyWith(
276 color: Theme.of(context).colorScheme.onSurfaceVariant,
277 fontWeight: FontWeight.w700,
278 ),
279 ),
280 TextSpan(
281 text: rep.uptime,
282 style: Theme.of(context).textTheme.bodyMedium!.copyWith(
283 color: Theme.of(context).colorScheme.onSurfaceVariant,
284 fontWeight: FontWeight.w900,
285 ),
286 ),
287 ],
288 ),
289 ),
290 ),
291 ],
292 ),
293 ),
294 Container(
295 margin: const EdgeInsetsDirectional.only(end: 24, start: 14),
296 child: Stack(
297 children: <Widget>[
298 Icon(
299 Icons.verified,
300 color: Theme.of(context).colorScheme.primary,
301 size: 50,
302 ),
303 Positioned.fill(
304 child: Container(
305 margin: EdgeInsets.all(13),
306 color: Theme.of(context).colorScheme.primary,
307 ),
308 ),
309 Container(
310 alignment: const AlignmentDirectional(-0.03, 0.03),
311 width: 50,
312 height: 50,
313 child: Text(
314 (rep.score).toString(),
315 textAlign: TextAlign.center,
316 style: Theme.of(context).textTheme.bodyMedium!.copyWith(
317 fontWeight: FontWeight.w800,
318 ),
319 ),
320 ),
321 ],
322 ),
323 ),
324 ],
325 ),
326 ),
327 ),
328 ],
329 );
330 }
331 }