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/theme/stack_colors.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/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/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
@ -51,6 +54,10 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
late TextEditingController amountController;
late TextEditingController noteController;
late final bool isDesktop;
late String _uriString;
bool didGenerate = false;
final _amountFocusNode = 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
void initState() {
isDesktop = Util.isDesktop;
_uriString = Uri(
scheme: widget.coin.uriScheme,
host: widget.receivingAddress,
).toString().replaceFirst("://", ":");
amountController = TextEditingController();
noteController = TextEditingController();
super.initState();
@ -101,7 +251,10 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
return Scaffold(
return ConditionalParent(
condition: !isDesktop,
builder: (child) => Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
@ -136,25 +289,52 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
child: IntrinsicHeight(
child: Padding(
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(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max,
children: [
if (!isDesktop)
RoundedWhiteContainer(
child: Text(
"The new QR code with your address, amount and note will appear in the pop up window.",
style: STextStyles.itemSubtitle(context),
),
),
if (!isDesktop)
const SizedBox(
height: 12,
),
Text(
"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,
),
const SizedBox(
height: 8,
SizedBox(
height: isDesktop ? 10 : 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
@ -165,15 +345,30 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
enableSuggestions: Util.isDesktop ? false : true,
controller: amountController,
focusNode: _amountFocusNode,
style: STextStyles.field(context),
keyboardType: const TextInputType.numberWithOptions(
decimal: true),
style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultText,
height: 1.8,
)
: STextStyles.field(context),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
onChanged: (_) => setState(() {}),
decoration: standardInputDecoration(
"Amount",
_amountFocusNode,
context,
).copyWith(
contentPadding: isDesktop
? const EdgeInsets.only(
left: 16,
top: 11,
bottom: 12,
right: 5,
)
: null,
suffixIcon: amountController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
@ -196,16 +391,22 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
),
),
),
const SizedBox(
height: 12,
SizedBox(
height: isDesktop ? 20 : 12,
),
Text(
"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,
),
const SizedBox(
height: 8,
SizedBox(
height: isDesktop ? 10 : 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
@ -216,13 +417,28 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
enableSuggestions: Util.isDesktop ? false : true,
controller: noteController,
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(() {}),
decoration: standardInputDecoration(
"Note",
_noteFocusNode,
context,
).copyWith(
contentPadding: isDesktop
? const EdgeInsets.only(
left: 16,
top: 11,
bottom: 12,
right: 5,
)
: null,
suffixIcon: noteController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
@ -245,91 +461,63 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
),
),
),
// SizedBox()
// Spacer(),
const SizedBox(
height: 8,
SizedBox(
height: isDesktop ? 20 : 8,
),
TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
onPressed: () {
final amountString = amountController.text;
final noteString = noteController.text;
if (amountString.isNotEmpty &&
Decimal.tryParse(amountString) == null) {
showFloatingFlushBar(
type: FlushBarType.warning,
message: "Invalid amount",
context: context,
);
PrimaryButton(
label: "Generate QR code",
onPressed: isDesktop
? () {
final uriString = _generateURI();
if (uriString == null) {
return;
}
String query = "";
if (amountString.isNotEmpty) {
query += "amount=$amountString";
setState(() {
didGenerate = true;
_uriString = uriString;
});
}
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);
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),
: onGeneratePressed,
desktopMed: true,
),
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(
height: 12,
height: 16,
),
Center(
child: RepaintBoundary(
key: _qrKey,
child: SizedBox(
width: width + 20,
height: width + 20,
width: 234,
height: 234,
child: QrImage(
data: uriString,
size: width,
backgroundColor: Theme.of(
context)
data: _uriString,
size: 220,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.popupBG,
foregroundColor: Theme.of(
context)
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
@ -338,79 +526,56 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
const SizedBox(
height: 12,
),
Center(
child: SizedBox(
width: width,
child: TextButton(
Row(
children: [
SecondaryButton(
width: 170,
desktopMed: true,
onPressed: () async {
// TODO: add save button as well
await _capturePng(true);
await _capturePng(false);
},
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonColor(
context),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
Center(
child: SvgPicture.asset(
label: "Share",
icon: 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(
width: 20,
height: 20,
color: Theme.of(context)
.extension<
StackColors>()!
.extension<StackColors>()!
.buttonTextSecondary,
),
),
const SizedBox(
height: 2,
width: 16,
),
],
),
],
),
),
),
),
],
),
);
PrimaryButton(
width: 170,
desktopMed: true,
onPressed: () async {
// TODO: add save functionality instead of share
await _capturePng(true);
},
);
},
child: Text(
"Generate QR Code",
style: STextStyles.button(context),
label: "Save",
icon: SvgPicture.asset(
Assets.svg.arrowDown,
width: 20,
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/text_styles.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/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:tuple/tuple.dart';
class DesktopReceive extends ConsumerStatefulWidget {
const DesktopReceive({
@ -216,6 +220,44 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
// TODO: create transparent button class to account for hover
GestureDetector(
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(
Navigator.of(context).push(
RouteGenerator.getRoute(
@ -230,6 +272,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
),
),
);
}
},
child: Container(
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/notification_views/notifications_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/send_view/send_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()}");
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 ============================================
case CreatePasswordView.routeName:
return getRoute(

View file

@ -145,6 +145,7 @@ class _SVG {
String get walletDesktop => "assets/svg/wallet-desktop.svg";
String get exitDesktop => "assets/svg/exit-desktop.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 ellipse2 => "assets/svg/Ellipse-42.svg";

View file

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