diff --git a/lib/pages_desktop_specific/ordinals/desktop_ordinal_details_view.dart b/lib/pages_desktop_specific/ordinals/desktop_ordinal_details_view.dart new file mode 100644 index 000000000..36c8e33cb --- /dev/null +++ b/lib/pages_desktop_specific/ordinals/desktop_ordinal_details_view.dart @@ -0,0 +1,269 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/models/ordinal.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/ordinals/widgets/dialogs.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.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 DesktopOrdinalDetailsView extends StatefulWidget { + const DesktopOrdinalDetailsView({ + Key? key, + required this.walletId, + required this.ordinal, + }) : super(key: key); + + final String walletId; + final Ordinal ordinal; + + static const routeName = "/desktopOrdinalDetailsView"; + + @override + _DesktopOrdinalDetailsViewState createState() => _DesktopOrdinalDetailsViewState(); +} + +class _DesktopOrdinalDetailsViewState extends State<DesktopOrdinalDetailsView> { + static const _spacing = 12.0; + + @override + Widget build(BuildContext context) { + return Background( + child: SafeArea( + child: Scaffold( + backgroundColor: + Theme.of(context).extension<StackColors>()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension<StackColors>()!.background, + leading: const AppBarBackButton(), + title: Text( + "Ordinal details", + style: STextStyles.navBarTitle(context), + ), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 39, + ), + child: _OrdinalImageGroup( + ordinal: widget.ordinal, + walletId: widget.walletId, + ), + ), + _DetailsItemWCopy( + title: "Inscription number", + data: widget.ordinal.inscriptionNumber.toString(), + ), + const SizedBox( + height: _spacing, + ), + _DetailsItemWCopy( + title: "ID", + data: widget.ordinal.inscriptionId, + ), + const SizedBox( + height: _spacing, + ), + // todo: add utxo status + const SizedBox( + height: _spacing, + ), + const _DetailsItemWCopy( + title: "Amount", + data: "TODO", // TODO infer from utxo utxoTXID:utxoVOUT + ), + const SizedBox( + height: _spacing, + ), + const _DetailsItemWCopy( + title: "Owner address", + data: "TODO", // infer from address associated w utxoTXID + ), + const SizedBox( + height: _spacing, + ), + _DetailsItemWCopy( + title: "Transaction ID", + data: widget.ordinal.utxoTXID, + ), + const SizedBox( + height: _spacing, + ), + ], + ), + ), + ), + ), + ), + ); + } +} + +class _DetailsItemWCopy extends StatelessWidget { + const _DetailsItemWCopy({ + Key? key, + required this.title, + required this.data, + }) : super(key: key); + + final String title; + final String data; + + @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), + ), + ], + ), + ); + } +} + +class _OrdinalImageGroup extends StatelessWidget { + const _OrdinalImageGroup({ + Key? key, + required this.walletId, + required this.ordinal, + }) : super(key: key); + + final String walletId; + final Ordinal ordinal; + + static const _spacing = 12.0; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Text( + // "${ordinal.inscriptionId}", // Use any other property you want + // style: STextStyles.w600_16(context), + // ), + // const SizedBox( + // height: _spacing, + // ), + AspectRatio( + aspectRatio: 1, + child: AspectRatio( + aspectRatio: 1, + child: Container( + color: Colors.red, + 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 + ), + ), + ), + ), + const SizedBox( + height: _spacing, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Download", + icon: SvgPicture.asset( + Assets.svg.arrowDown, + width: 10, + height: 12, + color: Theme.of(context) + .extension<StackColors>()! + .buttonTextSecondary, + ), + buttonHeight: ButtonHeight.l, + iconSpacing: 4, + onPressed: () { + // TODO: save and download image to device + }, + ), + ), + const SizedBox( + width: _spacing, + ), + Expanded( + child: PrimaryButton( + label: "Send", + icon: SvgPicture.asset( + Assets.svg.send, + width: 10, + height: 10, + color: Theme.of(context) + .extension<StackColors>()! + .buttonTextPrimary, + ), + buttonHeight: ButtonHeight.l, + iconSpacing: 4, + onPressed: () async { + final response = await showDialog<String?>( + context: context, + builder: (_) => const SendOrdinalUnfreezeDialog(), + ); + if (response == "unfreeze") { + // TODO: unfreeze and go to send ord screen + } + }, + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/ordinals/subwidgets/desktop_ordinal_card.dart b/lib/pages_desktop_specific/ordinals/subwidgets/desktop_ordinal_card.dart new file mode 100644 index 000000000..9d94a4b0a --- /dev/null +++ b/lib/pages_desktop_specific/ordinals/subwidgets/desktop_ordinal_card.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; + +import 'package:stackwallet/models/ordinal.dart'; +import 'package:stackwallet/pages_desktop_specific/ordinals/desktop_ordinal_details_view.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class DesktopOrdinalCard extends StatelessWidget { + const DesktopOrdinalCard({ + Key? key, + required this.walletId, + required this.ordinal, + }) : super(key: key); + + final String walletId; + final Ordinal ordinal; + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + radiusMultiplier: 2, + onPressed: () { + Navigator.of(context).pushNamed( + DesktopOrdinalDetailsView.routeName, + arguments: (walletId: walletId, ordinal: ordinal), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AspectRatio( + aspectRatio: 1, + child: Container( + color: Colors.red, + 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 + ), + ), + ), + const Spacer(), + Text( + 'INSC. ${ordinal.inscriptionNumber}', // infer from address associated with utxoTXID + style: STextStyles.w500_12(context), + ), + // const Spacer(), + // Text( + // "ID ${ordinal.inscriptionId}", + // style: STextStyles.w500_8(context), + // ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/ordinals/subwidgets/desktop_ordinals_list.dart b/lib/pages_desktop_specific/ordinals/subwidgets/desktop_ordinals_list.dart index f12fe6834..b30e94dd4 100644 --- a/lib/pages_desktop_specific/ordinals/subwidgets/desktop_ordinals_list.dart +++ b/lib/pages_desktop_specific/ordinals/subwidgets/desktop_ordinals_list.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/models/ordinal.dart'; -import 'package:stackwallet/pages/ordinals/widgets/ordinal_card.dart'; +import 'package:stackwallet/pages_desktop_specific/ordinals/subwidgets/desktop_ordinal_card.dart'; class DesktopOrdinalsList extends StatelessWidget { const DesktopOrdinalsList({ @@ -35,7 +35,7 @@ class DesktopOrdinalsList extends StatelessWidget { crossAxisCount: 4, childAspectRatio: 6 / 7, // was 3/4, less data displayed now ), - itemBuilder: (_, i) => OrdinalCard( + itemBuilder: (_, i) => DesktopOrdinalCard( walletId: walletId, ordinal: ordinals[i], ), diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 5a5c1df17..03c7f13bc 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -147,6 +147,7 @@ import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart'; import 'package:stackwallet/pages_desktop_specific/notifications/desktop_notifications_view.dart'; +import 'package:stackwallet/pages_desktop_specific/ordinals/desktop_ordinal_details_view.dart'; import 'package:stackwallet/pages_desktop_specific/ordinals/desktop_ordinals_view.dart'; import 'package:stackwallet/pages_desktop_specific/password/create_password_view.dart'; import 'package:stackwallet/pages_desktop_specific/password/delete_password_warning_view.dart'; @@ -453,6 +454,21 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case DesktopOrdinalDetailsView.routeName: + if (args is ({Ordinal ordinal, String walletId})) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => OrdinalDetailsView( + walletId: args.walletId, + ordinal: args.ordinal, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case OrdinalsFilterView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute,