desktop receiving popups

This commit is contained in:
julian 2022-10-31 12:03:21 -06:00
parent 3421602ba2
commit 7540e593a3
6 changed files with 545 additions and 315 deletions

View file

@ -0,0 +1,4 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 4.16602V15.8327" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.3327 10L10.4993 15.8333L4.66602 10" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 346 B

View file

@ -20,7 +20,10 @@ import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_dialog.dart';
@ -51,6 +54,10 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
late TextEditingController amountController; late TextEditingController amountController;
late TextEditingController noteController; late TextEditingController noteController;
late final bool isDesktop;
late String _uriString;
bool didGenerate = false;
final _amountFocusNode = FocusNode(); final _amountFocusNode = FocusNode();
final _noteFocusNode = FocusNode(); final _noteFocusNode = FocusNode();
@ -81,8 +88,151 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
} }
} }
String? _generateURI() {
final amountString = amountController.text;
final noteString = noteController.text;
if (amountString.isNotEmpty && Decimal.tryParse(amountString) == null) {
showFloatingFlushBar(
type: FlushBarType.warning,
message: "Invalid amount",
context: context,
);
return null;
}
String query = "";
if (amountString.isNotEmpty) {
query += "amount=$amountString";
}
if (noteString.isNotEmpty) {
if (query.isNotEmpty) {
query += "&";
}
query += "message=$noteString";
}
final uri = Uri(
scheme: widget.coin.uriScheme,
host: widget.receivingAddress,
query: query.isNotEmpty ? query : null,
);
final uriString = uri.toString().replaceFirst("://", ":");
Logging.instance.log("Generated receiving QR code for: $uriString",
level: LogLevel.Info);
return uriString;
}
void onGeneratePressed() {
final uriString = _generateURI();
if (uriString == null) {
return;
}
showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (_) {
final width = MediaQuery.of(context).size.width / 2;
return StackDialogBase(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Text(
"New QR code",
style: STextStyles.pageTitleH2(context),
),
),
const SizedBox(
height: 12,
),
Center(
child: RepaintBoundary(
key: _qrKey,
child: SizedBox(
width: width + 20,
height: width + 20,
child: QrImage(
data: uriString,
size: width,
backgroundColor:
Theme.of(context).extension<StackColors>()!.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
),
const SizedBox(
height: 12,
),
Center(
child: SizedBox(
width: width,
child: TextButton(
onPressed: () async {
// TODO: add save button as well
await _capturePng(true);
},
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(context),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: SvgPicture.asset(
Assets.svg.share,
width: 14,
height: 14,
),
),
const SizedBox(
width: 4,
),
Column(
children: [
Text(
"Share",
textAlign: TextAlign.center,
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
),
),
const SizedBox(
height: 2,
),
],
),
],
),
),
),
),
],
),
);
},
);
}
@override @override
void initState() { void initState() {
isDesktop = Util.isDesktop;
_uriString = Uri(
scheme: widget.coin.uriScheme,
host: widget.receivingAddress,
).toString().replaceFirst("://", ":");
amountController = TextEditingController(); amountController = TextEditingController();
noteController = TextEditingController(); noteController = TextEditingController();
super.initState(); super.initState();
@ -101,7 +251,10 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType"); debugPrint("BUILD: $runtimeType");
return Scaffold(
return ConditionalParent(
condition: !isDesktop,
builder: (child) => Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background, backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar( appBar: AppBar(
leading: AppBarBackButton( leading: AppBarBackButton(
@ -136,25 +289,52 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
child: IntrinsicHeight( child: IntrinsicHeight(
child: Padding( child: Padding(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
child: child,
),
),
),
),
);
},
),
),
child: Padding(
padding: isDesktop
? const EdgeInsets.only(
top: 12,
left: 32,
right: 32,
bottom: 32,
)
: const EdgeInsets.all(0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max,
children: [ children: [
if (!isDesktop)
RoundedWhiteContainer( RoundedWhiteContainer(
child: Text( child: Text(
"The new QR code with your address, amount and note will appear in the pop up window.", "The new QR code with your address, amount and note will appear in the pop up window.",
style: STextStyles.itemSubtitle(context), style: STextStyles.itemSubtitle(context),
), ),
), ),
if (!isDesktop)
const SizedBox( const SizedBox(
height: 12, height: 12,
), ),
Text( Text(
"Amount (Optional)", "Amount (Optional)",
style: STextStyles.smallMed12(context), style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
)
: STextStyles.smallMed12(context),
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
const SizedBox( SizedBox(
height: 8, height: isDesktop ? 10 : 8,
), ),
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
@ -165,15 +345,30 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
enableSuggestions: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true,
controller: amountController, controller: amountController,
focusNode: _amountFocusNode, focusNode: _amountFocusNode,
style: STextStyles.field(context), style: isDesktop
keyboardType: const TextInputType.numberWithOptions( ? STextStyles.desktopTextExtraExtraSmall(context).copyWith(
decimal: true), color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultText,
height: 1.8,
)
: STextStyles.field(context),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
onChanged: (_) => setState(() {}), onChanged: (_) => setState(() {}),
decoration: standardInputDecoration( decoration: standardInputDecoration(
"Amount", "Amount",
_amountFocusNode, _amountFocusNode,
context, context,
).copyWith( ).copyWith(
contentPadding: isDesktop
? const EdgeInsets.only(
left: 16,
top: 11,
bottom: 12,
right: 5,
)
: null,
suffixIcon: amountController.text.isNotEmpty suffixIcon: amountController.text.isNotEmpty
? Padding( ? Padding(
padding: const EdgeInsets.only(right: 0), padding: const EdgeInsets.only(right: 0),
@ -196,16 +391,22 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
), ),
), ),
), ),
const SizedBox( SizedBox(
height: 12, height: isDesktop ? 20 : 12,
), ),
Text( Text(
"Note (Optional)", "Note (Optional)",
style: STextStyles.smallMed12(context), style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
)
: STextStyles.smallMed12(context),
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
const SizedBox( SizedBox(
height: 8, height: isDesktop ? 10 : 8,
), ),
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
@ -216,13 +417,28 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
enableSuggestions: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true,
controller: noteController, controller: noteController,
focusNode: _noteFocusNode, focusNode: _noteFocusNode,
style: STextStyles.field(context), style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultText,
height: 1.8,
)
: STextStyles.field(context),
onChanged: (_) => setState(() {}), onChanged: (_) => setState(() {}),
decoration: standardInputDecoration( decoration: standardInputDecoration(
"Note", "Note",
_noteFocusNode, _noteFocusNode,
context, context,
).copyWith( ).copyWith(
contentPadding: isDesktop
? const EdgeInsets.only(
left: 16,
top: 11,
bottom: 12,
right: 5,
)
: null,
suffixIcon: noteController.text.isNotEmpty suffixIcon: noteController.text.isNotEmpty
? Padding( ? Padding(
padding: const EdgeInsets.only(right: 0), padding: const EdgeInsets.only(right: 0),
@ -245,91 +461,63 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
), ),
), ),
), ),
// SizedBox() SizedBox(
// Spacer(), height: isDesktop ? 20 : 8,
const SizedBox(
height: 8,
), ),
TextButton( PrimaryButton(
style: Theme.of(context) label: "Generate QR code",
.extension<StackColors>()! onPressed: isDesktop
.getPrimaryEnabledButtonColor(context), ? () {
onPressed: () { final uriString = _generateURI();
final amountString = amountController.text; if (uriString == null) {
final noteString = noteController.text;
if (amountString.isNotEmpty &&
Decimal.tryParse(amountString) == null) {
showFloatingFlushBar(
type: FlushBarType.warning,
message: "Invalid amount",
context: context,
);
return; return;
} }
String query = ""; setState(() {
didGenerate = true;
if (amountString.isNotEmpty) { _uriString = uriString;
query += "amount=$amountString"; });
} }
if (noteString.isNotEmpty) { : onGeneratePressed,
if (query.isNotEmpty) { desktopMed: true,
query += "&";
}
query += "message=$noteString";
}
final uri = Uri(
scheme: widget.coin.uriScheme,
host: widget.receivingAddress,
query: query.isNotEmpty ? query : null,
);
final uriString =
uri.toString().replaceFirst("://", ":");
Logging.instance.log(
"Generated receiving QR code for: $uriString",
level: LogLevel.Info);
showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (_) {
final width =
MediaQuery.of(context).size.width / 2;
return StackDialogBase(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Center(
child: Text(
"New QR code",
style:
STextStyles.pageTitleH2(context),
), ),
if (isDesktop && didGenerate)
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(
height: 20,
),
RoundedWhiteContainer(
borderColor: Theme.of(context)
.extension<StackColors>()!
.background,
child: Column(
children: [
Text(
"New QR Code",
style: STextStyles.desktopTextMedium(context),
), ),
const SizedBox( const SizedBox(
height: 12, height: 16,
), ),
Center( Center(
child: RepaintBoundary( child: RepaintBoundary(
key: _qrKey, key: _qrKey,
child: SizedBox( child: SizedBox(
width: width + 20, width: 234,
height: width + 20, height: 234,
child: QrImage( child: QrImage(
data: uriString, data: _uriString,
size: width, size: 220,
backgroundColor: Theme.of( backgroundColor: Theme.of(context)
context)
.extension<StackColors>()! .extension<StackColors>()!
.popupBG, .popupBG,
foregroundColor: Theme.of( foregroundColor: Theme.of(context)
context)
.extension<StackColors>()! .extension<StackColors>()!
.accentColorDark), .accentColorDark),
), ),
@ -338,79 +526,56 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
const SizedBox( const SizedBox(
height: 12, height: 12,
), ),
Center( Row(
child: SizedBox( children: [
width: width, SecondaryButton(
child: TextButton( width: 170,
desktopMed: true,
onPressed: () async { onPressed: () async {
// TODO: add save button as well await _capturePng(false);
await _capturePng(true);
}, },
style: Theme.of(context) label: "Share",
.extension<StackColors>()! icon: SvgPicture.asset(
.getSecondaryEnabledButtonColor(
context),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
Center(
child: SvgPicture.asset(
Assets.svg.share, Assets.svg.share,
width: 14, width: 20,
height: 14, height: 20,
),
),
const SizedBox(
width: 4,
),
Column(
children: [
Text(
"Share",
textAlign:
TextAlign.center,
style: STextStyles.button(
context)
.copyWith(
color: Theme.of(context) color: Theme.of(context)
.extension< .extension<StackColors>()!
StackColors>()!
.buttonTextSecondary, .buttonTextSecondary,
), ),
), ),
const SizedBox( const SizedBox(
height: 2, width: 16,
), ),
], PrimaryButton(
), width: 170,
], desktopMed: true,
), onPressed: () async {
), // TODO: add save functionality instead of share
), await _capturePng(true);
),
],
),
);
}, },
); label: "Save",
}, icon: SvgPicture.asset(
child: Text( Assets.svg.arrowDown,
"Generate QR Code", width: 20,
style: STextStyles.button(context), height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextPrimary,
),
),
],
)
],
), ),
), ),
], ],
), ),
],
), ),
],
), ),
), ),
),
);
},
),
); );
} }
} }

View file

@ -16,9 +16,13 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:tuple/tuple.dart';
class DesktopReceive extends ConsumerStatefulWidget { class DesktopReceive extends ConsumerStatefulWidget {
const DesktopReceive({ const DesktopReceive({
@ -216,6 +220,44 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
// TODO: create transparent button class to account for hover // TODO: create transparent button class to account for hover
GestureDetector( GestureDetector(
onTap: () async { onTap: () async {
if (Util.isDesktop) {
await showDialog<void>(
context: context,
builder: (context) => DesktopDialog(
maxHeight: double.infinity,
maxWidth: 580,
child: Column(
children: [
Row(
children: [
const AppBarBackButton(
size: 40,
iconSize: 24,
),
Text(
"Generate QR code",
style: STextStyles.desktopH3(context),
),
],
),
IntrinsicHeight(
child: Navigator(
onGenerateRoute: RouteGenerator.generateRoute,
onGenerateInitialRoutes: (_, __) => [
RouteGenerator.generateRoute(
RouteSettings(
name: GenerateUriQrCodeView.routeName,
arguments: Tuple2(coin, receivingAddress),
),
),
],
),
),
],
),
),
);
} else {
unawaited( unawaited(
Navigator.of(context).push( Navigator.of(context).push(
RouteGenerator.getRoute( RouteGenerator.getRoute(
@ -230,6 +272,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
), ),
), ),
); );
}
}, },
child: Container( child: Container(
color: Colors.transparent, color: Colors.transparent,

View file

@ -37,6 +37,7 @@ import 'package:stackwallet/pages/intro_view.dart';
import 'package:stackwallet/pages/manage_favorites_view/manage_favorites_view.dart'; import 'package:stackwallet/pages/manage_favorites_view/manage_favorites_view.dart';
import 'package:stackwallet/pages/notification_views/notifications_view.dart'; import 'package:stackwallet/pages/notification_views/notifications_view.dart';
import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart';
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
import 'package:stackwallet/pages/receive_view/receive_view.dart'; import 'package:stackwallet/pages/receive_view/receive_view.dart';
import 'package:stackwallet/pages/send_view/send_view.dart'; import 'package:stackwallet/pages/send_view/send_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/about_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/about_view.dart';
@ -955,6 +956,21 @@ class RouteGenerator {
} }
return _routeError("${settings.name} invalid args: ${args.toString()}"); return _routeError("${settings.name} invalid args: ${args.toString()}");
case GenerateUriQrCodeView.routeName:
if (args is Tuple2<Coin, String>) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => GenerateUriQrCodeView(
coin: args.item1,
receivingAddress: args.item2,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
// == Desktop specific routes ============================================ // == Desktop specific routes ============================================
case CreatePasswordView.routeName: case CreatePasswordView.routeName:
return getRoute( return getRoute(

View file

@ -145,6 +145,7 @@ class _SVG {
String get walletDesktop => "assets/svg/wallet-desktop.svg"; String get walletDesktop => "assets/svg/wallet-desktop.svg";
String get exitDesktop => "assets/svg/exit-desktop.svg"; String get exitDesktop => "assets/svg/exit-desktop.svg";
String get keys => "assets/svg/keys.svg"; String get keys => "assets/svg/keys.svg";
String get arrowDown => "assets/svg/arrow-down.svg";
String get ellipse1 => "assets/svg/Ellipse-43.svg"; String get ellipse1 => "assets/svg/Ellipse-43.svg";
String get ellipse2 => "assets/svg/Ellipse-42.svg"; String get ellipse2 => "assets/svg/Ellipse-42.svg";

View file

@ -310,6 +310,7 @@ flutter:
- assets/svg/wallet-desktop.svg - assets/svg/wallet-desktop.svg
- assets/svg/exit-desktop.svg - assets/svg/exit-desktop.svg
- assets/svg/keys.svg - assets/svg/keys.svg
- assets/svg/arrow-down.svg
# coin icons # coin icons
- assets/svg/coin_icons/Bitcoin.svg - assets/svg/coin_icons/Bitcoin.svg
- assets/svg/coin_icons/Litecoin.svg - assets/svg/coin_icons/Litecoin.svg