Skip to content

Commit cdcdafe

Browse files
committed
bare minimum but functional slatepack ui in both desktop and mobile UIs
1 parent 88add78 commit cdcdafe

4 files changed

Lines changed: 329 additions & 62 deletions

File tree

lib/pages/receive_view/receive_view.dart

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,14 @@ import '../../widgets/custom_buttons/blue_text_button.dart';
4848
import '../../widgets/custom_loading_overlay.dart';
4949
import '../../widgets/desktop/primary_button.dart';
5050
import '../../widgets/desktop/secondary_button.dart';
51+
import '../../widgets/dialogs/s_dialog.dart';
5152
import '../../widgets/qr.dart';
5253
import '../../widgets/rounded_white_container.dart';
54+
import '../../widgets/stack_dialog.dart';
5355
import 'addresses/wallet_addresses_view.dart';
5456
import 'generate_receiving_uri_qr_code_view.dart';
57+
import 'sub_widgets/mwc_slatepack_import_dialog.dart';
58+
import 'sub_widgets/slatepack_entry_dialog.dart';
5559

5660
class ReceiveView extends ConsumerStatefulWidget {
5761
const ReceiveView({
@@ -85,6 +89,58 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
8589
final Map<AddressType, String> _addressMap = {};
8690
final Map<AddressType, StreamSubscription<Address?>> _addressSubMap = {};
8791

92+
Future<void> _importSlatepack() async {
93+
final slatepackString = await showDialog<String>(
94+
context: context,
95+
builder: (context) => const SlatepackEntryDialog(),
96+
);
97+
98+
if (slatepackString == null) return;
99+
if (mounted) {
100+
final wallet =
101+
ref.read(pWallets).getWallet(walletId) as MimblewimblecoinWallet;
102+
103+
Exception? ex;
104+
final result = await showLoading(
105+
whileFuture: wallet.fullDecodeSlatepack(slatepackString),
106+
context: context,
107+
message: "Decoding slatepack...",
108+
onException: (e) => ex = e,
109+
);
110+
111+
if (result == null || ex != null) {
112+
if (mounted) {
113+
await showDialog<void>(
114+
context: context,
115+
builder:
116+
(context) => StackOkDialog(
117+
title: "Slatepack receive error",
118+
message:
119+
ex?.toString() ?? "Unexpected result without exception",
120+
),
121+
);
122+
}
123+
return;
124+
}
125+
126+
if (mounted) {
127+
await showDialog<void>(
128+
context: context,
129+
builder:
130+
(context) => SDialog(
131+
child: MwcSlatepackImportDialog(
132+
walletId: widget.walletId,
133+
clipboard: widget.clipboard,
134+
rawSlatepack: result.raw,
135+
decoded: result.result,
136+
slatepackType: result.type,
137+
),
138+
),
139+
);
140+
}
141+
}
142+
}
143+
88144
Future<void> generateNewAddress() async {
89145
final wallet = ref.read(pWallets).getWallet(walletId);
90146

@@ -698,19 +754,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
698754
const SizedBox(height: 12),
699755
SecondaryButton(
700756
label: "Import Slatepack",
701-
onPressed: () async {
702-
final wallet = ref.read(pWallets).getWallet(walletId);
703-
if (wallet is MimblewimblecoinWallet) {
704-
// await showDialog<void>(
705-
// context: context,
706-
// builder:
707-
// (context) => MwcSlatepackImportDialog(
708-
// wallet: wallet,
709-
// clipboard: clipboard,
710-
// ),
711-
// );
712-
}
713-
},
757+
onPressed: _importSlatepack,
714758
),
715759
],
716760
const SizedBox(height: 30),
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter/services.dart';
3+
import 'package:flutter_riverpod/flutter_riverpod.dart';
4+
5+
import '../../../providers/global/barcode_scanner_provider.dart';
6+
import '../../../themes/stack_colors.dart';
7+
import '../../../utilities/barcode_scanner_interface.dart';
8+
import '../../../utilities/clipboard_interface.dart';
9+
import '../../../utilities/constants.dart';
10+
import '../../../utilities/logger.dart';
11+
import '../../../utilities/text_styles.dart';
12+
import '../../../widgets/desktop/primary_button.dart';
13+
import '../../../widgets/desktop/secondary_button.dart';
14+
import '../../../widgets/icon_widgets/clipboard_icon.dart';
15+
import '../../../widgets/icon_widgets/qrcode_icon.dart';
16+
import '../../../widgets/icon_widgets/x_icon.dart';
17+
import '../../../widgets/stack_dialog.dart';
18+
import '../../../widgets/stack_text_field.dart';
19+
import '../../../widgets/textfield_icon_button.dart';
20+
21+
class SlatepackEntryDialog extends ConsumerStatefulWidget {
22+
const SlatepackEntryDialog({
23+
super.key,
24+
this.clipboard = const ClipboardWrapper(),
25+
});
26+
27+
final ClipboardInterface clipboard;
28+
29+
@override
30+
ConsumerState<SlatepackEntryDialog> createState() =>
31+
_SlatepackEntryDialogState();
32+
}
33+
34+
class _SlatepackEntryDialogState extends ConsumerState<SlatepackEntryDialog> {
35+
final _receiveSlateController = TextEditingController();
36+
final _slateFocusNode = FocusNode();
37+
38+
bool _slateToggleFlag = false;
39+
40+
Future<void> _pasteSlatepack() async {
41+
final ClipboardData? data = await widget.clipboard.getData(
42+
Clipboard.kTextPlain,
43+
);
44+
if (data?.text != null && data!.text!.isNotEmpty) {
45+
_receiveSlateController.text = data.text!;
46+
setState(() {
47+
_slateToggleFlag = _receiveSlateController.text.isNotEmpty;
48+
});
49+
}
50+
}
51+
52+
Future<void> _scanQr() async {
53+
try {
54+
if (_slateFocusNode.hasFocus) {
55+
_slateFocusNode.unfocus();
56+
await Future<void>.delayed(const Duration(milliseconds: 75));
57+
}
58+
59+
if (mounted) {
60+
final qrResult = await ref.read(pBarcodeScanner).scan(context: context);
61+
if (qrResult.rawContent.isNotEmpty && qrResult.rawContent != "null") {
62+
_receiveSlateController.text = qrResult.rawContent;
63+
setState(() {
64+
_slateToggleFlag = _receiveSlateController.text.isNotEmpty;
65+
});
66+
}
67+
}
68+
} on PlatformException catch (e, s) {
69+
if (mounted) {
70+
try {
71+
await checkCamPermDeniedMobileAndOpenAppSettings(
72+
context,
73+
logging: Logging.instance,
74+
);
75+
} catch (e, s) {
76+
Logging.instance.e(
77+
"Failed to check cam permissions",
78+
error: e,
79+
stackTrace: s,
80+
);
81+
}
82+
} else {
83+
Logging.instance.e(
84+
"Failed to get camera permissions while trying to scan qr code in SendView: ",
85+
error: e,
86+
stackTrace: s,
87+
);
88+
}
89+
}
90+
}
91+
92+
@override
93+
void dispose() {
94+
_receiveSlateController.dispose();
95+
_slateFocusNode.dispose();
96+
super.dispose();
97+
}
98+
99+
@override
100+
Widget build(BuildContext context) {
101+
return StackDialogBase(
102+
child: Column(
103+
mainAxisSize: MainAxisSize.min,
104+
children: [
105+
Text(
106+
"Receive Slatepack",
107+
style: STextStyles.desktopTextExtraSmall(context).copyWith(
108+
color:
109+
Theme.of(
110+
context,
111+
).extension<StackColors>()!.textFieldActiveSearchIconRight,
112+
),
113+
textAlign: TextAlign.left,
114+
),
115+
const SizedBox(height: 12),
116+
ClipRRect(
117+
borderRadius: BorderRadius.circular(
118+
Constants.size.circularBorderRadius,
119+
),
120+
child: TextField(
121+
minLines: 1,
122+
maxLines: 5,
123+
key: const Key("receiveViewSlatepackFieldKey"),
124+
controller: _receiveSlateController,
125+
readOnly: false,
126+
autocorrect: false,
127+
enableSuggestions: false,
128+
toolbarOptions: const ToolbarOptions(
129+
copy: false,
130+
cut: false,
131+
paste: true,
132+
selectAll: false,
133+
),
134+
onChanged: (newValue) {
135+
setState(() {
136+
_slateToggleFlag = newValue.isNotEmpty;
137+
});
138+
},
139+
focusNode: _slateFocusNode,
140+
style: STextStyles.desktopTextExtraSmall(context).copyWith(
141+
color:
142+
Theme.of(
143+
context,
144+
).extension<StackColors>()!.textFieldActiveText,
145+
height: 1.8,
146+
),
147+
decoration: standardInputDecoration(
148+
"Enter Slatepack Message",
149+
_slateFocusNode,
150+
context,
151+
desktopMed: true,
152+
).copyWith(
153+
contentPadding: const EdgeInsets.symmetric(
154+
horizontal: 16,
155+
vertical: 12, // Adjust vertical padding for better alignment
156+
),
157+
suffixIcon: Padding(
158+
padding:
159+
_receiveSlateController.text.isEmpty
160+
? const EdgeInsets.only(right: 8)
161+
: const EdgeInsets.only(right: 0),
162+
child: UnconstrainedBox(
163+
child: Row(
164+
mainAxisAlignment: MainAxisAlignment.spaceAround,
165+
children: [
166+
_slateToggleFlag
167+
? TextFieldIconButton(
168+
key: const Key(
169+
"receiveViewClearSlatepackFieldButtonKey",
170+
),
171+
onTap: () {
172+
_receiveSlateController.text = "";
173+
setState(() {
174+
_slateToggleFlag = false;
175+
});
176+
},
177+
child: const XIcon(),
178+
)
179+
: TextFieldIconButton(
180+
key: const Key(
181+
"receiveViewPasteSlatepackFieldButtonKey",
182+
),
183+
onTap: _pasteSlatepack,
184+
child:
185+
_receiveSlateController.text.isEmpty
186+
? const ClipboardIcon()
187+
: const XIcon(),
188+
),
189+
if (_receiveSlateController.text.isEmpty)
190+
TextFieldIconButton(
191+
semanticsLabel:
192+
"Scan QR Button. Opens Camera For Scanning QR Code.",
193+
key: const Key("sendViewScanQrButtonKey"),
194+
onTap: _scanQr,
195+
child: const QrCodeIcon(),
196+
),
197+
],
198+
),
199+
),
200+
),
201+
),
202+
),
203+
),
204+
const SizedBox(height: 16),
205+
PrimaryButton(
206+
label: "Import",
207+
enabled: _slateToggleFlag,
208+
onPressed:
209+
!_slateToggleFlag
210+
? null
211+
: () =>
212+
Navigator.of(context).pop(_receiveSlateController.text),
213+
),
214+
const SizedBox(height: 16),
215+
SecondaryButton(
216+
label: "Cancel",
217+
onPressed: Navigator.of(context).pop,
218+
),
219+
],
220+
),
221+
);
222+
}
223+
}

lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import 'package:tuple/tuple.dart';
2020

2121
import '../../../../models/isar/models/isar_models.dart';
2222
import '../../../../models/keys/view_only_wallet_data.dart';
23-
import '../../../../models/mwc_slatepack_models.dart';
2423
import '../../../../notifications/show_flush_bar.dart';
2524
import '../../../../pages/receive_view/generate_receiving_uri_qr_code_view.dart';
2625
import '../../../../pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart';
@@ -111,59 +110,13 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
111110
}
112111
}
113112

114-
Future<({SlatepackDecodeResult result, String type, String raw})?>
115-
_decodeSlatepack() async {
116-
// add delay for showloading exception catching hack fix
117-
await Future<void>.delayed(const Duration(seconds: 1));
118-
113+
Future<void> _onReceiveSlatePressed() async {
119114
final wallet =
120115
ref.read(pWallets).getWallet(walletId) as MimblewimblecoinWallet;
121-
final text = _receiveSlateController.text.trim();
122-
123-
if (text.isEmpty) {
124-
return null;
125-
}
126-
127-
// Basic format validation.
128-
final coin = wallet.cryptoCurrency as Mimblewimblecoin;
129-
if (!coin.isSlatepack(text)) {
130-
throw Exception("Invalid slatepack format");
131-
}
132-
133-
// Attempt to decode.
134-
final decoded = await wallet.decodeSlatepack(text);
135-
136-
if (decoded.success) {
137-
final analysis = await wallet.analyzeSlatepack(text);
138-
139-
String _determineSlatepackType(SlatepackDecodeResult decoded) {
140-
// Fallback analysis based on sender/recipient addresses.
141-
if (decoded.senderAddress != null && decoded.recipientAddress != null) {
142-
return "S2 (Response)";
143-
} else if (decoded.senderAddress != null) {
144-
return "S1 (Initial)";
145-
} else {
146-
return "Unknown";
147-
}
148-
}
149116

150-
final String slatepackType = switch (analysis.status) {
151-
'S1' => "S1 (Initial Send)",
152-
'S2' => "S2 (Response)",
153-
'S3' => "S3 (Finalized)",
154-
_ => _determineSlatepackType(decoded), // Fallback.
155-
};
156-
157-
return (result: decoded, type: slatepackType, raw: text);
158-
} else {
159-
throw Exception(decoded.error ?? "Failed to decode slatepack");
160-
}
161-
}
162-
163-
Future<void> _onReceiveSlatePressed() async {
164117
Exception? ex;
165118
final result = await showLoading(
166-
whileFuture: _decodeSlatepack(),
119+
whileFuture: wallet.fullDecodeSlatepack(_receiveSlateController.text),
167120
context: context,
168121
message: "Decoding slatepack...",
169122
rootNavigator: Util.isDesktop,

0 commit comments

Comments
 (0)