From 7f6be0e4fc3a87db7c3256f69a087a08036fa542 Mon Sep 17 00:00:00 2001 From: d-reader-luka <125265091+d-reader-luka@users.noreply.github.com> Date: Fri, 26 Apr 2024 11:42:50 +0200 Subject: [PATCH] chore: mint improvements * create settings repository to fetch splTokens on app startup * mint info display changes * use real data * fix wallet nto eligible after signing a message * fix library owned and favorite sorting * release --- lib/constants/constants.dart | 1 + .../domain/models/candy_machine_group.dart | 4 +- .../providers/candy_machine_providers.dart | 33 +- .../data/datasource/comic_remote_source.dart | 6 +- .../providers/comic_issue_providers.dart | 10 - .../controller/comic_issue_controller.dart | 3 +- .../screens/comic_issue_details.dart | 38 ++- .../widgets/tabs/about/about.dart | 34 +-- .../tabs/about/expandable_container.dart | 228 -------------- .../tabs/about/group_with_currency.dart | 83 ++++++ .../tabs/about/mint_info_container.dart | 281 ++++++++++++++++++ .../library/presentation/utils/utils.dart | 22 +- .../widgets/tabs/favorites/favorites.dart | 5 +- .../widgets/tabs/owned/owned.dart | 2 +- .../datasource/settings_remote_source.dart | 41 +++ .../settings_repository_impl.dart | 16 + .../settings/domain/models/spl_token.dart | 31 ++ .../domain/providers/settings_provider.dart | 26 ++ .../repositories/settings_repository.dart | 7 + .../providers/change_network.dart | 2 + .../presentation/providers/spl_tokens.dart | 10 + .../providers/solana/solana_notifier.dart | 12 +- .../solana/solana_transaction_notifier.dart | 11 +- lib/shared/utils/formatter.dart | 16 +- lib/shared/utils/utils.dart | 21 +- .../widgets/scaffolds/d_reader_scaffold.dart | 2 + .../widgets/unsorted/mint_price_widget.dart | 38 +++ publishing/.asset-manifest.json | 4 +- publishing/config.yaml | 4 +- pubspec.yaml | 2 +- 30 files changed, 669 insertions(+), 324 deletions(-) delete mode 100644 lib/features/comic_issue/presentation/widgets/tabs/about/expandable_container.dart create mode 100644 lib/features/comic_issue/presentation/widgets/tabs/about/group_with_currency.dart create mode 100644 lib/features/comic_issue/presentation/widgets/tabs/about/mint_info_container.dart create mode 100644 lib/features/settings/data/datasource/settings_remote_source.dart create mode 100644 lib/features/settings/data/repositories/settings_repository_impl.dart create mode 100644 lib/features/settings/domain/models/spl_token.dart create mode 100644 lib/features/settings/domain/providers/settings_provider.dart create mode 100644 lib/features/settings/domain/repositories/settings_repository.dart create mode 100644 lib/features/settings/presentation/providers/spl_tokens.dart create mode 100644 lib/shared/widgets/unsorted/mint_price_widget.dart diff --git a/lib/constants/constants.dart b/lib/constants/constants.dart index 99398082..bdadc1dd 100644 --- a/lib/constants/constants.dart +++ b/lib/constants/constants.dart @@ -15,3 +15,4 @@ const String powerSaveModeText = 'Cannot use wallet in the power save mode.'; const String successResult = 'OK'; const String usernameCriteriaText = 'Must be 3 to 20 characters long. Numbers, dashes and underscores are allowed.'; +const int splTokenHighestPriority = 1; diff --git a/lib/features/candy_machine/domain/models/candy_machine_group.dart b/lib/features/candy_machine/domain/models/candy_machine_group.dart index 63944e52..754fb675 100644 --- a/lib/features/candy_machine/domain/models/candy_machine_group.dart +++ b/lib/features/candy_machine/domain/models/candy_machine_group.dart @@ -3,7 +3,7 @@ import 'package:d_reader_flutter/features/wallet/domain/models/wallet_group.dart class CandyMachineGroupModel { final int itemsMinted, mintLimit, mintPrice, supply; - final String label, displayLabel; + final String label, displayLabel, splTokenAddress; final DateTime? startDate, endDate; final bool isActive; final WalletGroupModel? wallet; @@ -19,6 +19,7 @@ class CandyMachineGroupModel { required this.startDate, required this.endDate, required this.isActive, + required this.splTokenAddress, this.wallet, this.user, }); @@ -31,6 +32,7 @@ class CandyMachineGroupModel { supply: json['supply'] ?? 0, label: json['label'], displayLabel: json['displayLabel'], + splTokenAddress: json['splTokenAddress'], startDate: json['startDate'] != null ? DateTime.parse( json['startDate'], diff --git a/lib/features/candy_machine/presentations/providers/candy_machine_providers.dart b/lib/features/candy_machine/presentations/providers/candy_machine_providers.dart index 183ce024..064eff0d 100644 --- a/lib/features/candy_machine/presentations/providers/candy_machine_providers.dart +++ b/lib/features/candy_machine/presentations/providers/candy_machine_providers.dart @@ -1,6 +1,9 @@ +import 'package:d_reader_flutter/constants/constants.dart'; import 'package:d_reader_flutter/features/candy_machine/domain/models/candy_machine.dart'; +import 'package:d_reader_flutter/features/candy_machine/domain/models/candy_machine_group.dart'; import 'package:d_reader_flutter/features/candy_machine/domain/providers/candy_machine_provider.dart'; -import 'package:d_reader_flutter/features/comic_issue/presentation/providers/comic_issue_providers.dart'; +import 'package:d_reader_flutter/features/settings/domain/models/spl_token.dart'; +import 'package:d_reader_flutter/features/settings/presentation/providers/spl_tokens.dart'; import 'package:d_reader_flutter/shared/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -20,16 +23,34 @@ Future candyMachine( final response = await ref .read(candyMachineRepositoryProvider) .getCandyMachine(query: query); - + final splTokens = await ref.read(splTokensProvider.future); return response.fold((exception) => null, (result) { if (result != null) { ref.read(candyMachineStateProvider.notifier).update((state) => result); - final activeGroup = getActiveGroup(result.groups); - ref.read(activeCandyMachineGroup.notifier).update((state) => activeGroup); + final priorSplToken = getSplTokenWithHighestPriority(splTokens); + final selectedGroup = getSelectedGroup( + groups: result.groups, + selectedSplTokenAddress: priorSplToken.address, + ) ?? + result.groups.first; ref - .read(expandedCandyMachineGroup.notifier) - .update((state) => activeGroup?.label ?? ''); + .read(selectedCandyMachineGroup.notifier) + .update((state) => selectedGroup); } return result; }); } + +final selectedCandyMachineGroup = + StateProvider.autoDispose((ref) { + return null; +}); + +final activeSplToken = StateProvider.autoDispose( + (ref) { + return ref + .watch(splTokensProvider) + .value + ?.firstWhere((element) => element.priority == splTokenHighestPriority); + }, +); diff --git a/lib/features/comic/data/datasource/comic_remote_source.dart b/lib/features/comic/data/datasource/comic_remote_source.dart index 9cfb44cf..212217b7 100644 --- a/lib/features/comic/data/datasource/comic_remote_source.dart +++ b/lib/features/comic/data/datasource/comic_remote_source.dart @@ -92,7 +92,11 @@ class ComicRemoteDataSource implements ComicDataSource { item, ), ), - ), + )..sort( + (a, b) { + return a.title.toLowerCase().compareTo(b.title.toLowerCase()); + }, + ), ); }); } catch (exception) { diff --git a/lib/features/comic_issue/presentation/providers/comic_issue_providers.dart b/lib/features/comic_issue/presentation/providers/comic_issue_providers.dart index acd1f9e5..ba34f8c5 100644 --- a/lib/features/comic_issue/presentation/providers/comic_issue_providers.dart +++ b/lib/features/comic_issue/presentation/providers/comic_issue_providers.dart @@ -1,7 +1,6 @@ import 'dart:async' show Timer; import 'package:d_reader_flutter/constants/constants.dart'; -import 'package:d_reader_flutter/features/candy_machine/domain/models/candy_machine_group.dart'; import 'package:d_reader_flutter/features/comic_issue/domain/models/comic_issue.dart'; import 'package:d_reader_flutter/features/comic_issue/domain/models/owned_issue.dart'; import 'package:d_reader_flutter/features/comic_issue/domain/providers/comic_issue_provider.dart'; @@ -83,12 +82,3 @@ final lastSelectedTabIndex = StateProvider((ref) { final int? lastIndex = LocalStore.instance.get(issueLastSelectedTabKey); return lastIndex ?? 0; }); - -final expandedCandyMachineGroup = StateProvider((ref) { - return 'none'; -}); - -final activeCandyMachineGroup = - StateProvider.autoDispose((ref) { - return null; -}); diff --git a/lib/features/comic_issue/presentation/providers/controller/comic_issue_controller.dart b/lib/features/comic_issue/presentation/providers/controller/comic_issue_controller.dart index 7a7551cd..7ec3e273 100644 --- a/lib/features/comic_issue/presentation/providers/controller/comic_issue_controller.dart +++ b/lib/features/comic_issue/presentation/providers/controller/comic_issue_controller.dart @@ -8,7 +8,6 @@ import 'package:d_reader_flutter/features/nft/presentation/providers/nft_provide import 'package:d_reader_flutter/shared/domain/providers/environment/environment_notifier.dart'; import 'package:d_reader_flutter/shared/domain/providers/solana/solana_transaction_notifier.dart'; import 'package:d_reader_flutter/shared/presentations/providers/global/global_notifier.dart'; -import 'package:d_reader_flutter/shared/utils/utils.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'comic_issue_controller.g.dart'; @@ -45,7 +44,7 @@ class ComicIssueController extends _$ComicIssueController { return displaySnackbar( text: 'Failed to find candy machine', isError: false); } - final activeGroup = getActiveGroup(candyMachineState.groups); + final activeGroup = ref.read(selectedCandyMachineGroup); if (activeGroup == null) { return displaySnackbar( text: 'There is no active mint', diff --git a/lib/features/comic_issue/presentation/screens/comic_issue_details.dart b/lib/features/comic_issue/presentation/screens/comic_issue_details.dart index 00d5faa0..d18bec44 100644 --- a/lib/features/comic_issue/presentation/screens/comic_issue_details.dart +++ b/lib/features/comic_issue/presentation/screens/comic_issue_details.dart @@ -3,6 +3,7 @@ import 'package:d_reader_flutter/config/config.dart'; import 'package:d_reader_flutter/constants/constants.dart'; import 'package:d_reader_flutter/constants/routes.dart'; import 'package:d_reader_flutter/features/auction_house/presentation/providers/auction_house_providers.dart'; +import 'package:d_reader_flutter/features/candy_machine/presentations/providers/candy_machine_providers.dart'; import 'package:d_reader_flutter/features/comic_issue/domain/models/comic_issue.dart'; import 'package:d_reader_flutter/features/comic_issue/presentation/providers/comic_issue_providers.dart'; import 'package:d_reader_flutter/features/comic_issue/presentation/providers/controller/comic_issue_controller.dart'; @@ -23,6 +24,7 @@ import 'package:d_reader_flutter/shared/widgets/image_widgets/cached_image_bg_pl import 'package:d_reader_flutter/shared/widgets/unsorted/mature_audience.dart'; import 'package:d_reader_flutter/shared/widgets/icons/favorite_icon_count.dart'; import 'package:d_reader_flutter/shared/widgets/icons/rating_icon.dart'; +import 'package:d_reader_flutter/shared/widgets/unsorted/mint_price_widget.dart'; import 'package:d_reader_flutter/shared/widgets/unsorted/solana_price.dart'; import 'package:d_reader_flutter/features/creator/presentation/widgets/avatar.dart'; import 'package:flutter/material.dart'; @@ -173,10 +175,13 @@ class _ComicIssueDetailsState extends ConsumerState ); }, child: Column( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, children: [ Container( - margin: const EdgeInsets.only(top: 32), + margin: const EdgeInsets.only( + top: 32, + bottom: 16, + ), constraints: const BoxConstraints( maxWidth: 210, maxHeight: 304, @@ -347,7 +352,6 @@ class _ComicIssueDetailsState extends ConsumerState SliverToBoxAdapter( child: Container( padding: const EdgeInsets.only( - bottom: 4, left: 16, right: 16, ), @@ -392,7 +396,7 @@ class _ComicIssueDetailsState extends ConsumerState body: Padding( padding: const EdgeInsets.symmetric( horizontal: 16, - vertical: 4, + vertical: 16, ), child: issue.isSecondarySaleActive ? TabBarView( @@ -513,7 +517,11 @@ class BottomNavigation extends ConsumerWidget { ); }, text: 'Mint', - price: ref.watch(activeCandyMachineGroup)?.mintPrice ?? 0, + price: ref.watch(selectedCandyMachineGroup)?.mintPrice ?? 0, + isMultiGroup: + (ref.watch(candyMachineStateProvider)?.groups.length ?? + 0) > + 1, ), ) : issue.isSecondarySaleActive @@ -561,11 +569,10 @@ class BottomNavigation extends ConsumerWidget { } class TransactionButton extends StatelessWidget { - final bool isLoading; + final bool isListing, isLoading, isMultiGroup; final Function()? onPressed; final String text; final int? price; - final bool isListing; const TransactionButton({ super.key, required this.isLoading, @@ -573,6 +580,7 @@ class TransactionButton extends StatelessWidget { required this.text, this.price, this.isListing = false, + this.isMultiGroup = false, }); @override @@ -605,12 +613,16 @@ class TransactionButton extends StatelessWidget { width: 14, height: 10, ) - : SolanaPrice( - price: price != null && price! > 0 - ? Formatter.formatPriceWithSignificant(price!) - : null, - textColor: Colors.black, - ), + : isMultiGroup + ? const MintPriceWidget( + priceColor: Colors.black, + ) + : SolanaPrice( + price: price != null && price! > 0 + ? Formatter.formatPriceWithSignificant(price!) + : null, + textColor: Colors.black, + ), ], ), ); diff --git a/lib/features/comic_issue/presentation/widgets/tabs/about/about.dart b/lib/features/comic_issue/presentation/widgets/tabs/about/about.dart index e36d7503..559428f5 100644 --- a/lib/features/comic_issue/presentation/widgets/tabs/about/about.dart +++ b/lib/features/comic_issue/presentation/widgets/tabs/about/about.dart @@ -3,7 +3,7 @@ import 'package:d_reader_flutter/features/comic_issue/domain/models/comic_issue. import 'package:d_reader_flutter/shared/domain/providers/environment/environment_notifier.dart'; import 'package:d_reader_flutter/shared/theme/app_colors.dart'; import 'package:d_reader_flutter/features/comic_issue/presentation/widgets/tabs/about/author.dart'; -import 'package:d_reader_flutter/features/comic_issue/presentation/widgets/tabs/about/expandable_container.dart'; +import 'package:d_reader_flutter/features/comic_issue/presentation/widgets/tabs/about/mint_info_container.dart'; import 'package:d_reader_flutter/features/comic_issue/presentation/widgets/tabs/about/rarities.dart'; import 'package:d_reader_flutter/features/discover/genre/presentation/widgets/genre_tags_default.dart'; import 'package:flutter/material.dart'; @@ -41,34 +41,10 @@ class IssueAbout extends ConsumerWidget { snapshot.hasError) { return const SizedBox(); } - - return Column(children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Minting in progress', - style: textTheme.titleMedium, - ), - Text( - 'Total: ${snapshot.data?.itemsMinted}/${snapshot.data?.supply}', - style: textTheme.bodyMedium?.copyWith( - color: ColorPalette.greyscale100, - ), - ), - ], - ), - const SizedBox( - height: 16, - ), - ...snapshot.data?.groups.map((candyMachineGroup) { - return MintInfoContainer( - candyMachineGroup: candyMachineGroup, - totalSupply: snapshot.data?.supply ?? 0, - ); - }).toList() ?? - [], - ]); + return MintInfoContainer( + candyMachineGroups: snapshot.data?.groups ?? [], + totalSupply: snapshot.data?.supply ?? 0, + ); }, ), const SizedBox( diff --git a/lib/features/comic_issue/presentation/widgets/tabs/about/expandable_container.dart b/lib/features/comic_issue/presentation/widgets/tabs/about/expandable_container.dart deleted file mode 100644 index 9a0d2157..00000000 --- a/lib/features/comic_issue/presentation/widgets/tabs/about/expandable_container.dart +++ /dev/null @@ -1,228 +0,0 @@ -import 'package:d_reader_flutter/constants/constants.dart'; -import 'package:d_reader_flutter/features/candy_machine/domain/models/candy_machine_group.dart'; -import 'package:d_reader_flutter/features/candy_machine/presentations/providers/candy_machine_providers.dart'; -import 'package:d_reader_flutter/features/comic_issue/presentation/providers/comic_issue_providers.dart'; -import 'package:d_reader_flutter/shared/theme/app_colors.dart'; -import 'package:d_reader_flutter/shared/utils/formatter.dart'; -import 'package:d_reader_flutter/shared/widgets/unsorted/solana_price.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; - -String mintNumbersText({required int itemsMinted, required int totalSupply}) { - return '${itemsMinted > totalSupply ? totalSupply : itemsMinted}/$totalSupply'; -} - -String myMintNumbersText({required int itemsMinted, int? supply}) { - return 'You minted: $itemsMinted/${supply ?? '∞'}'; -} - -class MintInfoContainer extends ConsumerWidget { - final CandyMachineGroupModel candyMachineGroup; - final int totalSupply; - const MintInfoContainer({ - super.key, - required this.candyMachineGroup, - required this.totalSupply, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final textTheme = Theme.of(context).textTheme; - final isFutureMint = candyMachineGroup.startDate != null && - candyMachineGroup.startDate!.isAfter(DateTime.now()); - final candyMachineState = ref.watch(candyMachineStateProvider); - return GestureDetector( - onTap: () { - ref.read(expandedCandyMachineGroup.notifier).update((state) => - state != candyMachineGroup.label ? candyMachineGroup.label : ''); - }, - child: _DecoratedContainer( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Text( - 'Mint', - style: textTheme.titleMedium, - ), - const SizedBox( - width: 4, - ), - candyMachineGroup.isActive - ? const CircleAvatar( - backgroundColor: ColorPalette.dReaderYellow100, - radius: 6, - ) - : const SizedBox(), - const SizedBox( - width: 4, - ), - Text( - candyMachineGroup.isActive - ? 'Live' - : isFutureMint - ? 'Starts in ${Formatter.formatDateInRelative(candyMachineGroup.startDate)}' - : 'Ended', - style: textTheme.titleMedium?.copyWith( - color: candyMachineGroup.isActive - ? ColorPalette.dReaderYellow100 - : isFutureMint - ? ColorPalette.dReaderYellow300 - : ColorPalette.greyscale200, - ), - ), - ], - ), - SolanaPrice( - price: candyMachineGroup.mintPrice > 0 - ? Formatter.formatPriceWithSignificant( - candyMachineGroup.mintPrice.round(), - ) - : null, - ) - ], - ), - const SizedBox( - height: 24, - ), - if (candyMachineGroup.isActive) ...[ - LinearProgressIndicator( - backgroundColor: ColorPalette.greyscale400, - minHeight: 8, - valueColor: AlwaysStoppedAnimation( - candyMachineGroup.isActive - ? ColorPalette.dReaderYellow100 - : ColorPalette.greyscale200, - ), - value: candyMachineGroup.itemsMinted / candyMachineGroup.supply, - borderRadius: BorderRadius.circular(8), - ), - const SizedBox( - height: 24, - ), - ], - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - myMintNumbersText( - itemsMinted: candyMachineGroup.user?.itemsMinted ?? - candyMachineGroup.wallet?.itemsMinted ?? - 0, - supply: candyMachineGroup.user?.supply ?? - candyMachineGroup.wallet?.supply, - ), - style: textTheme.bodySmall?.copyWith( - color: ColorPalette.greyscale100, - ), - ), - Text( - candyMachineGroup.label == publicGroupLabel - ? mintNumbersText( - itemsMinted: candyMachineState?.itemsMinted ?? - candyMachineGroup.itemsMinted, - totalSupply: candyMachineState?.supply ?? - candyMachineGroup.supply) - : mintNumbersText( - itemsMinted: candyMachineGroup.itemsMinted, - totalSupply: candyMachineGroup.supply, - ), - style: textTheme.bodySmall?.copyWith( - color: ColorPalette.greyscale100, - ), - ), - ], - ), - const SizedBox( - height: 16, - ), - const _ComicVaultContainer(), - ], - ), - ), - ); - } -} - -class _ComicVaultContainer extends ConsumerWidget { - const _ComicVaultContainer(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final textTheme = Theme.of(context).textTheme; - return Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: ColorPalette.greyscale400, - ), - child: GestureDetector( - onTap: () { - ref - .read(expandedCandyMachineGroup.notifier) - .update((state) => state == 'ComicVault' ? '' : 'ComicVault'); - }, - child: Column( - children: [ - Row( - children: [ - SvgPicture.asset( - 'assets/icons/lock.svg', - ), - const SizedBox( - width: 8, - ), - Text( - 'Comic Vault', - style: textTheme.bodySmall?.copyWith( - color: ColorPalette.greyscale100, - ), - ), - ], - ), - // Column( - // children: [ - // const SizedBox( - // height: 8, - // ), - // Text( - // 'Comic Vault stores portion of the supply of each issue to later use in giveaways & other activities where we reward loyal users', - // style: textTheme.bodySmall?.copyWith( - // color: ColorPalette.greyscale100, - // ), - // ), - // ], - // ), - ], - ), - ), - ); - } -} - -class _DecoratedContainer extends StatelessWidget { - final Widget child; - const _DecoratedContainer({ - required this.child, - }); - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - physics: const NeverScrollableScrollPhysics(), - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: ColorPalette.greyscale500, - borderRadius: BorderRadius.circular(8), - ), - child: child, - ), - ); - } -} diff --git a/lib/features/comic_issue/presentation/widgets/tabs/about/group_with_currency.dart b/lib/features/comic_issue/presentation/widgets/tabs/about/group_with_currency.dart new file mode 100644 index 00000000..485dcbb4 --- /dev/null +++ b/lib/features/comic_issue/presentation/widgets/tabs/about/group_with_currency.dart @@ -0,0 +1,83 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:d_reader_flutter/features/candy_machine/domain/models/candy_machine_group.dart'; +import 'package:d_reader_flutter/features/candy_machine/presentations/providers/candy_machine_providers.dart'; +import 'package:d_reader_flutter/features/settings/domain/models/spl_token.dart'; +import 'package:d_reader_flutter/shared/theme/app_colors.dart'; +import 'package:d_reader_flutter/shared/utils/formatter.dart'; +import 'package:d_reader_flutter/shared/utils/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class GroupWithCurrencyRow extends ConsumerWidget { + final SplToken splToken; + final int mintPrice; + final List groups; + const GroupWithCurrencyRow({ + super.key, + required this.splToken, + required this.mintPrice, + required this.groups, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final textTheme = Theme.of(context).textTheme; + final bool isSelected = + ref.watch(selectedCandyMachineGroup)?.splTokenAddress == + splToken.address; + return GestureDetector( + onTap: () { + ref.read(selectedCandyMachineGroup.notifier).update((state) { + return getSelectedGroup( + groups: groups, + selectedSplTokenAddress: splToken.address, + ); + }); + ref.read(activeSplToken.notifier).update((state) => splToken); + }, + child: Container( + padding: const EdgeInsets.all(16), + margin: const EdgeInsets.only(bottom: 8), + decoration: BoxDecoration( + border: Border.all( + color: isSelected + ? ColorPalette.dReaderYellow100 + : ColorPalette.greyscale300), + borderRadius: BorderRadius.circular(8), + color: isSelected + ? const Color.fromARGB(255, 103, 96, 34).withOpacity(.1) + : ColorPalette.appBackgroundColor, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + CachedNetworkImage( + imageUrl: splToken.icon, + width: 16, + height: 16, + ), + const SizedBox( + width: 8, + ), + Text( + splToken.symbol.replaceFirst( + '\$', + '', + ), + style: textTheme.bodySmall, + ), + ], + ), + Text( + Formatter.formatPriceByCurrency( + mintPrice: mintPrice, splToken: splToken), + style: textTheme.bodySmall, + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/comic_issue/presentation/widgets/tabs/about/mint_info_container.dart b/lib/features/comic_issue/presentation/widgets/tabs/about/mint_info_container.dart new file mode 100644 index 00000000..948dfaa8 --- /dev/null +++ b/lib/features/comic_issue/presentation/widgets/tabs/about/mint_info_container.dart @@ -0,0 +1,281 @@ +import 'package:d_reader_flutter/features/candy_machine/domain/models/candy_machine_group.dart'; +import 'package:d_reader_flutter/features/candy_machine/presentations/providers/candy_machine_providers.dart'; +import 'package:d_reader_flutter/features/comic_issue/presentation/widgets/tabs/about/group_with_currency.dart'; +import 'package:d_reader_flutter/features/settings/presentation/providers/spl_tokens.dart'; +import 'package:d_reader_flutter/shared/theme/app_colors.dart'; +import 'package:d_reader_flutter/shared/utils/formatter.dart'; +import 'package:d_reader_flutter/shared/widgets/unsorted/mint_price_widget.dart'; +import 'package:d_reader_flutter/shared/widgets/unsorted/solana_price.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +String mintNumbersText({required int itemsMinted, required int totalSupply}) { + return '${itemsMinted > totalSupply ? totalSupply : itemsMinted}/$totalSupply'; +} + +String myMintNumbersText({required int itemsMinted, int? supply}) { + return 'You minted: $itemsMinted/${supply ?? '∞'}'; +} + +(bool, bool) getMintStatuses(CandyMachineGroupModel candyMachineGroup) { + bool isActive = candyMachineGroup.startDate == null || + candyMachineGroup.startDate!.isBefore(DateTime.now()); + + bool isEnded = candyMachineGroup.endDate != null && + candyMachineGroup.endDate!.isBefore(DateTime.now()); + + return (isActive, isEnded); +} + +class MintInfoContainer extends ConsumerWidget { + final List candyMachineGroups; + final int totalSupply; + const MintInfoContainer({ + super.key, + required this.candyMachineGroups, + required this.totalSupply, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final textTheme = Theme.of(context).textTheme; + final candyMachineGroup = ref.watch(selectedCandyMachineGroup); + if (candyMachineGroup == null) { + return const SizedBox(); + } + final (isMintActive, isMintEnded) = getMintStatuses(candyMachineGroup); + + final candyMachineState = ref.watch(candyMachineStateProvider); + final splTokens = ref.watch(splTokensProvider); + final displayDropdown = candyMachineGroups.length > 1 && + splTokens.value != null && + splTokens.value!.isNotEmpty; + return _DecoratedContainer( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + displayDropdown + ? Theme( + data: Theme.of(context) + .copyWith(dividerColor: Colors.transparent), + child: ExpansionTile( + tilePadding: EdgeInsets.zero, + iconColor: Colors.white, + collapsedIconColor: Colors.white, + childrenPadding: EdgeInsets.zero, + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + 'Mint', + style: textTheme.titleMedium, + ), + const SizedBox( + width: 4, + ), + isMintActive + ? const CircleAvatar( + backgroundColor: + ColorPalette.dReaderYellow100, + radius: 6, + ) + : const SizedBox(), + const SizedBox( + width: 4, + ), + Text( + isMintActive + ? 'Live' + : !isMintEnded + ? 'Starts in ${Formatter.formatDateInRelative(candyMachineGroup.startDate)}' + : 'Ended', + style: textTheme.titleMedium?.copyWith( + color: isMintActive + ? ColorPalette.dReaderYellow100 + : !isMintEnded + ? ColorPalette.dReaderYellow300 + : ColorPalette.greyscale200, + ), + ), + ], + ), + const MintPriceWidget(), + ], + ), + children: candyMachineGroups.map((group) { + final splToken = splTokens.value?.firstWhere((element) => + element.address == group.splTokenAddress); + if (splToken == null) { + return const SizedBox(); + } + return GroupWithCurrencyRow( + splToken: splToken, + mintPrice: group.mintPrice, + groups: candyMachineGroups, + ); + }).toList(), + ), + ) + : Padding( + padding: const EdgeInsets.only(top: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + 'Mint', + style: textTheme.titleMedium, + ), + const SizedBox( + width: 4, + ), + isMintActive + ? const CircleAvatar( + backgroundColor: + ColorPalette.dReaderYellow100, + radius: 6, + ) + : const SizedBox(), + const SizedBox( + width: 4, + ), + Text( + isMintActive + ? 'Live' + : !isMintEnded + ? 'Starts in ${Formatter.formatDateInRelative(candyMachineGroup.startDate)}' + : 'Ended', + style: textTheme.titleMedium?.copyWith( + color: isMintActive + ? ColorPalette.dReaderYellow100 + : !isMintEnded + ? ColorPalette.dReaderYellow300 + : ColorPalette.greyscale200, + ), + ), + ], + ), + SolanaPrice( + price: candyMachineGroup.mintPrice > 0 + ? Formatter.formatPriceWithSignificant( + candyMachineGroup.mintPrice.round(), + ) + : null, + ) + ], + ), + ), + SizedBox( + height: displayDropdown ? 12 : 24, + ), + if (isMintActive) ...[ + LinearProgressIndicator( + backgroundColor: ColorPalette.greyscale400, + minHeight: 8, + valueColor: const AlwaysStoppedAnimation( + ColorPalette.dReaderYellow100), + value: (candyMachineState?.itemsMinted ?? 0) / + (candyMachineState?.supply ?? 0), + borderRadius: BorderRadius.circular(8), + ), + const SizedBox( + height: 24, + ), + ], + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + myMintNumbersText( + itemsMinted: candyMachineGroup.user?.itemsMinted ?? + candyMachineGroup.wallet?.itemsMinted ?? + 0, + supply: candyMachineGroup.user?.supply ?? + candyMachineGroup.wallet?.supply, + ), + style: textTheme.bodySmall?.copyWith( + color: ColorPalette.greyscale100, + ), + ), + Text( + mintNumbersText( + itemsMinted: candyMachineState?.itemsMinted ?? 0, + totalSupply: candyMachineState?.supply ?? 0, + ), + style: textTheme.bodySmall?.copyWith( + color: ColorPalette.greyscale100, + ), + ), + ], + ), + const SizedBox( + height: 16, + ), + const _ComicVaultContainer(), + ], + ), + ); + } +} + +class _ComicVaultContainer extends ConsumerWidget { + const _ComicVaultContainer(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final textTheme = Theme.of(context).textTheme; + return Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: ColorPalette.greyscale400, + ), + child: Column( + children: [ + Row( + children: [ + SvgPicture.asset( + 'assets/icons/lock.svg', + ), + const SizedBox( + width: 8, + ), + Text( + 'Comic Vault', + style: textTheme.bodySmall?.copyWith( + color: ColorPalette.greyscale100, + ), + ), + ], + ), + ], + ), + ); + } +} + +class _DecoratedContainer extends StatelessWidget { + final Widget child; + const _DecoratedContainer({ + required this.child, + }); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + child: Container( + padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16), + decoration: BoxDecoration( + color: ColorPalette.greyscale500, + borderRadius: BorderRadius.circular(8), + ), + child: child, + ), + ); + } +} diff --git a/lib/features/library/presentation/utils/utils.dart b/lib/features/library/presentation/utils/utils.dart index b95db5f6..d899e958 100644 --- a/lib/features/library/presentation/utils/utils.dart +++ b/lib/features/library/presentation/utils/utils.dart @@ -1,19 +1,15 @@ import 'package:d_reader_flutter/features/comic/domain/models/comic_model.dart'; Map sortAndGetLetterOccurences(List comics) { - comics.sort( - (a, b) { - return a.title.compareTo(b.title); - }, - ); - return comics.fold( - {}, - (previousValue, element) => { - ...previousValue, - element.title[0]: previousValue[element.title[0]] != null - ? previousValue[element.title[0]]! + 1 - : 1 - }); + return comics.fold({}, (previousValue, element) { + final upperCaseChar = element.title[0].toUpperCase(); + return { + ...previousValue, + upperCaseChar: previousValue[upperCaseChar] != null + ? previousValue[upperCaseChar]! + 1 + : 1 + }; + }); } (int, int) getSublistBorders(Map sortedLetters, int currentIndex) { diff --git a/lib/features/library/presentation/widgets/tabs/favorites/favorites.dart b/lib/features/library/presentation/widgets/tabs/favorites/favorites.dart index 6b16a007..e4d8d4f3 100644 --- a/lib/features/library/presentation/widgets/tabs/favorites/favorites.dart +++ b/lib/features/library/presentation/widgets/tabs/favorites/favorites.dart @@ -77,7 +77,10 @@ class FavoriteComicsListBuilder extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - Map sortedLetters = sortAndGetLetterOccurences([...comics]); + Map sortedLetters = + sortAndGetLetterOccurences([...comics]..sort((a, b) { + return a.title.toLowerCase().compareTo(b.title.toLowerCase()); + })); return NotificationListener( onNotification: (notification) { if (notification is ScrollNotification) { diff --git a/lib/features/library/presentation/widgets/tabs/owned/owned.dart b/lib/features/library/presentation/widgets/tabs/owned/owned.dart index cca3c9ba..046dfb11 100644 --- a/lib/features/library/presentation/widgets/tabs/owned/owned.dart +++ b/lib/features/library/presentation/widgets/tabs/owned/owned.dart @@ -85,7 +85,7 @@ class OwnedComicsListBuilder extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - Map sortedLetters = sortAndGetLetterOccurences([...comics]); + Map sortedLetters = sortAndGetLetterOccurences(comics); return NotificationListener( onNotification: (notification) { if (notification is ScrollNotification) { diff --git a/lib/features/settings/data/datasource/settings_remote_source.dart b/lib/features/settings/data/datasource/settings_remote_source.dart new file mode 100644 index 00000000..8dc1f8f1 --- /dev/null +++ b/lib/features/settings/data/datasource/settings_remote_source.dart @@ -0,0 +1,41 @@ +import 'package:d_reader_flutter/features/settings/domain/models/spl_token.dart'; +import 'package:d_reader_flutter/shared/data/remote/network_service.dart'; +import 'package:d_reader_flutter/shared/domain/models/either.dart'; +import 'package:d_reader_flutter/shared/exceptions/exceptions.dart'; + +abstract class SettingsRemoteSource { + Future>> getSplTokens(); +} + +class SettingsRemoteDataSource implements SettingsRemoteSource { + final NetworkService networkService; + + SettingsRemoteDataSource(this.networkService); + + @override + Future>> getSplTokens() async { + try { + final response = await networkService.get('/settings/spl-token/get'); + return response.fold((exception) => Left(exception), (result) { + return Right( + List.from( + result.data.map( + (item) => SplToken.fromJson( + item, + ), + ), + ), + ); + }); + } catch (exception) { + return Left( + AppException( + message: 'Unknown exception occurred', + statusCode: 500, + identifier: + '${exception.toString()}SettingsRemoteDataSource.getSplTokens', + ), + ); + } + } +} diff --git a/lib/features/settings/data/repositories/settings_repository_impl.dart b/lib/features/settings/data/repositories/settings_repository_impl.dart new file mode 100644 index 00000000..4aa55d4b --- /dev/null +++ b/lib/features/settings/data/repositories/settings_repository_impl.dart @@ -0,0 +1,16 @@ +import 'package:d_reader_flutter/features/settings/data/datasource/settings_remote_source.dart'; +import 'package:d_reader_flutter/features/settings/domain/models/spl_token.dart'; +import 'package:d_reader_flutter/features/settings/domain/repositories/settings_repository.dart'; +import 'package:d_reader_flutter/shared/domain/models/either.dart'; +import 'package:d_reader_flutter/shared/exceptions/exceptions.dart'; + +class SettingsRepositoryImpl implements SettingsRepository { + final SettingsRemoteSource dataSource; + + SettingsRepositoryImpl(this.dataSource); + + @override + Future>> getSplTokens() { + return dataSource.getSplTokens(); + } +} diff --git a/lib/features/settings/domain/models/spl_token.dart b/lib/features/settings/domain/models/spl_token.dart new file mode 100644 index 00000000..263405c1 --- /dev/null +++ b/lib/features/settings/domain/models/spl_token.dart @@ -0,0 +1,31 @@ +class SplToken { + final int id, decimals, priority; + final String name, address, symbol, icon; + + SplToken({ + required this.id, + required this.decimals, + required this.priority, + required this.name, + required this.address, + required this.symbol, + required this.icon, + }); + + factory SplToken.fromJson(dynamic json) { + return SplToken( + id: json['id'], + decimals: json['decimals'], + priority: json['priority'], + name: json['name'], + address: json['address'], + symbol: json['symbol'], + icon: json['icon'], + ); + } + + @override + String toString() { + return 'id: $id, decimals: $decimals, priorty: $priority, name: $name, address: $address, symbol: $symbol, icon: $icon'; + } +} diff --git a/lib/features/settings/domain/providers/settings_provider.dart b/lib/features/settings/domain/providers/settings_provider.dart new file mode 100644 index 00000000..3b94b18b --- /dev/null +++ b/lib/features/settings/domain/providers/settings_provider.dart @@ -0,0 +1,26 @@ +import 'package:d_reader_flutter/features/settings/data/datasource/settings_remote_source.dart'; +import 'package:d_reader_flutter/features/settings/data/repositories/settings_repository_impl.dart'; +import 'package:d_reader_flutter/features/settings/domain/repositories/settings_repository.dart'; +import 'package:d_reader_flutter/shared/data/remote/network_service.dart'; +import 'package:d_reader_flutter/shared/domain/providers/dio_network_service_provider.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final settingsDataSourceProvider = + Provider.family( + (ref, networkService) { + return SettingsRemoteDataSource(networkService); + }, +); + +final settingsRepositoryProvider = Provider( + (ref) { + final NetworkService networkService = ref.watch(networkServiceProvider); + + final SettingsRemoteSource dataSource = ref.watch( + settingsDataSourceProvider( + networkService, + ), + ); + return SettingsRepositoryImpl(dataSource); + }, +); diff --git a/lib/features/settings/domain/repositories/settings_repository.dart b/lib/features/settings/domain/repositories/settings_repository.dart new file mode 100644 index 00000000..be42c8f8 --- /dev/null +++ b/lib/features/settings/domain/repositories/settings_repository.dart @@ -0,0 +1,7 @@ +import 'package:d_reader_flutter/features/settings/domain/models/spl_token.dart'; +import 'package:d_reader_flutter/shared/domain/models/either.dart'; +import 'package:d_reader_flutter/shared/exceptions/exceptions.dart'; + +abstract class SettingsRepository { + Future>> getSplTokens(); +} diff --git a/lib/features/settings/presentation/providers/change_network.dart b/lib/features/settings/presentation/providers/change_network.dart index 8cc90fe8..ae8a6128 100644 --- a/lib/features/settings/presentation/providers/change_network.dart +++ b/lib/features/settings/presentation/providers/change_network.dart @@ -6,6 +6,7 @@ import 'package:d_reader_flutter/features/creator/presentation/providers/creator import 'package:d_reader_flutter/features/home/carousel/presentation/providers/carousel_providers.dart'; import 'package:d_reader_flutter/features/library/presentation/providers/favorites/favorites_providers.dart'; import 'package:d_reader_flutter/features/library/presentation/providers/owned/owned_providers.dart'; +import 'package:d_reader_flutter/features/settings/presentation/providers/spl_tokens.dart'; import 'package:d_reader_flutter/shared/data/local/local_store.dart'; import 'package:d_reader_flutter/shared/domain/providers/environment/environment_notifier.dart'; import 'package:d_reader_flutter/shared/domain/providers/environment/state/environment_state.dart'; @@ -27,6 +28,7 @@ class ChangeNetworkController extends _$ChangeNetworkController { ref.invalidate(paginatedComicsProvider); ref.invalidate(ownedComicsProvider); ref.invalidate(favoriteComicsProvider); + ref.invalidate(splTokensProvider); } _doChangeNetworkProcess({ diff --git a/lib/features/settings/presentation/providers/spl_tokens.dart b/lib/features/settings/presentation/providers/spl_tokens.dart new file mode 100644 index 00000000..9295e977 --- /dev/null +++ b/lib/features/settings/presentation/providers/spl_tokens.dart @@ -0,0 +1,10 @@ +import 'package:d_reader_flutter/features/settings/domain/models/spl_token.dart'; +import 'package:d_reader_flutter/features/settings/domain/providers/settings_provider.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final splTokensProvider = FutureProvider>( + (ref) async { + final response = await ref.read(settingsRepositoryProvider).getSplTokens(); + return response.fold((exception) => [], (data) => data); + }, +); diff --git a/lib/shared/domain/providers/solana/solana_notifier.dart b/lib/shared/domain/providers/solana/solana_notifier.dart index 110e8ea7..0b95856e 100644 --- a/lib/shared/domain/providers/solana/solana_notifier.dart +++ b/lib/shared/domain/providers/solana/solana_notifier.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'package:d_reader_flutter/config/config.dart'; import 'package:d_reader_flutter/constants/constants.dart'; import 'package:d_reader_flutter/features/authentication/domain/providers/auth_provider.dart'; +import 'package:d_reader_flutter/features/candy_machine/presentations/providers/candy_machine_providers.dart'; import 'package:d_reader_flutter/features/user/presentation/providers/user_providers.dart'; import 'package:d_reader_flutter/features/wallet/presentation/providers/wallet_providers.dart'; import 'package:d_reader_flutter/shared/domain/models/either.dart'; @@ -314,9 +315,18 @@ class SolanaNotifier extends _$SolanaNotifier { ); return connectWalletResult.fold((failure) { return failure.message; - }, (message) { + }, (message) async { ref.invalidate(registerWalletToSocketEvents); ref.read(registerWalletToSocketEvents); + if (ref.read(selectedCandyMachineGroup) != null) { + final currentCMAddress = ref.read(candyMachineStateProvider)?.address; + ref.invalidate(candyMachineProvider); + await ref.read(candyMachineProvider( + query: + 'candyMachineAddress=$currentCMAddress${'&walletAddress=${signer.toBase58()}'}') + .future); + } + return message; }); }); diff --git a/lib/shared/domain/providers/solana/solana_transaction_notifier.dart b/lib/shared/domain/providers/solana/solana_transaction_notifier.dart index d82fabe0..01069040 100644 --- a/lib/shared/domain/providers/solana/solana_transaction_notifier.dart +++ b/lib/shared/domain/providers/solana/solana_transaction_notifier.dart @@ -7,6 +7,7 @@ import 'package:d_reader_flutter/features/candy_machine/domain/models/candy_mach import 'package:d_reader_flutter/features/candy_machine/presentations/providers/candy_machine_providers.dart'; import 'package:d_reader_flutter/features/nft/domain/models/buy_nft.dart'; import 'package:d_reader_flutter/features/nft/presentation/providers/nft_providers.dart'; +import 'package:d_reader_flutter/features/settings/presentation/providers/spl_tokens.dart'; import 'package:d_reader_flutter/features/transaction/domain/providers/transaction_provider.dart'; import 'package:d_reader_flutter/shared/domain/models/either.dart'; import 'package:d_reader_flutter/shared/domain/providers/environment/environment_notifier.dart'; @@ -40,15 +41,19 @@ class SolanaTransactionNotifier extends _$SolanaTransactionNotifier { required String candyMachineAddress, required String walletAddress, }) async { - final candyMachineState = ref.read(candyMachineStateProvider); - var activeGroup = getActiveGroup(candyMachineState?.groups ?? []); + var activeGroup = ref.read(selectedCandyMachineGroup); if (activeGroup == null || activeGroup.wallet?.supply == null) { final candyMachine = await ref.read(candyMachineProvider( query: 'candyMachineAddress=$candyMachineAddress&walletAddress=$walletAddress', ).future); - activeGroup = getActiveGroup(candyMachine?.groups ?? []); + activeGroup = getSelectedGroup( + groups: candyMachine?.groups ?? [], + selectedSplTokenAddress: getSplTokenWithHighestPriority( + await ref.read(splTokensProvider.future)) + .address, + ); if (activeGroup == null) { return false; } diff --git a/lib/shared/utils/formatter.dart b/lib/shared/utils/formatter.dart index 19670cf6..6994f4e5 100644 --- a/lib/shared/utils/formatter.dart +++ b/lib/shared/utils/formatter.dart @@ -1,3 +1,6 @@ +import 'dart:math'; + +import 'package:d_reader_flutter/features/settings/domain/models/spl_token.dart'; import 'package:intl/intl.dart'; import 'package:solana/solana.dart' show lamportsPerSol; @@ -82,7 +85,16 @@ class Formatter { } } - static String formatCount(int count) { - return NumberFormat.compact().format(count); + static String formatCount(dynamic count) { + return count == 0 ? 'Free' : NumberFormat.compact().format(count); + } + + static String formatPriceByCurrency( + {required int mintPrice, required SplToken? splToken}) { + if (splToken == null) { + return formatPriceWithSignificant(mintPrice); + } + final divideResult = mintPrice / pow(10, splToken.decimals); + return formatCount(divideResult); } } diff --git a/lib/shared/utils/utils.dart b/lib/shared/utils/utils.dart index bdb53a13..a4bb239b 100644 --- a/lib/shared/utils/utils.dart +++ b/lib/shared/utils/utils.dart @@ -1,6 +1,8 @@ import 'package:collection/collection.dart'; import 'package:d_reader_flutter/config/config.dart'; +import 'package:d_reader_flutter/constants/constants.dart'; import 'package:d_reader_flutter/features/candy_machine/domain/models/candy_machine_group.dart'; +import 'package:d_reader_flutter/features/settings/domain/models/spl_token.dart'; import 'package:d_reader_flutter/shared/domain/models/enums.dart'; import 'package:solana/solana.dart'; import 'package:solana_mobile_client/solana_mobile_client.dart'; @@ -52,20 +54,23 @@ Future requestAirdrop(String publicKey) async { } } -CandyMachineGroupModel? getActiveGroup(List groups) { - final currentDate = DateTime.now(); +CandyMachineGroupModel? getSelectedGroup({ + required List groups, + required String selectedSplTokenAddress, +}) { return groups.firstWhereOrNull( (group) { - final isStartDateLessOrEqualThanCurrent = group.startDate != null && - (group.startDate!.isBefore(currentDate) || - group.startDate!.isAtSameMomentAs(currentDate)); - final isEndDateBiggerThanCurrent = - group.endDate != null && currentDate.isBefore(group.endDate!); - return isStartDateLessOrEqualThanCurrent && isEndDateBiggerThanCurrent; + return group.splTokenAddress == selectedSplTokenAddress; }, ); } +SplToken getSplTokenWithHighestPriority(List splTokens) { + return splTokens.firstWhereOrNull( + (element) => element.priority == splTokenHighestPriority) ?? + splTokens.first; +} + String getSortDirection(SortDirection direction) { return direction == SortDirection.asc ? 'asc' : 'desc'; } diff --git a/lib/shared/widgets/scaffolds/d_reader_scaffold.dart b/lib/shared/widgets/scaffolds/d_reader_scaffold.dart index 28011782..7bfc990b 100644 --- a/lib/shared/widgets/scaffolds/d_reader_scaffold.dart +++ b/lib/shared/widgets/scaffolds/d_reader_scaffold.dart @@ -1,4 +1,5 @@ import 'package:d_reader_flutter/config/config.dart'; +import 'package:d_reader_flutter/features/settings/presentation/providers/spl_tokens.dart'; import 'package:d_reader_flutter/shared/domain/providers/environment/environment_notifier.dart'; import 'package:d_reader_flutter/shared/domain/providers/notification/notification_controller.dart'; import 'package:d_reader_flutter/shared/presentations/providers/global/scaffold_provider.dart'; @@ -94,6 +95,7 @@ class _DReaderScaffoldState extends ConsumerState { @override Widget build(BuildContext context) { + ref.watch(splTokensProvider); return GestureDetector( onTap: () => FocusManager.instance.primaryFocus?.unfocus(), child: SafeArea( diff --git a/lib/shared/widgets/unsorted/mint_price_widget.dart b/lib/shared/widgets/unsorted/mint_price_widget.dart new file mode 100644 index 00000000..8135ff45 --- /dev/null +++ b/lib/shared/widgets/unsorted/mint_price_widget.dart @@ -0,0 +1,38 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:d_reader_flutter/features/candy_machine/presentations/providers/candy_machine_providers.dart'; +import 'package:d_reader_flutter/shared/utils/formatter.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class MintPriceWidget extends ConsumerWidget { + final Color priceColor; + const MintPriceWidget({ + super.key, + this.priceColor = Colors.white, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Row( + children: [ + CachedNetworkImage( + imageUrl: ref.watch(activeSplToken)?.icon ?? '', + height: 18, + width: 18, + ), + const SizedBox( + width: 4, + ), + Text( + Formatter.formatPriceByCurrency( + mintPrice: ref.watch(selectedCandyMachineGroup)?.mintPrice ?? 0, + splToken: ref.watch(activeSplToken), + ), + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: priceColor, + ), + ), + ], + ); + } +} diff --git a/publishing/.asset-manifest.json b/publishing/.asset-manifest.json index b2879d7f..24b09d4a 100644 --- a/publishing/.asset-manifest.json +++ b/publishing/.asset-manifest.json @@ -12,8 +12,8 @@ }, "./dReader.apk": { "path": "./dReader.apk", - "sha256": "94ErZOMVfgLz89GQV14OIvS70M84QqHDGAlpC2jyBPg=", - "uri": "https://arweave.net/MYMJ1WUQRbZ5WZLIpP-SMyD1GdxCZBkUEGEToPYCiWU" + "sha256": "BNzmXA52qWK2ckcjM3Jw9XSSA745lwlTd4WN+udcx3s=", + "uri": "https://arweave.net/EAuWkudzZD2lJ1rvqJ5FN91eW2iiVFDRPFohuihHVV0" }, "./screenshots/1.png": { "path": "./screenshots/1.png", diff --git a/publishing/config.yaml b/publishing/config.yaml index 5e35d27c..96c8e253 100644 --- a/publishing/config.yaml +++ b/publishing/config.yaml @@ -19,7 +19,7 @@ app: - purpose: icon uri: ./512x512_rounded.png release: - address: 85sZz2gCr25T6aCQ5rZ2MUy7rMnFbAYxpKENr7mkZnwF + address: G4sKQBVPwkFNmawMzbg2GnbtQ6ayzL4zcD41MqHVaeuK media: - purpose: icon uri: ./512x512_rounded.png @@ -40,7 +40,7 @@ release: short_description: Digital comic book store long_description: Platform for discovering, trading, collecting, and reading digital graphic novels new_in_version: Consolidate auth flow - saga_features: Exclusive early access, free/discounted collectible comics + saga_features: New mint info container solana_mobile_dapp_publisher_portal: google_store_package: io.app.dreader testing_instructions: Run the app diff --git a/pubspec.yaml b/pubspec.yaml index 09923546..d8cfe33f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: dReader Flutter app. publish_to: 'none' -version: 1.3.0+43 # dappStore: 1.2.3+43 (next release: 1.3.1+44) google play: 1.3.0+69 (next release: 1.3.1+70) +version: 1.3.1+44 # dappStore: 1.3.1+44 (next release: 1.3.2+45) google play: 1.3.1+70 (next release: 1.3.2+71) environment: sdk: '>=3.0.0 <4.0.0'