From 58c97de86c03f60c887a287f086db78ede971cf8 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 18 Jul 2023 10:15:05 -0600 Subject: [PATCH] WIP ordinal ui --- dockerfile.linux | 17 -- lib/models/ordinal.dart | 13 ++ lib/pages/ordinals/ordinal_details_view.dart | 116 +++++++++++ lib/pages/ordinals/ordinals_list.dart | 36 ++++ lib/pages/ordinals/ordinals_view.dart | 197 ++++++++++++++++++ lib/pages/ordinals/widgets/ordinal_card.dart | 55 +++++ lib/pages/wallet_view/wallet_view.dart | 17 ++ lib/route_generator.dart | 32 +++ .../coins/litecoin/litecoin_wallet.dart | 8 +- lib/services/coins/manager.dart | 3 + lib/services/mixins/ordinals_interface.dart | 3 + lib/services/ordinals_api.dart | 9 + lib/utilities/text_styles.dart | 22 ++ 13 files changed, 510 insertions(+), 18 deletions(-) delete mode 100644 dockerfile.linux create mode 100644 lib/models/ordinal.dart create mode 100644 lib/pages/ordinals/ordinal_details_view.dart create mode 100644 lib/pages/ordinals/ordinals_list.dart create mode 100644 lib/pages/ordinals/ordinals_view.dart create mode 100644 lib/pages/ordinals/widgets/ordinal_card.dart create mode 100644 lib/services/mixins/ordinals_interface.dart create mode 100644 lib/services/ordinals_api.dart diff --git a/dockerfile.linux b/dockerfile.linux deleted file mode 100644 index 4a3867008..000000000 --- a/dockerfile.linux +++ /dev/null @@ -1,17 +0,0 @@ -FROM ubuntu:20.04 as base -COPY . /stack_wallet -WORKDIR /stack_wallet/scripts/linux -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y git=1:2.25.1-1ubuntu3.6 make=4.2.1-1.2 curl=7.68.0-1ubuntu2.14 cargo=0.62.0ubuntu0libgit2-0ubuntu0.20.04.1 \ - file=1:5.38-4 ca-certificates=20211016ubuntu0.20.04.1 cmake=3.16.3-1ubuntu1.20.04.1 cmake-data=3.16.3-1ubuntu1.20.04.1 g++=4:9.3.0-1ubuntu2 libgmp-dev=2:6.2.0+dfsg-4ubuntu0.1 libssl-dev=1.1.1f-1ubuntu2.16 \ - libclang-dev=1:10.0-50~exp1 unzip=6.0-25ubuntu1.1 python3=3.8.2-0ubuntu2 pkg-config=0.29.1-0ubuntu4 libglib2.0-dev=2.64.6-1~ubuntu20.04.4 libgcrypt20-dev=1.8.5-5ubuntu1.1 gettext-base=0.19.8.1-10build1 \ - libgirepository1.0-dev=1.64.1-1~ubuntu20.04.1 valac=0.48.6-0ubuntu1 xsltproc=1.1.34-4ubuntu0.20.04.1 docbook-xsl=1.79.1+dfsg-2 python3-pip=20.0.2-5ubuntu1.6 ninja-build=1.10.0-1build1 clang=1:10.0-50~exp1 \ - libgtk-3-dev=3.24.20-0ubuntu1.1 libunbound-dev=1.9.4-2ubuntu1.4 libzmq3-dev=4.3.2-2ubuntu1 libtool=2.4.6-14 autoconf=2.69-11.1 automake=1:1.16.1-4ubuntu6 bison=2:3.5.1+dfsg-1 \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* \ - && pip3 install --upgrade meson==0.64.1 markdown==3.4.1 markupsafe==2.1.1 jinja2==3.1.2 pygments==2.13.0 toml==0.10.2 typogrify==2.0.7 tomli==2.0.1 && cd .. && ./prebuild.sh && cd linux && ./build_all.sh -WORKDIR / -RUN git clone https://github.com/flutter/flutter.git -b 3.3.4 -ENV PATH "$PATH:/flutter/bin" -WORKDIR /stack_wallet -RUN flutter pub get Linux && flutter build linux -ENTRYPOINT ["/bin/bash"] diff --git a/lib/models/ordinal.dart b/lib/models/ordinal.dart new file mode 100644 index 000000000..9c4e15392 --- /dev/null +++ b/lib/models/ordinal.dart @@ -0,0 +1,13 @@ +class Ordinal { + final String name; + final String inscription; + final String rank; + + // TODO: make a proper class instead of this placeholder + + Ordinal({ + required this.name, + required this.inscription, + required this.rank, + }); +} diff --git a/lib/pages/ordinals/ordinal_details_view.dart b/lib/pages/ordinals/ordinal_details_view.dart new file mode 100644 index 000000000..c39679903 --- /dev/null +++ b/lib/pages/ordinals/ordinal_details_view.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/models/ordinal.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'; + +class OrdinalDetailsView extends StatefulWidget { + const OrdinalDetailsView({ + super.key, + required this.walletId, + required this.ordinal, + }); + + final String walletId; + final Ordinal ordinal; + + static const routeName = "/ordinalDetailsView"; + + @override + State createState() => _OrdinalDetailsViewState(); +} + +class _OrdinalDetailsViewState extends State { + @override + Widget build(BuildContext context) { + return Background( + child: SafeArea( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: const AppBarBackButton(), + title: Text( + "Ordinal details", + style: STextStyles.navBarTitle(context), + ), + ), + body: Column(), + ), + ), + ); + } +} + +class _OrdinalImageGroup extends StatelessWidget { + const _OrdinalImageGroup({ + super.key, + required this.ordinal, + }); + + final Ordinal ordinal; + + static const _spacing = 12.0; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + ordinal.name, + style: STextStyles.w600_16(context), + ), + const SizedBox( + height: _spacing, + ), + AspectRatio( + aspectRatio: 1, + child: Container( + color: Colors.red, + ), + ), + const SizedBox( + height: _spacing, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Download", + icon: SvgPicture.asset(Assets.svg.arrowDown), + buttonHeight: ButtonHeight.s, + onPressed: () { + // TODO: save and download image to device + }, + ), + ), + const SizedBox( + width: _spacing, + ), + Expanded( + child: PrimaryButton( + label: "Send", + icon: SvgPicture.asset( + Assets.svg.star, + ), + buttonHeight: ButtonHeight.s, + onPressed: () { + // TODO: try send + }, + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/pages/ordinals/ordinals_list.dart b/lib/pages/ordinals/ordinals_list.dart new file mode 100644 index 000000000..d3b3cd83e --- /dev/null +++ b/lib/pages/ordinals/ordinals_list.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/models/ordinal.dart'; +import 'package:stackwallet/pages/ordinals/widgets/ordinal_card.dart'; + +class OrdinalsList extends StatefulWidget { + const OrdinalsList({ + super.key, + required this.ordinals, + }); + + final List ordinals; + + @override + State createState() => _OrdinalsListState(); +} + +class _OrdinalsListState extends State { + static const spacing = 10.0; + + @override + Widget build(BuildContext context) { + return GridView.builder( + shrinkWrap: true, + itemCount: widget.ordinals.length, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisSpacing: spacing, + mainAxisSpacing: spacing, + crossAxisCount: 2, + childAspectRatio: 3 / 4, + ), + itemBuilder: (_, i) => OrdinalCard( + ordinal: widget.ordinals[i], + ), + ); + } +} diff --git a/lib/pages/ordinals/ordinals_view.dart b/lib/pages/ordinals/ordinals_view.dart new file mode 100644 index 000000000..2bfa2e9f3 --- /dev/null +++ b/lib/pages/ordinals/ordinals_view.dart @@ -0,0 +1,197 @@ +/* + * 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 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/ordinal.dart'; +import 'package:stackwallet/pages/ordinals/ordinals_list.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_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 OrdinalsView extends ConsumerStatefulWidget { + const OrdinalsView({ + super.key, + required this.walletId, + }); + + static const routeName = "/ordinalsView"; + + final String walletId; + + @override + ConsumerState createState() => _OrdinalsViewState(); +} + +class _OrdinalsViewState extends ConsumerState { + late final TextEditingController searchController; + late final FocusNode searchFocus; + + String _searchTerm = ""; + + @override + void initState() { + searchController = TextEditingController(); + searchFocus = FocusNode(); + super.initState(); + } + + @override + void dispose() { + searchController.dispose(); + searchFocus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Background( + child: SafeArea( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + automaticallyImplyLeading: false, + leading: const AppBarBackButton(), + title: Text( + "Ordinals", + style: STextStyles.navBarTitle(context), + ), + titleSpacing: 0, + actions: [ + AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + size: 36, + icon: SvgPicture.asset( + Assets.svg.arrowRotate, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + // todo refresh + }, + ), + ), + AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + size: 36, + icon: SvgPicture.asset( + Assets.svg.filter, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + // todo filter view + }, + ), + ), + ], + ), + body: Padding( + padding: const EdgeInsets.only( + left: 16, + right: 16, + top: 8, + ), + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: searchController, + focusNode: searchFocus, + onChanged: (value) { + setState(() { + _searchTerm = value; + }); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + searchFocus, + context, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), + ), + suffixIcon: searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + searchController.text = ""; + _searchTerm = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + const SizedBox( + height: 16, + ), + Expanded( + child: OrdinalsList( + ordinals: [ + for (int i = 0; i < 13; i++) + Ordinal( + name: "dummy name $i", + inscription: "insc$i", + rank: "r$i", + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/ordinals/widgets/ordinal_card.dart b/lib/pages/ordinals/widgets/ordinal_card.dart new file mode 100644 index 000000000..2af610241 --- /dev/null +++ b/lib/pages/ordinals/widgets/ordinal_card.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/models/ordinal.dart'; +import 'package:stackwallet/pages/ordinals/ordinal_details_view.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class OrdinalCard extends StatelessWidget { + const OrdinalCard({ + super.key, + required this.walletId, + required this.ordinal, + }); + + final String walletId; + final Ordinal ordinal; + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + radiusMultiplier: 2, + onPressed: () { + Navigator.of(context).pushNamed( + OrdinalDetailsView.routeName, + arguments: widget.walletId, + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AspectRatio( + aspectRatio: 1, + child: Container( + color: Colors.red, + child: const Center( + child: Text( + "replace red container with image", + ), + ), + ), + ), + const Spacer(), + Text( + ordinal.name, + style: STextStyles.w500_12(context), + ), + const Spacer(), + Text( + "INSC. ${ordinal.inscription} RANK ${ordinal.rank}", + style: STextStyles.w500_8(context), + ), + ], + ), + ); + } +} diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index 3e7fb37fa..278d43d35 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -23,6 +23,7 @@ import 'package:stackwallet/pages/coin_control/coin_control_view.dart'; import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/notification_views/notifications_view.dart'; +import 'package:stackwallet/pages/ordinals/ordinals_view.dart'; import 'package:stackwallet/pages/paynym/paynym_claim_view.dart'; import 'package:stackwallet/pages/paynym/paynym_home_view.dart'; import 'package:stackwallet/pages/receive_view/receive_view.dart'; @@ -1007,6 +1008,22 @@ class _WalletViewState extends ConsumerState { } }, ), + if (ref.watch( + walletsChangeNotifierProvider.select( + (value) => + value.getManager(widget.walletId).hasOrdinalsSupport, + ), + )) + WalletNavigationBarItemData( + label: "Ordinals", + icon: const CoinControlNavIcon(), + onTap: () { + Navigator.of(context).pushNamed( + OrdinalsView.routeName, + arguments: widget.walletId, + ); + }, + ), ], ), ], diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 7ccc378c9..cf82dce08 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -18,6 +18,7 @@ import 'package:stackwallet/models/buy/response_objects/quote.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/models/ordinal.dart'; import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; import 'package:stackwallet/models/send_view_auto_fill_data.dart'; import 'package:stackwallet/pages/add_wallet_views/add_token_view/add_custom_token_view.dart'; @@ -57,6 +58,8 @@ import 'package:stackwallet/pages/home_view/home_view.dart'; 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/ordinals/ordinal_details_view.dart'; +import 'package:stackwallet/pages/ordinals/ordinals_view.dart'; import 'package:stackwallet/pages/paynym/add_new_paynym_follow_view.dart'; import 'package:stackwallet/pages/paynym/paynym_claim_view.dart'; import 'package:stackwallet/pages/paynym/paynym_home_view.dart'; @@ -404,6 +407,35 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case OrdinalsView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => OrdinalsView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case OrdinalDetailsView.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 UtxoDetailsView.routeName: if (args is Tuple2) { return getRoute( diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index b84c6c6a0..2edc32fc6 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -36,6 +36,7 @@ import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_ import 'package:stackwallet/services/event_bus/global_event_bus.dart'; import 'package:stackwallet/services/mixins/coin_control_interface.dart'; import 'package:stackwallet/services/mixins/electrum_x_parsing.dart'; +import 'package:stackwallet/services/mixins/ordinals_interface.dart'; import 'package:stackwallet/services/mixins/wallet_cache.dart'; import 'package:stackwallet/services/mixins/wallet_db.dart'; import 'package:stackwallet/services/mixins/xpubable.dart'; @@ -109,7 +110,12 @@ String constructDerivePath({ } class LitecoinWallet extends CoinServiceAPI - with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface + with + WalletCache, + WalletDB, + ElectrumXParsing, + CoinControlInterface, + OrdinalsInterface implements XPubAble { LitecoinWallet({ required String walletId, diff --git a/lib/services/coins/manager.dart b/lib/services/coins/manager.dart index 4e3dd460e..1f5ae55f3 100644 --- a/lib/services/coins/manager.dart +++ b/lib/services/coins/manager.dart @@ -21,6 +21,7 @@ import 'package:stackwallet/services/event_bus/events/global/node_connection_sta import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; import 'package:stackwallet/services/mixins/coin_control_interface.dart'; +import 'package:stackwallet/services/mixins/ordinals_interface.dart'; import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; import 'package:stackwallet/services/mixins/xpubable.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; @@ -244,6 +245,8 @@ class Manager with ChangeNotifier { bool get hasCoinControlSupport => _currentWallet is CoinControlInterface; + bool get hasOrdinalsSupport => _currentWallet is OrdinalsInterface; + bool get hasTokenSupport => _currentWallet.coin == Coin.ethereum; bool get hasWhirlpoolSupport => false; diff --git a/lib/services/mixins/ordinals_interface.dart b/lib/services/mixins/ordinals_interface.dart new file mode 100644 index 000000000..06f2377e9 --- /dev/null +++ b/lib/services/mixins/ordinals_interface.dart @@ -0,0 +1,3 @@ +mixin OrdinalsInterface { + // TODO wallet ordinals functionality +} diff --git a/lib/services/ordinals_api.dart b/lib/services/ordinals_api.dart new file mode 100644 index 000000000..97cc45834 --- /dev/null +++ b/lib/services/ordinals_api.dart @@ -0,0 +1,9 @@ +import 'package:stackwallet/models/ordinal.dart'; + +class OrdinalsAPI { + // dummy class with sample functions to be changed / filled out + + static Future> fetch() async { + return []; + } +} diff --git a/lib/utilities/text_styles.dart b/lib/utilities/text_styles.dart index 4a1ced812..fab828bef 100644 --- a/lib/utilities/text_styles.dart +++ b/lib/utilities/text_styles.dart @@ -306,6 +306,17 @@ class STextStyles { } } + static TextStyle w600_16(BuildContext context) { + switch (_theme(context).themeId) { + default: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w600, + fontSize: 16, + ); + } + } + static TextStyle w500_14(BuildContext context) { switch (_theme(context).themeId) { default: @@ -339,6 +350,17 @@ class STextStyles { } } + static TextStyle w500_8(BuildContext context) { + switch (_theme(context).themeId) { + default: + return GoogleFonts.inter( + color: _theme(context).textSubtitle1, + fontWeight: FontWeight.w500, + fontSize: 8, + ); + } + } + static TextStyle w600_20(BuildContext context) { switch (_theme(context).themeId) { default: