mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-05 02:09:22 +00:00
feat: add qr code scanning and display
and correct spacing in explainer text
This commit is contained in:
parent
00932dbc0b
commit
81008841cc
3 changed files with 195 additions and 5 deletions
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:barcode_scan2/platform_wrapper.dart';
|
||||||
import 'package:bip48/bip48.dart';
|
import 'package:bip48/bip48.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -19,6 +20,7 @@ import '../../../providers/global/secure_store_provider.dart';
|
||||||
import '../../../providers/global/wallets_provider.dart';
|
import '../../../providers/global/wallets_provider.dart';
|
||||||
import '../../../services/transaction_notification_tracker.dart';
|
import '../../../services/transaction_notification_tracker.dart';
|
||||||
import '../../../utilities/enums/derive_path_type_enum.dart';
|
import '../../../utilities/enums/derive_path_type_enum.dart';
|
||||||
|
import '../../../utilities/logger.dart';
|
||||||
import '../../../utilities/util.dart';
|
import '../../../utilities/util.dart';
|
||||||
import '../../../wallets/crypto_currency/coins/bip48_bitcoin.dart';
|
import '../../../wallets/crypto_currency/coins/bip48_bitcoin.dart';
|
||||||
import '../../../wallets/crypto_currency/crypto_currency.dart';
|
import '../../../wallets/crypto_currency/crypto_currency.dart';
|
||||||
|
@ -27,6 +29,7 @@ import '../../../wallets/wallet/intermediate/bip39_hd_wallet.dart';
|
||||||
import '../../../wallets/wallet/wallet.dart';
|
import '../../../wallets/wallet/wallet.dart';
|
||||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
import '../../../widgets/desktop/primary_button.dart';
|
import '../../../widgets/desktop/primary_button.dart';
|
||||||
|
import '../../../widgets/desktop/qr_code_scanner_dialog.dart';
|
||||||
import '../../../widgets/desktop/secondary_button.dart';
|
import '../../../widgets/desktop/secondary_button.dart';
|
||||||
import '../../../widgets/icon_widgets/copy_icon.dart';
|
import '../../../widgets/icon_widgets/copy_icon.dart';
|
||||||
import '../../../widgets/icon_widgets/qrcode_icon.dart';
|
import '../../../widgets/icon_widgets/qrcode_icon.dart';
|
||||||
|
@ -34,6 +37,7 @@ import '../../add_wallet_views/restore_wallet_view/sub_widgets/restore_failed_di
|
||||||
import '../../add_wallet_views/restore_wallet_view/sub_widgets/restore_succeeded_dialog.dart';
|
import '../../add_wallet_views/restore_wallet_view/sub_widgets/restore_succeeded_dialog.dart';
|
||||||
import '../../add_wallet_views/restore_wallet_view/sub_widgets/restoring_dialog.dart';
|
import '../../add_wallet_views/restore_wallet_view/sub_widgets/restoring_dialog.dart';
|
||||||
import '../../home_view/home_view.dart';
|
import '../../home_view/home_view.dart';
|
||||||
|
import 'xpub_qr_popup.dart';
|
||||||
|
|
||||||
final multisigCoordinatorStateProvider =
|
final multisigCoordinatorStateProvider =
|
||||||
StateNotifierProvider<MultisigCoordinatorState, MultisigCoordinatorData>(
|
StateNotifierProvider<MultisigCoordinatorState, MultisigCoordinatorData>(
|
||||||
|
@ -310,7 +314,12 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
|
||||||
.buttonTextSecondary,
|
.buttonTextSecondary,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// TODO: Implement QR code scanning
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => XpubQrPopup(
|
||||||
|
xpub: _myXpub,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
@ -393,9 +402,7 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.buttonTextSecondary,
|
.buttonTextSecondary,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () => {scanQr(i - 1)},
|
||||||
// TODO: Implement QR code scanning
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
SecondaryButton(
|
SecondaryButton(
|
||||||
|
@ -629,6 +636,55 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
|
||||||
await WakelockPlus.disable();
|
await WakelockPlus.disable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> scanQr(int cosignerIndex) async {
|
||||||
|
try {
|
||||||
|
if (Platform.isAndroid || Platform.isIOS) {
|
||||||
|
if (FocusScope.of(context).hasFocus) {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
await Future<void>.delayed(
|
||||||
|
const Duration(milliseconds: 75),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final qrResult = await BarcodeScanner.scan();
|
||||||
|
|
||||||
|
xpubControllers[cosignerIndex].text = qrResult.rawContent;
|
||||||
|
|
||||||
|
ref
|
||||||
|
.read(multisigCoordinatorStateProvider.notifier)
|
||||||
|
.addCosignerXpub(qrResult.rawContent);
|
||||||
|
|
||||||
|
setState(() {}); // Trigger rebuild to update button state.
|
||||||
|
} else {
|
||||||
|
// Platform.isLinux, Platform.isWindows, or Platform.isMacOS.
|
||||||
|
final qrResult = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const QrCodeScannerDialog(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (qrResult == null) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"QR scanning cancelled",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
xpubControllers[cosignerIndex].text = qrResult;
|
||||||
|
|
||||||
|
ref
|
||||||
|
.read(multisigCoordinatorStateProvider.notifier)
|
||||||
|
.addCosignerXpub(qrResult);
|
||||||
|
|
||||||
|
setState(() {}); // Trigger rebuild to update button state.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} on PlatformException catch (e, s) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
||||||
|
level: LogLevel.Warning,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MultisigCoordinatorData {
|
class MultisigCoordinatorData {
|
||||||
|
|
|
@ -157,7 +157,7 @@ class _MultisigSetupViewState extends ConsumerState<MultisigSetupView> {
|
||||||
"Multisignature accounts, also called shared accounts, require "
|
"Multisignature accounts, also called shared accounts, require "
|
||||||
"multiple signatures to authorize a transaction. This can "
|
"multiple signatures to authorize a transaction. This can "
|
||||||
"increase security by preventing a single point of failure or "
|
"increase security by preventing a single point of failure or "
|
||||||
"allow multiple parties to jointly control funds."
|
"allow multiple parties to jointly control funds. "
|
||||||
"For example, in a 2-of-3 multisig account, two of the three "
|
"For example, in a 2-of-3 multisig account, two of the three "
|
||||||
"cosigners are required in order to sign a transaction and spend "
|
"cosigners are required in order to sign a transaction and spend "
|
||||||
"funds.",
|
"funds.",
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import '../../../../notifications/show_flush_bar.dart';
|
||||||
|
import '../../../../themes/stack_colors.dart';
|
||||||
|
import '../../../../utilities/assets.dart';
|
||||||
|
import '../../../../utilities/text_styles.dart';
|
||||||
|
import '../../../../utilities/util.dart';
|
||||||
|
import '../../../../widgets/desktop/desktop_dialog.dart';
|
||||||
|
import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||||
|
import '../../../../widgets/desktop/primary_button.dart';
|
||||||
|
import '../../../../widgets/detail_item.dart';
|
||||||
|
import '../../../../widgets/qr.dart';
|
||||||
|
import '../../../../widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
|
class XpubQrPopup extends StatelessWidget {
|
||||||
|
const XpubQrPopup({
|
||||||
|
super.key,
|
||||||
|
required this.xpub,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String xpub;
|
||||||
|
|
||||||
|
Future<void> _copy(BuildContext context) async {
|
||||||
|
await Clipboard.setData(
|
||||||
|
ClipboardData(text: xpub),
|
||||||
|
);
|
||||||
|
if (context.mounted) {
|
||||||
|
unawaited(
|
||||||
|
showFloatingFlushBar(
|
||||||
|
type: FlushBarType.info,
|
||||||
|
message: "Copied to clipboard",
|
||||||
|
iconAsset: Assets.svg.copy,
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isDesktop = Util.isDesktop;
|
||||||
|
|
||||||
|
return DesktopDialog(
|
||||||
|
maxWidth: isDesktop ? 600 : MediaQuery.of(context).size.width - 32,
|
||||||
|
maxHeight: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 32),
|
||||||
|
child: Text(
|
||||||
|
"Your xPub",
|
||||||
|
style: STextStyles.desktopH2(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DesktopDialogCloseButton(
|
||||||
|
onPressedOverride:
|
||||||
|
Navigator.of(context, rootNavigator: true).pop,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(32, 0, 32, 32),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: isDesktop ? 12 : 16,
|
||||||
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "Derivation path",
|
||||||
|
detail: "m/48'/0'/0'/2'", // TODO: Get actual derivation path
|
||||||
|
horizontal: true,
|
||||||
|
borderColor: isDesktop
|
||||||
|
? Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldDefaultBG
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: isDesktop ? 12 : 16,
|
||||||
|
),
|
||||||
|
QR(
|
||||||
|
data: xpub,
|
||||||
|
size:
|
||||||
|
isDesktop ? 256 : MediaQuery.of(context).size.width / 1.5,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: isDesktop ? 12 : 16,
|
||||||
|
),
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
borderColor: isDesktop
|
||||||
|
? Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldDefaultBG
|
||||||
|
: null,
|
||||||
|
child: SelectableText(
|
||||||
|
xpub,
|
||||||
|
style: STextStyles.w500_14(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: isDesktop ? 12 : 16,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (isDesktop) const Spacer(),
|
||||||
|
if (isDesktop)
|
||||||
|
const SizedBox(
|
||||||
|
width: 16,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: PrimaryButton(
|
||||||
|
label: "Copy",
|
||||||
|
onPressed: () => _copy(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue