stack_wallet/lib/pages/receive_view/addresses/address_card.dart

392 lines
14 KiB
Dart

/*
* 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<AddressCard> createState() => _AddressCardState();
}
class _AddressCardState extends ConsumerState<AddressCard> {
final _qrKey = GlobalKey();
final isDesktop = Util.isDesktop;
late Stream<AddressLabel?> stream;
late final Address address;
AddressLabel? label;
Future<void> _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<AddressLabel?>(
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<void>(
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<StackColors>()!
.textSubtitle1,
),
),
const SizedBox(
height: 16,
),
Center(
child: RepaintBoundary(
key: _qrKey,
child: QR(
data: AddressUtils.buildUriString(
widget.coin,
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<StackColors>()!
.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<StackColors>()!
.buttonTextPrimary,
),
),
),
],
),
],
),
);
},
);
},
),
],
),
// if (label!.tags != null && label!.tags!.isNotEmpty)
// Wrap(
// spacing: 10,
// runSpacing: 10,
// children: label!.tags!
// .map(
// (e) => AddressTag(
// tag: e,
// ),
// )
// .toList(),
// ),
],
),
);
},
),
);
}
}