mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-17 09:47:37 +00:00
commit
4c2a58693c
6 changed files with 594 additions and 349 deletions
|
@ -4,11 +4,12 @@ import 'dart:typed_data';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/services/coins/banano/banano_wallet.dart';
|
||||
import 'package:stackwallet/services/monkey_service.dart';
|
||||
import 'package:stackwallet/themes/coin_icon_provider.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
|
@ -20,6 +21,8 @@ import 'package:stackwallet/widgets/background.dart';
|
|||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
|
@ -44,75 +47,14 @@ class _MonkeyViewState extends ConsumerState<MonkeyView> {
|
|||
late final String walletId;
|
||||
List<int>? imageBytes;
|
||||
|
||||
String receivingAddress = "";
|
||||
|
||||
Future<void> getMonkeyImage(String address) async {
|
||||
if (address.isEmpty) {
|
||||
//address shouldn't be empty
|
||||
return;
|
||||
}
|
||||
|
||||
final http.Response response = await http
|
||||
.get(Uri.parse('https://monkey.banano.cc/api/v1/monkey/$address'));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
final decodedResponse = response.bodyBytes;
|
||||
await (manager.wallet as BananoWallet)
|
||||
.updateMonkeyImageBytes(decodedResponse);
|
||||
} else {
|
||||
throw Exception("Failed to get MonKey");
|
||||
}
|
||||
Future<void> _updateWalletMonKey(Uint8List monKeyBytes) async {
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
await (manager.wallet as BananoWallet)
|
||||
.updateMonkeyImageBytes(monKeyBytes.toList());
|
||||
}
|
||||
|
||||
// void getMonkeySVG(String address) async {
|
||||
// if (address.isEmpty) {
|
||||
// //address shouldn't be empty
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// final http.Response response = await http
|
||||
// .get(Uri.parse('https://monkey.banano.cc/api/v1/monkey/$address'));
|
||||
//
|
||||
// if (response.statusCode == 200) {
|
||||
// final decodedResponse = response.bodyBytes;
|
||||
// Directory directory = await getApplicationDocumentsDirectory();
|
||||
// late Directory sampleFolder;
|
||||
//
|
||||
// if (Platform.isAndroid) {
|
||||
// directory = Directory("/storage/emulated/0/");
|
||||
// sampleFolder = Directory('${directory!.path}Documents');
|
||||
// } else if (Platform.isIOS) {
|
||||
// sampleFolder = Directory(directory!.path);
|
||||
// } else if (Platform.isLinux) {
|
||||
// sampleFolder = Directory('${directory!.path}Documents');
|
||||
// } else if (Platform.isWindows) {
|
||||
// sampleFolder = Directory('${directory!.path}Documents');
|
||||
// } else if (Platform.isMacOS) {
|
||||
// sampleFolder = Directory('${directory!.path}Documents');
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// if (!sampleFolder.existsSync()) {
|
||||
// sampleFolder.createSync(recursive: true);
|
||||
// }
|
||||
// } catch (e, s) {
|
||||
// // todo: come back to this
|
||||
// debugPrint("$e $s");
|
||||
// }
|
||||
//
|
||||
// final docPath = sampleFolder.path;
|
||||
// final filePath = "$docPath/monkey.svg";
|
||||
//
|
||||
// File imgFile = File(filePath);
|
||||
// await imgFile.writeAsBytes(decodedResponse);
|
||||
// } else {
|
||||
// throw Exception("Failed to get MonKey");
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<Directory?> getDocsDir() async {
|
||||
Future<Directory?> _getDocsDir() async {
|
||||
try {
|
||||
if (Platform.isAndroid) {
|
||||
return Directory("/storage/emulated/0/Documents");
|
||||
|
@ -124,80 +66,45 @@ class _MonkeyViewState extends ConsumerState<MonkeyView> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> downloadMonkey(String address, bool isPNG) async {
|
||||
if (address.isEmpty) {
|
||||
//address shouldn't be empty
|
||||
return;
|
||||
Future<void> _saveMonKeyToFile({
|
||||
required Uint8List bytes,
|
||||
bool isPNG = false,
|
||||
bool overwrite = false,
|
||||
}) async {
|
||||
if (Platform.isAndroid) {
|
||||
await Permission.storage.request();
|
||||
}
|
||||
|
||||
String url = "https://monkey.banano.cc/api/v1/monkey/$address";
|
||||
|
||||
if (isPNG) {
|
||||
url += '?format=png&size=512&background=false';
|
||||
final dir = await _getDocsDir();
|
||||
if (dir == null) {
|
||||
throw Exception("Failed to get documents directory to save monKey image");
|
||||
}
|
||||
|
||||
final http.Response response = await http.get(Uri.parse(url));
|
||||
final address = await ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletId)
|
||||
.currentReceivingAddress;
|
||||
final docPath = dir.path;
|
||||
String filePath = "$docPath/monkey_$address";
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
if (Platform.isAndroid) {
|
||||
await Permission.storage.request();
|
||||
}
|
||||
filePath += isPNG ? ".png" : ".svg";
|
||||
|
||||
final decodedResponse = response.bodyBytes;
|
||||
final Directory? sampleFolder = await getDocsDir();
|
||||
File imgFile = File(filePath);
|
||||
|
||||
print("PATH: ${sampleFolder?.path}");
|
||||
|
||||
if (sampleFolder == null) {
|
||||
print("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
return;
|
||||
}
|
||||
|
||||
// try {
|
||||
// if (!sampleFolder.existsSync()) {
|
||||
// sampleFolder.createSync(recursive: true);
|
||||
// }
|
||||
// } catch (e, s) {
|
||||
// // todo: come back to this
|
||||
// debugPrint("$e $s");
|
||||
// }
|
||||
|
||||
final docPath = sampleFolder.path;
|
||||
String filePath = "$docPath/monkey_$address";
|
||||
|
||||
filePath += isPNG ? ".png" : ".svg";
|
||||
|
||||
// todo check if monkey.png exists
|
||||
|
||||
File imgFile = File(filePath);
|
||||
await imgFile.writeAsBytes(decodedResponse);
|
||||
} else {
|
||||
throw Exception("Failed to get MonKey");
|
||||
if (imgFile.existsSync() && !overwrite) {
|
||||
throw Exception("File already exists");
|
||||
}
|
||||
|
||||
await imgFile.writeAsBytes(bytes);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
walletId = widget.walletId;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
final address = await ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletId)
|
||||
.currentReceivingAddress;
|
||||
setState(() {
|
||||
receivingAddress = address;
|
||||
});
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final manager = ref.watch(walletsChangeNotifierProvider
|
||||
|
@ -208,7 +115,6 @@ class _MonkeyViewState extends ConsumerState<MonkeyView> {
|
|||
|
||||
imageBytes ??= (manager.wallet as BananoWallet).getMonkeyImageBytes();
|
||||
|
||||
//edit for desktop
|
||||
return Background(
|
||||
child: ConditionalParent(
|
||||
condition: isDesktop,
|
||||
|
@ -235,64 +141,116 @@ class _MonkeyViewState extends ConsumerState<MonkeyView> {
|
|||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: () {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
SvgPicture.asset(Assets.svg.monkey),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.monkey,
|
||||
width: 32,
|
||||
height: 32,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
"MonKey",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return const StackDialog(
|
||||
title: "About MonKeys",
|
||||
message:
|
||||
"A MonKey is a visual representation of your Banano address.",
|
||||
);
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.circleQuestion,
|
||||
color: Colors.blue[800],
|
||||
),
|
||||
const SizedBox(
|
||||
width: 6,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(
|
||||
"What is MonKey?",
|
||||
style: STextStyles.desktopTextSmall(context).copyWith(
|
||||
color: Colors.blue[800],
|
||||
trailing: RawMaterialButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return DesktopDialog(
|
||||
maxHeight: double.infinity,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 32),
|
||||
child: Text(
|
||||
"About MonKeys",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"A MonKey is a visual representation of your Banano address.",
|
||||
style:
|
||||
STextStyles.desktopTextMedium(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(
|
||||
32,
|
||||
),
|
||||
child: PrimaryButton(
|
||||
width: 272.5,
|
||||
label: "OK",
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 19,
|
||||
horizontal: 32,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.circleQuestion,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.customTextButtonEnabledText,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Text(
|
||||
"What is MonKey?",
|
||||
style:
|
||||
STextStyles.desktopMenuItemSelected(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.customTextButtonEnabledText,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -318,22 +276,24 @@ class _MonkeyViewState extends ConsumerState<MonkeyView> {
|
|||
AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AppBarIconButton(
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circleQuestion,
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return const StackOkDialog(
|
||||
title: "About MonKeys",
|
||||
message:
|
||||
"A MonKey is a visual representation of your Banano address.",
|
||||
);
|
||||
});
|
||||
}),
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circleQuestion,
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return const StackOkDialog(
|
||||
title: "About MonKeys",
|
||||
message:
|
||||
"A MonKey is a visual representation of your Banano address.",
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -376,26 +336,95 @@ class _MonkeyViewState extends ConsumerState<MonkeyView> {
|
|||
SecondaryButton(
|
||||
label: "Save as SVG",
|
||||
onPressed: () async {
|
||||
bool didError = false;
|
||||
await showLoading(
|
||||
whileFuture:
|
||||
downloadMonkey(receivingAddress, false),
|
||||
whileFuture: Future.wait([
|
||||
_saveMonKeyToFile(
|
||||
bytes: Uint8List.fromList(
|
||||
(manager.wallet as BananoWallet)
|
||||
.getMonkeyImageBytes()!),
|
||||
),
|
||||
Future<void>.delayed(
|
||||
const Duration(seconds: 2),
|
||||
),
|
||||
]),
|
||||
context: context,
|
||||
isDesktop: Util.isDesktop,
|
||||
message: "Saving MonKey svg",
|
||||
onException: (e) {
|
||||
didError = true;
|
||||
String msg = e.toString();
|
||||
while (msg.isNotEmpty &&
|
||||
msg.startsWith("Exception:")) {
|
||||
msg = msg.substring(10).trim();
|
||||
}
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: msg,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (!didError && mounted) {
|
||||
await showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
message: "SVG MonKey image saved",
|
||||
context: context,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SecondaryButton(
|
||||
label: "Download as PNG",
|
||||
onPressed: () async {
|
||||
bool didError = false;
|
||||
await showLoading(
|
||||
whileFuture:
|
||||
downloadMonkey(receivingAddress, true),
|
||||
whileFuture: Future.wait([
|
||||
manager.currentReceivingAddress.then(
|
||||
(address) async => await ref
|
||||
.read(pMonKeyService)
|
||||
.fetchMonKey(
|
||||
address: address,
|
||||
png: true,
|
||||
)
|
||||
.then(
|
||||
(monKeyBytes) async =>
|
||||
await _saveMonKeyToFile(
|
||||
bytes: monKeyBytes,
|
||||
isPNG: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Future<void>.delayed(
|
||||
const Duration(seconds: 2)),
|
||||
]),
|
||||
context: context,
|
||||
isDesktop: Util.isDesktop,
|
||||
message: "Downloading MonKey png",
|
||||
onException: (e) {
|
||||
didError = true;
|
||||
String msg = e.toString();
|
||||
while (msg.isNotEmpty &&
|
||||
msg.startsWith("Exception:")) {
|
||||
msg = msg.substring(10).trim();
|
||||
}
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: msg,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (!didError && mounted) {
|
||||
await showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
message: "PNG MonKey image saved",
|
||||
context: context,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
|
@ -453,17 +482,37 @@ class _MonkeyViewState extends ConsumerState<MonkeyView> {
|
|||
child: PrimaryButton(
|
||||
label: "Fetch MonKey",
|
||||
onPressed: () async {
|
||||
final future = Future.wait([
|
||||
getMonkeyImage(receivingAddress),
|
||||
Future<void>.delayed(const Duration(seconds: 2)),
|
||||
]);
|
||||
|
||||
await showLoading(
|
||||
whileFuture: future,
|
||||
whileFuture: Future.wait([
|
||||
manager.currentReceivingAddress.then(
|
||||
(address) async => await ref
|
||||
.read(pMonKeyService)
|
||||
.fetchMonKey(address: address)
|
||||
.then(
|
||||
(monKeyBytes) async =>
|
||||
await _updateWalletMonKey(
|
||||
monKeyBytes,
|
||||
),
|
||||
),
|
||||
),
|
||||
Future<void>.delayed(const Duration(seconds: 2)),
|
||||
]),
|
||||
context: context,
|
||||
isDesktop: Util.isDesktop,
|
||||
message: "Fetching MonKey",
|
||||
subMessage: "We are fetching your MonKey",
|
||||
onException: (e) {
|
||||
String msg = e.toString();
|
||||
while (msg.isNotEmpty &&
|
||||
msg.startsWith("Exception:")) {
|
||||
msg = msg.substring(10).trim();
|
||||
}
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: msg,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
imageBytes = (manager.wallet as BananoWallet)
|
||||
|
@ -472,17 +521,6 @@ class _MonkeyViewState extends ConsumerState<MonkeyView> {
|
|||
if (imageBytes != null) {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
// if (isDesktop) {
|
||||
// Navigator.of(context).popUntil(
|
||||
// ModalRoute.withName(
|
||||
// DesktopWalletView.routeName),
|
||||
// );
|
||||
// } else {
|
||||
// Navigator.of(context).popUntil(
|
||||
// ModalRoute.withName(WalletView.routeName),
|
||||
// );
|
||||
// }
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,21 +1,32 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart';
|
||||
import 'package:stackwallet/models/isar/ordinal.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/ordinals/widgets/dialogs.dart';
|
||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/widgets/background.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/rounded_white_container.dart';
|
||||
|
||||
class OrdinalDetailsView extends StatefulWidget {
|
||||
class OrdinalDetailsView extends ConsumerStatefulWidget {
|
||||
const OrdinalDetailsView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
|
@ -28,14 +39,25 @@ class OrdinalDetailsView extends StatefulWidget {
|
|||
static const routeName = "/ordinalDetailsView";
|
||||
|
||||
@override
|
||||
State<OrdinalDetailsView> createState() => _OrdinalDetailsViewState();
|
||||
ConsumerState<OrdinalDetailsView> createState() => _OrdinalDetailsViewState();
|
||||
}
|
||||
|
||||
class _OrdinalDetailsViewState extends State<OrdinalDetailsView> {
|
||||
class _OrdinalDetailsViewState extends ConsumerState<OrdinalDetailsView> {
|
||||
static const _spacing = 12.0;
|
||||
|
||||
late final UTXO? utxo;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
utxo = widget.ordinal.getUTXO(ref.read(mainDBProvider));
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final coin = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId).coin));
|
||||
|
||||
return Background(
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
|
@ -73,26 +95,33 @@ class _OrdinalDetailsViewState extends State<OrdinalDetailsView> {
|
|||
height: _spacing,
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "ID",
|
||||
title: "Inscription ID",
|
||||
data: widget.ordinal.inscriptionId,
|
||||
),
|
||||
// const SizedBox(
|
||||
// height: _spacing,
|
||||
// ),
|
||||
// // todo: add utxo status
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
// todo: add utxo status
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
const _DetailsItemWCopy(
|
||||
_DetailsItemWCopy(
|
||||
title: "Amount",
|
||||
data: "TODO", // TODO infer from utxo utxoTXID:utxoVOUT
|
||||
data: utxo == null
|
||||
? "ERROR"
|
||||
: ref.watch(pAmountFormatter(coin)).format(
|
||||
Amount(
|
||||
rawValue: BigInt.from(utxo!.value),
|
||||
fractionDigits: coin.decimals,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
const _DetailsItemWCopy(
|
||||
_DetailsItemWCopy(
|
||||
title: "Owner address",
|
||||
data: "TODO", // infer from address associated w utxoTXID
|
||||
data: utxo?.address ?? "ERROR",
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
|
@ -150,11 +179,27 @@ class _DetailsItemWCopy extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
||||
width: 12,
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemIcons,
|
||||
width: 12,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 6,
|
||||
),
|
||||
Text(
|
||||
"Copy",
|
||||
style: STextStyles.infoSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemIcons,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -184,6 +229,36 @@ class _OrdinalImageGroup extends StatelessWidget {
|
|||
|
||||
static const _spacing = 12.0;
|
||||
|
||||
Future<void> _savePngToFile() async {
|
||||
final response = await get(Uri.parse(ordinal.content));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception(
|
||||
"statusCode=${response.statusCode} body=${response.bodyBytes}");
|
||||
}
|
||||
|
||||
final bytes = response.bodyBytes;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
await Permission.storage.request();
|
||||
}
|
||||
|
||||
final dir = Platform.isAndroid
|
||||
? Directory("/storage/emulated/0/Documents")
|
||||
: await getApplicationDocumentsDirectory();
|
||||
|
||||
final docPath = dir.path;
|
||||
final filePath = "$docPath/ordinal_${ordinal.inscriptionNumber}.png";
|
||||
|
||||
File imgFile = File(filePath);
|
||||
|
||||
if (imgFile.existsSync()) {
|
||||
throw Exception("File already exists");
|
||||
}
|
||||
|
||||
await imgFile.writeAsBytes(bytes);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
|
@ -197,15 +272,20 @@ class _OrdinalImageGroup extends StatelessWidget {
|
|||
// const SizedBox(
|
||||
// height: _spacing,
|
||||
// ),
|
||||
AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Image.network(
|
||||
ordinal.content, // Use the preview URL as the image source
|
||||
fit: BoxFit.cover,
|
||||
filterQuality:
|
||||
FilterQuality.none, // Set the filter mode to nearest
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Image.network(
|
||||
ordinal.content, // Use the preview URL as the image source
|
||||
fit: BoxFit.cover,
|
||||
filterQuality:
|
||||
FilterQuality.none, // Set the filter mode to nearest
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -227,8 +307,37 @@ class _OrdinalImageGroup extends StatelessWidget {
|
|||
),
|
||||
buttonHeight: ButtonHeight.l,
|
||||
iconSpacing: 4,
|
||||
onPressed: () {
|
||||
// TODO: save and download image to device
|
||||
onPressed: () async {
|
||||
bool didError = false;
|
||||
await showLoading(
|
||||
whileFuture: Future.wait([
|
||||
_savePngToFile(),
|
||||
Future<void>.delayed(const Duration(seconds: 2)),
|
||||
]),
|
||||
context: context,
|
||||
isDesktop: true,
|
||||
message: "Saving ordinal image",
|
||||
onException: (e) {
|
||||
didError = true;
|
||||
String msg = e.toString();
|
||||
while (msg.isNotEmpty && msg.startsWith("Exception:")) {
|
||||
msg = msg.substring(10).trim();
|
||||
}
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: msg,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (!didError && context.mounted) {
|
||||
await showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
message: "Image saved",
|
||||
context: context,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart';
|
||||
import 'package:stackwallet/models/isar/ordinal.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
|
@ -15,10 +18,12 @@ import 'package:stackwallet/utilities/amount/amount_formatter.dart';
|
|||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class DesktopOrdinalDetailsView extends ConsumerStatefulWidget {
|
||||
|
@ -44,6 +49,36 @@ class _DesktopOrdinalDetailsViewState
|
|||
|
||||
late final UTXO? utxo;
|
||||
|
||||
Future<void> _savePngToFile() async {
|
||||
final response = await get(Uri.parse(widget.ordinal.content));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception(
|
||||
"statusCode=${response.statusCode} body=${response.bodyBytes}");
|
||||
}
|
||||
|
||||
final bytes = response.bodyBytes;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
await Permission.storage.request();
|
||||
}
|
||||
|
||||
final dir = Platform.isAndroid
|
||||
? Directory("/storage/emulated/0/Documents")
|
||||
: await getApplicationDocumentsDirectory();
|
||||
|
||||
final docPath = dir.path;
|
||||
final filePath = "$docPath/ordinal_${widget.ordinal.inscriptionNumber}.png";
|
||||
|
||||
File imgFile = File(filePath);
|
||||
|
||||
if (imgFile.existsSync()) {
|
||||
throw Exception("File already exists");
|
||||
}
|
||||
|
||||
await imgFile.writeAsBytes(bytes);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
utxo = widget.ordinal.getUTXO(ref.read(mainDBProvider));
|
||||
|
@ -137,7 +172,7 @@ class _DesktopOrdinalDetailsViewState
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
SelectableText(
|
||||
"INSC. ${widget.ordinal.inscriptionNumber}",
|
||||
style: STextStyles.w600_20(context),
|
||||
),
|
||||
|
@ -174,74 +209,102 @@ class _DesktopOrdinalDetailsViewState
|
|||
// const SizedBox(
|
||||
// width: 16,
|
||||
// ),
|
||||
// SecondaryButton(
|
||||
// width: 150,
|
||||
// label: "Download",
|
||||
// icon: SvgPicture.asset(
|
||||
// Assets.svg.arrowDown,
|
||||
// width: 13,
|
||||
// height: 18,
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .buttonTextSecondary,
|
||||
// ),
|
||||
// buttonHeight: ButtonHeight.l,
|
||||
// iconSpacing: 8,
|
||||
// onPressed: () {
|
||||
// // TODO: save and download image to device
|
||||
// },
|
||||
// ),
|
||||
SecondaryButton(
|
||||
width: 150,
|
||||
label: "Download",
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.arrowDown,
|
||||
width: 13,
|
||||
height: 18,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
buttonHeight: ButtonHeight.l,
|
||||
iconSpacing: 8,
|
||||
onPressed: () async {
|
||||
bool didError = false;
|
||||
await showLoading(
|
||||
whileFuture: Future.wait([
|
||||
_savePngToFile(),
|
||||
Future<void>.delayed(
|
||||
const Duration(seconds: 2)),
|
||||
]),
|
||||
context: context,
|
||||
isDesktop: true,
|
||||
message: "Saving ordinal image",
|
||||
onException: (e) {
|
||||
didError = true;
|
||||
String msg = e.toString();
|
||||
while (msg.isNotEmpty &&
|
||||
msg.startsWith("Exception:")) {
|
||||
msg = msg.substring(10).trim();
|
||||
}
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: msg,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (!didError && mounted) {
|
||||
await showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
message: "Image saved",
|
||||
context: context,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "Inscription number",
|
||||
data: widget.ordinal.inscriptionNumber.toString(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "Inscription ID",
|
||||
data: widget.ordinal.inscriptionId,
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
// todo: add utxo status
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "Amount",
|
||||
data: utxo == null
|
||||
? "ERROR"
|
||||
: ref.watch(pAmountFormatter(coin)).format(
|
||||
Amount(
|
||||
rawValue: BigInt.from(utxo!.value),
|
||||
fractionDigits: coin.decimals,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "Owner address",
|
||||
data: utxo?.address ?? "ERROR",
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "Transaction ID",
|
||||
data: widget.ordinal.utxoTXID,
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_DetailsItemWCopy(
|
||||
title: "Inscription number",
|
||||
data: widget.ordinal.inscriptionNumber.toString(),
|
||||
),
|
||||
const _Divider(),
|
||||
_DetailsItemWCopy(
|
||||
title: "Inscription ID",
|
||||
data: widget.ordinal.inscriptionId,
|
||||
),
|
||||
// const SizedBox(
|
||||
// height: _spacing,
|
||||
// ),
|
||||
// // todo: add utxo status
|
||||
const _Divider(),
|
||||
_DetailsItemWCopy(
|
||||
title: "Amount",
|
||||
data: utxo == null
|
||||
? "ERROR"
|
||||
: ref.watch(pAmountFormatter(coin)).format(
|
||||
Amount(
|
||||
rawValue: BigInt.from(utxo!.value),
|
||||
fractionDigits: coin.decimals,
|
||||
),
|
||||
),
|
||||
),
|
||||
const _Divider(),
|
||||
_DetailsItemWCopy(
|
||||
title: "Owner address",
|
||||
data: utxo?.address ?? "ERROR",
|
||||
),
|
||||
const _Divider(),
|
||||
_DetailsItemWCopy(
|
||||
title: "Transaction ID",
|
||||
data: widget.ordinal.utxoTXID,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -255,6 +318,23 @@ class _DesktopOrdinalDetailsViewState
|
|||
}
|
||||
}
|
||||
|
||||
class _Divider extends StatelessWidget {
|
||||
const _Divider({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
),
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).extension<StackColors>()!.backgroundAppBar,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DetailsItemWCopy extends StatelessWidget {
|
||||
const _DetailsItemWCopy({
|
||||
Key? key,
|
||||
|
@ -267,48 +347,29 @@ class _DetailsItemWCopy extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedWhiteContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await Clipboard.setData(ClipboardData(text: data));
|
||||
if (context.mounted) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
||||
width: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
SelectableText(
|
||||
data,
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
IconCopyButton(
|
||||
data: data,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
SelectableText(
|
||||
data,
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,17 +16,12 @@ import 'package:stackwallet/providers/providers.dart';
|
|||
import 'package:stackwallet/services/mixins/ordinals_interface.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||
|
||||
class DesktopOrdinalsView extends ConsumerStatefulWidget {
|
||||
const DesktopOrdinalsView({
|
||||
|
@ -102,6 +97,8 @@ class _DesktopOrdinals extends ConsumerState<DesktopOrdinalsView> {
|
|||
Assets.svg.ordinal,
|
||||
width: 32,
|
||||
height: 32,
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.textSubtitle1,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
|
|
40
lib/services/monkey_service.dart
Normal file
40
lib/services/monkey_service.dart
Normal file
|
@ -0,0 +1,40 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
||||
final pMonKeyService = Provider((ref) => MonKeyService());
|
||||
|
||||
class MonKeyService {
|
||||
static const baseURL = "https://monkey.banano.cc/api/v1/monkey/";
|
||||
|
||||
Future<Uint8List> fetchMonKey({
|
||||
required String address,
|
||||
bool png = false,
|
||||
}) async {
|
||||
try {
|
||||
String url = "https://monkey.banano.cc/api/v1/monkey/$address";
|
||||
|
||||
if (png) {
|
||||
url += '?format=png&size=512&background=false';
|
||||
}
|
||||
|
||||
final response = await http.get(Uri.parse(url));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return response.bodyBytes;
|
||||
} else {
|
||||
throw Exception(
|
||||
"statusCode=${response.statusCode} body=${response.body}",
|
||||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed fetchMonKey($address): $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ final pThemeService = Provider<ThemeService>((ref) {
|
|||
});
|
||||
|
||||
class ThemeService {
|
||||
static const _currentDefaultThemeVersion = 3;
|
||||
static const _currentDefaultThemeVersion = 4;
|
||||
ThemeService._();
|
||||
static ThemeService? _instance;
|
||||
static ThemeService get instance => _instance ??= ThemeService._();
|
||||
|
|
Loading…
Reference in a new issue