From da2bf463ff24a6aaf75b8b8783202693ab296e57 Mon Sep 17 00:00:00 2001 From: d-reader-luka <125265091+d-reader-luka@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:08:55 +0200 Subject: [PATCH] after mint improvements * add ticker on mint info container when mint starts in 1 hour max * improve and clean home screen a bit comic details improve code reading * publishing --- Makefile | 6 +- .../presentation/screens/sign_up/sign_up.dart | 37 +- .../providers/candy_machine_providers.dart | 19 + .../data/datasource/comic_remote_source.dart | 4 +- .../repositories/comic_repository_impl.dart | 2 +- .../domain/repositories/comic_repository.dart | 2 +- .../providers/comic_providers.dart | 2 +- .../presentation/screens/comic_details.dart | 22 +- .../widgets/cards/comic_card.dart | 201 +++--- .../widgets/comics_list_view.dart | 4 +- .../widgets/details/scaffold.dart | 596 ++++++++++-------- .../screens/comic_issue_details.dart | 9 +- .../widgets/comic_issues_list.dart | 10 +- .../tabs/about/mint_info_container.dart | 305 +++++---- .../widgets/tabs/cards/comic_issue_card.dart | 164 ++--- .../presentation/widgets/creators_grid.dart | 46 +- .../widgets/tabs/collectibles/tab.dart | 1 - .../providers/digital_asset_controller.dart | 6 +- .../animations/mint_animation_screen.dart | 3 +- .../home/presentation/screens/home.dart | 130 ++-- .../presentation/widgets/section_heading.dart | 2 +- lib/firebase_options_dev.dart | 2 +- lib/firebase_options_prod.dart | 2 +- lib/{main_prod.dart => main.dart} | 14 +- lib/main_dev.dart | 157 ----- .../solana/solana_transaction_notifier.dart | 3 +- lib/shared/utils/render_carrot_error.dart | 5 +- .../widgets/unsorted/carrot_error_widget.dart | 19 +- publishing/.asset-manifest.json | 4 +- publishing/config.yaml | 12 +- pubspec.yaml | 2 +- test/widget_test.dart | 2 +- 32 files changed, 891 insertions(+), 902 deletions(-) rename lib/{main_prod.dart => main.dart} (91%) delete mode 100644 lib/main_dev.dart diff --git a/Makefile b/Makefile index 4a0214ce..ace860d9 100644 --- a/Makefile +++ b/Makefile @@ -31,17 +31,17 @@ all: build-dev build-prod build-prod-apk build-dev: flutter clean && \ - flutter build apk --split-per-abi --release --dart-define=apiUrl=$(API_URL_DEV_DEVNET) --dart-define=apiUrlDevnet=$(API_URL_DEV_DEVNET) --dart-define=sentryDsn=${sentryDsn} --flavor dev --target lib/main_dev.dart && \ + flutter build apk --split-per-abi --release --dart-define=apiUrl=$(API_URL_DEV_DEVNET) --dart-define=apiUrlDevnet=$(API_URL_DEV_DEVNET) --dart-define=sentryDsn=${sentryDsn} --flavor dev --target lib/main.dart && \ mv ./build/app/outputs/flutter-apk/app-armeabi-v7a-dev-release.apk ./apks/dReader-dev.apk build-prod: flutter clean && \ - flutter build appbundle --release --dart-define=apiUrl=$(API_URL_PROD) --dart-define=apiUrlDevnet=$(API_URL_PROD) --dart-define=sentryDsn=${sentryDsn} --flavor prod --target lib/main_prod.dart && \ + flutter build appbundle --release --dart-define=apiUrl=$(API_URL_PROD) --dart-define=apiUrlDevnet=$(API_URL_PROD) --dart-define=sentryDsn=${sentryDsn} --flavor prod --target lib/main.dart && \ mv ./build/app/outputs/bundle/prodRelease/app-prod-release.aab ./apks/dReader.aab build-prod-apk: flutter clean && \ - flutter build apk --split-per-abi --release --dart-define=apiUrl=$(API_URL_PROD) --dart-define=apiUrlDevnet=$(API_URL_PROD) --dart-define=sentryDsn=${sentryDsn} --flavor prod --target lib/main_prod.dart && \ + flutter build apk --split-per-abi --release --dart-define=apiUrl=$(API_URL_PROD) --dart-define=apiUrlDevnet=$(API_URL_PROD) --dart-define=sentryDsn=${sentryDsn} --flavor prod --target lib/main.dart && \ mv ./build/app/outputs/flutter-apk/app-arm64-v8a-prod-release.apk ./apks/dReader.apk start-saga-release: diff --git a/lib/features/authentication/presentation/screens/sign_up/sign_up.dart b/lib/features/authentication/presentation/screens/sign_up/sign_up.dart index ea513a24..4fe13a06 100644 --- a/lib/features/authentication/presentation/screens/sign_up/sign_up.dart +++ b/lib/features/authentication/presentation/screens/sign_up/sign_up.dart @@ -20,13 +20,9 @@ class SignUpScreen extends ConsumerStatefulWidget { class _SignUpScreenState extends ConsumerState { final PageController _pageController = PageController(); - @override - void initState() { - super.initState(); - } - @override void dispose() { + _pageController.dispose(); super.dispose(); } @@ -45,21 +41,20 @@ class _SignUpScreenState extends ConsumerState { left: 16, right: 16, ), - child: const Heading(), + child: const _Heading(), ), ), body: ref.watch(signUpDataNotifierProvider).googleAccessToken.isNotEmpty - ? GoogleSignUpForm(pageController: _pageController) - : RegularSignUpForm(pageController: _pageController), + ? _GoogleSignUpForm(pageController: _pageController) + : _RegularSignUpForm(pageController: _pageController), ), ); } } -class RegularSignUpForm extends ConsumerWidget { +class _RegularSignUpForm extends ConsumerWidget { final PageController pageController; - const RegularSignUpForm({ - super.key, + const _RegularSignUpForm({ required this.pageController, }); @@ -117,10 +112,9 @@ class RegularSignUpForm extends ConsumerWidget { } } -class GoogleSignUpForm extends ConsumerWidget { +class _GoogleSignUpForm extends ConsumerWidget { final PageController pageController; - const GoogleSignUpForm({ - super.key, + const _GoogleSignUpForm({ required this.pageController, }); @@ -165,15 +159,15 @@ class GoogleSignUpForm extends ConsumerWidget { } } -class Heading extends ConsumerWidget { - const Heading({super.key}); +class _Heading extends ConsumerWidget { + const _Heading(); @override Widget build(BuildContext context, WidgetRef ref) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const HeadingItem( + const _HeadingItem( step: 1, title: 'Username', color: Colors.white, @@ -191,7 +185,7 @@ class Heading extends ConsumerWidget { .watch(signUpDataNotifierProvider) .googleAccessToken .isEmpty) ...[ - HeadingItem( + _HeadingItem( step: 2, title: 'Email & pass', color: ref.watch(signUpPageProvider) > 0 @@ -208,7 +202,7 @@ class Heading extends ConsumerWidget { ), ), ], - HeadingItem( + _HeadingItem( step: ref.watch(signUpDataNotifierProvider).googleAccessToken.isNotEmpty ? 2 @@ -223,12 +217,11 @@ class Heading extends ConsumerWidget { } } -class HeadingItem extends StatelessWidget { +class _HeadingItem extends StatelessWidget { final int step; final String title; final Color color; - const HeadingItem({ - super.key, + const _HeadingItem({ required this.step, required this.title, required this.color, 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 064eff0d..0e774891 100644 --- a/lib/features/candy_machine/presentations/providers/candy_machine_providers.dart +++ b/lib/features/candy_machine/presentations/providers/candy_machine_providers.dart @@ -2,8 +2,10 @@ 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/widgets/tabs/about/mint_info_container.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/formatter.dart'; import 'package:d_reader_flutter/shared/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -54,3 +56,20 @@ final activeSplToken = StateProvider.autoDispose( ?.firstWhere((element) => element.priority == splTokenHighestPriority); }, ); + +final timeUntilMintStarts = StateProvider.autoDispose( + (ref) { + final candyMachineGroup = ref.read(selectedCandyMachineGroup); + return Formatter.formatDateInRelative(candyMachineGroup?.startDate); + }, +); + +final mintStatusesProvider = StateProvider.autoDispose<(bool, bool)>( + (ref) { + final candyMachineGroup = ref.read(selectedCandyMachineGroup); + + return candyMachineGroup == null + ? (false, false) + : getMintStatuses(candyMachineGroup); + }, +); diff --git a/lib/features/comic/data/datasource/comic_remote_source.dart b/lib/features/comic/data/datasource/comic_remote_source.dart index 212217b7..dd99c88b 100644 --- a/lib/features/comic/data/datasource/comic_remote_source.dart +++ b/lib/features/comic/data/datasource/comic_remote_source.dart @@ -6,7 +6,7 @@ import 'package:d_reader_flutter/shared/exceptions/exceptions.dart'; abstract class ComicDataSource { Future>> getComics( {String? queryString}); - Future> getComic(String slug); + Future> getComic(String slug); Future>> getOwnedComics({ required int userId, required String query, @@ -34,7 +34,7 @@ class ComicRemoteDataSource implements ComicDataSource { } @override - Future> getComic(String slug) async { + Future> getComic(String slug) async { try { final response = await networkService.get('/comic/get/$slug'); return response.fold((exception) => Left(exception), (result) { diff --git a/lib/features/comic/data/repositories/comic_repository_impl.dart b/lib/features/comic/data/repositories/comic_repository_impl.dart index e49b3adb..866ed64c 100644 --- a/lib/features/comic/data/repositories/comic_repository_impl.dart +++ b/lib/features/comic/data/repositories/comic_repository_impl.dart @@ -15,7 +15,7 @@ class ComicRepositoryImpl implements ComicRepository { } @override - Future> getComic(String slug) { + Future> getComic(String slug) { return dataSource.getComic(slug); } diff --git a/lib/features/comic/domain/repositories/comic_repository.dart b/lib/features/comic/domain/repositories/comic_repository.dart index 0bae58e4..97bf7840 100644 --- a/lib/features/comic/domain/repositories/comic_repository.dart +++ b/lib/features/comic/domain/repositories/comic_repository.dart @@ -5,7 +5,7 @@ import 'package:d_reader_flutter/shared/exceptions/exceptions.dart'; abstract class ComicRepository { Future>> getComics( {String? queryString}); - Future> getComic(String slug); + Future> getComic(String slug); Future>> getOwnedComics({ required int userId, required String query, diff --git a/lib/features/comic/presentation/providers/comic_providers.dart b/lib/features/comic/presentation/providers/comic_providers.dart index f276e98b..4a2d7115 100644 --- a/lib/features/comic/presentation/providers/comic_providers.dart +++ b/lib/features/comic/presentation/providers/comic_providers.dart @@ -46,7 +46,7 @@ final paginatedComicsProvider = StateNotifierProvider.family< }); final comicSlugProvider = - FutureProvider.autoDispose.family((ref, slug) async { + FutureProvider.autoDispose.family((ref, slug) async { return ref.read(comicRepositoryProvider).getComic(slug).then( (result) => result.fold((exception) => throw exception, (data) => data)); }); diff --git a/lib/features/comic/presentation/screens/comic_details.dart b/lib/features/comic/presentation/screens/comic_details.dart index 9b151c5b..ad38636e 100644 --- a/lib/features/comic/presentation/screens/comic_details.dart +++ b/lib/features/comic/presentation/screens/comic_details.dart @@ -10,6 +10,7 @@ import 'package:d_reader_flutter/features/comic_issue/presentation/widgets/tabs/ import 'package:d_reader_flutter/features/comic/presentation/widgets/details/scaffold.dart'; import 'package:d_reader_flutter/features/discover/root/presentation/widgets/common/on_going_bottom.dart'; import 'package:d_reader_flutter/features/discover/root/presentation/widgets/tabs/issues/issues_gallery_builder.dart'; +import 'package:d_reader_flutter/shared/widgets/unsorted/carrot_error_widget.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -22,18 +23,16 @@ class ComicDetails extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final AsyncValue provider = ref.watch(comicSlugProvider(slug)); + final AsyncValue provider = ref.watch(comicSlugProvider(slug)); final issuesProvider = ref.watch( paginatedIssuesProvider( 'comicSlug=$slug&sortTag=latest&sortOrder=${getSortDirection(ref.watch(comicSortDirectionProvider))}', ), ); - + final bool isDetailedViewMode = + ref.watch(comicViewModeProvider) == ViewMode.detailed; return provider.when( data: (comic) { - if (comic == null) { - return const SizedBox(); - } return ComicDetailsScaffold( comic: comic, loadMore: ref @@ -53,25 +52,28 @@ class ComicDetails extends ConsumerWidget { (context, index) { return issuesProvider.when( data: (List issues) { - return ref.watch(comicViewModeProvider) == - ViewMode.detailed + return isDetailedViewMode ? _IssuesList(issues: issues) : IssuesGalleryBuilder( issues: issues, ); }, error: (Object? e, StackTrace? stk) { - return const Text('Failed to fetch data.'); + return const CarrotErrorWidget(); }, loading: () { return const SizedBox(); }, onGoingError: (List items, Object? e, StackTrace? stk) { - return _IssuesList(issues: items); + return isDetailedViewMode + ? _IssuesList(issues: items) + : IssuesGalleryBuilder(issues: items); }, onGoingLoading: (List items) { - return _IssuesList(issues: items); + return isDetailedViewMode + ? _IssuesList(issues: items) + : IssuesGalleryBuilder(issues: items); }, ); }, diff --git a/lib/features/comic/presentation/widgets/cards/comic_card.dart b/lib/features/comic/presentation/widgets/cards/comic_card.dart index 52ea9ed0..f094fb8a 100644 --- a/lib/features/comic/presentation/widgets/cards/comic_card.dart +++ b/lib/features/comic/presentation/widgets/cards/comic_card.dart @@ -9,9 +9,8 @@ import 'package:d_reader_flutter/shared/widgets/texts/author_verified.dart'; import 'package:d_reader_flutter/shared/widgets/image_widgets/cached_image_bg_placeholder.dart'; import 'package:d_reader_flutter/shared/widgets/icons/hot_icon.dart'; import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -class ComicCard extends ConsumerWidget { +class ComicCard extends StatelessWidget { final ComicModel comic; const ComicCard({ super.key, @@ -19,8 +18,7 @@ class ComicCard extends ConsumerWidget { }); @override - Widget build(BuildContext context, WidgetRef ref) { - TextTheme textTheme = Theme.of(context).textTheme; + Widget build(BuildContext context) { return GestureDetector( onTap: () { nextScreenPush( @@ -37,98 +35,119 @@ class ComicCard extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: Stack( - children: [ - AspectRatio( - aspectRatio: comicAspectRatio, - child: LayoutBuilder(builder: (context, constraint) { - return CachedImageBgPlaceholder( - width: constraint.maxWidth.toDouble(), - cacheHeight: constraint.maxHeight.cacheSize(context), - cacheWidth: constraint.maxWidth.cacheSize(context), - imageUrl: comic.cover, - opacity: .4, - padding: EdgeInsets.zero, - overrideBorderRadius: const BorderRadius.only( - topLeft: Radius.circular( - 8, - ), - topRight: Radius.circular( - 8, - ), - ), - ); - }), - ), - Positioned.fill( - top: 0, - left: 0, - child: Stack( - children: [ - comic.isPopular - ? const HotIconSmall() - : const SizedBox(), - comic.logo.isNotEmpty - ? Container( - alignment: Alignment.center, - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - child: AspectRatio( - aspectRatio: comicLogoAspectRatio, - child: LayoutBuilder( - builder: (context, constraints) { - return CachedNetworkImage( - imageUrl: comic.logo, - memCacheHeight: - (constraints.maxHeight * .6) - .cacheSize(context), - ); - }), - ), - ) - : const SizedBox(), - ], - ), - ), - ], - ), - ), - Container( - height: 92, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - comic.title, - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: textTheme.titleMedium, - ), - const SizedBox( - height: 4, - ), - AuthorVerified( - authorName: comic.creator?.name ?? '', - isVerified: comic.creator?.isVerified ?? false, - fontSize: 14, - textColor: ColorPalette.greyscale100, - ), - const SizedBox( - height: 8, - ), - Text( - '${comic.stats?.issuesCount ?? 0} ${pluralizeString(comic.stats?.issuesCount ?? 0, 'EP')}', - style: textTheme.bodySmall, - ), - ], + child: _ComicCoverStack( + comic: comic, ), ), + _ComicInfoContainer(comic: comic), ], ), ), ); } } + +class _ComicCoverStack extends StatelessWidget { + final ComicModel comic; + const _ComicCoverStack({ + required this.comic, + }); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + AspectRatio( + aspectRatio: comicAspectRatio, + child: LayoutBuilder(builder: (context, constraint) { + return CachedImageBgPlaceholder( + width: constraint.maxWidth.toDouble(), + cacheHeight: constraint.maxHeight.cacheSize(context), + cacheWidth: constraint.maxWidth.cacheSize(context), + imageUrl: comic.cover, + opacity: .4, + padding: EdgeInsets.zero, + overrideBorderRadius: const BorderRadius.only( + topLeft: Radius.circular( + 8, + ), + topRight: Radius.circular( + 8, + ), + ), + ); + }), + ), + Positioned.fill( + top: 0, + left: 0, + child: Stack( + children: [ + comic.isPopular ? const HotIconSmall() : const SizedBox(), + comic.logo.isNotEmpty + ? Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + child: AspectRatio( + aspectRatio: comicLogoAspectRatio, + child: LayoutBuilder(builder: (context, constraints) { + return CachedNetworkImage( + imageUrl: comic.logo, + memCacheHeight: + (constraints.maxHeight * .6).cacheSize(context), + ); + }), + ), + ) + : const SizedBox(), + ], + ), + ), + ], + ); + } +} + +class _ComicInfoContainer extends StatelessWidget { + final ComicModel comic; + const _ComicInfoContainer({required this.comic}); + + @override + Widget build(BuildContext context) { + TextTheme textTheme = Theme.of(context).textTheme; + return Container( + height: 92, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + comic.title, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: textTheme.titleMedium, + ), + const SizedBox( + height: 4, + ), + AuthorVerified( + authorName: comic.creator?.name ?? '', + isVerified: comic.creator?.isVerified ?? false, + fontSize: 14, + textColor: ColorPalette.greyscale100, + ), + const SizedBox( + height: 8, + ), + Text( + '${comic.stats?.issuesCount ?? 0} ${pluralizeString(comic.stats?.issuesCount ?? 0, 'EP')}', + style: textTheme.bodySmall, + ), + ], + ), + ); + } +} diff --git a/lib/features/comic/presentation/widgets/comics_list_view.dart b/lib/features/comic/presentation/widgets/comics_list_view.dart index 94f2aefd..635d7677 100644 --- a/lib/features/comic/presentation/widgets/comics_list_view.dart +++ b/lib/features/comic/presentation/widgets/comics_list_view.dart @@ -1,6 +1,7 @@ import 'package:d_reader_flutter/features/comic/domain/models/comic_model.dart'; import 'package:d_reader_flutter/features/comic/presentation/providers/comic_providers.dart'; import 'package:d_reader_flutter/features/comic/presentation/widgets/cards/comic_card.dart'; +import 'package:d_reader_flutter/shared/theme/app_colors.dart'; import 'package:d_reader_flutter/shared/widgets/cards/skeleton_card.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -33,7 +34,6 @@ class ComicsListView extends ConsumerWidget { height: 235, child: ListView.builder( itemCount: data.length, - shrinkWrap: true, scrollDirection: Axis.horizontal, itemBuilder: (context, index) => Container( margin: const EdgeInsets.only(right: 16), @@ -48,7 +48,7 @@ class ComicsListView extends ConsumerWidget { error: (err, stack) { return const Text( "Couldn't fetch the data", - style: TextStyle(color: Colors.red), + style: TextStyle(color: ColorPalette.dReaderRed), ); }, loading: () => SizedBox( diff --git a/lib/features/comic/presentation/widgets/details/scaffold.dart b/lib/features/comic/presentation/widgets/details/scaffold.dart index e2e44d35..0c65122f 100644 --- a/lib/features/comic/presentation/widgets/details/scaffold.dart +++ b/lib/features/comic/presentation/widgets/details/scaffold.dart @@ -62,310 +62,348 @@ class _ComicDetailsScaffoldState extends State @override Widget build(BuildContext context) { - TextTheme textTheme = Theme.of(context).textTheme; - return NotificationListener( - onNotification: (notification) { - if (notification is ScrollNotification) { - double maxScroll = notification.metrics.maxScrollExtent; - double currentScroll = notification.metrics.pixels; - double delta = MediaQuery.sizeOf(context).width * 0.1; - - if (maxScroll - currentScroll <= delta) { - widget.loadMore(); - } - if (notification.metrics.pixels > 70) { - _controller.forward(); - } else if (notification.metrics.pixels < 70) { - _controller.reverse(); - } - } - return true; - }, - child: Scaffold( - backgroundColor: ColorPalette.appBackgroundColor, - appBar: PreferredSize( - preferredSize: const Size(0, 64), - child: AnimatedAppBar( - animation: _animation, - actions: [ - GestureDetector( - onTap: () { - nextScreenPush( - context: context, - path: RoutePath.comicDetailsInfo, - extra: widget.comic, - ); - }, - child: Padding( - padding: const EdgeInsets.only(right: 16), - child: SvgPicture.asset('assets/icons/more_with_border.svg'), - ), + return Scaffold( + backgroundColor: ColorPalette.appBackgroundColor, + appBar: PreferredSize( + preferredSize: const Size(0, 64), + child: AnimatedAppBar( + animation: _animation, + actions: [ + GestureDetector( + onTap: () { + nextScreenPush( + context: context, + path: RoutePath.comicDetailsInfo, + extra: widget.comic, + ); + }, + child: Padding( + padding: const EdgeInsets.only(right: 16), + child: SvgPicture.asset('assets/icons/more_with_border.svg'), ), - ], - ), + ), + ], ), - extendBodyBehindAppBar: true, - body: ListView( + ), + extendBodyBehindAppBar: true, + body: NotificationListener( + onNotification: (notification) { + if (notification is ScrollNotification) { + double maxScroll = notification.metrics.maxScrollExtent; + double currentScroll = notification.metrics.pixels; + double delta = MediaQuery.sizeOf(context).width * 0.1; + + if (maxScroll - currentScroll <= delta) { + widget.loadMore(); + } + if (notification.metrics.pixels > 70) { + _controller.forward(); + } else if (notification.metrics.pixels < 70) { + _controller.reverse(); + } + } + return true; + }, + child: ListView( padding: const EdgeInsets.only(top: 0), keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, children: [ Column( children: [ - Stack( - children: [ - AspectRatio( - aspectRatio: comicAspectRatio, - child: LayoutBuilder(builder: (context, constraints) { - return CachedImageBgPlaceholder( - imageUrl: widget.comic.cover, - cacheHeight: constraints.maxHeight.cacheSize(context), - cacheWidth: constraints.maxWidth.cacheSize(context), - overrideBorderRadius: BorderRadius.circular(0), - foregroundDecoration: const BoxDecoration( - gradient: LinearGradient( - colors: [ - ColorPalette.appBackgroundColor, - Colors.transparent, - ColorPalette.appBackgroundColor, - ], - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - stops: [0.0, .6406, 1], - ), - ), - ); - }), - ), - Positioned.fill( - child: Container( - alignment: Alignment.center, - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 4, - ), - child: AspectRatio( - aspectRatio: comicLogoAspectRatio, - child: LayoutBuilder( - builder: (context, constraints) { - return CachedNetworkImage( - imageUrl: widget.comic.logo, - memCacheHeight: (constraints.maxHeight * .6) - .cacheSize(context), - ); - }, - ), - ), - ), - ), - Positioned.fill( - child: Container( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.comic.title, - style: textTheme.headlineLarge, - ), - ], - ), - ), - ), + _ComicBannerHeader(comic: widget.comic), + _BelowBannerContent(comic: widget.comic), + ], + ), + _ComicStatsRow(comic: widget.comic), + const SizedBox( + height: 16, + ), + _BodyFilters(comic: widget.comic), + const SizedBox( + height: 16, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 8, + ), + child: widget.body, + ) + ], + ), + ), + ); + } +} + +class _ComicBannerHeader extends StatelessWidget { + final ComicModel comic; + const _ComicBannerHeader({required this.comic}); + + @override + Widget build(BuildContext context) { + TextTheme textTheme = Theme.of(context).textTheme; + return Stack( + children: [ + AspectRatio( + aspectRatio: comicAspectRatio, + child: LayoutBuilder(builder: (context, constraints) { + return CachedImageBgPlaceholder( + imageUrl: comic.cover, + cacheHeight: constraints.maxHeight.cacheSize(context), + cacheWidth: constraints.maxWidth.cacheSize(context), + overrideBorderRadius: BorderRadius.circular(0), + foregroundDecoration: const BoxDecoration( + gradient: LinearGradient( + colors: [ + ColorPalette.appBackgroundColor, + Colors.transparent, + ColorPalette.appBackgroundColor, ], + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + stops: [0.0, .6406, 1], ), - Padding( - padding: const EdgeInsets.only( - right: 16, - left: 16, - top: 4, - bottom: 4, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextWithViewMore( - text: widget.comic.description, - maxLines: 2, - textAlign: TextAlign.start, - onLinkTap: () { - nextScreenPush( - context: context, - path: RoutePath.comicDetailsInfo, - extra: widget.comic, - ); - }, - ), - const SizedBox( - height: 16, - ), - GenreTagsDefault( - genres: widget.comic.genres, - withHorizontalScroll: true, - ), - const SizedBox( - height: 16, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - RatingIcon( - initialRating: - widget.comic.stats?.averageRating ?? 0, - isRatedByMe: - widget.comic.myStats?.rating != null, - comicSlug: widget.comic.slug, - isContainerWidget: true, - ), - const SizedBox( - width: 8, - ), - FavoriteIconCount( - favouritesCount: - widget.comic.stats?.favouritesCount ?? 0, - isFavourite: - widget.comic.myStats?.isFavourite ?? false, - slug: widget.comic.slug, - isContainerWidget: true, - ), - const SizedBox( - width: 8, - ), - BookmarkIcon( - isBookmarked: - widget.comic.myStats?.isBookmarked ?? false, - slug: widget.comic.slug, - ), - ], - ), - MatureAudience( - audienceType: widget.comic.audienceType, - ), - ], - ), - const SizedBox( - height: 8, - ), - const Divider( - thickness: 1, - color: ColorPalette.greyscale400, - ), - const SizedBox( - height: 8, - ), - GestureDetector( - onTap: () { - nextScreenPush( - context: context, - path: - '${RoutePath.creatorDetails}/${widget.comic.creator?.slug}', - ); - }, - child: Row( - children: [ - CreatorAvatar( - avatar: widget.comic.creator?.avatar ?? '', - radius: 24, - height: 32, - width: 32, - ), - const SizedBox(width: 12), - Expanded( - child: Text( - widget.comic.creator?.name ?? '', - style: textTheme.bodyMedium, - ), - ), - ], - ), - ), - const SizedBox( - height: 8, - ), - const Divider( - thickness: 1, - color: ColorPalette.greyscale400, - ), - const SizedBox( - height: 8, - ), - ], - ), + ), + ); + }), + ), + Positioned.fill( + child: Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 4, + ), + child: AspectRatio( + aspectRatio: comicLogoAspectRatio, + child: LayoutBuilder( + builder: (context, constraints) { + return CachedNetworkImage( + imageUrl: comic.logo, + memCacheHeight: + (constraints.maxHeight * .6).cacheSize(context), + ); + }, + ), + ), + ), + ), + Positioned.fill( + child: Container( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + comic.title, + style: textTheme.headlineLarge, ), ], ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + ), + ), + ], + ); + } +} + +class _BelowBannerContent extends StatelessWidget { + final ComicModel comic; + const _BelowBannerContent({required this.comic}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only( + right: 16, + left: 16, + top: 4, + bottom: 4, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextWithViewMore( + text: comic.description, + maxLines: 2, + textAlign: TextAlign.start, + onLinkTap: () { + nextScreenPush( + context: context, + path: RoutePath.comicDetailsInfo, + extra: comic, + ); + }, + ), + const SizedBox( + height: 16, + ), + GenreTagsDefault( + genres: comic.genres, + withHorizontalScroll: true, + ), + const SizedBox( + height: 16, + ), + _ActionsRow(comic: comic), + const _DividerLine(), + GestureDetector( + onTap: () { + nextScreenPush( + context: context, + path: '${RoutePath.creatorDetails}/${comic.creator?.slug}', + ); + }, + child: Row( children: [ - StatsInfo( - title: 'TOTAL VOL', - stats: - '${Formatter.formatLamportPrice(widget.comic.stats?.totalVolume) ?? 0}', - ), - StatsInfo( - title: 'ISSUES', - stats: '${widget.comic.stats?.issuesCount}', - ), - StatsInfo( - title: 'READERS', - stats: '${widget.comic.stats?.readersCount}', + CreatorAvatar( + avatar: comic.creator?.avatar ?? '', + radius: 24, + height: 32, + width: 32, ), - StatsInfo( - title: widget.comic.isCompleted ? 'COMPLETED' : 'ONGOING', - stats: '', - statsWidget: Icon( - widget.comic.isCompleted - ? Icons.check - : Icons.arrow_right_alt_outlined, - color: Colors.white, + const SizedBox(width: 12), + Expanded( + child: Text( + comic.creator?.name ?? '', + style: Theme.of(context).textTheme.bodyMedium, ), - isLastItem: true, ), ], ), + ), + const _DividerLine(), + ], + ), + ); + } +} + +class _ActionsRow extends StatelessWidget { + final ComicModel comic; + const _ActionsRow({required this.comic}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + RatingIcon( + initialRating: comic.stats?.averageRating ?? 0, + isRatedByMe: comic.myStats?.rating != null, + comicSlug: comic.slug, + isContainerWidget: true, + ), const SizedBox( - height: 16, + width: 8, ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Issues', - style: textTheme.headlineMedium, - ), - widget.comic.stats?.issuesCount != null && - widget.comic.stats!.issuesCount > 1 - ? const Row( - children: [ - SortDirectionContainer(), - SizedBox( - width: 8, - ), - ViewModeContainer(), - ], - ) - : const ViewModeContainer(), - ], - ), + FavoriteIconCount( + favouritesCount: comic.stats?.favouritesCount ?? 0, + isFavourite: comic.myStats?.isFavourite ?? false, + slug: comic.slug, + isContainerWidget: true, ), const SizedBox( - height: 16, + width: 8, + ), + BookmarkIcon( + isBookmarked: comic.myStats?.isBookmarked ?? false, + slug: comic.slug, ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 8, - ), - child: widget.body, - ) ], ), + MatureAudience( + audienceType: comic.audienceType, + ), + ], + ); + } +} + +class _DividerLine extends StatelessWidget { + const _DividerLine(); + + @override + Widget build(BuildContext context) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Divider( + thickness: 1, + color: ColorPalette.greyscale400, + ), + ); + } +} + +class _ComicStatsRow extends StatelessWidget { + final ComicModel comic; + const _ComicStatsRow({required this.comic}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + StatsInfo( + title: 'TOTAL VOL', + stats: + '${Formatter.formatLamportPrice(comic.stats?.totalVolume) ?? 0}', + ), + StatsInfo( + title: 'ISSUES', + stats: '${comic.stats?.issuesCount}', + ), + StatsInfo( + title: 'READERS', + stats: '${comic.stats?.readersCount}', + ), + StatsInfo( + title: comic.isCompleted ? 'COMPLETED' : 'ONGOING', + stats: '', + statsWidget: Icon( + comic.isCompleted ? Icons.check : Icons.arrow_right_alt_outlined, + color: Colors.white, + ), + isLastItem: true, + ), + ], + ); + } +} + +class _BodyFilters extends StatelessWidget { + final ComicModel comic; + const _BodyFilters({required this.comic}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Issues', + style: Theme.of(context).textTheme.headlineMedium, + ), + comic.stats?.issuesCount != null && comic.stats!.issuesCount > 1 + ? const Row( + children: [ + SortDirectionContainer(), + SizedBox( + width: 8, + ), + ViewModeContainer(), + ], + ) + : const ViewModeContainer(), + ], ), ); } 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 a74e3aba..e1750c1a 100644 --- a/lib/features/comic_issue/presentation/screens/comic_issue_details.dart +++ b/lib/features/comic_issue/presentation/screens/comic_issue_details.dart @@ -6,7 +6,6 @@ import 'package:d_reader_flutter/features/candy_machine/presentations/providers/ 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'; -import 'package:d_reader_flutter/features/comic_issue/presentation/widgets/tabs/about/mint_info_container.dart'; import 'package:d_reader_flutter/features/creator/presentation/utils/utils.dart'; import 'package:d_reader_flutter/shared/domain/providers/solana/solana_providers.dart'; import 'package:d_reader_flutter/shared/exceptions/exceptions.dart'; @@ -472,12 +471,8 @@ class BottomNavigation extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final candyMachineGroup = ref.watch(selectedCandyMachineGroup); - final (isMintActive, isEnded) = candyMachineGroup != null - ? getMintStatuses(candyMachineGroup) - : (null, null); - final shouldDisableMintButton = - isMintActive != null && !isMintActive && isEnded != null && !isEnded; + final (isMintActive, isEnded) = ref.watch(mintStatusesProvider); + final shouldDisableMintButton = !isMintActive && !isEnded; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/lib/features/comic_issue/presentation/widgets/comic_issues_list.dart b/lib/features/comic_issue/presentation/widgets/comic_issues_list.dart index df7b2812..5748600c 100644 --- a/lib/features/comic_issue/presentation/widgets/comic_issues_list.dart +++ b/lib/features/comic_issue/presentation/widgets/comic_issues_list.dart @@ -1,18 +1,17 @@ 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/widgets/tabs/cards/comic_issue_card.dart'; +import 'package:d_reader_flutter/shared/theme/app_colors.dart'; import 'package:d_reader_flutter/shared/widgets/cards/skeleton_card.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; class ComicIssuesList extends ConsumerWidget { final String? query; - final bool onlyFree; const ComicIssuesList({ super.key, this.query, - this.onlyFree = false, }); @override @@ -22,15 +21,11 @@ class ComicIssuesList extends ConsumerWidget { final screenWidth = MediaQuery.sizeOf(context).width; return comicIssues.when( data: (data) { - if (onlyFree) { - data = data.where((element) => element.isFreeToRead).toList(); - } return data.isNotEmpty ? SizedBox( height: 227, child: ListView.builder( itemCount: data.length, - shrinkWrap: true, scrollDirection: Axis.horizontal, itemBuilder: (context, index) { return Container( @@ -48,14 +43,13 @@ class ComicIssuesList extends ConsumerWidget { error: (err, stack) { return const Text( "Fetch data error occured.", - style: TextStyle(color: Colors.red), + style: TextStyle(color: ColorPalette.dReaderRed), ); }, loading: () => SizedBox( height: 227, child: ListView.builder( itemCount: 3, - shrinkWrap: true, scrollDirection: Axis.horizontal, itemBuilder: (context, index) => SkeletonCard( margin: const EdgeInsets.only( 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 index b5b67121..0d0744e0 100644 --- 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 @@ -1,3 +1,4 @@ +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/presentations/providers/candy_machine_providers.dart'; import 'package:d_reader_flutter/features/comic_issue/presentation/widgets/tabs/about/group_with_currency.dart'; @@ -7,28 +8,35 @@ 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/scheduler.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -String mintNumbersText({required int itemsMinted, required int totalSupply}) { +String _mintSupplyText({required int itemsMinted, required int totalSupply}) { return '${itemsMinted > totalSupply ? totalSupply : itemsMinted}/$totalSupply'; } -String myMintNumbersText({required int itemsMinted, int? supply}) { +String _myMintSupplyText({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()); - + candyMachineGroup.startDate! + .isBefore(DateTime.now().add(const Duration(seconds: 1))); bool isEnded = candyMachineGroup.endDate != null && candyMachineGroup.endDate!.isBefore(DateTime.now()); return (isActive, isEnded); } -class MintInfoContainer extends ConsumerWidget { +bool _shouldInitTicker(DateTime mintStartDate) { + final currentDate = DateTime.now(); + final difference = mintStartDate.difference(currentDate); + return difference.inDays < 1 && difference.inHours < 1; +} + +class MintInfoContainer extends ConsumerStatefulWidget { final List candyMachineGroups; final int totalSupply; const MintInfoContainer({ @@ -38,19 +46,63 @@ class MintInfoContainer extends ConsumerWidget { }); @override - Widget build(BuildContext context, WidgetRef ref) { - final textTheme = Theme.of(context).textTheme; + ConsumerState createState() => + _MintInfoContainerState(); +} + +class _MintInfoContainerState extends ConsumerState + with SingleTickerProviderStateMixin { + late final Ticker _ticker; + + @override + void initState() { + super.initState(); + final candyMachineGroup = ref.read(selectedCandyMachineGroup); + if (candyMachineGroup?.startDate != null && + _shouldInitTicker(candyMachineGroup!.startDate!)) { + _initTicker(); + } + } + + void _initTicker() { + _ticker = createTicker( + (elapsed) { + final candyMachineGroup = ref.read(selectedCandyMachineGroup)!; + final (isActive, isEnded) = getMintStatuses(candyMachineGroup); + + ref.read(mintStatusesProvider.notifier).update( + (state) => (isActive, isEnded), + ); + ref.read(timeUntilMintStarts.notifier).update( + (state) => + Formatter.formatDateInRelative(candyMachineGroup.startDate), + ); + if (isActive) { + _ticker.stop(); + } + }, + ); + _ticker.start(); + } + + @override + void dispose() { + _ticker.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { 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 && + final displayDropdown = widget.candyMachineGroups.length > 1 && splTokens.value != null && splTokens.value!.isNotEmpty; + final bool isMintActive = ref.watch(mintStatusesProvider).$1; return _DecoratedContainer( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -64,48 +116,11 @@ class MintInfoContainer extends ConsumerWidget { 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(), - ], + title: _HeadingRow( + candyMachineGroup: candyMachineGroup, + suffix: const MintPriceWidget(), ), - children: candyMachineGroups.map((group) { + children: widget.candyMachineGroups.map((group) { final splToken = splTokens.value?.firstWhere((element) => element.address == group.splTokenAddress); if (splToken == null) { @@ -114,60 +129,23 @@ class MintInfoContainer extends ConsumerWidget { return GroupWithCurrencyRow( splToken: splToken, mintPrice: group.mintPrice, - groups: candyMachineGroups, + groups: widget.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.formatPriceByCurrency( - mintPrice: candyMachineGroup.mintPrice, - splToken: ref.watch(activeSplToken), - ) - : null, - ) - ], + child: _HeadingRow( + candyMachineGroup: candyMachineGroup, + suffix: SolanaPrice( + price: candyMachineGroup.mintPrice > 0 + ? Formatter.formatPriceByCurrency( + mintPrice: candyMachineGroup.mintPrice, + splToken: ref.watch(activeSplToken), + ) + : null, + ), ), ), SizedBox( @@ -187,31 +165,9 @@ class MintInfoContainer extends ConsumerWidget { 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, - ), - ), - ], + _MintStatus( + candyMachine: candyMachineState, + candyMachineGroup: candyMachineGroup, ), const SizedBox( height: 16, @@ -223,6 +179,107 @@ class MintInfoContainer extends ConsumerWidget { } } +class _HeadingRow extends ConsumerWidget { + final CandyMachineGroupModel candyMachineGroup; + final Widget suffix; + const _HeadingRow({ + required this.candyMachineGroup, + required this.suffix, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final textTheme = Theme.of(context).textTheme; + final (isMintActive, isMintEnded) = ref.watch(mintStatusesProvider); + return Row( + key: key, + 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, + ), + Consumer( + builder: (context, ref, child) { + return Text( + isMintActive + ? 'Live' + : !isMintEnded + ? 'Starts in ${ref.watch(timeUntilMintStarts)}' + : 'Ended', + style: textTheme.titleMedium?.copyWith( + color: isMintActive + ? ColorPalette.dReaderYellow100 + : !isMintEnded + ? ColorPalette.dReaderYellow300 + : ColorPalette.greyscale200, + ), + ); + }, + ) + ], + ), + suffix, + ], + ); + } +} + +class _MintStatus extends StatelessWidget { + final CandyMachineModel? candyMachine; + final CandyMachineGroupModel candyMachineGroup; + const _MintStatus({ + required this.candyMachine, + required this.candyMachineGroup, + }); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + return Row( + key: key, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _myMintSupplyText( + itemsMinted: candyMachineGroup.user?.itemsMinted ?? + candyMachineGroup.wallet?.itemsMinted ?? + 0, + supply: candyMachineGroup.user?.supply ?? + candyMachineGroup.wallet?.supply, + ), + style: textTheme.bodySmall?.copyWith( + color: ColorPalette.greyscale100, + ), + ), + Text( + _mintSupplyText( + itemsMinted: candyMachine?.itemsMinted ?? 0, + totalSupply: candyMachine?.supply ?? 0, + ), + style: textTheme.bodySmall?.copyWith( + color: ColorPalette.greyscale100, + ), + ), + ], + ); + } +} + class _ComicVaultContainer extends ConsumerWidget { const _ComicVaultContainer(); diff --git a/lib/features/comic_issue/presentation/widgets/tabs/cards/comic_issue_card.dart b/lib/features/comic_issue/presentation/widgets/tabs/cards/comic_issue_card.dart index 687e39b3..6c1e32f1 100644 --- a/lib/features/comic_issue/presentation/widgets/tabs/cards/comic_issue_card.dart +++ b/lib/features/comic_issue/presentation/widgets/tabs/cards/comic_issue_card.dart @@ -19,7 +19,6 @@ class ComicIssueCard extends StatelessWidget { @override Widget build(BuildContext context) { - TextTheme textTheme = Theme.of(context).textTheme; return GestureDetector( onTap: () { nextScreenPush( @@ -29,78 +28,101 @@ class ComicIssueCard extends StatelessWidget { }, child: Stack( children: [ - AspectRatio( - aspectRatio: comicIssueAspectRatio, - child: Builder(builder: (context) { - return LayoutBuilder(builder: (context, constraints) { - return CachedImageBgPlaceholder( - imageUrl: issue.cover, - padding: EdgeInsets.zero, - cacheHeight: constraints.maxHeight.cacheSize(context), - cacheWidth: constraints.maxWidth.cacheSize(context), - foregroundDecoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - gradient: const LinearGradient( - colors: [ - ColorPalette.greyscale500, - Colors.transparent, - ], - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - stops: [0.0, .5], - ), - ), - child: issue.isPopular - ? const Align( - alignment: Alignment.topLeft, - child: HotIconSmall(), - ) - : null, - ); - }); - }), + _IssueCoverBgContainer(issue: issue), + _IssueInfoContainer( + issue: issue, ), - Padding( - padding: const EdgeInsets.all(12), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - issue.comic?.title ?? '', - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: textTheme.bodySmall?.copyWith( - color: ColorPalette.greyscale100, - ), - ), - const SizedBox( - height: 4, - ), - Text( - issue.title, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: textTheme.titleMedium, - ), - const SizedBox( - height: 4, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'EP ${issue.number}/${issue.stats?.totalIssuesCount}', - style: textTheme.bodySmall, - ), - SolanaPrice( - price: Formatter.formatLamportPrice(issue.stats?.price), - mainAxisAlignment: MainAxisAlignment.end, - ), - ], - ) - ], + ], + ), + ); + } +} + +class _IssueCoverBgContainer extends StatelessWidget { + final ComicIssueModel issue; + const _IssueCoverBgContainer({required this.issue}); + + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: comicIssueAspectRatio, + child: LayoutBuilder( + builder: (context, constraints) { + return CachedImageBgPlaceholder( + imageUrl: issue.cover, + padding: EdgeInsets.zero, + cacheHeight: constraints.maxHeight.cacheSize(context), + cacheWidth: constraints.maxWidth.cacheSize(context), + foregroundDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + gradient: const LinearGradient( + colors: [ + ColorPalette.greyscale500, + Colors.transparent, + ], + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + stops: [0.0, .5], + ), ), + child: issue.isPopular + ? const Align( + alignment: Alignment.topLeft, + child: HotIconSmall(), + ) + : null, + ); + }, + ), + ); + } +} + +class _IssueInfoContainer extends StatelessWidget { + final ComicIssueModel issue; + const _IssueInfoContainer({required this.issue}); + + @override + Widget build(BuildContext context) { + TextTheme textTheme = Theme.of(context).textTheme; + return Padding( + padding: const EdgeInsets.all(12), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + issue.comic?.title ?? '', + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: textTheme.bodySmall?.copyWith( + color: ColorPalette.greyscale100, + ), + ), + const SizedBox( + height: 4, + ), + Text( + issue.title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: textTheme.titleMedium, + ), + const SizedBox( + height: 4, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'EP ${issue.number}/${issue.stats?.totalIssuesCount}', + style: textTheme.bodySmall, + ), + SolanaPrice( + price: Formatter.formatLamportPrice(issue.stats?.price), + mainAxisAlignment: MainAxisAlignment.end, + ), + ], ), ], ), diff --git a/lib/features/creator/presentation/widgets/creators_grid.dart b/lib/features/creator/presentation/widgets/creators_grid.dart index 7bc7fbeb..2faa6519 100644 --- a/lib/features/creator/presentation/widgets/creators_grid.dart +++ b/lib/features/creator/presentation/widgets/creators_grid.dart @@ -1,6 +1,6 @@ import 'package:d_reader_flutter/features/creator/domain/models/creator.dart'; import 'package:d_reader_flutter/features/creator/presentation/providers/creator_providers.dart'; -import 'package:d_reader_flutter/shared/widgets/cards/skeleton_card.dart'; +import 'package:d_reader_flutter/shared/theme/app_colors.dart'; import 'package:d_reader_flutter/features/creator/presentation/widgets/list_tile.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -20,38 +20,34 @@ class CreatorsGrid extends ConsumerWidget { return creators.when( data: (data) { - return GridView.builder( - itemCount: data.length, - primary: false, - padding: const EdgeInsets.only(right: 8), - shrinkWrap: true, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: isTablet ? 3 : 2, - crossAxisSpacing: 4, - mainAxisSpacing: 16, - mainAxisExtent: 60, + return SizedBox( + height: 140, + child: GridView.builder( + itemCount: data.length, + primary: false, + padding: const EdgeInsets.only(right: 8), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isTablet ? 3 : 2, + crossAxisSpacing: 4, + mainAxisSpacing: 16, + mainAxisExtent: 60, + ), + itemBuilder: (context, index) { + return CreatorListTile( + creator: data[index], + ); + }, ), - itemBuilder: (context, index) { - return CreatorListTile( - creator: data[index], - ); - }, ); }, error: (err, stack) { return const Text( "Couldn't fetch the data", - style: TextStyle(color: Colors.red), + style: TextStyle(color: ColorPalette.dReaderRed), ); }, - loading: () => SizedBox( - height: 90, - child: ListView.builder( - itemCount: 2, - shrinkWrap: true, - scrollDirection: Axis.horizontal, - itemBuilder: (context, index) => const SkeletonCard(), - ), + loading: () => const SizedBox( + height: 140, ), ); } diff --git a/lib/features/creator/presentation/widgets/tabs/collectibles/tab.dart b/lib/features/creator/presentation/widgets/tabs/collectibles/tab.dart index ae621d4f..26ffc4df 100644 --- a/lib/features/creator/presentation/widgets/tabs/collectibles/tab.dart +++ b/lib/features/creator/presentation/widgets/tabs/collectibles/tab.dart @@ -12,7 +12,6 @@ class CreatorCollectiblesTab extends StatelessWidget { ); // ListView.builder( // itemCount: 1, - // shrinkWrap: true, // itemBuilder: (context, index) { // return const Padding( // padding: EdgeInsets.all(12.0), diff --git a/lib/features/digital_asset/presentation/providers/digital_asset_controller.dart b/lib/features/digital_asset/presentation/providers/digital_asset_controller.dart index 71478299..2f7ce935 100644 --- a/lib/features/digital_asset/presentation/providers/digital_asset_controller.dart +++ b/lib/features/digital_asset/presentation/providers/digital_asset_controller.dart @@ -16,6 +16,9 @@ import 'package:video_player/video_player.dart'; part 'digital_asset_controller.g.dart'; +const String failedTransactionMessage = + 'Could not confirm the transaction, asset might appear in your wallet at a later time.'; + @riverpod class DigitalAssetController extends _$DigitalAssetController { @override @@ -136,8 +139,7 @@ class DigitalAssetController extends _$DigitalAssetController { } else if (transactionMessage == TransactionStatusMessage.success.getString() && !isMinted) { - onFail( - 'Transaction is sent but not confirmed. Please use sync-wallet and check your wallet for the asset'); + onFail(failedTransactionMessage); } else if (transactionMessage == TransactionStatusMessage.timeout.getString()) { onTimeout(); diff --git a/lib/features/digital_asset/presentation/screens/animations/mint_animation_screen.dart b/lib/features/digital_asset/presentation/screens/animations/mint_animation_screen.dart index cd757552..94c7f9cf 100644 --- a/lib/features/digital_asset/presentation/screens/animations/mint_animation_screen.dart +++ b/lib/features/digital_asset/presentation/screens/animations/mint_animation_screen.dart @@ -315,8 +315,7 @@ class _DoneMintingAnimationState extends State // handle exception }, (twitterUri) async { - final uri = Uri.encodeFull(twitterUri); - await openUrl(uri); + await openUrl(twitterUri); }, ); }, diff --git a/lib/features/home/presentation/screens/home.dart b/lib/features/home/presentation/screens/home.dart index 911d05ff..e2e2cbbb 100644 --- a/lib/features/home/presentation/screens/home.dart +++ b/lib/features/home/presentation/screens/home.dart @@ -3,7 +3,6 @@ import 'package:d_reader_flutter/features/comic/presentation/providers/comic_pro import 'package:d_reader_flutter/features/comic_issue/presentation/providers/comic_issue_providers.dart'; import 'package:d_reader_flutter/features/creator/presentation/providers/creator_providers.dart'; import 'package:d_reader_flutter/features/home/carousel/presentation/providers/carousel_providers.dart'; -import 'package:d_reader_flutter/main_dev.dart'; import 'package:d_reader_flutter/shared/domain/models/enums.dart'; import 'package:d_reader_flutter/shared/theme/app_colors.dart'; import 'package:d_reader_flutter/features/discover/root/presentation/screens/discover.dart'; @@ -21,7 +20,6 @@ class HomeView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - showOversizedImages(); return CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ @@ -54,94 +52,60 @@ class HomeView extends ConsumerWidget { return const Padding( padding: EdgeInsets.only( left: 16, - top: 8.0, + top: 32.0, + bottom: 32, ), - child: Column( + child: Wrap( + runSpacing: 32, children: [ - SizedBox( - height: 24, - ), - SectionHeading( + _SectionItem( title: 'Popular Comics', initialTab: DiscoverTabViewEnum.comics, filter: FilterId.popular, + child: ComicsListView( + query: 'skip=0&take=12&filterTag=popular', + ), ), - SizedBox( - height: 24, - ), - ComicsListView( - query: 'skip=0&take=12&filterTag=popular', - ), - SizedBox( - height: 32, - ), - SectionHeading( + _SectionItem( title: 'New Episodes', initialTab: DiscoverTabViewEnum.issues, sort: SortByEnum.latest, + child: ComicIssuesList( + query: 'skip=0&take=12&sortTag=latest', + ), ), - SizedBox( - height: 24, - ), - ComicIssuesList( - query: 'skip=0&take=12&sortTag=latest', - ), - SizedBox( - height: 32, - ), - SectionHeading( + _SectionItem( title: 'Top Creators', initialTab: DiscoverTabViewEnum.creators, + child: CreatorsGrid( + query: 'skip=0&take=4&sortTag=followers&sortOrder=desc', + ), ), - SizedBox( - height: 24, - ), - CreatorsGrid(query: 'skip=0&take=4'), - SizedBox( - height: 32, - ), - SectionHeading( + _SectionItem( title: 'New Comics', initialTab: DiscoverTabViewEnum.comics, sort: SortByEnum.latest, + child: ComicsListView( + query: + 'skip=0&take=12&sortTag=published&sortOrder=desc', + ), ), - SizedBox( - height: 24, - ), - ComicsListView( - query: 'skip=0&take=12&sortTag=published&sortOrder=desc', - ), - SizedBox( - height: 32, - ), - SectionHeading( + _SectionItem( title: 'Free Episodes', initialTab: DiscoverTabViewEnum.issues, filter: FilterId.free, + child: ComicIssuesList( + query: 'skip=0&take=20&filterTag=free', + ), ), - SizedBox( - height: 24, - ), - ComicIssuesList( - query: 'skip=0&take=20&filterTag=free', - ), - SizedBox( - height: 32, - ), - SectionHeading( + _SectionItem( title: 'Spicy action', initialTab: DiscoverTabViewEnum.comics, sort: SortByEnum.viewers, - ), - SizedBox( - height: 24, - ), - ComicsListView( - query: - 'skip=0&take=12&sortTag=viewers&sortOrder=desc&genreSlugs[]=action', - ), - SizedBox( - height: 32, + child: ComicsListView( + query: + 'skip=0&take=12&sortTag=viewers&sortOrder=desc&genreSlugs[]=action', + ), ), ], ), @@ -154,3 +118,37 @@ class HomeView extends ConsumerWidget { ); } } + +class _SectionItem extends StatelessWidget { + final FilterId? filter; + final DiscoverTabViewEnum? initialTab; + final SortByEnum? sort; + final String title; + final Widget child; + + const _SectionItem({ + required this.child, + this.filter, + this.initialTab, + this.sort, + required this.title, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SectionHeading( + title: title, + filter: filter, + initialTab: initialTab, + sort: sort, + ), + const SizedBox( + height: 24, + ), + child, + ], + ); + } +} diff --git a/lib/features/home/presentation/widgets/section_heading.dart b/lib/features/home/presentation/widgets/section_heading.dart index f50375d9..b3e0ec60 100644 --- a/lib/features/home/presentation/widgets/section_heading.dart +++ b/lib/features/home/presentation/widgets/section_heading.dart @@ -48,7 +48,7 @@ class SectionHeading extends ConsumerWidget { ref.read(scaffoldPageController).animateToPage( 1, curve: Curves.linear, - duration: const Duration(milliseconds: 350), + duration: const Duration(milliseconds: 200), ); ref.invalidate(selectedGenresProvider); if (filter != null) { diff --git a/lib/firebase_options_dev.dart b/lib/firebase_options_dev.dart index 5831bebf..39254dde 100644 --- a/lib/firebase_options_dev.dart +++ b/lib/firebase_options_dev.dart @@ -14,7 +14,7 @@ import 'package:flutter/foundation.dart' /// options: DefaultFirebaseOptions.currentPlatform, /// ); /// ``` -class DefaultFirebaseOptions { +class DevDefaultFirebaseOptions { static FirebaseOptions get currentPlatform { if (kIsWeb) { throw UnsupportedError( diff --git a/lib/firebase_options_prod.dart b/lib/firebase_options_prod.dart index 0ab97a60..6037392f 100644 --- a/lib/firebase_options_prod.dart +++ b/lib/firebase_options_prod.dart @@ -14,7 +14,7 @@ import 'package:flutter/foundation.dart' /// options: DefaultFirebaseOptions.currentPlatform, /// ); /// ``` -class DefaultFirebaseOptions { +class ProdDefaultFirebaseOptions { static FirebaseOptions get currentPlatform { if (kIsWeb) { throw UnsupportedError( diff --git a/lib/main_prod.dart b/lib/main.dart similarity index 91% rename from lib/main_prod.dart rename to lib/main.dart index 42f355d9..9ac7e757 100644 --- a/lib/main_prod.dart +++ b/lib/main.dart @@ -1,3 +1,5 @@ +import 'package:d_reader_flutter/firebase_options_dev.dart'; +import 'package:d_reader_flutter/firebase_options_prod.dart'; import 'package:d_reader_flutter/shared/data/remote/notification_service.dart'; import 'package:d_reader_flutter/routing/router.dart'; import 'package:d_reader_flutter/shared/data/local/local_store.dart'; @@ -12,15 +14,19 @@ import 'package:hive_flutter/hive_flutter.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:firebase_core/firebase_core.dart'; -import 'firebase_options_prod.dart'; +const bool isProd = appFlavor != null && appFlavor == 'prod'; // Defines a top-level named handler which background/terminated messages will call @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { NotificationService notificationsService = NotificationService(); + final firebaseOptions = isProd + ? ProdDefaultFirebaseOptions.currentPlatform + : DevDefaultFirebaseOptions.currentPlatform; + try { await Firebase.initializeApp( - options: DefaultFirebaseOptions.currentPlatform, + options: firebaseOptions, ); } catch (exception, stackTrace) { Sentry.captureException(exception, stackTrace: stackTrace); @@ -40,7 +46,9 @@ void main() async { await LocalStore().init(); try { await Firebase.initializeApp( - options: DefaultFirebaseOptions.currentPlatform, + options: isProd + ? ProdDefaultFirebaseOptions.currentPlatform + : DevDefaultFirebaseOptions.currentPlatform, ); } catch (exception, stackTrace) { Sentry.captureException(exception, stackTrace: stackTrace); diff --git a/lib/main_dev.dart b/lib/main_dev.dart deleted file mode 100644 index 98264588..00000000 --- a/lib/main_dev.dart +++ /dev/null @@ -1,157 +0,0 @@ -import 'package:d_reader_flutter/shared/data/remote/notification_service.dart'; -import 'package:d_reader_flutter/routing/router.dart'; -import 'package:d_reader_flutter/shared/data/local/local_store.dart'; -import 'package:d_reader_flutter/shared/theme/app_colors.dart'; -import 'package:d_reader_flutter/shared/theme/theme.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:hive_flutter/hive_flutter.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:firebase_core/firebase_core.dart'; -import 'firebase_options_dev.dart'; - -// Defines a top-level named handler which background/terminated messages will call -@pragma('vm:entry-point') -Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { - NotificationService notificationsService = NotificationService(); - await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - await notificationsService.initNotificationsHandler(); - notificationsService.displayNotification(message); -} - -void showOversizedImages() { - debugInvertOversizedImages = true; -} - -void main() async { - WidgetsFlutterBinding.ensureInitialized(); - await Future.wait( - [ - dotenv.load(fileName: ".env"), - Hive.initFlutter(), - ], - ); - await LocalStore().init(); - await Firebase.initializeApp( - options: DefaultFirebaseOptions.currentPlatform, - ); - FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); - SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( - statusBarColor: ColorPalette.appBackgroundColor, - systemNavigationBarColor: ColorPalette.appBackgroundColor, - )); - if (kReleaseMode) { - await SentryFlutter.init( - (options) { - options.dsn = const String.fromEnvironment('sentryDsn'); - options.tracesSampleRate = 0.1; - }, - appRunner: initApp, - ); - } else { - initApp(); - } -} - -void initApp() { - runApp( - const ProviderScope( - child: MyApp(), - ), - ); -} - -class MyApp extends ConsumerWidget { - const MyApp({ - super.key, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final router = ref.watch(routerProvider); - return MaterialApp.router( - title: 'dReader', - routerConfig: router, - theme: ThemeData( - appBarTheme: const AppBarTheme( - foregroundColor: Colors.white, - systemOverlayStyle: SystemUiOverlayStyle( - systemNavigationBarColor: ColorPalette.appBackgroundColor, - statusBarColor: ColorPalette.appBackgroundColor, - ), - ), - tabBarTheme: TabBarTheme( - indicatorSize: TabBarIndicatorSize.tab, - labelStyle: Theme.of(context).textTheme.titleMedium, - unselectedLabelStyle: - Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w500, - ), - indicatorColor: ColorPalette.dReaderYellow100, - labelColor: ColorPalette.dReaderYellow100, - unselectedLabelColor: ColorPalette.greyscale200, - dividerColor: ColorPalette.greyscale200, - indicator: const UnderlineTabIndicator( - borderSide: BorderSide( - width: 4, - color: ColorPalette.dReaderYellow100, - ), - ), - ), - navigationBarTheme: NavigationBarThemeData( - height: 48, - labelTextStyle: WidgetStateProperty.resolveWith( - (Set states) => TextStyle( - color: states.contains(WidgetState.selected) - ? ColorPalette.dReaderYellow100 - : Colors.white, - fontSize: 12, - fontWeight: FontWeight.w500, - letterSpacing: .2, - ), - ), - ), - progressIndicatorTheme: const ProgressIndicatorThemeData( - color: ColorPalette.dReaderYellow100, - ), - textSelectionTheme: const TextSelectionThemeData( - cursorColor: Colors.white, - selectionColor: ColorPalette.dReaderBlue, - selectionHandleColor: ColorPalette.dReaderYellow100, - ), - dialogTheme: DialogTheme( - backgroundColor: ColorPalette.greyscale400, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - contentTextStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.white, - ), - titleTextStyle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w700, - color: Colors.white, - ), - ), - pageTransitionsTheme: const PageTransitionsTheme(builders: { - TargetPlatform.android: CupertinoPageTransitionsBuilder(), - TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), - }), - scaffoldBackgroundColor: ColorPalette.appBackgroundColor, - fontFamily: 'Satoshi', - unselectedWidgetColor: ColorPalette.greyscale100, - textTheme: getTextTheme(), - ), - debugShowCheckedModeBanner: false, - supportedLocales: const [ - Locale('en', ''), - ], - ); - } -} diff --git a/lib/shared/domain/providers/solana/solana_transaction_notifier.dart b/lib/shared/domain/providers/solana/solana_transaction_notifier.dart index 300e49e3..c6ce0f95 100644 --- a/lib/shared/domain/providers/solana/solana_transaction_notifier.dart +++ b/lib/shared/domain/providers/solana/solana_transaction_notifier.dart @@ -125,7 +125,8 @@ class SolanaTransactionNotifier extends _$SolanaTransactionNotifier { return Left( AppException( - message: 'Something got broken. We are working on fix.', + message: + 'Unknown error occurred and report has been sent to our development team.', identifier: 'SolanaTransactionNotifier._signAndSendMint', statusCode: 500, ), diff --git a/lib/shared/utils/render_carrot_error.dart b/lib/shared/utils/render_carrot_error.dart index a2f0852d..09635bfb 100644 --- a/lib/shared/utils/render_carrot_error.dart +++ b/lib/shared/utils/render_carrot_error.dart @@ -3,12 +3,13 @@ import 'package:d_reader_flutter/shared/widgets/unsorted/carrot_error_widget.dar import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +const String _noInternetMessage = 'No internet connection'; + Widget renderCarrotErrorWidget(WidgetRef ref) { return ref.watch(internetAccessProvider).when( data: (hasInternet) { return CarrotErrorWidget( - mainErrorText: - hasInternet ? 'Something broke!' : 'No internet connection', + mainErrorText: hasInternet ? defaultErrorMessage : _noInternetMessage, ); }, error: (error, stackTrace) { diff --git a/lib/shared/widgets/unsorted/carrot_error_widget.dart b/lib/shared/widgets/unsorted/carrot_error_widget.dart index 64d89227..c19845df 100644 --- a/lib/shared/widgets/unsorted/carrot_error_widget.dart +++ b/lib/shared/widgets/unsorted/carrot_error_widget.dart @@ -2,13 +2,17 @@ import 'package:d_reader_flutter/shared/theme/app_colors.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +const defaultErrorMessage = 'Something broke!'; +const defaultAdviceText = + 'Try resetting the app and make sure it\'s running on the latest version'; + class CarrotErrorScaffold extends StatelessWidget { final String adviceText, errorText; - const CarrotErrorScaffold( - {super.key, - this.adviceText = - 'Try resetting the app and make sure it\'s running on the latest version', - this.errorText = 'Something broke!'}); + const CarrotErrorScaffold({ + super.key, + this.adviceText = defaultAdviceText, + this.errorText = defaultErrorMessage, + }); @override Widget build(BuildContext context) { @@ -29,9 +33,8 @@ class CarrotErrorWidget extends StatelessWidget { final Widget? additionalChild; const CarrotErrorWidget({ super.key, - this.adviceText = - 'Try resetting the app and make sure it\'s running on the latest version', - this.mainErrorText = 'Something broke!', + this.adviceText = defaultAdviceText, + this.mainErrorText = defaultErrorMessage, this.height = 300, this.padding, this.additionalChild, diff --git a/publishing/.asset-manifest.json b/publishing/.asset-manifest.json index 36fba0ae..96150643 100644 --- a/publishing/.asset-manifest.json +++ b/publishing/.asset-manifest.json @@ -12,8 +12,8 @@ }, "./dReader.apk": { "path": "./dReader.apk", - "sha256": "NzEjG86RSew9IrcrlXLYczlagdoEhNiP7x6lUCEjagc=", - "uri": "https://s3.us-east-1.amazonaws.com/d-reader-main-mainnet/jES9GGtHuhRQjxsCqGGO" + "sha256": "/YHh8fslT8lhQKAr0WK+aNYc8abKOF5YmBzReVRAQXQ=", + "uri": "https://s3.us-east-1.amazonaws.com/d-reader-main-mainnet/NvCO3AeMcOnxfQKWG3bP" }, "./screenshots/1.png": { "path": "./screenshots/1.png", diff --git a/publishing/config.yaml b/publishing/config.yaml index 8874d714..d36eb8ae 100644 --- a/publishing/config.yaml +++ b/publishing/config.yaml @@ -19,7 +19,7 @@ app: - purpose: icon uri: ./512x512_rounded.png release: - address: AL9CRWtAk4TPXHQvvbaUhhNeYhFizTnxtXwdudmFHvqw + address: 49s8uRH6RwcPpU2Dnh8e6NFVs94qgcEYe9g69KC1RjTx media: - purpose: icon uri: ./512x512_rounded.png @@ -41,14 +41,14 @@ release: name: dReader short_description: Digital comic book store long_description: Platform for discovering, trading, collecting, and reading digital graphic novels - new_in_version: Fetch twitter content from API + new_in_version: After mint improvements saga_features: Exclusive early access, free/discounted collectible comics solana_mobile_dapp_publisher_portal: google_store_package: io.app.dreader testing_instructions: Run the app lastSubmittedVersionOnChain: - address: AL9CRWtAk4TPXHQvvbaUhhNeYhFizTnxtXwdudmFHvqw - version_code: 2048 - apk_hash: NzEjG86RSew9IrcrlXLYczlagdoEhNiP7x6lUCEjagc= + address: 49s8uRH6RwcPpU2Dnh8e6NFVs94qgcEYe9g69KC1RjTx + version_code: 2049 + apk_hash: /YHh8fslT8lhQKAr0WK+aNYc8abKOF5YmBzReVRAQXQ= lastUpdatedVersionOnStore: - address: AL9CRWtAk4TPXHQvvbaUhhNeYhFizTnxtXwdudmFHvqw + address: 49s8uRH6RwcPpU2Dnh8e6NFVs94qgcEYe9g69KC1RjTx diff --git a/pubspec.yaml b/pubspec.yaml index 0f043b6b..20b44e6e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: dReader Flutter app. publish_to: 'none' -version: 1.3.8+48 # # dappStore: 1.3.7+47 (next release: 1.3.8+48) google play: 1.3.7+76 (next release: 1.3.8+77) +version: 1.3.9+49 # # dappStore: 1.3.9+49 (next release: 1.4.0+50) google play: 1.3.9+79 (next release: 1.4.0+80) environment: sdk: '>=3.0.0 <4.0.0' diff --git a/test/widget_test.dart b/test/widget_test.dart index 5cfb8439..998b434f 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -8,7 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:d_reader_flutter/main_dev.dart'; +import 'package:d_reader_flutter/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async {