/* * This file is part of Stack Wallet. * * Copyright (c) 2023 Cypher Stack * All Rights Reserved. * The code is distributed under GPLv3 license, see LICENSE file for details. * Generated by Cypher Stack on 2023-05-26 * */ import 'dart:async'; import 'dart:io'; import 'dart:ui' as ui; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:isar/isar.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import '../../../db/isar/main_db.dart'; import '../../../models/isar/models/isar_models.dart'; import '../../../notifications/show_flush_bar.dart'; import '../../../themes/coin_icon_provider.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/address_utils.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/clipboard_interface.dart'; import '../../../utilities/text_styles.dart'; import '../../../utilities/util.dart'; import '../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../widgets/conditional_parent.dart'; import '../../../widgets/custom_buttons/blue_text_button.dart'; import '../../../widgets/custom_buttons/simple_edit_button.dart'; import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/desktop/secondary_button.dart'; import '../../../widgets/qr.dart'; import '../../../widgets/rounded_white_container.dart'; import '../../../widgets/stack_dialog.dart'; class AddressCard extends ConsumerStatefulWidget { const AddressCard({ super.key, required this.addressId, required this.walletId, required this.coin, this.onPressed, this.clipboard = const ClipboardWrapper(), }); final int addressId; final String walletId; final CryptoCurrency coin; final ClipboardInterface clipboard; final VoidCallback? onPressed; @override ConsumerState createState() => _AddressCardState(); } class _AddressCardState extends ConsumerState { final _qrKey = GlobalKey(); final isDesktop = Util.isDesktop; late Stream stream; late final Address address; AddressLabel? label; Future _capturePng(bool shouldSaveInsteadOfShare) async { try { final RenderRepaintBoundary boundary = _qrKey.currentContext?.findRenderObject() as RenderRepaintBoundary; final ui.Image image = await boundary.toImage(); final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); final Uint8List pngBytes = byteData!.buffer.asUint8List(); if (shouldSaveInsteadOfShare) { if (Util.isDesktop) { final dir = Directory("${Platform.environment['HOME']}"); if (!dir.existsSync()) { throw Exception( "Home dir not found while trying to open filepicker on QR image save", ); } final path = await FilePicker.platform.saveFile( fileName: "qrcode.png", initialDirectory: dir.path, ); if (path != null) { final file = File(path); if (file.existsSync()) { if (mounted) { unawaited( showFloatingFlushBar( type: FlushBarType.warning, message: "$path already exists!", context: context, ), ); } } else { await file.writeAsBytes(pngBytes); if (mounted) { unawaited( showFloatingFlushBar( type: FlushBarType.success, message: "$path saved!", context: context, ), ); } } } } else { // await DocumentFileSavePlus.saveFile( // pngBytes, // "receive_qr_code_${DateTime.now().toLocal().toIso8601String()}.png", // "image/png"); } } else { final tempDir = await getTemporaryDirectory(); final file = await File("${tempDir.path}/qrcode.png").create(); await file.writeAsBytes(pngBytes); await Share.shareFiles( ["${tempDir.path}/qrcode.png"], text: "Receive URI QR Code", ); } } catch (e) { //todo: comeback to this debugPrint(e.toString()); } } @override void initState() { address = MainDB.instance.isar.addresses .where() .idEqualTo(widget.addressId) .findFirstSync()!; label = MainDB.instance.getAddressLabelSync(widget.walletId, address.value); Id? id = label?.id; if (id == null) { label = AddressLabel( walletId: widget.walletId, addressString: address.value, value: "", tags: address.subType == AddressSubType.receiving ? ["receiving"] : address.subType == AddressSubType.change ? ["change"] : null, ); id = MainDB.instance.putAddressLabelSync(label!); } stream = MainDB.instance.watchAddressLabel(id: id); super.initState(); } @override Widget build(BuildContext context) { return RoundedWhiteContainer( onPressed: widget.onPressed, child: StreamBuilder( stream: stream, builder: (context, snapshot) { if (snapshot.hasData) { label = snapshot.data!; } return ConditionalParent( condition: isDesktop, builder: (child) => Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SvgPicture.file( File( ref.watch( coinIconProvider(widget.coin), ), ), width: 32, height: 32, ), const SizedBox( width: 12, ), Expanded( child: child, ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label!.value.isNotEmpty ? label!.value : "No label", style: STextStyles.itemSubtitle(context), textAlign: TextAlign.left, ), SimpleEditButton( editValue: label!.value, editLabel: 'label', overrideTitle: 'Edit label', disableIcon: true, onValueChanged: (value) { MainDB.instance.putAddressLabel( label!.copyWith( label: value, ), ); }, ), ], ), SizedBox( height: isDesktop ? 2 : 8, ), Row( children: [ Expanded( child: Text( address.value, style: STextStyles.itemSubtitle12(context), ), ), ], ), const SizedBox( height: 10, ), Row( children: [ CustomTextButton( text: "Copy address", onTap: () { widget.clipboard .setData( ClipboardData( text: address.value, ), ) .then((value) { if (context.mounted) { unawaited( showFloatingFlushBar( type: FlushBarType.info, message: "Copied to clipboard", context: context, ), ); } }); }, ), const SizedBox( width: 16, ), CustomTextButton( text: "Show QR code", onTap: () async { await showDialog( context: context, builder: (_) { return StackDialogBase( child: Column( children: [ if (label!.value.isNotEmpty) Text( label!.value, style: STextStyles.w600_18(context), ), if (label!.value.isNotEmpty) const SizedBox( height: 8, ), Text( address.value, style: STextStyles.w500_16(context).copyWith( color: Theme.of(context) .extension()! .textSubtitle1, ), ), const SizedBox( height: 16, ), Center( child: RepaintBoundary( key: _qrKey, child: QR( data: AddressUtils.buildUriString( widget.coin.uriScheme, address.value, {}, ), size: 220, ), ), ), const SizedBox( height: 16, ), Row( children: [ if (!isDesktop) Expanded( child: SecondaryButton( label: "Share", buttonHeight: isDesktop ? ButtonHeight.l : null, icon: SvgPicture.asset( Assets.svg.share, width: 14, height: 14, color: Theme.of(context) .extension()! .buttonTextSecondary, ), onPressed: () async { await _capturePng(false); }, ), ), if (isDesktop) Expanded( child: PrimaryButton( buttonHeight: isDesktop ? ButtonHeight.l : null, onPressed: () async { // TODO: add save functionality instead of share // save works on linux at the moment await _capturePng(true); }, label: "Save", icon: SvgPicture.asset( Assets.svg.arrowDown, width: 20, height: 20, color: Theme.of(context) .extension()! .buttonTextPrimary, ), ), ), ], ), ], ), ); }, ); }, ), ], ), // if (label!.tags != null && label!.tags!.isNotEmpty) // Wrap( // spacing: 10, // runSpacing: 10, // children: label!.tags! // .map( // (e) => AddressTag( // tag: e, // ), // ) // .toList(), // ), ], ), ); }, ), ); } }