diff --git a/board_games_companion/lib/common/animation_tags.dart b/board_games_companion/lib/common/animation_tags.dart index 6b2bc1fd..ae09689d 100644 --- a/board_games_companion/lib/common/animation_tags.dart +++ b/board_games_companion/lib/common/animation_tags.dart @@ -1,4 +1,5 @@ class AnimationTags { - static const String boardGameHeroTag = 'boardGameDetalsImageHeroTag'; + static const String boardGameHeroTag = 'boardGameDetailsImageHeroTag'; + static const String gameSpinnerBoardGameHeroTag = 'gameSpinnerImageHeroTag'; static const String playerImageHeroTag = 'playerHeroTag'; } diff --git a/board_games_companion/lib/common/app_styles.dart b/board_games_companion/lib/common/app_styles.dart index ea90fbf2..b50664ba 100644 --- a/board_games_companion/lib/common/app_styles.dart +++ b/board_games_companion/lib/common/app_styles.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +import 'app_colors.dart'; +import 'app_theme.dart'; + class AppStyles { static const double _maxOpacityDecimal = 255; static const double _hundredPercent = 100; @@ -35,4 +38,18 @@ class AppStyles { ); static const double panelContainerCornerRadius = defaultCornerRadius * 3; + + static const BoxDecoration tileGradientBoxDecoration = BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + stops: [0.2, 0.5, 0.9], + colors: [ + AppColors.endDefaultPageBackgroundColorGradient, + AppColors.startDefaultPageBackgroundColorGradient, + AppColors.endDefaultPageBackgroundColorGradient, + ], + ), + borderRadius: AppTheme.defaultBorderRadius, + ); } diff --git a/board_games_companion/lib/common/app_text.dart b/board_games_companion/lib/common/app_text.dart index 23a0ae59..56dc926c 100644 --- a/board_games_companion/lib/common/app_text.dart +++ b/board_games_companion/lib/common/app_text.dart @@ -21,9 +21,13 @@ class AppText { static const dayBeforeYesteday = 'day before yesterday'; static const daysAgoFormat = '%s days ago'; - static const homePageGamesTabTitle = 'Games'; + static const ownedCollectionToggleButtonText = 'Owned'; + static const friendsCollectionToggleButtonText = 'Friends'; + static const whishlistCollectionToggleButtonText = 'Whishlist'; + + static const homePageCollectionsTabTitle = 'Collections'; static const homePageSearchTabTitle = 'Search'; - static const homePageGamesHistoryTabTitle = 'Play History'; + static const homePagePlaysTabTitle = 'Plays'; static const homePageGamesPlayersTabTitle = 'Players'; static const aboutPageAuthorSectionTitle = 'Author'; @@ -182,10 +186,6 @@ class AppText { static const settingsPageRestoreFailedMessage = 'Unfortunately we ran into a problem with restoring your data. Please try again or contact support at feedback@progrunning.net'; - static const playHistoryPageEmptyTitle = "You haven't played any games yet"; - static const playHistoryPageEmptyTextPartTwo = - 'Start recording your plays in the app and this screen will automatically populate with a history of your playthroughs.'; - static const gamePlaytimeFormat = '%s min'; static const gamePlayersSingularFormat = '%i players'; static const gamePlayersPluralFormat = '%i players'; @@ -195,7 +195,28 @@ class AppText { static const collectionsPageTitle = 'Collections'; static const settingsPageTitle = 'Settings'; static const newPlayerPageTitle = 'New Player'; - static const playHistoryPageTitle = 'Play History'; + static const playsPageTitle = 'Plays'; + + static const playsPageHistoryTabEmptyTitle = "You haven't played any games yet"; + static const playPageHistoryTabEmptySubtitle = + 'Start recording your plays in the app and this screen will automatically populate with a history of your playthroughs.'; + + static const playsPageSelectGameTabEmptyTitle = 'Your collections are empty'; + static const playsPageSelectGameTabEmptySubtitle = + 'Add board games to your collection(s) by searching for them or by importing them from your BGG collection(s).'; + + static const playsPageSelectGameNoBoardGamesToShuffleTitle = "There's no games to select from"; + static const playsPageSelectGameNoBoardGamesToShuffleSubtitle = + "There's no matches for the filters you picked. Refine them to spin the wheel."; + + static const playsPageHistoryTabTitle = 'History'; + static const playsPageStatisticsTabTitle = 'Stats'; + static const playsPageSelectGameTabTitle = 'Select Game'; + static const playsPageGameSpinnerFilterSectionTitle = 'Filters'; + static const playsPageGameSpinnerCollectionsFilter = 'Collections'; + static const playsPageGameSpinnerSpinButtonText = 'Spin'; + static const playsPageGameSpinnerSelectedGamePlayButtonText = 'Play'; + static const playsPageGameSpinnerSelectedGameSpinAgainButtonText = 'Spin Again'; static const drawerVersionFormat = 'Version %s'; static const drawerReleaseNotes = 'Release notes'; diff --git a/board_games_companion/lib/common/app_theme.dart b/board_games_companion/lib/common/app_theme.dart index 759bb020..2daf620b 100644 --- a/board_games_companion/lib/common/app_theme.dart +++ b/board_games_companion/lib/common/app_theme.dart @@ -22,7 +22,7 @@ class AppTheme { color: AppColors.secondaryTextColor, ); - static const BorderRadius defaultBoxRadius = BorderRadius.all( + static const BorderRadius defaultBorderRadius = BorderRadius.all( Radius.circular(AppStyles.boardGameTileImageCircularRadius), ); @@ -119,6 +119,27 @@ class AppTheme { buttonTheme: ThemeData.light().buttonTheme.copyWith( buttonColor: AppColors.accentColor, ), + toggleButtonsTheme: ThemeData.light().toggleButtonsTheme.copyWith( + selectedColor: AppColors.accentColor, + color: AppColors.deselectedTabIconColor, + splashColor: AppColors.accentColor.withAlpha(AppStyles.opacity30Percent), + fillColor: Colors.transparent, + selectedBorderColor: Colors.transparent, + disabledBorderColor: Colors.transparent, + borderColor: Colors.transparent, + textStyle: TextStyle( + fontSize: Dimensions.smallFontSize, + color: MaterialStateColor.resolveWith( + (states) { + if (states.contains(MaterialState.selected)) { + return AppColors.accentColor; + } + + return AppColors.deselectedTabIconColor; + }, + ), + ), + ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom(foregroundColor: AppColors.accentColor), ), diff --git a/board_games_companion/lib/common/dimensions.dart b/board_games_companion/lib/common/dimensions.dart index 01c9d714..71127552 100644 --- a/board_games_companion/lib/common/dimensions.dart +++ b/board_games_companion/lib/common/dimensions.dart @@ -26,6 +26,8 @@ class Dimensions { static const double collectionSearchResultBoardGameImageHeight = 100; static const double collectionSearchResultBoardGameImageWidth = 100; + static const double gameSpinnerSelectedGameImageHeight = 200; + static const double collectionSearchResultExpansionsImageHeight = 80; static const double collectionSearchResultExpansionsImageWidth = 80; @@ -60,6 +62,11 @@ class Dimensions { static const double detailsItemHeight = 60; + static const double gameSpinnerHeight = 300; + static const double gameSpinnerMaxWidth = 800; + + static const double boardGameTileHeight = 200; + static const EdgeInsets snackbarMargin = EdgeInsets.only( left: Dimensions.standardSpacing, right: Dimensions.standardSpacing, diff --git a/board_games_companion/lib/common/enums/games_tab.dart b/board_games_companion/lib/common/enums/games_tab.dart index 8623b6c4..b2bad148 100644 --- a/board_games_companion/lib/common/enums/games_tab.dart +++ b/board_games_companion/lib/common/enums/games_tab.dart @@ -7,7 +7,7 @@ enum GamesTab { } extension ToGamesTab on int { - GamesTab toGamesTab() { + GamesTab toCollectionsTab() { switch (this) { case 0: return GamesTab.owned; diff --git a/board_games_companion/lib/common/enums/plays_tab.dart b/board_games_companion/lib/common/enums/plays_tab.dart new file mode 100644 index 00000000..521ca73b --- /dev/null +++ b/board_games_companion/lib/common/enums/plays_tab.dart @@ -0,0 +1,18 @@ +enum PlaysTab { + history, + statistics, + selectGame, +} + +extension ToPlaysTab on int { + PlaysTab toPlaysTab() { + switch (this) { + case 0: + return PlaysTab.history; + case 1: + return PlaysTab.selectGame; + } + + return PlaysTab.history; + } +} diff --git a/board_games_companion/lib/extensions/board_game_extentions.dart b/board_games_companion/lib/extensions/board_game_extentions.dart new file mode 100644 index 00000000..3c87da7a --- /dev/null +++ b/board_games_companion/lib/extensions/board_game_extentions.dart @@ -0,0 +1,16 @@ +import 'package:board_games_companion/models/hive/board_game_details.dart'; + +import '../common/enums/collection_type.dart'; + +extension BoardGameDetailsExtensions on Iterable { + Iterable inCollection(CollectionType collectionType) { + switch (collectionType) { + case CollectionType.owned: + return where((boardGame) => boardGame.isOwned ?? false); + case CollectionType.friends: + return where((boardGame) => boardGame.isFriends ?? false); + case CollectionType.wishlist: + return where((boardGame) => boardGame.isOnWishlist ?? false); + } + } +} diff --git a/board_games_companion/lib/injectable.config.dart b/board_games_companion/lib/injectable.config.dart index aac7165c..816659ff 100644 --- a/board_games_companion/lib/injectable.config.dart +++ b/board_games_companion/lib/injectable.config.dart @@ -10,20 +10,19 @@ import 'package:get_it/get_it.dart' as _i1; import 'package:injectable/injectable.dart' as _i2; import 'pages/board_game_details/board_game_details_view_model.dart' as _i41; +import 'pages/collections/collection_search_result_view_model.dart' as _i29; +import 'pages/collections/collections_view_model.dart' as _i30; import 'pages/edit_playthrough/edit_playthrough_view_model.dart' as _i42; -import 'pages/edit_playthrough/playthrough_note_view_model.dart' as _i32; -import 'pages/games/collection_search_result_view_model.dart' as _i29; -import 'pages/games/games_view_model.dart' as _i31; +import 'pages/edit_playthrough/playthrough_note_view_model.dart' as _i33; import 'pages/home/home_view_model.dart' as _i43; import 'pages/player/player_view_model.dart' as _i24; import 'pages/players/players_view_model.dart' as _i11; -import 'pages/playthroughs/playthrough_statistics_view_model.dart' as _i33; -import 'pages/playthroughs/playthroughs_game_settings_view_model.dart' as _i34; +import 'pages/plays/plays_view_model.dart' as _i32; +import 'pages/playthroughs/playthrough_statistics_view_model.dart' as _i34; +import 'pages/playthroughs/playthroughs_game_settings_view_model.dart' as _i35; import 'pages/playthroughs/playthroughs_history_view_model.dart' as _i36; import 'pages/playthroughs/playthroughs_log_game_view_model.dart' as _i37; import 'pages/playthroughs/playthroughs_view_model.dart' as _i38; -import 'pages/playthroughs_history/playthroughs_history_view_model.dart' - as _i35; import 'pages/search_board_games/search_board_games_view_model.dart' as _i39; import 'pages/settings/settings_view_model.dart' as _i40; import 'services/analytics_service.dart' as _i20; @@ -42,7 +41,7 @@ import 'services/user_service.dart' as _i18; import 'stores/app_store.dart' as _i3; import 'stores/board_games_filters_store.dart' as _i21; import 'stores/board_games_store.dart' as _i28; -import 'stores/game_playthroughs_details_store.dart' as _i30; +import 'stores/game_playthroughs_details_store.dart' as _i31; import 'stores/players_store.dart' as _i10; import 'stores/playthroughs_store.dart' as _i26; import 'stores/scores_store.dart' as _i15; @@ -102,10 +101,7 @@ _i1.GetIt $initGetIt(_i1.GetIt get, get<_i3.AppStore>())); gh.factory<_i29.CollectionSearchResultViewModel>( () => _i29.CollectionSearchResultViewModel(get<_i28.BoardGamesStore>())); - gh.singleton<_i30.GamePlaythroughsDetailsStore>( - _i30.GamePlaythroughsDetailsStore(get<_i26.PlaythroughsStore>(), - get<_i15.ScoresStore>(), get<_i10.PlayersStore>())); - gh.factory<_i31.GamesViewModel>(() => _i31.GamesViewModel( + gh.factory<_i30.CollectionsViewModel>(() => _i30.CollectionsViewModel( get<_i19.UserStore>(), get<_i28.BoardGamesStore>(), get<_i21.BoardGamesFiltersStore>(), @@ -114,30 +110,32 @@ _i1.GetIt $initGetIt(_i1.GetIt get, get<_i10.PlayersStore>(), get<_i17.SearchStore>(), get<_i20.AnalyticsService>())); - gh.factory<_i32.PlaythroughNoteViewModel>(() => - _i32.PlaythroughNoteViewModel(get<_i30.GamePlaythroughsDetailsStore>())); - gh.singleton<_i33.PlaythroughStatisticsViewModel>( - _i33.PlaythroughStatisticsViewModel(get<_i9.PlayerService>(), - get<_i15.ScoresStore>(), get<_i30.GamePlaythroughsDetailsStore>())); - gh.factory<_i34.PlaythroughsGameSettingsViewModel>(() => - _i34.PlaythroughsGameSettingsViewModel(get<_i28.BoardGamesStore>(), - get<_i30.GamePlaythroughsDetailsStore>())); - gh.factory<_i35.PlaythroughsHistoryViewModel>(() => - _i35.PlaythroughsHistoryViewModel( - get<_i26.PlaythroughsStore>(), - get<_i28.BoardGamesStore>(), - get<_i10.PlayersStore>(), - get<_i15.ScoresStore>())); + gh.singleton<_i31.GamePlaythroughsDetailsStore>( + _i31.GamePlaythroughsDetailsStore(get<_i26.PlaythroughsStore>(), + get<_i15.ScoresStore>(), get<_i10.PlayersStore>())); + gh.factory<_i32.PlaysViewModel>(() => _i32.PlaysViewModel( + get<_i26.PlaythroughsStore>(), + get<_i28.BoardGamesStore>(), + get<_i10.PlayersStore>(), + get<_i15.ScoresStore>())); + gh.factory<_i33.PlaythroughNoteViewModel>(() => + _i33.PlaythroughNoteViewModel(get<_i31.GamePlaythroughsDetailsStore>())); + gh.singleton<_i34.PlaythroughStatisticsViewModel>( + _i34.PlaythroughStatisticsViewModel(get<_i9.PlayerService>(), + get<_i15.ScoresStore>(), get<_i31.GamePlaythroughsDetailsStore>())); + gh.factory<_i35.PlaythroughsGameSettingsViewModel>(() => + _i35.PlaythroughsGameSettingsViewModel(get<_i28.BoardGamesStore>(), + get<_i31.GamePlaythroughsDetailsStore>())); gh.factory<_i36.PlaythroughsHistoryViewModel>(() => _i36.PlaythroughsHistoryViewModel( - get<_i30.GamePlaythroughsDetailsStore>())); + get<_i31.GamePlaythroughsDetailsStore>())); gh.factory<_i37.PlaythroughsLogGameViewModel>(() => _i37.PlaythroughsLogGameViewModel( get<_i10.PlayersStore>(), - get<_i30.GamePlaythroughsDetailsStore>(), + get<_i31.GamePlaythroughsDetailsStore>(), get<_i20.AnalyticsService>())); gh.factory<_i38.PlaythroughsViewModel>(() => _i38.PlaythroughsViewModel( - get<_i30.GamePlaythroughsDetailsStore>(), + get<_i31.GamePlaythroughsDetailsStore>(), get<_i10.PlayersStore>(), get<_i20.AnalyticsService>(), get<_i23.BoardGamesService>(), @@ -162,15 +160,15 @@ _i1.GetIt $initGetIt(_i1.GetIt get, _i41.BoardGameDetailsViewModel( get<_i28.BoardGamesStore>(), get<_i20.AnalyticsService>())); gh.factory<_i42.EditPlaythoughViewModel>(() => _i42.EditPlaythoughViewModel( - get<_i30.GamePlaythroughsDetailsStore>(), get<_i28.BoardGamesStore>())); + get<_i31.GamePlaythroughsDetailsStore>(), get<_i28.BoardGamesStore>())); gh.factory<_i43.HomeViewModel>(() => _i43.HomeViewModel( get<_i20.AnalyticsService>(), get<_i13.RateAndReviewService>(), get<_i11.PlayersViewModel>(), get<_i21.BoardGamesFiltersStore>(), - get<_i31.GamesViewModel>(), + get<_i30.CollectionsViewModel>(), get<_i39.SearchBoardGamesViewModel>(), - get<_i35.PlaythroughsHistoryViewModel>())); + get<_i32.PlaysViewModel>())); return get; } diff --git a/board_games_companion/lib/models/hive/board_game_details.dart b/board_games_companion/lib/models/hive/board_game_details.dart index 6f6e7418..bed1a87d 100644 --- a/board_games_companion/lib/models/hive/board_game_details.dart +++ b/board_games_companion/lib/models/hive/board_game_details.dart @@ -17,6 +17,8 @@ import 'board_game_publisher.dart'; import 'board_game_rank.dart'; import 'board_game_settings.dart'; +export '../../extensions/board_game_extentions.dart'; + part 'board_game_details.freezed.dart'; part 'board_game_details.g.dart'; diff --git a/board_games_companion/lib/pages/base_page_state.dart b/board_games_companion/lib/pages/base_page_state.dart index 242c0ef1..7d8e2209 100644 --- a/board_games_companion/lib/pages/base_page_state.dart +++ b/board_games_companion/lib/pages/base_page_state.dart @@ -47,7 +47,7 @@ abstract class BasePageState extends State { ], ), elevation: Dimensions.defaultElevation, - actions: [ + actions: [ TextButton( child: const Text( AppText.aontAskAgain, diff --git a/board_games_companion/lib/pages/board_game_details/board_game_details_page.dart b/board_games_companion/lib/pages/board_game_details/board_game_details_page.dart index 58281e75..110e470e 100644 --- a/board_games_companion/lib/pages/board_game_details/board_game_details_page.dart +++ b/board_games_companion/lib/pages/board_game_details/board_game_details_page.dart @@ -1,3 +1,4 @@ +import 'package:board_games_companion/common/app_text.dart'; import 'package:board_games_companion/widgets/elevated_container.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -18,6 +19,7 @@ import '../../stores/board_games_store.dart'; import '../../utilities/launcher_helper.dart'; import '../../widgets/board_games/board_game_image.dart'; import '../../widgets/board_games/board_game_rating_hexagon.dart'; +import '../../widgets/common/collection_toggle_button.dart'; import '../../widgets/common/default_icon.dart'; import '../../widgets/common/elevated_icon_button.dart'; import '../../widgets/common/loading_indicator_widget.dart'; @@ -630,22 +632,16 @@ class _CollectionFlags extends StatelessWidget { return Column( children: [ ToggleButtons( - splashColor: AppColors.accentColor.withAlpha(AppStyles.opacity30Percent), - fillColor: Colors.transparent, - selectedColor: Colors.white, - selectedBorderColor: Colors.transparent, - disabledBorderColor: Colors.transparent, - borderColor: Colors.transparent, isSelected: [isOnFriendsList, isOnWishlist], children: [ - _CollectionFlag( + CollectionToggleButton( icon: Icons.group, - title: 'Friends', + title: AppText.friendsCollectionToggleButtonText, isSelected: isOnFriendsList, ), - _CollectionFlag( + CollectionToggleButton( icon: Icons.card_giftcard, - title: 'Wishlist', + title: AppText.whishlistCollectionToggleButtonText, isSelected: isOnWishlist, ), ], @@ -654,16 +650,11 @@ class _CollectionFlags extends StatelessWidget { ), ), ToggleButtons( - splashColor: AppColors.accentColor.withAlpha(AppStyles.opacity30Percent), - fillColor: Colors.transparent, - selectedColor: Colors.white, - selectedBorderColor: Colors.transparent, - borderColor: Colors.transparent, isSelected: [isOwned], children: [ - _CollectionFlag( + CollectionToggleButton( icon: Icons.grid_on, - title: 'Owned', + title: AppText.ownedCollectionToggleButtonText, isSelected: isOwned, ), ], @@ -674,47 +665,6 @@ class _CollectionFlags extends StatelessWidget { } } -class _CollectionFlag extends StatelessWidget { - const _CollectionFlag({ - required this.icon, - required this.title, - required this.isSelected, - Key? key, - }) : super(key: key); - - final IconData icon; - final String title; - final bool isSelected; - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: Dimensions.standardSpacing, vertical: Dimensions.halfStandardSpacing), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - icon, - color: isSelected ? AppColors.accentColor : AppColors.deselectedTabIconColor, - ), - Text( - title, - style: TextStyle( - fontSize: Dimensions.smallFontSize, - color: isSelected ? AppColors.defaultTextColor : AppColors.secondaryTextColor, - ), - ), - ], - ), - ), - ], - ); - } -} - class _FirstRowGeneralInfoPanels extends StatelessWidget { const _FirstRowGeneralInfoPanels({ required this.boardGameDetails, diff --git a/board_games_companion/lib/pages/games/collection_search_result_view_model.dart b/board_games_companion/lib/pages/collections/collection_search_result_view_model.dart similarity index 100% rename from board_games_companion/lib/pages/games/collection_search_result_view_model.dart rename to board_games_companion/lib/pages/collections/collection_search_result_view_model.dart diff --git a/board_games_companion/lib/pages/games/collection_search_result_view_model.g.dart b/board_games_companion/lib/pages/collections/collection_search_result_view_model.g.dart similarity index 100% rename from board_games_companion/lib/pages/games/collection_search_result_view_model.g.dart rename to board_games_companion/lib/pages/collections/collection_search_result_view_model.g.dart diff --git a/board_games_companion/lib/pages/games/games_filter_panel.dart b/board_games_companion/lib/pages/collections/collections_filter_panel.dart similarity index 93% rename from board_games_companion/lib/pages/games/games_filter_panel.dart rename to board_games_companion/lib/pages/collections/collections_filter_panel.dart index c62e018f..fd9ac49b 100644 --- a/board_games_companion/lib/pages/games/games_filter_panel.dart +++ b/board_games_companion/lib/pages/collections/collections_filter_panel.dart @@ -13,21 +13,21 @@ import '../../common/dimensions.dart'; import '../../common/enums/order_by.dart'; import '../../models/sort_by.dart'; import '../../widgets/board_games/board_game_rating_hexagon.dart'; -import 'games_view_model.dart'; +import 'collections_view_model.dart'; -class GamesFilterPanel extends StatefulWidget { - const GamesFilterPanel({ - required this.gamesViewModel, +class CollectionsFilterPanel extends StatefulWidget { + const CollectionsFilterPanel({ + required this.viewModel, Key? key, }) : super(key: key); - final GamesViewModel gamesViewModel; + final CollectionsViewModel viewModel; @override - GamesFilterPanelState createState() => GamesFilterPanelState(); + CollectionsFilterPanelState createState() => CollectionsFilterPanelState(); } -class GamesFilterPanelState extends State { +class CollectionsFilterPanelState extends State { @override Widget build(BuildContext context) { return SingleChildScrollView( @@ -38,8 +38,8 @@ class GamesFilterPanelState extends State { ), child: Column( children: [ - _SortBy(gamesViewModel: widget.gamesViewModel), - _Filters(gamesViewModel: widget.gamesViewModel), + _SortBy(gamesViewModel: widget.viewModel), + _Filters(gamesViewModel: widget.viewModel), const SizedBox(height: Dimensions.standardSpacing), Align( alignment: Alignment.bottomRight, @@ -49,8 +49,8 @@ class GamesFilterPanelState extends State { icon: const Icon(Icons.clear), title: AppText.filterGamesPanelClearFiltersButtonText, color: AppColors.accentColor, - onPressed: widget.gamesViewModel.anyFiltersApplied - ? () => widget.gamesViewModel.clearFilters() + onPressed: widget.viewModel.anyFiltersApplied + ? () => widget.viewModel.clearFilters() : null, ); }, @@ -66,11 +66,11 @@ class GamesFilterPanelState extends State { class _SortBy extends StatelessWidget { const _SortBy({ Key? key, - required GamesViewModel gamesViewModel, + required CollectionsViewModel gamesViewModel, }) : _gamesViewModel = gamesViewModel, super(key: key); - final GamesViewModel _gamesViewModel; + final CollectionsViewModel _gamesViewModel; @override Widget build(BuildContext context) { @@ -150,7 +150,7 @@ class _Filters extends StatelessWidget { Key? key, }) : super(key: key); - final GamesViewModel gamesViewModel; + final CollectionsViewModel gamesViewModel; @override Widget build(BuildContext context) { @@ -237,7 +237,7 @@ class _FilterNumberOfPlayersSlider extends StatelessWidget { Key? key, }) : super(key: key); - final GamesViewModel gamesViewModel; + final CollectionsViewModel gamesViewModel; @override Widget build(BuildContext context) { diff --git a/board_games_companion/lib/pages/games/games_page.dart b/board_games_companion/lib/pages/collections/collections_page.dart similarity index 93% rename from board_games_companion/lib/pages/games/games_page.dart rename to board_games_companion/lib/pages/collections/collections_page.dart index 5332e60a..715be27c 100644 --- a/board_games_companion/lib/pages/games/games_page.dart +++ b/board_games_companion/lib/pages/collections/collections_page.dart @@ -4,7 +4,7 @@ import 'package:basics/basics.dart'; import 'package:board_games_companion/common/app_text.dart'; import 'package:board_games_companion/injectable.dart'; import 'package:board_games_companion/models/hive/search_history_entry.dart'; -import 'package:board_games_companion/pages/games/games_view_model.dart'; +import 'package:board_games_companion/pages/collections/collections_view_model.dart'; import 'package:board_games_companion/widgets/common/panel_container.dart'; import 'package:board_games_companion/widgets/common/slivers/bgc_sliver_header_delegate.dart'; import 'package:flutter/material.dart'; @@ -30,8 +30,10 @@ import '../../services/analytics_service.dart'; import '../../services/rate_and_review_service.dart'; import '../../stores/board_games_filters_store.dart'; import '../../widgets/board_games/board_game_tile.dart'; +import '../../widgets/common/app_bar/app_bar_bottom_tab.dart'; import '../../widgets/common/bgg_community_member_text_widget.dart'; import '../../widgets/common/bgg_community_member_user_name_text_field_widget.dart'; +import '../../widgets/common/board_game/board_game_property.dart'; import '../../widgets/common/default_icon.dart'; import '../../widgets/common/elevated_icon_button.dart'; import '../../widgets/common/generic_error_message_widget.dart'; @@ -42,7 +44,7 @@ import '../../widgets/common/rating_hexagon.dart'; import '../board_game_details/board_game_details_page.dart'; import '../playthroughs/playthroughs_page.dart'; import 'collection_search_result_view_model.dart'; -import 'games_filter_panel.dart'; +import 'collections_filter_panel.dart'; import 'search_suggestion.dart'; enum BoardGameResultActionType { @@ -57,8 +59,8 @@ typedef BoardGameResultAction = void Function( typedef SearchCallback = Future> Function(String query); -class GamesPage extends StatefulWidget { - const GamesPage( +class CollectionsPage extends StatefulWidget { + const CollectionsPage( this.viewModel, this.boardGamesFiltersStore, this.analyticsService, @@ -66,16 +68,16 @@ class GamesPage extends StatefulWidget { Key? key, }) : super(key: key); - final GamesViewModel viewModel; + final CollectionsViewModel viewModel; final BoardGamesFiltersStore boardGamesFiltersStore; final AnalyticsService analyticsService; final RateAndReviewService rateAndReviewService; @override - GamesPageState createState() => GamesPageState(); + CollectionsPageState createState() => CollectionsPageState(); } -class GamesPageState extends State +class CollectionsPageState extends State with SingleTickerProviderStateMixin, WidgetsBindingObserver { late TabController _topTabController; @@ -157,7 +159,7 @@ class _Collection extends StatelessWidget { Key? key, }) : super(key: key); - final GamesViewModel viewModel; + final CollectionsViewModel viewModel; final bool isCollectionEmpty; final bool hasMainGames; final List mainGames; @@ -232,7 +234,7 @@ class _AppBar extends StatefulWidget { Key? key, }) : super(key: key); - final GamesViewModel viewModel; + final CollectionsViewModel viewModel; final TabController topTabController; final AnalyticsService analyticsService; final RateAndReviewService rateAndReviewService; @@ -290,20 +292,20 @@ class _AppBarState extends State<_AppBar> { preferredSize: const Size.fromHeight(74), child: Observer(builder: (_) { return TabBar( - onTap: (int index) => widget.viewModel.selectedTab = index.toGamesTab(), + onTap: (int index) => widget.viewModel.selectedTab = index.toCollectionsTab(), controller: widget.topTabController, tabs: [ - _TopTab( + AppBarBottomTab( 'Owned', Icons.grid_on, isSelected: widget.viewModel.selectedTab == GamesTab.owned, ), - _TopTab( + AppBarBottomTab( 'Friends', Icons.group, isSelected: widget.viewModel.selectedTab == GamesTab.friends, ), - _TopTab( + AppBarBottomTab( 'Wishlist', Icons.card_giftcard, isSelected: widget.viewModel.selectedTab == GamesTab.wishlist, @@ -327,7 +329,7 @@ class _AppBarState extends State<_AppBar> { ), context: context, builder: (_) { - return GamesFilterPanel(gamesViewModel: widget.viewModel); + return CollectionsFilterPanel(viewModel: widget.viewModel); }, ); } @@ -345,7 +347,7 @@ class _AppBarState extends State<_AppBar> { boardGameId: boardGameDetails.id, boardGameName: boardGameDetails.name, boardGameImageHeroId: boardGameDetails.id, - navigatingFromType: GamesPage, + navigatingFromType: CollectionsPage, boardGameImageUrl: boardGameDetails.imageUrl, ), )); @@ -588,43 +590,6 @@ class _EmptyCollection extends StatelessWidget { } } -class _TopTab extends StatelessWidget { - const _TopTab( - this.title, - this.icon, { - this.isSelected = true, - Key? key, - }) : super(key: key); - - final String title; - final IconData icon; - final bool isSelected; - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - Tab( - icon: Icon( - icon, - color: isSelected ? AppColors.selectedTabIconColor : AppColors.deselectedTabIconColor, - ), - iconMargin: const EdgeInsets.only( - bottom: Dimensions.halfStandardSpacing, - ), - child: Text( - title, - style: AppTheme.titleTextStyle.copyWith( - fontSize: Dimensions.standardFontSize, - color: isSelected ? AppColors.defaultTextColor : AppColors.deselectedTabIconColor, - ), - ), - ), - ], - ); - } -} - class _CollectionsSearch extends SearchDelegate { _CollectionsSearch({ required this.viewModel, @@ -634,7 +599,7 @@ class _CollectionsSearch extends SearchDelegate { static const int _maxSearchHistoryEntriesToShow = 10; - final GamesViewModel viewModel; + final CollectionsViewModel viewModel; final BoardGameResultAction onResultAction; final SearchCallback onSearch; @@ -1004,6 +969,7 @@ class _SearchResultGameDetails extends StatelessWidget { final BoardGameDetails boardGame; static const double _gameStatIconSize = 16; + static const double _gamePropertyIconSize = 20; @override Widget build(BuildContext context) { @@ -1016,20 +982,23 @@ class _SearchResultGameDetails extends StatelessWidget { style: AppTheme.theme.textTheme.bodyLarge, ), const SizedBox(height: Dimensions.standardSpacing), - _SearchResultGameGeneralStats( + BoardGameProperty( icon: const Icon(Icons.people, size: _gameStatIconSize), - statistic: boardGame.playersFormatted, + iconWidth: _gamePropertyIconSize, + propertyName: boardGame.playersFormatted, ), const SizedBox(height: Dimensions.standardSpacing), - _SearchResultGameGeneralStats( + BoardGameProperty( icon: const Icon(Icons.hourglass_bottom, size: _gameStatIconSize), - statistic: boardGame.playtimeFormatted, + iconWidth: _gamePropertyIconSize, + propertyName: boardGame.playtimeFormatted, ), if (boardGame.avgWeight != null) ...[ const SizedBox(height: Dimensions.standardSpacing), - _SearchResultGameGeneralStats( + BoardGameProperty( icon: const FaIcon(FontAwesomeIcons.scaleUnbalanced, size: _gameStatIconSize), - statistic: sprintf( + iconWidth: _gamePropertyIconSize, + propertyName: sprintf( AppText.gamesPageSearchResultComplexityGameStatFormat, [boardGame.avgWeight!.toStringAsFixed(2)], ), @@ -1037,9 +1006,10 @@ class _SearchResultGameDetails extends StatelessWidget { ], if (boardGame.rating != null) ...[ const SizedBox(height: Dimensions.standardSpacing), - _SearchResultGameGeneralStats( + BoardGameProperty( icon: const RatingHexagon(width: _gameStatIconSize, height: _gameStatIconSize), - statistic: + iconWidth: _gamePropertyIconSize, + propertyName: boardGame.rating!.toStringAsFixed(Constants.boardGameRatingNumberOfDecimalPlaces), ), ] @@ -1076,35 +1046,6 @@ class _SearchResultGameActions extends StatelessWidget { } } -class _SearchResultGameGeneralStats extends StatelessWidget { - const _SearchResultGameGeneralStats({ - Key? key, - required this.icon, - required this.statistic, - }) : super(key: key); - - final Widget icon; - final String statistic; - - static const double _uniformedIconSize = 20; - - @override - Widget build(BuildContext context) { - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox(width: _uniformedIconSize, child: Center(child: icon)), - const SizedBox(width: Dimensions.standardSpacing), - Text( - statistic, - overflow: TextOverflow.ellipsis, - style: AppTheme.subTitleTextStyle.copyWith(color: AppColors.whiteColor), - ), - ], - ); - } -} - class _NoSearchResults extends StatelessWidget { const _NoSearchResults({ Key? key, diff --git a/board_games_companion/lib/pages/games/games_view_model.dart b/board_games_companion/lib/pages/collections/collections_view_model.dart similarity index 98% rename from board_games_companion/lib/pages/games/games_view_model.dart rename to board_games_companion/lib/pages/collections/collections_view_model.dart index b23bac13..26bcd057 100644 --- a/board_games_companion/lib/pages/games/games_view_model.dart +++ b/board_games_companion/lib/pages/collections/collections_view_model.dart @@ -31,13 +31,13 @@ import '../../models/hive/board_game_details.dart'; import '../../stores/board_games_store.dart'; import '../../stores/players_store.dart'; -part 'games_view_model.g.dart'; +part 'collections_view_model.g.dart'; @injectable -class GamesViewModel = _GamesViewModel with _$GamesViewModel; +class CollectionsViewModel = _CollectionsViewModel with _$CollectionsViewModel; -abstract class _GamesViewModel with Store { - _GamesViewModel( +abstract class _CollectionsViewModel with Store { + _CollectionsViewModel( this._userStore, this._boardGamesStore, this._boardGamesFiltersStore, diff --git a/board_games_companion/lib/pages/games/games_view_model.g.dart b/board_games_companion/lib/pages/collections/collections_view_model.g.dart similarity index 72% rename from board_games_companion/lib/pages/games/games_view_model.g.dart rename to board_games_companion/lib/pages/collections/collections_view_model.g.dart index db03cbd4..b5ff0b73 100644 --- a/board_games_companion/lib/pages/games/games_view_model.g.dart +++ b/board_games_companion/lib/pages/collections/collections_view_model.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'games_view_model.dart'; +part of 'collections_view_model.dart'; // ************************************************************************** // StoreGenerator @@ -8,20 +8,20 @@ part of 'games_view_model.dart'; // ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers -mixin _$GamesViewModel on _GamesViewModel, Store { +mixin _$CollectionsViewModel on _CollectionsViewModel, Store { Computed>? _$searchHistoryComputed; @override List get searchHistory => (_$searchHistoryComputed ??= Computed>(() => super.searchHistory, - name: '_GamesViewModel.searchHistory')) + name: '_CollectionsViewModel.searchHistory')) .value; Computed>? _$allMainGamesComputed; @override List get allMainGames => (_$allMainGamesComputed ??= Computed>(() => super.allMainGames, - name: '_GamesViewModel.allMainGames')) + name: '_CollectionsViewModel.allMainGames')) .value; Computed>? _$_mainBoardGameByExpansionIdComputed; @@ -31,35 +31,35 @@ mixin _$GamesViewModel on _GamesViewModel, Store { (_$_mainBoardGameByExpansionIdComputed ??= Computed>( () => super._mainBoardGameByExpansionId, - name: '_GamesViewModel._mainBoardGameByExpansionId')) + name: '_CollectionsViewModel._mainBoardGameByExpansionId')) .value; Computed>? _$sortByOptionsComputed; @override List get sortByOptions => (_$sortByOptionsComputed ??= Computed>(() => super.sortByOptions, - name: '_GamesViewModel.sortByOptions')) + name: '_CollectionsViewModel.sortByOptions')) .value; Computed? _$selectedSortByComputed; @override SortBy? get selectedSortBy => (_$selectedSortByComputed ??= Computed( () => super.selectedSortBy, - name: '_GamesViewModel.selectedSortBy')) + name: '_CollectionsViewModel.selectedSortBy')) .value; Computed? _$anyFiltersAppliedComputed; @override bool get anyFiltersApplied => (_$anyFiltersAppliedComputed ??= Computed( () => super.anyFiltersApplied, - name: '_GamesViewModel.anyFiltersApplied')) + name: '_CollectionsViewModel.anyFiltersApplied')) .value; Computed? _$filterByRatingComputed; @override double? get filterByRating => (_$filterByRatingComputed ??= Computed( () => super.filterByRating, - name: '_GamesViewModel.filterByRating')) + name: '_CollectionsViewModel.filterByRating')) .value; Computed>? _$filteredBoardGamesComputed; @@ -68,7 +68,7 @@ mixin _$GamesViewModel on _GamesViewModel, Store { (_$filteredBoardGamesComputed ??= Computed>( () => super.filteredBoardGames, - name: '_GamesViewModel.filteredBoardGames')) + name: '_CollectionsViewModel.filteredBoardGames')) .value; Computed? _$anyBoardGamesInCollectionsComputed; @@ -76,28 +76,28 @@ mixin _$GamesViewModel on _GamesViewModel, Store { bool get anyBoardGamesInCollections => (_$anyBoardGamesInCollectionsComputed ??= Computed( () => super.anyBoardGamesInCollections, - name: '_GamesViewModel.anyBoardGamesInCollections')) + name: '_CollectionsViewModel.anyBoardGamesInCollections')) .value; Computed? _$anyBoardGamesComputed; @override bool get anyBoardGames => (_$anyBoardGamesComputed ??= Computed(() => super.anyBoardGames, - name: '_GamesViewModel.anyBoardGames')) + name: '_CollectionsViewModel.anyBoardGames')) .value; Computed? _$minNumberOfPlayersComputed; @override double get minNumberOfPlayers => (_$minNumberOfPlayersComputed ??= Computed(() => super.minNumberOfPlayers, - name: '_GamesViewModel.minNumberOfPlayers')) + name: '_CollectionsViewModel.minNumberOfPlayers')) .value; Computed? _$filterByNumberOfPlayersComputed; @override int? get filterByNumberOfPlayers => (_$filterByNumberOfPlayersComputed ??= Computed(() => super.filterByNumberOfPlayers, - name: '_GamesViewModel.filterByNumberOfPlayers')) + name: '_CollectionsViewModel.filterByNumberOfPlayers')) .value; Computed? _$numberOfPlayersSliderValueComputed; @@ -105,21 +105,21 @@ mixin _$GamesViewModel on _GamesViewModel, Store { String get numberOfPlayersSliderValue => (_$numberOfPlayersSliderValueComputed ??= Computed( () => super.numberOfPlayersSliderValue, - name: '_GamesViewModel.numberOfPlayersSliderValue')) + name: '_CollectionsViewModel.numberOfPlayersSliderValue')) .value; Computed? _$maxNumberOfPlayersComputed; @override double get maxNumberOfPlayers => (_$maxNumberOfPlayersComputed ??= Computed(() => super.maxNumberOfPlayers, - name: '_GamesViewModel.maxNumberOfPlayers')) + name: '_CollectionsViewModel.maxNumberOfPlayers')) .value; Computed>? _$allBoardGamesComputed; @override List get allBoardGames => (_$allBoardGamesComputed ??= Computed>(() => super.allBoardGames, - name: '_GamesViewModel.allBoardGames')) + name: '_CollectionsViewModel.allBoardGames')) .value; Computed>? _$boardGamesInCollectionComputed; @@ -127,14 +127,14 @@ mixin _$GamesViewModel on _GamesViewModel, Store { List get boardGamesInCollection => (_$boardGamesInCollectionComputed ??= Computed>( () => super.boardGamesInCollection, - name: '_GamesViewModel.boardGamesInCollection')) + name: '_CollectionsViewModel.boardGamesInCollection')) .value; Computed? _$isCollectionEmptyComputed; @override bool get isCollectionEmpty => (_$isCollectionEmptyComputed ??= Computed( () => super.isCollectionEmpty, - name: '_GamesViewModel.isCollectionEmpty')) + name: '_CollectionsViewModel.isCollectionEmpty')) .value; Computed>? _$mainGamesInCollectionComputed; @@ -142,7 +142,7 @@ mixin _$GamesViewModel on _GamesViewModel, Store { List get mainGamesInCollection => (_$mainGamesInCollectionComputed ??= Computed>( () => super.mainGamesInCollection, - name: '_GamesViewModel.mainGamesInCollection')) + name: '_CollectionsViewModel.mainGamesInCollection')) .value; Computed>? _$expansionsInCollectionComputed; @@ -150,14 +150,14 @@ mixin _$GamesViewModel on _GamesViewModel, Store { List get expansionsInCollection => (_$expansionsInCollectionComputed ??= Computed>( () => super.expansionsInCollection, - name: '_GamesViewModel.expansionsInCollection')) + name: '_CollectionsViewModel.expansionsInCollection')) .value; Computed? _$anyMainGamesInCollectionComputed; @override bool get anyMainGamesInCollection => (_$anyMainGamesInCollectionComputed ??= Computed(() => super.anyMainGamesInCollection, - name: '_GamesViewModel.anyMainGamesInCollection')) + name: '_CollectionsViewModel.anyMainGamesInCollection')) .value; Computed? _$totalMainGamesInCollectionComputed; @@ -165,7 +165,7 @@ mixin _$GamesViewModel on _GamesViewModel, Store { int get totalMainGamesInCollection => (_$totalMainGamesInCollectionComputed ??= Computed( () => super.totalMainGamesInCollection, - name: '_GamesViewModel.totalMainGamesInCollection')) + name: '_CollectionsViewModel.totalMainGamesInCollection')) .value; Computed? _$totalExpansionsInCollectionComputed; @@ -173,14 +173,14 @@ mixin _$GamesViewModel on _GamesViewModel, Store { int get totalExpansionsInCollection => (_$totalExpansionsInCollectionComputed ??= Computed( () => super.totalExpansionsInCollection, - name: '_GamesViewModel.totalExpansionsInCollection')) + name: '_CollectionsViewModel.totalExpansionsInCollection')) .value; Computed? _$anyExpansionsInCollectionComputed; @override bool get anyExpansionsInCollection => (_$anyExpansionsInCollectionComputed ??= Computed(() => super.anyExpansionsInCollection, - name: '_GamesViewModel.anyExpansionsInCollection')) + name: '_CollectionsViewModel.anyExpansionsInCollection')) .value; Computed, List>>? _$expansionsInCollectionMapComputed; @@ -190,32 +190,32 @@ mixin _$GamesViewModel on _GamesViewModel, Store { get expansionsInCollectionMap => (_$expansionsInCollectionMapComputed ??= Computed, List>>( () => super.expansionsInCollectionMap, - name: '_GamesViewModel.expansionsInCollectionMap')) + name: '_CollectionsViewModel.expansionsInCollectionMap')) .value; Computed>? _$_allExpansionsComputed; @override List get _allExpansions => (_$_allExpansionsComputed ??= Computed>(() => super._allExpansions, - name: '_GamesViewModel._allExpansions')) + name: '_CollectionsViewModel._allExpansions')) .value; Computed? _$userNameComputed; @override String? get userName => (_$userNameComputed ??= Computed(() => super.userName, - name: '_GamesViewModel.userName')) + name: '_CollectionsViewModel.userName')) .value; Computed? _$isUserNameEmptyComputed; @override bool get isUserNameEmpty => (_$isUserNameEmptyComputed ??= Computed(() => super.isUserNameEmpty, - name: '_GamesViewModel.isUserNameEmpty')) + name: '_CollectionsViewModel.isUserNameEmpty')) .value; late final _$selectedTabAtom = - Atom(name: '_GamesViewModel.selectedTab', context: context); + Atom(name: '_CollectionsViewModel.selectedTab', context: context); @override GamesTab get selectedTab { @@ -230,8 +230,8 @@ mixin _$GamesViewModel on _GamesViewModel, Store { }); } - late final _$futureLoadBoardGamesAtom = - Atom(name: '_GamesViewModel.futureLoadBoardGames', context: context); + late final _$futureLoadBoardGamesAtom = Atom( + name: '_CollectionsViewModel.futureLoadBoardGames', context: context); @override ObservableFuture? get futureLoadBoardGames { @@ -248,90 +248,90 @@ mixin _$GamesViewModel on _GamesViewModel, Store { } late final _$searchAsyncAction = - AsyncAction('_GamesViewModel.search', context: context); + AsyncAction('_CollectionsViewModel.search', context: context); @override Future> search(String query) { return _$searchAsyncAction.run(() => super.search(query)); } - late final _$_GamesViewModelActionController = - ActionController(name: '_GamesViewModel', context: context); + late final _$_CollectionsViewModelActionController = + ActionController(name: '_CollectionsViewModel', context: context); @override void setSelectedTab(GamesTab newlySelectedTab) { - final _$actionInfo = _$_GamesViewModelActionController.startAction( - name: '_GamesViewModel.setSelectedTab'); + final _$actionInfo = _$_CollectionsViewModelActionController.startAction( + name: '_CollectionsViewModel.setSelectedTab'); try { return super.setSelectedTab(newlySelectedTab); } finally { - _$_GamesViewModelActionController.endAction(_$actionInfo); + _$_CollectionsViewModelActionController.endAction(_$actionInfo); } } @override void loadBoardGames() { - final _$actionInfo = _$_GamesViewModelActionController.startAction( - name: '_GamesViewModel.loadBoardGames'); + final _$actionInfo = _$_CollectionsViewModelActionController.startAction( + name: '_CollectionsViewModel.loadBoardGames'); try { return super.loadBoardGames(); } finally { - _$_GamesViewModelActionController.endAction(_$actionInfo); + _$_CollectionsViewModelActionController.endAction(_$actionInfo); } } @override void updateSortBySelection(SortBy sortBy) { - final _$actionInfo = _$_GamesViewModelActionController.startAction( - name: '_GamesViewModel.updateSortBySelection'); + final _$actionInfo = _$_CollectionsViewModelActionController.startAction( + name: '_CollectionsViewModel.updateSortBySelection'); try { return super.updateSortBySelection(sortBy); } finally { - _$_GamesViewModelActionController.endAction(_$actionInfo); + _$_CollectionsViewModelActionController.endAction(_$actionInfo); } } @override Future clearFilters() { - final _$actionInfo = _$_GamesViewModelActionController.startAction( - name: '_GamesViewModel.clearFilters'); + final _$actionInfo = _$_CollectionsViewModelActionController.startAction( + name: '_CollectionsViewModel.clearFilters'); try { return super.clearFilters(); } finally { - _$_GamesViewModelActionController.endAction(_$actionInfo); + _$_CollectionsViewModelActionController.endAction(_$actionInfo); } } @override Future changeNumberOfPlayers(int? numberOfPlayers) { - final _$actionInfo = _$_GamesViewModelActionController.startAction( - name: '_GamesViewModel.changeNumberOfPlayers'); + final _$actionInfo = _$_CollectionsViewModelActionController.startAction( + name: '_CollectionsViewModel.changeNumberOfPlayers'); try { return super.changeNumberOfPlayers(numberOfPlayers); } finally { - _$_GamesViewModelActionController.endAction(_$actionInfo); + _$_CollectionsViewModelActionController.endAction(_$actionInfo); } } @override Future updateNumberOfPlayersFilter() { - final _$actionInfo = _$_GamesViewModelActionController.startAction( - name: '_GamesViewModel.updateNumberOfPlayersFilter'); + final _$actionInfo = _$_CollectionsViewModelActionController.startAction( + name: '_CollectionsViewModel.updateNumberOfPlayersFilter'); try { return super.updateNumberOfPlayersFilter(); } finally { - _$_GamesViewModelActionController.endAction(_$actionInfo); + _$_CollectionsViewModelActionController.endAction(_$actionInfo); } } @override Future updateFilterByRating(double? rating) { - final _$actionInfo = _$_GamesViewModelActionController.startAction( - name: '_GamesViewModel.updateFilterByRating'); + final _$actionInfo = _$_CollectionsViewModelActionController.startAction( + name: '_CollectionsViewModel.updateFilterByRating'); try { return super.updateFilterByRating(rating); } finally { - _$_GamesViewModelActionController.endAction(_$actionInfo); + _$_CollectionsViewModelActionController.endAction(_$actionInfo); } } diff --git a/board_games_companion/lib/pages/collections/games_view_model.g.dart b/board_games_companion/lib/pages/collections/games_view_model.g.dart new file mode 100644 index 00000000..06093d37 --- /dev/null +++ b/board_games_companion/lib/pages/collections/games_view_model.g.dart @@ -0,0 +1,348 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'games_view_model.dart'; + +// ************************************************************************** +// StoreGenerator +// ************************************************************************** + +// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers + +mixin _$CollectionsViewModel on _GamesViewModel, Store { + Computed>? _$searchHistoryComputed; + + @override + List get searchHistory => + (_$searchHistoryComputed ??= Computed>(() => super.searchHistory, + name: '_GamesViewModel.searchHistory')) + .value; + Computed>? _$allMainGamesComputed; + + @override + List get allMainGames => + (_$allMainGamesComputed ??= Computed>(() => super.allMainGames, + name: '_GamesViewModel.allMainGames')) + .value; + Computed>? _$_mainBoardGameByExpansionIdComputed; + + @override + ObservableMap get _mainBoardGameByExpansionId => + (_$_mainBoardGameByExpansionIdComputed ??= Computed>( + () => super._mainBoardGameByExpansionId, + name: '_GamesViewModel._mainBoardGameByExpansionId')) + .value; + Computed>? _$sortByOptionsComputed; + + @override + List get sortByOptions => (_$sortByOptionsComputed ??= + Computed>(() => super.sortByOptions, name: '_GamesViewModel.sortByOptions')) + .value; + Computed? _$selectedSortByComputed; + + @override + SortBy? get selectedSortBy => (_$selectedSortByComputed ??= + Computed(() => super.selectedSortBy, name: '_GamesViewModel.selectedSortBy')) + .value; + Computed? _$anyFiltersAppliedComputed; + + @override + bool get anyFiltersApplied => (_$anyFiltersAppliedComputed ??= + Computed(() => super.anyFiltersApplied, name: '_GamesViewModel.anyFiltersApplied')) + .value; + Computed? _$filterByRatingComputed; + + @override + double? get filterByRating => (_$filterByRatingComputed ??= + Computed(() => super.filterByRating, name: '_GamesViewModel.filterByRating')) + .value; + Computed>? _$filteredBoardGamesComputed; + + @override + ObservableList get filteredBoardGames => (_$filteredBoardGamesComputed ??= + Computed>(() => super.filteredBoardGames, + name: '_GamesViewModel.filteredBoardGames')) + .value; + Computed? _$anyBoardGamesInCollectionsComputed; + + @override + bool get anyBoardGamesInCollections => (_$anyBoardGamesInCollectionsComputed ??= Computed( + () => super.anyBoardGamesInCollections, + name: '_GamesViewModel.anyBoardGamesInCollections')) + .value; + Computed? _$anyBoardGamesComputed; + + @override + bool get anyBoardGames => (_$anyBoardGamesComputed ??= + Computed(() => super.anyBoardGames, name: '_GamesViewModel.anyBoardGames')) + .value; + Computed? _$minNumberOfPlayersComputed; + + @override + double get minNumberOfPlayers => + (_$minNumberOfPlayersComputed ??= Computed(() => super.minNumberOfPlayers, + name: '_GamesViewModel.minNumberOfPlayers')) + .value; + Computed? _$filterByNumberOfPlayersComputed; + + @override + int? get filterByNumberOfPlayers => + (_$filterByNumberOfPlayersComputed ??= Computed(() => super.filterByNumberOfPlayers, + name: '_GamesViewModel.filterByNumberOfPlayers')) + .value; + Computed? _$numberOfPlayersSliderValueComputed; + + @override + String get numberOfPlayersSliderValue => (_$numberOfPlayersSliderValueComputed ??= + Computed(() => super.numberOfPlayersSliderValue, + name: '_GamesViewModel.numberOfPlayersSliderValue')) + .value; + Computed? _$maxNumberOfPlayersComputed; + + @override + double get maxNumberOfPlayers => + (_$maxNumberOfPlayersComputed ??= Computed(() => super.maxNumberOfPlayers, + name: '_GamesViewModel.maxNumberOfPlayers')) + .value; + Computed>? _$allBoardGamesComputed; + + @override + List get allBoardGames => + (_$allBoardGamesComputed ??= Computed>(() => super.allBoardGames, + name: '_GamesViewModel.allBoardGames')) + .value; + Computed>? _$boardGamesInCollectionComputed; + + @override + List get boardGamesInCollection => (_$boardGamesInCollectionComputed ??= + Computed>(() => super.boardGamesInCollection, + name: '_GamesViewModel.boardGamesInCollection')) + .value; + Computed? _$isCollectionEmptyComputed; + + @override + bool get isCollectionEmpty => (_$isCollectionEmptyComputed ??= + Computed(() => super.isCollectionEmpty, name: '_GamesViewModel.isCollectionEmpty')) + .value; + Computed>? _$mainGamesInCollectionComputed; + + @override + List get mainGamesInCollection => (_$mainGamesInCollectionComputed ??= + Computed>(() => super.mainGamesInCollection, + name: '_GamesViewModel.mainGamesInCollection')) + .value; + Computed>? _$expansionsInCollectionComputed; + + @override + List get expansionsInCollection => (_$expansionsInCollectionComputed ??= + Computed>(() => super.expansionsInCollection, + name: '_GamesViewModel.expansionsInCollection')) + .value; + Computed? _$anyMainGamesInCollectionComputed; + + @override + bool get anyMainGamesInCollection => + (_$anyMainGamesInCollectionComputed ??= Computed(() => super.anyMainGamesInCollection, + name: '_GamesViewModel.anyMainGamesInCollection')) + .value; + Computed? _$totalMainGamesInCollectionComputed; + + @override + int get totalMainGamesInCollection => (_$totalMainGamesInCollectionComputed ??= Computed( + () => super.totalMainGamesInCollection, + name: '_GamesViewModel.totalMainGamesInCollection')) + .value; + Computed? _$totalExpansionsInCollectionComputed; + + @override + int get totalExpansionsInCollection => (_$totalExpansionsInCollectionComputed ??= Computed( + () => super.totalExpansionsInCollection, + name: '_GamesViewModel.totalExpansionsInCollection')) + .value; + Computed? _$anyExpansionsInCollectionComputed; + + @override + bool get anyExpansionsInCollection => + (_$anyExpansionsInCollectionComputed ??= Computed(() => super.anyExpansionsInCollection, + name: '_GamesViewModel.anyExpansionsInCollection')) + .value; + Computed, List>>? + _$expansionsInCollectionMapComputed; + + @override + Map, List> get expansionsInCollectionMap => + (_$expansionsInCollectionMapComputed ??= + Computed, List>>( + () => super.expansionsInCollectionMap, + name: '_GamesViewModel.expansionsInCollectionMap')) + .value; + Computed>? _$_allExpansionsComputed; + + @override + List get _allExpansions => + (_$_allExpansionsComputed ??= Computed>(() => super._allExpansions, + name: '_GamesViewModel._allExpansions')) + .value; + Computed? _$userNameComputed; + + @override + String? get userName => (_$userNameComputed ??= + Computed(() => super.userName, name: '_GamesViewModel.userName')) + .value; + Computed? _$isUserNameEmptyComputed; + + @override + bool get isUserNameEmpty => (_$isUserNameEmptyComputed ??= + Computed(() => super.isUserNameEmpty, name: '_GamesViewModel.isUserNameEmpty')) + .value; + + late final _$selectedTabAtom = Atom(name: '_GamesViewModel.selectedTab', context: context); + + @override + GamesTab get selectedTab { + _$selectedTabAtom.reportRead(); + return super.selectedTab; + } + + @override + set selectedTab(GamesTab value) { + _$selectedTabAtom.reportWrite(value, super.selectedTab, () { + super.selectedTab = value; + }); + } + + late final _$futureLoadBoardGamesAtom = + Atom(name: '_GamesViewModel.futureLoadBoardGames', context: context); + + @override + ObservableFuture? get futureLoadBoardGames { + _$futureLoadBoardGamesAtom.reportRead(); + return super.futureLoadBoardGames; + } + + @override + set futureLoadBoardGames(ObservableFuture? value) { + _$futureLoadBoardGamesAtom.reportWrite(value, super.futureLoadBoardGames, () { + super.futureLoadBoardGames = value; + }); + } + + late final _$searchAsyncAction = AsyncAction('_GamesViewModel.search', context: context); + + @override + Future> search(String query) { + return _$searchAsyncAction.run(() => super.search(query)); + } + + late final _$_GamesViewModelActionController = + ActionController(name: '_GamesViewModel', context: context); + + @override + void setSelectedTab(GamesTab newlySelectedTab) { + final _$actionInfo = + _$_GamesViewModelActionController.startAction(name: '_GamesViewModel.setSelectedTab'); + try { + return super.setSelectedTab(newlySelectedTab); + } finally { + _$_GamesViewModelActionController.endAction(_$actionInfo); + } + } + + @override + void loadBoardGames() { + final _$actionInfo = + _$_GamesViewModelActionController.startAction(name: '_GamesViewModel.loadBoardGames'); + try { + return super.loadBoardGames(); + } finally { + _$_GamesViewModelActionController.endAction(_$actionInfo); + } + } + + @override + void updateSortBySelection(SortBy sortBy) { + final _$actionInfo = _$_GamesViewModelActionController.startAction( + name: '_GamesViewModel.updateSortBySelection'); + try { + return super.updateSortBySelection(sortBy); + } finally { + _$_GamesViewModelActionController.endAction(_$actionInfo); + } + } + + @override + Future clearFilters() { + final _$actionInfo = + _$_GamesViewModelActionController.startAction(name: '_GamesViewModel.clearFilters'); + try { + return super.clearFilters(); + } finally { + _$_GamesViewModelActionController.endAction(_$actionInfo); + } + } + + @override + Future changeNumberOfPlayers(int? numberOfPlayers) { + final _$actionInfo = _$_GamesViewModelActionController.startAction( + name: '_GamesViewModel.changeNumberOfPlayers'); + try { + return super.changeNumberOfPlayers(numberOfPlayers); + } finally { + _$_GamesViewModelActionController.endAction(_$actionInfo); + } + } + + @override + Future updateNumberOfPlayersFilter() { + final _$actionInfo = _$_GamesViewModelActionController.startAction( + name: '_GamesViewModel.updateNumberOfPlayersFilter'); + try { + return super.updateNumberOfPlayersFilter(); + } finally { + _$_GamesViewModelActionController.endAction(_$actionInfo); + } + } + + @override + Future updateFilterByRating(double? rating) { + final _$actionInfo = + _$_GamesViewModelActionController.startAction(name: '_GamesViewModel.updateFilterByRating'); + try { + return super.updateFilterByRating(rating); + } finally { + _$_GamesViewModelActionController.endAction(_$actionInfo); + } + } + + @override + String toString() { + return ''' +selectedTab: ${selectedTab}, +futureLoadBoardGames: ${futureLoadBoardGames}, +searchHistory: ${searchHistory}, +allMainGames: ${allMainGames}, +sortByOptions: ${sortByOptions}, +selectedSortBy: ${selectedSortBy}, +anyFiltersApplied: ${anyFiltersApplied}, +filterByRating: ${filterByRating}, +filteredBoardGames: ${filteredBoardGames}, +anyBoardGamesInCollections: ${anyBoardGamesInCollections}, +anyBoardGames: ${anyBoardGames}, +minNumberOfPlayers: ${minNumberOfPlayers}, +filterByNumberOfPlayers: ${filterByNumberOfPlayers}, +numberOfPlayersSliderValue: ${numberOfPlayersSliderValue}, +maxNumberOfPlayers: ${maxNumberOfPlayers}, +allBoardGames: ${allBoardGames}, +boardGamesInCollection: ${boardGamesInCollection}, +isCollectionEmpty: ${isCollectionEmpty}, +mainGamesInCollection: ${mainGamesInCollection}, +expansionsInCollection: ${expansionsInCollection}, +anyMainGamesInCollection: ${anyMainGamesInCollection}, +totalMainGamesInCollection: ${totalMainGamesInCollection}, +totalExpansionsInCollection: ${totalExpansionsInCollection}, +anyExpansionsInCollection: ${anyExpansionsInCollection}, +expansionsInCollectionMap: ${expansionsInCollectionMap}, +userName: ${userName}, +isUserNameEmpty: ${isUserNameEmpty} + '''; + } +} diff --git a/board_games_companion/lib/pages/games/search_suggestion.dart b/board_games_companion/lib/pages/collections/search_suggestion.dart similarity index 100% rename from board_games_companion/lib/pages/games/search_suggestion.dart rename to board_games_companion/lib/pages/collections/search_suggestion.dart diff --git a/board_games_companion/lib/pages/games/search_suggestion.freezed.dart b/board_games_companion/lib/pages/collections/search_suggestion.freezed.dart similarity index 100% rename from board_games_companion/lib/pages/games/search_suggestion.freezed.dart rename to board_games_companion/lib/pages/collections/search_suggestion.freezed.dart diff --git a/board_games_companion/lib/pages/home/home_page.dart b/board_games_companion/lib/pages/home/home_page.dart index 1fea9d25..efe70525 100644 --- a/board_games_companion/lib/pages/home/home_page.dart +++ b/board_games_companion/lib/pages/home/home_page.dart @@ -1,6 +1,5 @@ import 'package:board_games_companion/common/app_text.dart'; import 'package:board_games_companion/pages/home/home_view_model.dart'; -import 'package:board_games_companion/pages/playthroughs_history/playthroughs_history_page.dart'; import 'package:convex_bottom_bar/convex_bottom_bar.dart'; import 'package:flutter/material.dart'; @@ -9,8 +8,9 @@ import '../../common/dimensions.dart'; import '../../widgets/bottom_tab_icon.dart'; import '../../widgets/common/page_container.dart'; import '../base_page_state.dart'; -import '../games/games_page.dart'; +import '../collections/collections_page.dart'; import '../players/players_page.dart'; +import '../plays/plays_page.dart'; import '../search_board_games/search_board_games_page.dart'; import 'home_page_drawer.dart'; @@ -59,14 +59,14 @@ class HomePageState extends BasePageState with SingleTickerProviderSta child: TabBarView( controller: tabController, children: [ - GamesPage( - widget.viewModel.gamesViewModel, + CollectionsPage( + widget.viewModel.collectionsViewModel, widget.viewModel.boardGamesFiltersStore, widget.viewModel.analyticsService, widget.viewModel.rateAndReviewService, ), SearchBoardGamesPage(viewModel: widget.viewModel.searchBoardGamesViewModel), - PlaythroughsHistoryPage(viewModel: widget.viewModel.playthroughsHistoryViewModel), + PlaysPage(viewModel: widget.viewModel.playthroughsHistoryViewModel), PlayersPage(viewModel: widget.viewModel.playersViewModel), ], ), @@ -78,9 +78,9 @@ class HomePageState extends BasePageState with SingleTickerProviderSta top: -Dimensions.bottomTabTopHeight, items: const [ TabItem( - title: AppText.homePageGamesTabTitle, - icon: BottomTabIcon(iconData: Icons.video_library), - activeIcon: BottomTabIcon(iconData: Icons.video_library, isActive: true), + title: AppText.homePageCollectionsTabTitle, + icon: BottomTabIcon(iconData: Icons.grid_on), + activeIcon: BottomTabIcon(iconData: Icons.grid_on, isActive: true), ), TabItem( title: AppText.homePageSearchTabTitle, @@ -88,9 +88,9 @@ class HomePageState extends BasePageState with SingleTickerProviderSta activeIcon: BottomTabIcon(iconData: Icons.search, isActive: true), ), TabItem( - title: AppText.homePageGamesHistoryTabTitle, - icon: BottomTabIcon(iconData: Icons.history), - activeIcon: BottomTabIcon(iconData: Icons.history, isActive: true), + title: AppText.homePagePlaysTabTitle, + icon: BottomTabIcon(iconData: Icons.video_library), + activeIcon: BottomTabIcon(iconData: Icons.video_library, isActive: true), ), TabItem( title: AppText.homePageGamesPlayersTabTitle, diff --git a/board_games_companion/lib/pages/home/home_view_model.dart b/board_games_companion/lib/pages/home/home_view_model.dart index da89fb86..daeec710 100644 --- a/board_games_companion/lib/pages/home/home_view_model.dart +++ b/board_games_companion/lib/pages/home/home_view_model.dart @@ -1,6 +1,5 @@ // ignore_for_file: library_private_types_in_public_api -import 'package:board_games_companion/pages/playthroughs_history/playthroughs_history_view_model.dart'; import 'package:injectable/injectable.dart'; import 'package:mobx/mobx.dart'; import 'package:tuple/tuple.dart'; @@ -8,8 +7,9 @@ import 'package:tuple/tuple.dart'; import '../../services/analytics_service.dart'; import '../../services/rate_and_review_service.dart'; import '../../stores/board_games_filters_store.dart'; -import '../games/games_view_model.dart'; +import '../collections/collections_view_model.dart'; import '../players/players_view_model.dart'; +import '../plays/plays_view_model.dart'; import '../search_board_games/search_board_games_view_model.dart'; part 'home_view_model.g.dart'; @@ -23,7 +23,7 @@ abstract class _HomeViewModelBase with Store { this.rateAndReviewService, this.playersViewModel, this.boardGamesFiltersStore, - this.gamesViewModel, + this.collectionsViewModel, this.searchBoardGamesViewModel, this.playthroughsHistoryViewModel, ); @@ -32,9 +32,9 @@ abstract class _HomeViewModelBase with Store { final RateAndReviewService rateAndReviewService; final PlayersViewModel playersViewModel; final BoardGamesFiltersStore boardGamesFiltersStore; - final GamesViewModel gamesViewModel; + final CollectionsViewModel collectionsViewModel; final SearchBoardGamesViewModel searchBoardGamesViewModel; - final PlaythroughsHistoryViewModel playthroughsHistoryViewModel; + final PlaysViewModel playthroughsHistoryViewModel; static const Map> _screenViewByTabIndex = { 0: Tuple2('Games', 'GamesPage'), diff --git a/board_games_companion/lib/pages/playthroughs_history/board_game_playthrough.dart b/board_games_companion/lib/pages/plays/board_game_playthrough.dart similarity index 100% rename from board_games_companion/lib/pages/playthroughs_history/board_game_playthrough.dart rename to board_games_companion/lib/pages/plays/board_game_playthrough.dart diff --git a/board_games_companion/lib/pages/playthroughs_history/board_game_playthrough.freezed.dart b/board_games_companion/lib/pages/plays/board_game_playthrough.freezed.dart similarity index 100% rename from board_games_companion/lib/pages/playthroughs_history/board_game_playthrough.freezed.dart rename to board_games_companion/lib/pages/plays/board_game_playthrough.freezed.dart diff --git a/board_games_companion/lib/pages/plays/game_spinner_filters.dart b/board_games_companion/lib/pages/plays/game_spinner_filters.dart new file mode 100644 index 00000000..b9ff4b24 --- /dev/null +++ b/board_games_companion/lib/pages/plays/game_spinner_filters.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../common/enums/collection_type.dart'; + +part 'game_spinner_filters.freezed.dart'; + +@freezed +class GameSpinnerFilters with _$GameSpinnerFilters { + const factory GameSpinnerFilters({ + required Set collections, + }) = _GameSpinnerFilters; + + const GameSpinnerFilters._(); + + bool get hasOwnedCollection => collections.contains(CollectionType.owned); + + bool get hasFriendsCollection => collections.contains(CollectionType.friends); +} diff --git a/board_games_companion/lib/pages/plays/game_spinner_filters.freezed.dart b/board_games_companion/lib/pages/plays/game_spinner_filters.freezed.dart new file mode 100644 index 00000000..bbfe4d9d --- /dev/null +++ b/board_games_companion/lib/pages/plays/game_spinner_filters.freezed.dart @@ -0,0 +1,140 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target + +part of 'game_spinner_filters.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$GameSpinnerFilters { + Set get collections => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $GameSpinnerFiltersCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GameSpinnerFiltersCopyWith<$Res> { + factory $GameSpinnerFiltersCopyWith( + GameSpinnerFilters value, $Res Function(GameSpinnerFilters) then) = + _$GameSpinnerFiltersCopyWithImpl<$Res>; + $Res call({Set collections}); +} + +/// @nodoc +class _$GameSpinnerFiltersCopyWithImpl<$Res> + implements $GameSpinnerFiltersCopyWith<$Res> { + _$GameSpinnerFiltersCopyWithImpl(this._value, this._then); + + final GameSpinnerFilters _value; + // ignore: unused_field + final $Res Function(GameSpinnerFilters) _then; + + @override + $Res call({ + Object? collections = freezed, + }) { + return _then(_value.copyWith( + collections: collections == freezed + ? _value.collections + : collections // ignore: cast_nullable_to_non_nullable + as Set, + )); + } +} + +/// @nodoc +abstract class _$$_GameSpinnerFiltersCopyWith<$Res> + implements $GameSpinnerFiltersCopyWith<$Res> { + factory _$$_GameSpinnerFiltersCopyWith(_$_GameSpinnerFilters value, + $Res Function(_$_GameSpinnerFilters) then) = + __$$_GameSpinnerFiltersCopyWithImpl<$Res>; + @override + $Res call({Set collections}); +} + +/// @nodoc +class __$$_GameSpinnerFiltersCopyWithImpl<$Res> + extends _$GameSpinnerFiltersCopyWithImpl<$Res> + implements _$$_GameSpinnerFiltersCopyWith<$Res> { + __$$_GameSpinnerFiltersCopyWithImpl( + _$_GameSpinnerFilters _value, $Res Function(_$_GameSpinnerFilters) _then) + : super(_value, (v) => _then(v as _$_GameSpinnerFilters)); + + @override + _$_GameSpinnerFilters get _value => super._value as _$_GameSpinnerFilters; + + @override + $Res call({ + Object? collections = freezed, + }) { + return _then(_$_GameSpinnerFilters( + collections: collections == freezed + ? _value._collections + : collections // ignore: cast_nullable_to_non_nullable + as Set, + )); + } +} + +/// @nodoc + +class _$_GameSpinnerFilters extends _GameSpinnerFilters { + const _$_GameSpinnerFilters({required final Set collections}) + : _collections = collections, + super._(); + + final Set _collections; + @override + Set get collections { + // ignore: implicit_dynamic_type + return EqualUnmodifiableSetView(_collections); + } + + @override + String toString() { + return 'GameSpinnerFilters(collections: $collections)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_GameSpinnerFilters && + const DeepCollectionEquality() + .equals(other._collections, _collections)); + } + + @override + int get hashCode => Object.hash( + runtimeType, const DeepCollectionEquality().hash(_collections)); + + @JsonKey(ignore: true) + @override + _$$_GameSpinnerFiltersCopyWith<_$_GameSpinnerFilters> get copyWith => + __$$_GameSpinnerFiltersCopyWithImpl<_$_GameSpinnerFilters>( + this, _$identity); +} + +abstract class _GameSpinnerFilters extends GameSpinnerFilters { + const factory _GameSpinnerFilters( + {required final Set collections}) = _$_GameSpinnerFilters; + const _GameSpinnerFilters._() : super._(); + + @override + Set get collections; + @override + @JsonKey(ignore: true) + _$$_GameSpinnerFiltersCopyWith<_$_GameSpinnerFilters> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/board_games_companion/lib/pages/plays/game_spinner_game_selected_dialog.dart b/board_games_companion/lib/pages/plays/game_spinner_game_selected_dialog.dart new file mode 100644 index 00000000..e4586b7c --- /dev/null +++ b/board_games_companion/lib/pages/plays/game_spinner_game_selected_dialog.dart @@ -0,0 +1,297 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:board_games_companion/models/hive/board_game_details.dart'; +import 'package:board_games_companion/models/navigation/board_game_details_page_arguments.dart'; +import 'package:board_games_companion/pages/board_game_details/board_game_details_page.dart'; +import 'package:board_games_companion/pages/playthroughs/playthroughs_page.dart'; +import 'package:board_games_companion/widgets/board_games/board_game_tile.dart'; +import 'package:board_games_companion/widgets/common/board_game/board_game_property.dart'; +import 'package:confetti/confetti.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:sprintf/sprintf.dart'; + +import '../../common/animation_tags.dart'; +import '../../common/app_colors.dart'; +import '../../common/app_styles.dart'; +import '../../common/app_text.dart'; +import '../../common/constants.dart'; +import '../../common/dimensions.dart'; +import '../../models/navigation/playthroughs_page_arguments.dart'; +import '../../widgets/common/elevated_icon_button.dart'; +import '../../widgets/common/rating_hexagon.dart'; +import '../../widgets/elevated_container.dart'; + +class GameSpinnerGameSelectedDialog extends StatefulWidget { + const GameSpinnerGameSelectedDialog({ + required this.selectedBoardGame, + super.key, + }); + + final BoardGameDetails selectedBoardGame; + + @override + State createState() => _GameSpinnerGameSelectedDialogState(); +} + +class _GameSpinnerGameSelectedDialogState extends State { + static const double _minWidth = 340; + static const double _maxWidth = 380; + + late ConfettiController _confettiController; + + @override + void initState() { + super.initState(); + + _confettiController = ConfettiController(duration: const Duration(seconds: 3)); + WidgetsBinding.instance.addPostFrameCallback( + (_) => _confettiController.play(), + ); + } + + @override + void dispose() { + _confettiController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + final dialogWidth = + max(_minWidth, min(width - 2 * Dimensions.doubleStandardSpacing, _maxWidth)); + + return Center( + child: SizedBox( + width: dialogWidth, + child: ElevatedContainer( + backgroundColor: AppColors.primaryColorLight, + elevation: AppStyles.defaultElevation, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Stack( + children: [ + _Header( + selectedBoardGame: widget.selectedBoardGame, + confettiController: _confettiController, + ), + Material( + color: Colors.transparent, + child: SizedBox( + width: dialogWidth, + height: Dimensions.gameSpinnerSelectedGameImageHeight, + child: Align( + alignment: Alignment.topRight, + child: IconButton( + icon: const Icon(Icons.info), + onPressed: () => _navigateToBoardGameDetails(context), + ), + ), + ), + ), + ], + ), + const SizedBox(height: Dimensions.doubleStandardSpacing), + Padding( + padding: const EdgeInsets.symmetric(horizontal: Dimensions.standardSpacing), + child: _Body(selectedBoardGame: widget.selectedBoardGame), + ), + const SizedBox(height: Dimensions.doubleStandardSpacing), + Padding( + padding: const EdgeInsets.symmetric(horizontal: Dimensions.standardSpacing), + child: _Footer( + onPlayPressed: () => _navigateToBoardGamePlaythrough(context), + onSpinAgainPressed: () => Navigator.pop(context), + ), + ), + const SizedBox(height: Dimensions.standardSpacing), + ], + ), + ), + ), + ); + } + + void _navigateToBoardGameDetails(BuildContext context) { + unawaited(Navigator.pushNamed( + context, + BoardGamesDetailsPage.pageRoute, + arguments: BoardGameDetailsPageArguments( + boardGameId: widget.selectedBoardGame.id, + boardGameName: widget.selectedBoardGame.name, + navigatingFromType: GameSpinnerGameSelectedDialog, + boardGameImageHeroId: + '${AnimationTags.gameSpinnerBoardGameHeroTag}${widget.selectedBoardGame.id}', + ), + )); + } + + void _navigateToBoardGamePlaythrough(BuildContext context) { + unawaited(Navigator.pushNamed( + context, + PlaythroughsPage.pageRoute, + arguments: PlaythroughsPageArguments( + boardGameDetails: widget.selectedBoardGame, + boardGameImageHeroId: + '${AnimationTags.gameSpinnerBoardGameHeroTag}${widget.selectedBoardGame.id}', + ), + )); + } +} + +class _Header extends StatelessWidget { + const _Header({ + required this.selectedBoardGame, + required this.confettiController, + }); + + final ConfettiController confettiController; + final BoardGameDetails selectedBoardGame; + + @override + Widget build(BuildContext context) => SizedBox( + height: Dimensions.gameSpinnerSelectedGameImageHeight, + child: Stack( + children: [ + BoardGameTile( + id: '${AnimationTags.gameSpinnerBoardGameHeroTag}${selectedBoardGame.id}', + imageUrl: selectedBoardGame.imageUrl ?? '', + name: selectedBoardGame.name, + nameFontSize: Dimensions.largeFontSize, + elevation: AppStyles.defaultElevation, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(AppStyles.defaultCornerRadius), + ), + ), + Align( + alignment: Alignment.center, + child: ConfettiWidget( + confettiController: confettiController, + blastDirectionality: BlastDirectionality.explosive, + colors: const [ + AppColors.accentColor, + AppColors.primaryColor, + AppColors.primaryColorLight, + ...AppColors.chartColorPallete, + ], + emissionFrequency: 0.03, + createParticlePath: drawStar, + ), + ), + ], + ), + ); +} + +class _Body extends StatelessWidget { + const _Body({ + Key? key, + required this.selectedBoardGame, + }) : super(key: key); + + static const double _gameStatIconSize = 24; + static const double _gamePropertyIconSize = 28; + + final BoardGameDetails selectedBoardGame; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + BoardGameProperty( + icon: const Icon(Icons.people, size: _gameStatIconSize), + iconWidth: _gamePropertyIconSize, + propertyName: selectedBoardGame.playersFormatted, + fontSize: Dimensions.mediumFontSize, + ), + const SizedBox(height: Dimensions.standardSpacing), + BoardGameProperty( + icon: const Icon(Icons.hourglass_bottom, size: _gameStatIconSize), + iconWidth: _gamePropertyIconSize, + propertyName: selectedBoardGame.playtimeFormatted, + fontSize: Dimensions.mediumFontSize, + ), + if (selectedBoardGame.avgWeight != null) ...[ + const SizedBox(height: Dimensions.standardSpacing), + BoardGameProperty( + icon: const FaIcon(FontAwesomeIcons.scaleUnbalanced, size: _gameStatIconSize), + iconWidth: _gamePropertyIconSize, + propertyName: sprintf( + AppText.gamesPageSearchResultComplexityGameStatFormat, + [selectedBoardGame.avgWeight!.toStringAsFixed(2)], + ), + fontSize: Dimensions.mediumFontSize, + ), + ], + if (selectedBoardGame.rating != null) ...[ + const SizedBox(height: Dimensions.standardSpacing), + BoardGameProperty( + icon: const RatingHexagon(width: _gameStatIconSize, height: _gameStatIconSize), + iconWidth: _gamePropertyIconSize, + propertyName: selectedBoardGame.rating! + .toStringAsFixed(Constants.boardGameRatingNumberOfDecimalPlaces), + fontSize: Dimensions.mediumFontSize, + ), + ] + ], + ); + } +} + +class _Footer extends StatelessWidget { + const _Footer({ + required this.onPlayPressed, + required this.onSpinAgainPressed, + }); + + final VoidCallback onPlayPressed; + final VoidCallback onSpinAgainPressed; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + ElevatedIconButton( + title: AppText.playsPageGameSpinnerSelectedGameSpinAgainButtonText, + icon: const FaIcon(FontAwesomeIcons.arrowsSpin), + color: AppColors.blueColor, + onPressed: () => onSpinAgainPressed(), + ), + const Expanded(child: SizedBox.shrink()), + ElevatedIconButton( + title: AppText.playsPageGameSpinnerSelectedGamePlayButtonText, + icon: const FaIcon(FontAwesomeIcons.dice), + color: AppColors.accentColor, + onPressed: () => onPlayPressed(), + ), + ], + ); + } +} + +Path drawStar(Size size) { + // Method to convert degree to radians + double degToRad(double deg) => deg * (pi / 180.0); + + const numberOfPoints = 5; + final halfWidth = size.width / 2; + final externalRadius = halfWidth; + final internalRadius = halfWidth / 2.5; + final degreesPerStep = degToRad(360 / numberOfPoints); + final halfDegreesPerStep = degreesPerStep / 2; + final path = Path(); + final fullAngle = degToRad(360); + path.moveTo(size.width, halfWidth); + + for (double step = 0; step < fullAngle; step += degreesPerStep) { + path.lineTo(halfWidth + externalRadius * cos(step), halfWidth + externalRadius * sin(step)); + path.lineTo(halfWidth + internalRadius * cos(step + halfDegreesPerStep), + halfWidth + internalRadius * sin(step + halfDegreesPerStep)); + } + path.close(); + return path; +} diff --git a/board_games_companion/lib/pages/playthroughs_history/grouped_board_game_playthroughs.dart b/board_games_companion/lib/pages/plays/grouped_board_game_playthroughs.dart similarity index 100% rename from board_games_companion/lib/pages/playthroughs_history/grouped_board_game_playthroughs.dart rename to board_games_companion/lib/pages/plays/grouped_board_game_playthroughs.dart diff --git a/board_games_companion/lib/pages/playthroughs_history/grouped_board_game_playthroughs.freezed.dart b/board_games_companion/lib/pages/plays/grouped_board_game_playthroughs.freezed.dart similarity index 100% rename from board_games_companion/lib/pages/playthroughs_history/grouped_board_game_playthroughs.freezed.dart rename to board_games_companion/lib/pages/plays/grouped_board_game_playthroughs.freezed.dart diff --git a/board_games_companion/lib/pages/plays/plays_page.dart b/board_games_companion/lib/pages/plays/plays_page.dart new file mode 100644 index 00000000..f39decd5 --- /dev/null +++ b/board_games_companion/lib/pages/plays/plays_page.dart @@ -0,0 +1,802 @@ +import 'package:board_games_companion/common/enums/collection_type.dart'; +import 'package:board_games_companion/common/enums/plays_tab.dart'; +import 'package:board_games_companion/extensions/int_extensions.dart'; +import 'package:board_games_companion/models/hive/board_game_details.dart'; +import 'package:board_games_companion/pages/edit_playthrough/edit_playthrough_page.dart'; +import 'package:board_games_companion/pages/plays/game_spinner_filters.dart'; +import 'package:board_games_companion/pages/plays/game_spinner_game_selected_dialog.dart'; +import 'package:board_games_companion/pages/plays/plays_page_visual_states.dart'; +import 'package:board_games_companion/pages/playthroughs/playthroughs_page.dart'; +import 'package:board_games_companion/widgets/common/collection_toggle_button.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:mobx/mobx.dart'; +import 'package:sliver_tools/sliver_tools.dart'; + +import '../../common/animation_tags.dart'; +import '../../common/app_colors.dart'; +import '../../common/app_styles.dart'; +import '../../common/app_text.dart'; +import '../../common/app_theme.dart'; +import '../../common/dimensions.dart'; +import '../../models/navigation/board_game_details_page_arguments.dart'; +import '../../models/navigation/edit_playthrough_page_arguments.dart'; +import '../../models/navigation/playthroughs_page_arguments.dart'; +import '../../widgets/board_games/board_game_name.dart'; +import '../../widgets/board_games/board_game_tile.dart'; +import '../../widgets/common/app_bar/app_bar_bottom_tab.dart'; +import '../../widgets/common/loading_indicator_widget.dart'; +import '../../widgets/common/panel_container.dart'; +import '../../widgets/common/slivers/bgc_sliver_header_delegate.dart'; +import '../board_game_details/board_game_details_page.dart'; +import '../home/home_page.dart'; +import 'board_game_playthrough.dart'; +import 'grouped_board_game_playthroughs.dart'; +import 'plays_view_model.dart'; + +class PlaysPage extends StatefulWidget { + const PlaysPage({ + required this.viewModel, + Key? key, + }) : super(key: key); + + final PlaysViewModel viewModel; + + @override + State createState() => _PlaysPageState(); +} + +class _PlaysPageState extends State with SingleTickerProviderStateMixin { + static const int _spinAnimationTimeInMilliseconds = 2000; + + late TabController _tabController; + late FixedExtentScrollController _scrollController; + + @override + void initState() { + super.initState(); + + _tabController = TabController( + length: 2, + vsync: this, + initialIndex: 0, + ); + _tabController + .addListener(() => widget.viewModel.setSelectTab(_tabController.index.toPlaysTab())); + + _scrollController = FixedExtentScrollController(); + + widget.viewModel.loadGamesPlaythroughs(); + } + + @override + void dispose() { + _tabController.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => Observer( + builder: (_) { + switch (widget.viewModel.futureLoadGamesPlaythroughs?.status ?? FutureStatus.pending) { + case FutureStatus.pending: + case FutureStatus.rejected: + return CustomScrollView( + slivers: [ + Observer( + builder: (_) { + return _AppBar( + tabVisualState: widget.viewModel.visualState, + tabController: _tabController, + ); + }, + ), + const SliverFillRemaining(child: LoadingIndicator()), + ], + ); + case FutureStatus.fulfilled: + return CustomScrollView( + slivers: [ + Observer( + builder: (_) { + return _AppBar( + tabVisualState: widget.viewModel.visualState, + tabController: _tabController, + ); + }, + ), + Observer( + builder: (_) { + return widget.viewModel.visualState?.when( + history: (tab, finishedPlaythroughs) { + if (!widget.viewModel.hasAnyFinishedPlaythroughs) { + return const _NoPlaythroughsSliver(); + } else { + return MultiSliver( + children: [ + if (widget.viewModel.hasAnyFinishedPlaythroughs) ...[ + for (final groupedBoardGamePlaythroughs + in widget.viewModel.finishedBoardGamePlaythroughs) ...[ + _PlaythroughGroupHeaderSliver( + widget: widget, + groupedBoardGamePlaythroughs: + groupedBoardGamePlaythroughs, + ), + _PlaythroughGroupListSliver( + groupedBoardGamePlaythroughs: + groupedBoardGamePlaythroughs, + ), + if (groupedBoardGamePlaythroughs == + widget.viewModel.finishedBoardGamePlaythroughs.last) + const SliverToBoxAdapter( + child: SizedBox(height: Dimensions.bottomTabTopHeight), + ), + ] + ] + ], + ); + } + }, + statistics: (tab) => const SliverToBoxAdapter(), + selectGame: (tab, shuffledBoardGames) { + if (!widget.viewModel.hasAnyBoardGames) { + return const _NoBoardGamesSliver(); + } + + return MultiSliver( + children: [ + if (!widget.viewModel.hasAnyBoardGamesToShuffle) + const _NoBoardGamesToShuffleSliver(), + if (widget.viewModel.hasAnyBoardGamesToShuffle) + _GameSpinnerSliver( + scrollController: _scrollController, + shuffledBoardGames: shuffledBoardGames, + onSpin: () => _spin(), + onGameSelected: () => _selectGame(), + ), + SliverPersistentHeader( + delegate: BgcSliverHeaderDelegate( + primaryTitle: AppText.playsPageGameSpinnerFilterSectionTitle, + ), + ), + Observer( + builder: (_) { + return _GameSpinnerFilters( + gameSpinnerFilters: widget.viewModel.gameSpinnerFilters, + onCollectionToggled: (collectionTyp) => widget.viewModel + .toggleGameSpinnerCollectionFilter(collectionTyp), + ); + }, + ), + ], + ); + }, + ) ?? + const SliverToBoxAdapter(); + }, + ), + ], + ); + } + }, + ); + + Future _spin() async { + await _scrollController.animateToItem( + widget.viewModel.randomItemIndex, + duration: const Duration(milliseconds: _spinAnimationTimeInMilliseconds), + curve: Curves.elasticInOut, + ); + } + + Future _selectGame() async { + final selectedBoardGame = widget.viewModel.shuffledBoardGames[ + _scrollController.selectedItem % widget.viewModel.shuffledBoardGames.length]; + await showGeneralDialog( + context: context, + barrierDismissible: true, + barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, + pageBuilder: (_, __, ___) { + return GameSpinnerGameSelectedDialog(selectedBoardGame: selectedBoardGame); + }, + ); + } +} + +class _GameSpinnerFilters extends StatelessWidget { + const _GameSpinnerFilters({ + required this.gameSpinnerFilters, + required this.onCollectionToggled, + Key? key, + }) : super(key: key); + + final GameSpinnerFilters gameSpinnerFilters; + final void Function(CollectionType collectionTyp) onCollectionToggled; + + static const Map collectionsMap = { + 0: CollectionType.owned, + 1: CollectionType.friends, + }; + + @override + Widget build(BuildContext context) => Padding( + padding: const EdgeInsets.all(Dimensions.standardSpacing), + child: Column( + children: [ + Row( + children: [ + Text( + AppText.playsPageGameSpinnerCollectionsFilter, + style: AppTheme.theme.textTheme.bodyMedium, + ), + const Expanded(child: SizedBox.shrink()), + ToggleButtons( + isSelected: [ + gameSpinnerFilters.hasOwnedCollection, + gameSpinnerFilters.hasFriendsCollection, + ], + onPressed: (int index) => onCollectionToggled(collectionsMap[index]!), + children: [ + CollectionToggleButton( + icon: Icons.grid_on, + title: AppText.ownedCollectionToggleButtonText, + isSelected: gameSpinnerFilters.hasOwnedCollection, + ), + CollectionToggleButton( + icon: Icons.group, + title: AppText.friendsCollectionToggleButtonText, + isSelected: gameSpinnerFilters.hasFriendsCollection, + ), + ], + ) + ], + ), + ], + ), + ); +} + +class _GameSpinnerSliver extends StatelessWidget { + const _GameSpinnerSliver({ + Key? key, + required this.scrollController, + required this.shuffledBoardGames, + required this.onSpin, + required this.onGameSelected, + }) : super(key: key); + + static const double _gameSpinnerItemHeight = 80; + static const double _gameSpinnerItemSqueeze = 1.2; + + final ScrollController scrollController; + final List shuffledBoardGames; + final VoidCallback onSpin; + final VoidCallback onGameSelected; + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(Dimensions.standardSpacing), + child: SizedBox( + height: Dimensions.gameSpinnerHeight, + child: Row( + children: [ + Expanded( + child: NotificationListener( + onNotification: (scrollNotification) { + if (scrollNotification is ScrollEndNotification) { + onGameSelected(); + } + + return false; + }, + child: ListWheelScrollView.useDelegate( + controller: scrollController, + itemExtent: _gameSpinnerItemHeight, + squeeze: _gameSpinnerItemSqueeze, + perspective: 0.0035, + childDelegate: ListWheelChildLoopingListDelegate( + children: [ + for (final boardGame in shuffledBoardGames) ...[ + Center( + child: Container( + constraints: const BoxConstraints( + maxWidth: Dimensions.gameSpinnerMaxWidth, + ), + child: Stack( + children: [ + _GameSpinnerItem(boardGame: boardGame), + Center( + child: BoardGameName( + name: boardGame.name, + fontSize: Dimensions.mediumFontSize, + ), + ), + ], + ), + ), + ), + ], + ], + ), + ), + ), + ), + const SizedBox(width: Dimensions.standardSpacing), + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => onSpin(), + child: Padding( + padding: const EdgeInsets.all(Dimensions.standardSpacing), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + FaIcon(FontAwesomeIcons.arrowsSpin), + Text(AppText.playsPageGameSpinnerSpinButtonText), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} + +class _GameSpinnerItem extends StatelessWidget { + const _GameSpinnerItem({ + Key? key, + required this.boardGame, + }) : super(key: key); + + final BoardGameDetails boardGame; + + @override + Widget build(BuildContext context) { + return Hero( + tag: '${AnimationTags.gameSpinnerBoardGameHeroTag}${boardGame.id}', + child: LayoutBuilder( + builder: (_, BoxConstraints boxConstraints) { + return CachedNetworkImage( + maxHeightDiskCache: boxConstraints.maxWidth.toInt(), + imageUrl: boardGame.imageUrl ?? '', + imageBuilder: (context, imageProvider) => Container( + decoration: BoxDecoration( + borderRadius: AppTheme.defaultBorderRadius, + image: DecorationImage(image: imageProvider, fit: BoxFit.cover), + ), + ), + fit: BoxFit.fitWidth, + placeholder: (context, url) => Container( + decoration: AppStyles.tileGradientBoxDecoration, + ), + ); + }, + ), + ); + } +} + +class _PlaythroughGroupListSliver extends StatelessWidget { + const _PlaythroughGroupListSliver({ + Key? key, + required this.groupedBoardGamePlaythroughs, + }) : super(key: key); + + final GroupedBoardGamePlaythroughs groupedBoardGamePlaythroughs; + + @override + Widget build(BuildContext context) { + return SliverList( + delegate: SliverChildBuilderDelegate( + (_, index) { + final boardGamePlaythrough = groupedBoardGamePlaythroughs.boardGamePlaythroughs[index]; + final isFirst = index == 0; + return Padding( + padding: EdgeInsets.only( + top: isFirst ? Dimensions.standardSpacing : 0, + bottom: Dimensions.standardSpacing, + left: Dimensions.standardSpacing, + right: Dimensions.standardSpacing, + ), + child: PanelContainer( + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(AppStyles.panelContainerCornerRadius), + onTap: () => _navigateToEditPlaythrough( + context, + boardGamePlaythrough.boardGameDetails.id, + boardGamePlaythrough.playthrough.id, + ), + child: Padding( + padding: const EdgeInsets.all(Dimensions.standardSpacing), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IntrinsicHeight( + child: Row( + children: [ + SizedBox( + height: Dimensions.collectionSearchResultBoardGameImageHeight, + width: Dimensions.collectionSearchResultBoardGameImageWidth, + child: BoardGameTile( + id: boardGamePlaythrough.id, + imageUrl: + boardGamePlaythrough.boardGameDetails.thumbnailUrl ?? '', + ), + ), + const SizedBox(width: Dimensions.standardSpacing), + Expanded( + child: + _PlaythroughDetails(boardGamePlaythrough: boardGamePlaythrough), + ), + _PlaythroughActions( + onTapBoardGameDetails: () => + _navigateToBoardGameDetails(context, boardGamePlaythrough), + onTapPlaythroughs: () => + _navigateToPlaythrough(context, boardGamePlaythrough), + ), + ], + ), + ) + ], + ), + ), + ), + ), + ), + ); + }, + childCount: groupedBoardGamePlaythroughs.boardGamePlaythroughs.length, + ), + ); + } + + Future _navigateToPlaythrough( + BuildContext context, + BoardGamePlaythrough boardGamePlaythrough, + ) => + Navigator.pushNamed( + context, + PlaythroughsPage.pageRoute, + arguments: PlaythroughsPageArguments( + boardGameDetails: boardGamePlaythrough.boardGameDetails, + boardGameImageHeroId: boardGamePlaythrough.id, + ), + ); + + void _navigateToBoardGameDetails( + BuildContext context, + BoardGamePlaythrough boardGamePlaythrough, + ) { + Navigator.pushNamed( + context, + BoardGamesDetailsPage.pageRoute, + arguments: BoardGameDetailsPageArguments( + boardGameId: boardGamePlaythrough.boardGameDetails.id, + boardGameName: boardGamePlaythrough.boardGameDetails.name, + boardGameImageHeroId: boardGamePlaythrough.id, + navigatingFromType: PlaysPage, + ), + ); + } + + void _navigateToEditPlaythrough( + BuildContext context, + String boardGameId, + String playthroughId, + ) { + Navigator.pushNamed( + context, + EditPlaythroughPage.pageRoute, + arguments: EditPlaythroughPageArguments( + boardGameId: boardGameId, + playthroughId: playthroughId, + goBackPageRoute: HomePage.pageRoute, + ), + ); + } +} + +class _PlaythroughDetails extends StatelessWidget { + const _PlaythroughDetails({ + Key? key, + required this.boardGamePlaythrough, + }) : super(key: key); + + static const double _playthroughStatsIconSize = 16; + static const double _playthroughStatsFontAwesomeIconSize = _playthroughStatsIconSize - 4; + + final BoardGamePlaythrough boardGamePlaythrough; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + boardGamePlaythrough.boardGameDetails.name, + overflow: TextOverflow.ellipsis, + style: AppTheme.theme.textTheme.bodyLarge, + ), + const SizedBox(height: Dimensions.standardSpacing), + _PlaythroughGeneralStats( + icon: const Icon( + FontAwesomeIcons.trophy, + size: _playthroughStatsFontAwesomeIconSize, + ), + statistic: + '${boardGamePlaythrough.winner.player?.name ?? ''} (${boardGamePlaythrough.winner.score.valueInt} points)', + ), + _PlaythroughGeneralStats( + icon: const Icon(Icons.people, size: _playthroughStatsIconSize), + statistic: '${boardGamePlaythrough.playthrough.playerScores.length} players', + ), + _PlaythroughGeneralStats( + icon: const Icon(Icons.hourglass_bottom, size: _playthroughStatsIconSize), + statistic: boardGamePlaythrough.playthrough.duration.inSeconds + .toPlaytimeDuration(showSeconds: false), + ), + ], + ); + } +} + +class _PlaythroughGeneralStats extends StatelessWidget { + const _PlaythroughGeneralStats({ + Key? key, + required this.icon, + required this.statistic, + }) : super(key: key); + + final Widget icon; + final String statistic; + + static const double _uniformedIconSize = 20; + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: _uniformedIconSize, child: Center(child: icon)), + const SizedBox(width: Dimensions.standardSpacing), + Text( + statistic, + overflow: TextOverflow.ellipsis, + style: AppTheme.subTitleTextStyle.copyWith(color: AppColors.whiteColor), + ), + ], + ); + } +} + +class _PlaythroughActions extends StatelessWidget { + const _PlaythroughActions({ + Key? key, + required this.onTapBoardGameDetails, + required this.onTapPlaythroughs, + }) : super(key: key); + + final VoidCallback onTapBoardGameDetails; + final VoidCallback onTapPlaythroughs; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + IconButton( + icon: const Icon(Icons.info), + onPressed: () => onTapBoardGameDetails(), + ), + const Expanded(child: SizedBox.shrink()), + IconButton( + icon: const FaIcon(FontAwesomeIcons.dice), + onPressed: () => onTapPlaythroughs(), + ), + ], + ); + } +} + +class _PlaythroughGroupHeaderSliver extends StatelessWidget { + const _PlaythroughGroupHeaderSliver({ + Key? key, + required this.widget, + required this.groupedBoardGamePlaythroughs, + }) : super(key: key); + + final PlaysPage widget; + final GroupedBoardGamePlaythroughs groupedBoardGamePlaythroughs; + + @override + Widget build(BuildContext context) { + return SliverPersistentHeader( + delegate: BgcSliverHeaderDelegate(primaryTitle: groupedBoardGamePlaythroughs.dateFormtted), + ); + } +} + +class _NoPlaythroughsSliver extends StatelessWidget { + const _NoPlaythroughsSliver({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) => SliverPadding( + padding: const EdgeInsets.all(Dimensions.doubleStandardSpacing), + sliver: SliverToBoxAdapter( + child: Column( + mainAxisSize: MainAxisSize.min, + children: const [ + SizedBox(height: Dimensions.emptyPageTitleTopSpacing), + Center( + child: Text( + AppText.playsPageHistoryTabEmptyTitle, + style: TextStyle(fontSize: Dimensions.extraLargeFontSize), + ), + ), + SizedBox(height: Dimensions.doubleStandardSpacing), + FaIcon( + FontAwesomeIcons.dice, + size: Dimensions.emptyPageTitleIconSize, + color: AppColors.primaryColor, + ), + SizedBox(height: Dimensions.doubleStandardSpacing), + Text.rich( + TextSpan( + children: [ + TextSpan(text: AppText.playPageHistoryTabEmptySubtitle), + ], + ), + textAlign: TextAlign.justify, + style: TextStyle(fontSize: Dimensions.mediumFontSize), + ), + ], + ), + ), + ); +} + +class _NoBoardGamesSliver extends StatelessWidget { + const _NoBoardGamesSliver({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) => SliverPadding( + padding: const EdgeInsets.all(Dimensions.doubleStandardSpacing), + sliver: SliverToBoxAdapter( + child: Column( + mainAxisSize: MainAxisSize.min, + children: const [ + SizedBox(height: Dimensions.emptyPageTitleTopSpacing), + Center( + child: Text( + AppText.playsPageSelectGameTabEmptyTitle, + style: TextStyle(fontSize: Dimensions.extraLargeFontSize), + ), + ), + SizedBox(height: Dimensions.doubleStandardSpacing), + FaIcon( + Icons.grid_on, + size: Dimensions.emptyPageTitleIconSize, + color: AppColors.primaryColor, + ), + SizedBox(height: Dimensions.doubleStandardSpacing), + Text.rich( + TextSpan( + children: [ + TextSpan(text: AppText.playsPageSelectGameTabEmptySubtitle), + ], + ), + textAlign: TextAlign.justify, + style: TextStyle(fontSize: Dimensions.mediumFontSize), + ), + ], + ), + ), + ); +} + +class _NoBoardGamesToShuffleSliver extends StatelessWidget { + const _NoBoardGamesToShuffleSliver({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) => SliverPadding( + padding: const EdgeInsets.symmetric( + horizontal: Dimensions.doubleStandardSpacing, + vertical: Dimensions.standardSpacing, + ), + sliver: SliverToBoxAdapter( + child: SizedBox( + height: Dimensions.gameSpinnerHeight, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: const [ + Center( + child: Text( + AppText.playsPageSelectGameNoBoardGamesToShuffleTitle, + style: TextStyle(fontSize: Dimensions.extraLargeFontSize), + ), + ), + SizedBox(height: Dimensions.doubleStandardSpacing), + FaIcon( + FontAwesomeIcons.arrowsSpin, + size: Dimensions.emptyPageTitleIconSize, + color: AppColors.primaryColor, + ), + SizedBox(height: Dimensions.doubleStandardSpacing), + Text.rich( + TextSpan( + children: [ + TextSpan(text: AppText.playsPageSelectGameNoBoardGamesToShuffleSubtitle), + ], + ), + textAlign: TextAlign.justify, + style: TextStyle(fontSize: Dimensions.mediumFontSize), + ), + ], + ), + ), + ), + ), + ); +} + +class _AppBar extends StatelessWidget { + const _AppBar({ + required this.tabVisualState, + required this.tabController, + Key? key, + }) : super(key: key); + + final PlaysPageVisualState? tabVisualState; + final TabController tabController; + + @override + Widget build(BuildContext context) { + return SliverAppBar( + pinned: true, + floating: true, + forceElevated: true, + elevation: Dimensions.defaultElevation, + titleSpacing: Dimensions.standardSpacing, + foregroundColor: AppColors.accentColor, + title: const Text(AppText.playsPageTitle, style: AppTheme.titleTextStyle), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(74), + child: TabBar( + overlayColor: MaterialStateColor.resolveWith((states) => AppColors.accentColor), + controller: tabController, + tabs: [ + AppBarBottomTab( + AppText.playsPageHistoryTabTitle, + Icons.history, + isSelected: tabVisualState?.playsTab == PlaysTab.history, + ), + // TODO Add stats page + // AppBarBottomTab( + // AppText.playsPageStatisticsTabTitle, + // Icons.multiline_chart, + // isSelected: tabVisualState?.playsTab == PlaysTab.statistics, + // ), + AppBarBottomTab( + AppText.playsPageSelectGameTabTitle, + Icons.shuffle, + isSelected: tabVisualState?.playsTab == PlaysTab.selectGame, + ), + ], + indicatorColor: AppColors.accentColor, + ), + ), + ); + } +} diff --git a/board_games_companion/lib/pages/plays/plays_page_visual_states.dart b/board_games_companion/lib/pages/plays/plays_page_visual_states.dart new file mode 100644 index 00000000..39666b5a --- /dev/null +++ b/board_games_companion/lib/pages/plays/plays_page_visual_states.dart @@ -0,0 +1,20 @@ +import 'package:board_games_companion/common/enums/plays_tab.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../models/hive/board_game_details.dart'; +import 'grouped_board_game_playthroughs.dart'; + +part 'plays_page_visual_states.freezed.dart'; + +@freezed +class PlaysPageVisualState with _$PlaysPageVisualState { + const factory PlaysPageVisualState.history( + PlaysTab playsTab, + List finishedBoardGamePlaythroughs, + ) = _History; + const factory PlaysPageVisualState.statistics(PlaysTab playsTab) = _Statistics; + const factory PlaysPageVisualState.selectGame( + PlaysTab playsTab, + List shuffledBoardGames, + ) = _SelectGame; +} diff --git a/board_games_companion/lib/pages/plays/plays_page_visual_states.freezed.dart b/board_games_companion/lib/pages/plays/plays_page_visual_states.freezed.dart new file mode 100644 index 00000000..40c1683f --- /dev/null +++ b/board_games_companion/lib/pages/plays/plays_page_visual_states.freezed.dart @@ -0,0 +1,620 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target + +part of 'plays_page_visual_states.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$PlaysPageVisualState { + PlaysTab get playsTab => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(PlaysTab playsTab, + List finishedBoardGamePlaythroughs) + history, + required TResult Function(PlaysTab playsTab) statistics, + required TResult Function( + PlaysTab playsTab, List shuffledBoardGames) + selectGame, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(PlaysTab playsTab, + List finishedBoardGamePlaythroughs)? + history, + TResult Function(PlaysTab playsTab)? statistics, + TResult Function( + PlaysTab playsTab, List shuffledBoardGames)? + selectGame, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(PlaysTab playsTab, + List finishedBoardGamePlaythroughs)? + history, + TResult Function(PlaysTab playsTab)? statistics, + TResult Function( + PlaysTab playsTab, List shuffledBoardGames)? + selectGame, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_History value) history, + required TResult Function(_Statistics value) statistics, + required TResult Function(_SelectGame value) selectGame, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_History value)? history, + TResult Function(_Statistics value)? statistics, + TResult Function(_SelectGame value)? selectGame, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_History value)? history, + TResult Function(_Statistics value)? statistics, + TResult Function(_SelectGame value)? selectGame, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $PlaysPageVisualStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PlaysPageVisualStateCopyWith<$Res> { + factory $PlaysPageVisualStateCopyWith(PlaysPageVisualState value, + $Res Function(PlaysPageVisualState) then) = + _$PlaysPageVisualStateCopyWithImpl<$Res>; + $Res call({PlaysTab playsTab}); +} + +/// @nodoc +class _$PlaysPageVisualStateCopyWithImpl<$Res> + implements $PlaysPageVisualStateCopyWith<$Res> { + _$PlaysPageVisualStateCopyWithImpl(this._value, this._then); + + final PlaysPageVisualState _value; + // ignore: unused_field + final $Res Function(PlaysPageVisualState) _then; + + @override + $Res call({ + Object? playsTab = freezed, + }) { + return _then(_value.copyWith( + playsTab: playsTab == freezed + ? _value.playsTab + : playsTab // ignore: cast_nullable_to_non_nullable + as PlaysTab, + )); + } +} + +/// @nodoc +abstract class _$$_HistoryCopyWith<$Res> + implements $PlaysPageVisualStateCopyWith<$Res> { + factory _$$_HistoryCopyWith( + _$_History value, $Res Function(_$_History) then) = + __$$_HistoryCopyWithImpl<$Res>; + @override + $Res call( + {PlaysTab playsTab, + List finishedBoardGamePlaythroughs}); +} + +/// @nodoc +class __$$_HistoryCopyWithImpl<$Res> + extends _$PlaysPageVisualStateCopyWithImpl<$Res> + implements _$$_HistoryCopyWith<$Res> { + __$$_HistoryCopyWithImpl(_$_History _value, $Res Function(_$_History) _then) + : super(_value, (v) => _then(v as _$_History)); + + @override + _$_History get _value => super._value as _$_History; + + @override + $Res call({ + Object? playsTab = freezed, + Object? finishedBoardGamePlaythroughs = freezed, + }) { + return _then(_$_History( + playsTab == freezed + ? _value.playsTab + : playsTab // ignore: cast_nullable_to_non_nullable + as PlaysTab, + finishedBoardGamePlaythroughs == freezed + ? _value._finishedBoardGamePlaythroughs + : finishedBoardGamePlaythroughs // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$_History implements _History { + const _$_History(this.playsTab, + final List finishedBoardGamePlaythroughs) + : _finishedBoardGamePlaythroughs = finishedBoardGamePlaythroughs; + + @override + final PlaysTab playsTab; + final List _finishedBoardGamePlaythroughs; + @override + List get finishedBoardGamePlaythroughs { + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_finishedBoardGamePlaythroughs); + } + + @override + String toString() { + return 'PlaysPageVisualState.history(playsTab: $playsTab, finishedBoardGamePlaythroughs: $finishedBoardGamePlaythroughs)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_History && + const DeepCollectionEquality().equals(other.playsTab, playsTab) && + const DeepCollectionEquality().equals( + other._finishedBoardGamePlaythroughs, + _finishedBoardGamePlaythroughs)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(playsTab), + const DeepCollectionEquality().hash(_finishedBoardGamePlaythroughs)); + + @JsonKey(ignore: true) + @override + _$$_HistoryCopyWith<_$_History> get copyWith => + __$$_HistoryCopyWithImpl<_$_History>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(PlaysTab playsTab, + List finishedBoardGamePlaythroughs) + history, + required TResult Function(PlaysTab playsTab) statistics, + required TResult Function( + PlaysTab playsTab, List shuffledBoardGames) + selectGame, + }) { + return history(playsTab, finishedBoardGamePlaythroughs); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(PlaysTab playsTab, + List finishedBoardGamePlaythroughs)? + history, + TResult Function(PlaysTab playsTab)? statistics, + TResult Function( + PlaysTab playsTab, List shuffledBoardGames)? + selectGame, + }) { + return history?.call(playsTab, finishedBoardGamePlaythroughs); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(PlaysTab playsTab, + List finishedBoardGamePlaythroughs)? + history, + TResult Function(PlaysTab playsTab)? statistics, + TResult Function( + PlaysTab playsTab, List shuffledBoardGames)? + selectGame, + required TResult orElse(), + }) { + if (history != null) { + return history(playsTab, finishedBoardGamePlaythroughs); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_History value) history, + required TResult Function(_Statistics value) statistics, + required TResult Function(_SelectGame value) selectGame, + }) { + return history(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_History value)? history, + TResult Function(_Statistics value)? statistics, + TResult Function(_SelectGame value)? selectGame, + }) { + return history?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_History value)? history, + TResult Function(_Statistics value)? statistics, + TResult Function(_SelectGame value)? selectGame, + required TResult orElse(), + }) { + if (history != null) { + return history(this); + } + return orElse(); + } +} + +abstract class _History implements PlaysPageVisualState { + const factory _History( + final PlaysTab playsTab, + final List + finishedBoardGamePlaythroughs) = _$_History; + + @override + PlaysTab get playsTab; + List get finishedBoardGamePlaythroughs; + @override + @JsonKey(ignore: true) + _$$_HistoryCopyWith<_$_History> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$_StatisticsCopyWith<$Res> + implements $PlaysPageVisualStateCopyWith<$Res> { + factory _$$_StatisticsCopyWith( + _$_Statistics value, $Res Function(_$_Statistics) then) = + __$$_StatisticsCopyWithImpl<$Res>; + @override + $Res call({PlaysTab playsTab}); +} + +/// @nodoc +class __$$_StatisticsCopyWithImpl<$Res> + extends _$PlaysPageVisualStateCopyWithImpl<$Res> + implements _$$_StatisticsCopyWith<$Res> { + __$$_StatisticsCopyWithImpl( + _$_Statistics _value, $Res Function(_$_Statistics) _then) + : super(_value, (v) => _then(v as _$_Statistics)); + + @override + _$_Statistics get _value => super._value as _$_Statistics; + + @override + $Res call({ + Object? playsTab = freezed, + }) { + return _then(_$_Statistics( + playsTab == freezed + ? _value.playsTab + : playsTab // ignore: cast_nullable_to_non_nullable + as PlaysTab, + )); + } +} + +/// @nodoc + +class _$_Statistics implements _Statistics { + const _$_Statistics(this.playsTab); + + @override + final PlaysTab playsTab; + + @override + String toString() { + return 'PlaysPageVisualState.statistics(playsTab: $playsTab)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_Statistics && + const DeepCollectionEquality().equals(other.playsTab, playsTab)); + } + + @override + int get hashCode => + Object.hash(runtimeType, const DeepCollectionEquality().hash(playsTab)); + + @JsonKey(ignore: true) + @override + _$$_StatisticsCopyWith<_$_Statistics> get copyWith => + __$$_StatisticsCopyWithImpl<_$_Statistics>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(PlaysTab playsTab, + List finishedBoardGamePlaythroughs) + history, + required TResult Function(PlaysTab playsTab) statistics, + required TResult Function( + PlaysTab playsTab, List shuffledBoardGames) + selectGame, + }) { + return statistics(playsTab); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(PlaysTab playsTab, + List finishedBoardGamePlaythroughs)? + history, + TResult Function(PlaysTab playsTab)? statistics, + TResult Function( + PlaysTab playsTab, List shuffledBoardGames)? + selectGame, + }) { + return statistics?.call(playsTab); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(PlaysTab playsTab, + List finishedBoardGamePlaythroughs)? + history, + TResult Function(PlaysTab playsTab)? statistics, + TResult Function( + PlaysTab playsTab, List shuffledBoardGames)? + selectGame, + required TResult orElse(), + }) { + if (statistics != null) { + return statistics(playsTab); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_History value) history, + required TResult Function(_Statistics value) statistics, + required TResult Function(_SelectGame value) selectGame, + }) { + return statistics(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_History value)? history, + TResult Function(_Statistics value)? statistics, + TResult Function(_SelectGame value)? selectGame, + }) { + return statistics?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_History value)? history, + TResult Function(_Statistics value)? statistics, + TResult Function(_SelectGame value)? selectGame, + required TResult orElse(), + }) { + if (statistics != null) { + return statistics(this); + } + return orElse(); + } +} + +abstract class _Statistics implements PlaysPageVisualState { + const factory _Statistics(final PlaysTab playsTab) = _$_Statistics; + + @override + PlaysTab get playsTab; + @override + @JsonKey(ignore: true) + _$$_StatisticsCopyWith<_$_Statistics> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$_SelectGameCopyWith<$Res> + implements $PlaysPageVisualStateCopyWith<$Res> { + factory _$$_SelectGameCopyWith( + _$_SelectGame value, $Res Function(_$_SelectGame) then) = + __$$_SelectGameCopyWithImpl<$Res>; + @override + $Res call({PlaysTab playsTab, List shuffledBoardGames}); +} + +/// @nodoc +class __$$_SelectGameCopyWithImpl<$Res> + extends _$PlaysPageVisualStateCopyWithImpl<$Res> + implements _$$_SelectGameCopyWith<$Res> { + __$$_SelectGameCopyWithImpl( + _$_SelectGame _value, $Res Function(_$_SelectGame) _then) + : super(_value, (v) => _then(v as _$_SelectGame)); + + @override + _$_SelectGame get _value => super._value as _$_SelectGame; + + @override + $Res call({ + Object? playsTab = freezed, + Object? shuffledBoardGames = freezed, + }) { + return _then(_$_SelectGame( + playsTab == freezed + ? _value.playsTab + : playsTab // ignore: cast_nullable_to_non_nullable + as PlaysTab, + shuffledBoardGames == freezed + ? _value._shuffledBoardGames + : shuffledBoardGames // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$_SelectGame implements _SelectGame { + const _$_SelectGame( + this.playsTab, final List shuffledBoardGames) + : _shuffledBoardGames = shuffledBoardGames; + + @override + final PlaysTab playsTab; + final List _shuffledBoardGames; + @override + List get shuffledBoardGames { + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_shuffledBoardGames); + } + + @override + String toString() { + return 'PlaysPageVisualState.selectGame(playsTab: $playsTab, shuffledBoardGames: $shuffledBoardGames)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_SelectGame && + const DeepCollectionEquality().equals(other.playsTab, playsTab) && + const DeepCollectionEquality() + .equals(other._shuffledBoardGames, _shuffledBoardGames)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(playsTab), + const DeepCollectionEquality().hash(_shuffledBoardGames)); + + @JsonKey(ignore: true) + @override + _$$_SelectGameCopyWith<_$_SelectGame> get copyWith => + __$$_SelectGameCopyWithImpl<_$_SelectGame>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(PlaysTab playsTab, + List finishedBoardGamePlaythroughs) + history, + required TResult Function(PlaysTab playsTab) statistics, + required TResult Function( + PlaysTab playsTab, List shuffledBoardGames) + selectGame, + }) { + return selectGame(playsTab, shuffledBoardGames); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(PlaysTab playsTab, + List finishedBoardGamePlaythroughs)? + history, + TResult Function(PlaysTab playsTab)? statistics, + TResult Function( + PlaysTab playsTab, List shuffledBoardGames)? + selectGame, + }) { + return selectGame?.call(playsTab, shuffledBoardGames); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(PlaysTab playsTab, + List finishedBoardGamePlaythroughs)? + history, + TResult Function(PlaysTab playsTab)? statistics, + TResult Function( + PlaysTab playsTab, List shuffledBoardGames)? + selectGame, + required TResult orElse(), + }) { + if (selectGame != null) { + return selectGame(playsTab, shuffledBoardGames); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_History value) history, + required TResult Function(_Statistics value) statistics, + required TResult Function(_SelectGame value) selectGame, + }) { + return selectGame(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_History value)? history, + TResult Function(_Statistics value)? statistics, + TResult Function(_SelectGame value)? selectGame, + }) { + return selectGame?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_History value)? history, + TResult Function(_Statistics value)? statistics, + TResult Function(_SelectGame value)? selectGame, + required TResult orElse(), + }) { + if (selectGame != null) { + return selectGame(this); + } + return orElse(); + } +} + +abstract class _SelectGame implements PlaysPageVisualState { + const factory _SelectGame(final PlaysTab playsTab, + final List shuffledBoardGames) = _$_SelectGame; + + @override + PlaysTab get playsTab; + List get shuffledBoardGames; + @override + @JsonKey(ignore: true) + _$$_SelectGameCopyWith<_$_SelectGame> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/board_games_companion/lib/pages/plays/plays_view_model.dart b/board_games_companion/lib/pages/plays/plays_view_model.dart new file mode 100644 index 00000000..fe0f586b --- /dev/null +++ b/board_games_companion/lib/pages/plays/plays_view_model.dart @@ -0,0 +1,211 @@ +// ignore_for_file: library_private_types_in_public_api + +import 'dart:math'; + +import 'package:basics/basics.dart'; +import 'package:board_games_companion/common/enums/collection_type.dart'; +import 'package:board_games_companion/common/enums/plays_tab.dart'; +import 'package:board_games_companion/models/hive/board_game_details.dart'; +import 'package:board_games_companion/models/hive/playthrough.dart'; +import 'package:board_games_companion/models/player_score.dart'; +import 'package:board_games_companion/models/playthrough_details.dart'; +import 'package:board_games_companion/stores/board_games_store.dart'; +import 'package:board_games_companion/stores/players_store.dart'; +import 'package:board_games_companion/stores/scores_store.dart'; +import 'package:collection/collection.dart'; +import 'package:injectable/injectable.dart'; +import 'package:mobx/mobx.dart'; + +import '../../models/hive/score.dart'; +import '../../stores/playthroughs_store.dart'; +import 'board_game_playthrough.dart'; +import 'game_spinner_filters.dart'; +import 'grouped_board_game_playthroughs.dart'; +import 'plays_page_visual_states.dart'; + +part 'plays_view_model.g.dart'; + +@injectable +class PlaysViewModel = _PlaysViewModel with _$PlaysViewModel; + +abstract class _PlaysViewModel with Store { + _PlaysViewModel( + this._playthroughsStore, + this._boardGamesStore, + this._playersStore, + this._scoreStore, + ); + + static const int _numberOfTimesSpinnerCanTurn = 3; + + final PlaythroughsStore _playthroughsStore; + final BoardGamesStore _boardGamesStore; + final PlayersStore _playersStore; + final ScoresStore _scoreStore; + + @observable + List _shuffledBoardGames = []; + + @observable + ObservableFuture? futureLoadGamesPlaythroughs; + + @observable + PlaysPageVisualState? visualState; + + @observable + GameSpinnerFilters gameSpinnerFilters = const GameSpinnerFilters( + collections: {}, + ); + + @computed + Map get _scores { + return { + for (final Score score + in _scoreStore.scores.where((Score score) => score.playthroughId.isNotNullOrBlank)) + score.toMapKey(): score + }; + } + + @computed + List get finishedPlaythroughs => _playthroughsStore.finishedPlaythroughs + .where((playthrough) => + _boardGamesStore.allBoardGamesInCollectionsMap.containsKey(playthrough.boardGameId)) + .toList(); + + @computed + List get finishedBoardGamePlaythroughs { + final result = []; + final finishedPlaythroughsGrouped = groupBy( + finishedPlaythroughs + ..sort((playthroughA, playthroughB) => + playthroughB.endDate!.compareTo(playthroughA.endDate!)), + (Playthrough playthrough) => playthroughGroupingDateFormat.format(playthrough.endDate!)); + + for (final playthroughsEntry in finishedPlaythroughsGrouped.entries) { + result.add( + GroupedBoardGamePlaythroughs( + date: playthroughGroupingDateFormat.parse(playthroughsEntry.key), + boardGamePlaythroughs: playthroughsEntry.value + .map((playthrough) => BoardGamePlaythrough( + playthrough: PlaythroughDetails( + playthrough: playthrough, + playerScores: [ + for (final playerId in playthrough.playerIds) + PlayerScore( + player: _playersStore.playersById[playerId], + score: _scores['${playthrough.id}$playerId'] ?? + Score( + id: '', + playerId: playerId, + boardGameId: playthrough.boardGameId, + ), + ) + ], + ), + boardGameDetails: + _boardGamesStore.allBoardGamesInCollectionsMap[playthrough.boardGameId]!, + )) + .toList(), + ), + ); + } + + return result; + } + + @computed + bool get hasAnyFinishedPlaythroughs => finishedPlaythroughs.isNotEmpty; + + @computed + bool get hasAnyBoardGames => _boardGamesStore.allBoardGamesInCollections.isNotEmpty; + + @computed + bool get hasAnyBoardGamesToShuffle => shuffledBoardGames.isNotEmpty; + + @computed + List get shuffledBoardGames { + final filteredShuffledBoardGames = []; + if (gameSpinnerFilters.hasOwnedCollection) { + filteredShuffledBoardGames.addAll(_shuffledBoardGames.inCollection(CollectionType.owned)); + } + if (gameSpinnerFilters.hasFriendsCollection) { + filteredShuffledBoardGames.addAll(_shuffledBoardGames.inCollection(CollectionType.friends)); + } + + return filteredShuffledBoardGames; + } + + /// Picking a pseudo-random item index based on the number of shuffled board games + /// multipled by an arbitrary number of how many times the wheel could spin (added for better effect). + /// + /// NOTE: The list will spin in a loop so it's safe to go over the actual board games collection size. + @computed + int get randomItemIndex => + Random().nextInt(shuffledBoardGames.length * _numberOfTimesSpinnerCanTurn); + + @action + void loadGamesPlaythroughs() => + futureLoadGamesPlaythroughs = ObservableFuture(_loadGamesPlaythroughs()); + + @action + void setSelectTab(PlaysTab selectedTab) { + switch (selectedTab) { + case PlaysTab.history: + visualState = PlaysPageVisualState.history(PlaysTab.history, finishedBoardGamePlaythroughs); + break; + + case PlaysTab.statistics: + visualState = const PlaysPageVisualState.statistics(PlaysTab.statistics); + break; + + case PlaysTab.selectGame: + visualState = PlaysPageVisualState.selectGame(PlaysTab.selectGame, shuffledBoardGames); + break; + + default: + } + } + + @action + void toggleGameSpinnerCollectionFilter(CollectionType collectionTypeToggled) { + // MK Remove collection filter only if other filter is active (i.e. if we remove both collection there won't be any games to pick from) + if (gameSpinnerFilters.collections.contains(collectionTypeToggled) && + gameSpinnerFilters.collections.length > 1) { + gameSpinnerFilters = gameSpinnerFilters.copyWith( + collections: Set.from(gameSpinnerFilters.collections)..remove(collectionTypeToggled), + ); + } else { + gameSpinnerFilters = gameSpinnerFilters.copyWith( + collections: Set.from(gameSpinnerFilters.collections)..add(collectionTypeToggled), + ); + } + + visualState = PlaysPageVisualState.selectGame(PlaysTab.selectGame, shuffledBoardGames); + } + + Future _loadGamesPlaythroughs() async { + await _scoreStore.loadScores(); + await _playersStore.loadPlayers(); + await _playthroughsStore.loadPlaythroughs(); + + _shuffledBoardGames = _boardGamesStore.allBoardGames..shuffle(); + _setupGameSpinnerFilters(); + visualState = PlaysPageVisualState.history( + PlaysTab.history, + finishedBoardGamePlaythroughs, + ); + } + + void _setupGameSpinnerFilters() { + final filterCollections = {}; + if (_shuffledBoardGames.inCollection(CollectionType.owned).isNotEmpty) { + filterCollections.add(CollectionType.owned); + } + if (_shuffledBoardGames.inCollection(CollectionType.friends).isNotEmpty) { + filterCollections.add(CollectionType.friends); + } + gameSpinnerFilters = gameSpinnerFilters.copyWith( + collections: filterCollections, + ); + } +} diff --git a/board_games_companion/lib/pages/plays/plays_view_model.g.dart b/board_games_companion/lib/pages/plays/plays_view_model.g.dart new file mode 100644 index 00000000..d83ffe35 --- /dev/null +++ b/board_games_companion/lib/pages/plays/plays_view_model.g.dart @@ -0,0 +1,191 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'plays_view_model.dart'; + +// ************************************************************************** +// StoreGenerator +// ************************************************************************** + +// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers + +mixin _$PlaysViewModel on _PlaysViewModel, Store { + Computed>? _$_scoresComputed; + + @override + Map get _scores => + (_$_scoresComputed ??= Computed>(() => super._scores, + name: '_PlaysViewModel._scores')) + .value; + Computed>? _$finishedPlaythroughsComputed; + + @override + List get finishedPlaythroughs => + (_$finishedPlaythroughsComputed ??= Computed>( + () => super.finishedPlaythroughs, + name: '_PlaysViewModel.finishedPlaythroughs')) + .value; + Computed>? + _$finishedBoardGamePlaythroughsComputed; + + @override + List get finishedBoardGamePlaythroughs => + (_$finishedBoardGamePlaythroughsComputed ??= + Computed>( + () => super.finishedBoardGamePlaythroughs, + name: '_PlaysViewModel.finishedBoardGamePlaythroughs')) + .value; + Computed? _$hasAnyFinishedPlaythroughsComputed; + + @override + bool get hasAnyFinishedPlaythroughs => + (_$hasAnyFinishedPlaythroughsComputed ??= Computed( + () => super.hasAnyFinishedPlaythroughs, + name: '_PlaysViewModel.hasAnyFinishedPlaythroughs')) + .value; + Computed? _$hasAnyBoardGamesComputed; + + @override + bool get hasAnyBoardGames => (_$hasAnyBoardGamesComputed ??= Computed( + () => super.hasAnyBoardGames, + name: '_PlaysViewModel.hasAnyBoardGames')) + .value; + Computed? _$hasAnyBoardGamesToShuffleComputed; + + @override + bool get hasAnyBoardGamesToShuffle => (_$hasAnyBoardGamesToShuffleComputed ??= + Computed(() => super.hasAnyBoardGamesToShuffle, + name: '_PlaysViewModel.hasAnyBoardGamesToShuffle')) + .value; + Computed>? _$shuffledBoardGamesComputed; + + @override + List get shuffledBoardGames => + (_$shuffledBoardGamesComputed ??= Computed>( + () => super.shuffledBoardGames, + name: '_PlaysViewModel.shuffledBoardGames')) + .value; + Computed? _$randomItemIndexComputed; + + @override + int get randomItemIndex => + (_$randomItemIndexComputed ??= Computed(() => super.randomItemIndex, + name: '_PlaysViewModel.randomItemIndex')) + .value; + + late final _$_shuffledBoardGamesAtom = + Atom(name: '_PlaysViewModel._shuffledBoardGames', context: context); + + @override + List get _shuffledBoardGames { + _$_shuffledBoardGamesAtom.reportRead(); + return super._shuffledBoardGames; + } + + @override + set _shuffledBoardGames(List value) { + _$_shuffledBoardGamesAtom.reportWrite(value, super._shuffledBoardGames, () { + super._shuffledBoardGames = value; + }); + } + + late final _$futureLoadGamesPlaythroughsAtom = Atom( + name: '_PlaysViewModel.futureLoadGamesPlaythroughs', context: context); + + @override + ObservableFuture? get futureLoadGamesPlaythroughs { + _$futureLoadGamesPlaythroughsAtom.reportRead(); + return super.futureLoadGamesPlaythroughs; + } + + @override + set futureLoadGamesPlaythroughs(ObservableFuture? value) { + _$futureLoadGamesPlaythroughsAtom + .reportWrite(value, super.futureLoadGamesPlaythroughs, () { + super.futureLoadGamesPlaythroughs = value; + }); + } + + late final _$visualStateAtom = + Atom(name: '_PlaysViewModel.visualState', context: context); + + @override + PlaysPageVisualState? get visualState { + _$visualStateAtom.reportRead(); + return super.visualState; + } + + @override + set visualState(PlaysPageVisualState? value) { + _$visualStateAtom.reportWrite(value, super.visualState, () { + super.visualState = value; + }); + } + + late final _$gameSpinnerFiltersAtom = + Atom(name: '_PlaysViewModel.gameSpinnerFilters', context: context); + + @override + GameSpinnerFilters get gameSpinnerFilters { + _$gameSpinnerFiltersAtom.reportRead(); + return super.gameSpinnerFilters; + } + + @override + set gameSpinnerFilters(GameSpinnerFilters value) { + _$gameSpinnerFiltersAtom.reportWrite(value, super.gameSpinnerFilters, () { + super.gameSpinnerFilters = value; + }); + } + + late final _$_PlaysViewModelActionController = + ActionController(name: '_PlaysViewModel', context: context); + + @override + void loadGamesPlaythroughs() { + final _$actionInfo = _$_PlaysViewModelActionController.startAction( + name: '_PlaysViewModel.loadGamesPlaythroughs'); + try { + return super.loadGamesPlaythroughs(); + } finally { + _$_PlaysViewModelActionController.endAction(_$actionInfo); + } + } + + @override + void setSelectTab(PlaysTab selectedTab) { + final _$actionInfo = _$_PlaysViewModelActionController.startAction( + name: '_PlaysViewModel.setSelectTab'); + try { + return super.setSelectTab(selectedTab); + } finally { + _$_PlaysViewModelActionController.endAction(_$actionInfo); + } + } + + @override + void toggleGameSpinnerCollectionFilter(CollectionType collectionTypeToggled) { + final _$actionInfo = _$_PlaysViewModelActionController.startAction( + name: '_PlaysViewModel.toggleGameSpinnerCollectionFilter'); + try { + return super.toggleGameSpinnerCollectionFilter(collectionTypeToggled); + } finally { + _$_PlaysViewModelActionController.endAction(_$actionInfo); + } + } + + @override + String toString() { + return ''' +futureLoadGamesPlaythroughs: ${futureLoadGamesPlaythroughs}, +visualState: ${visualState}, +gameSpinnerFilters: ${gameSpinnerFilters}, +finishedPlaythroughs: ${finishedPlaythroughs}, +finishedBoardGamePlaythroughs: ${finishedBoardGamePlaythroughs}, +hasAnyFinishedPlaythroughs: ${hasAnyFinishedPlaythroughs}, +hasAnyBoardGames: ${hasAnyBoardGames}, +hasAnyBoardGamesToShuffle: ${hasAnyBoardGamesToShuffle}, +shuffledBoardGames: ${shuffledBoardGames}, +randomItemIndex: ${randomItemIndex} + '''; + } +} diff --git a/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_view_model.g.dart b/board_games_companion/lib/pages/plays/playthroughs_history_view_model.g.dart similarity index 88% rename from board_games_companion/lib/pages/playthroughs_history/playthroughs_history_view_model.g.dart rename to board_games_companion/lib/pages/plays/playthroughs_history_view_model.g.dart index 6a0f8a40..ced36239 100644 --- a/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_view_model.g.dart +++ b/board_games_companion/lib/pages/plays/playthroughs_history_view_model.g.dart @@ -43,6 +43,14 @@ mixin _$PlaythroughsHistoryViewModel on _PlaythroughsHistoryViewModel, Store { () => super.hasAnyFinishedPlaythroughs, name: '_PlaythroughsHistoryViewModel.hasAnyFinishedPlaythroughs')) .value; + Computed>? _$shuffledBoardGamesComputed; + + @override + List get shuffledBoardGames => + (_$shuffledBoardGamesComputed ??= Computed>( + () => super.shuffledBoardGames, + name: '_PlaythroughsHistoryViewModel.shuffledBoardGames')) + .value; late final _$futureLoadGamesPlaythroughsAtom = Atom( name: '_PlaythroughsHistoryViewModel.futureLoadGamesPlaythroughs', @@ -83,7 +91,8 @@ mixin _$PlaythroughsHistoryViewModel on _PlaythroughsHistoryViewModel, Store { futureLoadGamesPlaythroughs: ${futureLoadGamesPlaythroughs}, finishedPlaythroughs: ${finishedPlaythroughs}, finishedBoardGamePlaythroughs: ${finishedBoardGamePlaythroughs}, -hasAnyFinishedPlaythroughs: ${hasAnyFinishedPlaythroughs} +hasAnyFinishedPlaythroughs: ${hasAnyFinishedPlaythroughs}, +shuffledBoardGames: ${shuffledBoardGames} '''; } } diff --git a/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_page.dart b/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_page.dart deleted file mode 100644 index a1046de1..00000000 --- a/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_page.dart +++ /dev/null @@ -1,381 +0,0 @@ -import 'package:board_games_companion/extensions/int_extensions.dart'; -import 'package:board_games_companion/pages/edit_playthrough/edit_playthrough_page.dart'; -import 'package:board_games_companion/pages/playthroughs/playthroughs_page.dart'; -import 'package:board_games_companion/pages/playthroughs_history/board_game_playthrough.dart'; -import 'package:board_games_companion/pages/playthroughs_history/grouped_board_game_playthroughs.dart'; -import 'package:board_games_companion/pages/playthroughs_history/playthroughs_history_view_model.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:mobx/mobx.dart'; - -import '../../common/app_colors.dart'; -import '../../common/app_styles.dart'; -import '../../common/app_text.dart'; -import '../../common/app_theme.dart'; -import '../../common/dimensions.dart'; -import '../../models/navigation/board_game_details_page_arguments.dart'; -import '../../models/navigation/edit_playthrough_page_arguments.dart'; -import '../../models/navigation/playthroughs_page_arguments.dart'; -import '../../widgets/board_games/board_game_tile.dart'; -import '../../widgets/common/loading_indicator_widget.dart'; -import '../../widgets/common/panel_container.dart'; -import '../../widgets/common/slivers/bgc_sliver_header_delegate.dart'; -import '../board_game_details/board_game_details_page.dart'; -import '../home/home_page.dart'; - -class PlaythroughsHistoryPage extends StatefulWidget { - const PlaythroughsHistoryPage({ - required this.viewModel, - Key? key, - }) : super(key: key); - - final PlaythroughsHistoryViewModel viewModel; - - @override - State createState() => _PlaythroughsHistoryPageState(); -} - -class _PlaythroughsHistoryPageState extends State { - @override - void initState() { - super.initState(); - - widget.viewModel.loadGamesPlaythroughs(); - } - - @override - Widget build(BuildContext context) => Observer( - builder: (_) { - switch (widget.viewModel.futureLoadGamesPlaythroughs?.status ?? FutureStatus.pending) { - case FutureStatus.pending: - case FutureStatus.rejected: - return const CustomScrollView( - slivers: [ - _AppBar(), - SliverFillRemaining(child: LoadingIndicator()), - ], - ); - case FutureStatus.fulfilled: - return CustomScrollView( - slivers: [ - const _AppBar(), - if (!widget.viewModel.hasAnyFinishedPlaythroughs) const _NoPlaythroughsSliver(), - for (final groupedBoardGamePlaythroughs - in widget.viewModel.finishedBoardGamePlaythroughs) ...[ - _PlaythroughGroupHeaderSliver( - widget: widget, - groupedBoardGamePlaythroughs: groupedBoardGamePlaythroughs, - ), - _PlaythroughGroupListSliver( - groupedBoardGamePlaythroughs: groupedBoardGamePlaythroughs, - ), - if (groupedBoardGamePlaythroughs == - widget.viewModel.finishedBoardGamePlaythroughs.last) - const SliverToBoxAdapter( - child: SizedBox(height: Dimensions.bottomTabTopHeight), - ), - ] - ], - ); - } - }, - ); -} - -class _PlaythroughGroupListSliver extends StatelessWidget { - const _PlaythroughGroupListSliver({ - Key? key, - required this.groupedBoardGamePlaythroughs, - }) : super(key: key); - - final GroupedBoardGamePlaythroughs groupedBoardGamePlaythroughs; - - @override - Widget build(BuildContext context) { - return SliverList( - delegate: SliverChildBuilderDelegate( - (_, index) { - final boardGamePlaythrough = groupedBoardGamePlaythroughs.boardGamePlaythroughs[index]; - final isFirst = index == 0; - return Padding( - padding: EdgeInsets.only( - top: isFirst ? Dimensions.standardSpacing : 0, - bottom: Dimensions.standardSpacing, - left: Dimensions.standardSpacing, - right: Dimensions.standardSpacing, - ), - child: PanelContainer( - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(AppStyles.panelContainerCornerRadius), - onTap: () => _navigateToEditPlaythrough( - context, - boardGamePlaythrough.boardGameDetails.id, - boardGamePlaythrough.playthrough.id, - ), - child: Padding( - padding: const EdgeInsets.all(Dimensions.standardSpacing), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - IntrinsicHeight( - child: Row( - children: [ - SizedBox( - height: Dimensions.collectionSearchResultBoardGameImageHeight, - width: Dimensions.collectionSearchResultBoardGameImageWidth, - child: BoardGameTile( - id: boardGamePlaythrough.id, - imageUrl: - boardGamePlaythrough.boardGameDetails.thumbnailUrl ?? '', - ), - ), - const SizedBox(width: Dimensions.standardSpacing), - Expanded( - child: - _PlaythroughDetails(boardGamePlaythrough: boardGamePlaythrough), - ), - _PlaythroughActions( - onTapBoardGameDetails: () => - _navigateToBoardGameDetails(context, boardGamePlaythrough), - onTapPlaythroughs: () => - _navigateToPlaythrough(context, boardGamePlaythrough), - ), - ], - ), - ) - ], - ), - ), - ), - ), - ), - ); - }, - childCount: groupedBoardGamePlaythroughs.boardGamePlaythroughs.length, - ), - ); - } - - Future _navigateToPlaythrough( - BuildContext context, - BoardGamePlaythrough boardGamePlaythrough, - ) => - Navigator.pushNamed( - context, - PlaythroughsPage.pageRoute, - arguments: PlaythroughsPageArguments( - boardGameDetails: boardGamePlaythrough.boardGameDetails, - boardGameImageHeroId: boardGamePlaythrough.id, - ), - ); - - void _navigateToBoardGameDetails( - BuildContext context, - BoardGamePlaythrough boardGamePlaythrough, - ) { - Navigator.pushNamed( - context, - BoardGamesDetailsPage.pageRoute, - arguments: BoardGameDetailsPageArguments( - boardGameId: boardGamePlaythrough.boardGameDetails.id, - boardGameName: boardGamePlaythrough.boardGameDetails.name, - boardGameImageHeroId: boardGamePlaythrough.id, - navigatingFromType: PlaythroughsHistoryPage, - ), - ); - } - - void _navigateToEditPlaythrough( - BuildContext context, - String boardGameId, - String playthroughId, - ) { - Navigator.pushNamed( - context, - EditPlaythroughPage.pageRoute, - arguments: EditPlaythroughPageArguments( - boardGameId: boardGameId, - playthroughId: playthroughId, - goBackPageRoute: HomePage.pageRoute, - ), - ); - } -} - -class _PlaythroughDetails extends StatelessWidget { - const _PlaythroughDetails({ - Key? key, - required this.boardGamePlaythrough, - }) : super(key: key); - - static const double _playthroughStatsIconSize = 16; - static const double _playthroughStatsFontAwesomeIconSize = _playthroughStatsIconSize - 4; - - final BoardGamePlaythrough boardGamePlaythrough; - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - boardGamePlaythrough.boardGameDetails.name, - overflow: TextOverflow.ellipsis, - style: AppTheme.theme.textTheme.bodyLarge, - ), - const SizedBox(height: Dimensions.standardSpacing), - _PlaythroughGeneralStats( - icon: const Icon( - FontAwesomeIcons.trophy, - size: _playthroughStatsFontAwesomeIconSize, - ), - statistic: - '${boardGamePlaythrough.winner.player?.name ?? ''} (${boardGamePlaythrough.winner.score.valueInt} points)', - ), - _PlaythroughGeneralStats( - icon: const Icon(Icons.people, size: _playthroughStatsIconSize), - statistic: '${boardGamePlaythrough.playthrough.playerScores.length} players', - ), - _PlaythroughGeneralStats( - icon: const Icon(Icons.hourglass_bottom, size: _playthroughStatsIconSize), - statistic: boardGamePlaythrough.playthrough.duration.inSeconds - .toPlaytimeDuration(showSeconds: false), - ), - ], - ); - } -} - -class _PlaythroughGeneralStats extends StatelessWidget { - const _PlaythroughGeneralStats({ - Key? key, - required this.icon, - required this.statistic, - }) : super(key: key); - - final Widget icon; - final String statistic; - - static const double _uniformedIconSize = 20; - - @override - Widget build(BuildContext context) { - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox(width: _uniformedIconSize, child: Center(child: icon)), - const SizedBox(width: Dimensions.standardSpacing), - Text( - statistic, - overflow: TextOverflow.ellipsis, - style: AppTheme.subTitleTextStyle.copyWith(color: AppColors.whiteColor), - ), - ], - ); - } -} - -class _PlaythroughActions extends StatelessWidget { - const _PlaythroughActions({ - Key? key, - required this.onTapBoardGameDetails, - required this.onTapPlaythroughs, - }) : super(key: key); - - final VoidCallback onTapBoardGameDetails; - final VoidCallback onTapPlaythroughs; - - @override - Widget build(BuildContext context) { - return Column( - children: [ - IconButton( - icon: const Icon(Icons.info), - onPressed: () => onTapBoardGameDetails(), - ), - const Expanded(child: SizedBox.shrink()), - IconButton( - icon: const FaIcon(FontAwesomeIcons.dice), - onPressed: () => onTapPlaythroughs(), - ), - ], - ); - } -} - -class _PlaythroughGroupHeaderSliver extends StatelessWidget { - const _PlaythroughGroupHeaderSliver({ - Key? key, - required this.widget, - required this.groupedBoardGamePlaythroughs, - }) : super(key: key); - - final PlaythroughsHistoryPage widget; - final GroupedBoardGamePlaythroughs groupedBoardGamePlaythroughs; - - @override - Widget build(BuildContext context) { - return SliverPersistentHeader( - delegate: BgcSliverHeaderDelegate(primaryTitle: groupedBoardGamePlaythroughs.dateFormtted), - ); - } -} - -class _NoPlaythroughsSliver extends StatelessWidget { - const _NoPlaythroughsSliver({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => SliverPadding( - padding: const EdgeInsets.all(Dimensions.doubleStandardSpacing), - sliver: SliverToBoxAdapter( - child: Column( - mainAxisSize: MainAxisSize.min, - children: const [ - SizedBox(height: Dimensions.emptyPageTitleTopSpacing), - Center( - child: Text( - AppText.playHistoryPageEmptyTitle, - style: TextStyle(fontSize: Dimensions.extraLargeFontSize), - ), - ), - SizedBox(height: Dimensions.doubleStandardSpacing), - FaIcon( - FontAwesomeIcons.dice, - size: Dimensions.emptyPageTitleIconSize, - color: AppColors.primaryColor, - ), - SizedBox(height: Dimensions.doubleStandardSpacing), - Text.rich( - TextSpan( - children: [ - TextSpan(text: AppText.playHistoryPageEmptyTextPartTwo), - ], - ), - textAlign: TextAlign.justify, - style: TextStyle(fontSize: Dimensions.mediumFontSize), - ), - ], - ), - ), - ); -} - -class _AppBar extends StatelessWidget { - const _AppBar({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return const SliverAppBar( - pinned: true, - floating: true, - forceElevated: true, - elevation: Dimensions.defaultElevation, - titleSpacing: Dimensions.standardSpacing, - foregroundColor: AppColors.accentColor, - title: Text(AppText.playHistoryPageTitle, style: AppTheme.titleTextStyle), - ); - } -} diff --git a/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_view_model.dart b/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_view_model.dart deleted file mode 100644 index b2453e26..00000000 --- a/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_view_model.dart +++ /dev/null @@ -1,106 +0,0 @@ -// ignore_for_file: library_private_types_in_public_api - -import 'package:basics/basics.dart'; -import 'package:board_games_companion/models/hive/playthrough.dart'; -import 'package:board_games_companion/models/player_score.dart'; -import 'package:board_games_companion/models/playthrough_details.dart'; -import 'package:board_games_companion/stores/board_games_store.dart'; -import 'package:board_games_companion/stores/players_store.dart'; -import 'package:board_games_companion/stores/scores_store.dart'; -import 'package:collection/collection.dart'; -import 'package:injectable/injectable.dart'; -import 'package:mobx/mobx.dart'; - -import '../../models/hive/score.dart'; -import '../../stores/playthroughs_store.dart'; -import 'board_game_playthrough.dart'; -import 'grouped_board_game_playthroughs.dart'; - -part 'playthroughs_history_view_model.g.dart'; - -@injectable -class PlaythroughsHistoryViewModel = _PlaythroughsHistoryViewModel - with _$PlaythroughsHistoryViewModel; - -abstract class _PlaythroughsHistoryViewModel with Store { - _PlaythroughsHistoryViewModel( - this._playthroughsStore, - this._boardGamesStore, - this._playersStore, - this._scoreStore, - ); - - final PlaythroughsStore _playthroughsStore; - final BoardGamesStore _boardGamesStore; - final PlayersStore _playersStore; - final ScoresStore _scoreStore; - - @computed - Map get _scores { - return { - for (final Score score - in _scoreStore.scores.where((Score score) => score.playthroughId.isNotNullOrBlank)) - score.toMapKey(): score - }; - } - - @observable - ObservableFuture? futureLoadGamesPlaythroughs; - - @computed - List get finishedPlaythroughs => _playthroughsStore.finishedPlaythroughs.toList(); - - @computed - List get finishedBoardGamePlaythroughs { - final result = []; - final finishedPlaythroughsGrouped = groupBy( - finishedPlaythroughs - ..sort((playthroughA, playthroughB) => - playthroughB.endDate!.compareTo(playthroughA.endDate!)), - (Playthrough playthrough) => playthroughGroupingDateFormat.format(playthrough.endDate!)); - - for (final playthroughsEntry in finishedPlaythroughsGrouped.entries) { - result.add( - GroupedBoardGamePlaythroughs( - date: playthroughGroupingDateFormat.parse(playthroughsEntry.key), - boardGamePlaythroughs: playthroughsEntry.value - .map((playthrough) => BoardGamePlaythrough( - playthrough: PlaythroughDetails( - playthrough: playthrough, - playerScores: [ - for (final playerId in playthrough.playerIds) - PlayerScore( - player: _playersStore.playersById[playerId], - score: _scores['${playthrough.id}$playerId'] ?? - Score( - id: '', - playerId: playerId, - boardGameId: playthrough.boardGameId, - ), - ) - ], - ), - boardGameDetails: - _boardGamesStore.allBoardGamesInCollectionsMap[playthrough.boardGameId]!, - )) - .toList(), - ), - ); - } - - return result; - } - - @computed - bool get hasAnyFinishedPlaythroughs => finishedPlaythroughs.isNotEmpty; - - @action - void loadGamesPlaythroughs() => - futureLoadGamesPlaythroughs = ObservableFuture(_loadGamesPlaythroughs()); - - Future _loadGamesPlaythroughs() async { - await _scoreStore.loadScores(); - await _playersStore.loadPlayers(); - await _playthroughsStore.loadPlaythroughs(); - } -} diff --git a/board_games_companion/lib/widgets/board_games/board_game_name.dart b/board_games_companion/lib/widgets/board_games/board_game_name.dart new file mode 100644 index 00000000..d4487a93 --- /dev/null +++ b/board_games_companion/lib/widgets/board_games/board_game_name.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +import '../../common/app_colors.dart'; +import '../../common/app_styles.dart'; +import '../../common/dimensions.dart'; + +class BoardGameName extends StatelessWidget { + const BoardGameName({ + Key? key, + required this.name, + this.fontSize = Dimensions.smallFontSize, + }) : super(key: key); + + final String name; + final double fontSize; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only( + bottom: Dimensions.standardSpacing, + left: Dimensions.halfStandardSpacing, + right: Dimensions.standardSpacing, + ), + child: Container( + decoration: BoxDecoration( + color: AppColors.accentColor.withAlpha(AppStyles.opacity70Percent), + borderRadius: const BorderRadius.all(Radius.circular(AppStyles.defaultCornerRadius)), + ), + child: Padding( + padding: const EdgeInsets.all(Dimensions.halfStandardSpacing), + child: Text( + name, + textAlign: TextAlign.center, + style: TextStyle( + color: AppColors.defaultTextColor, + fontSize: fontSize, + ), + ), + ), + ), + ); + } +} diff --git a/board_games_companion/lib/widgets/board_games/board_game_tile.dart b/board_games_companion/lib/widgets/board_games/board_game_tile.dart index cd02e6b3..2a9f2f2d 100644 --- a/board_games_companion/lib/widgets/board_games/board_game_tile.dart +++ b/board_games_companion/lib/widgets/board_games/board_game_tile.dart @@ -1,15 +1,15 @@ import 'package:basics/basics.dart'; +import 'package:board_games_companion/common/app_theme.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import '../../common/animation_tags.dart'; import '../../common/app_colors.dart'; -import '../../common/app_styles.dart'; -import '../../common/app_theme.dart'; import '../../common/constants.dart'; import '../../common/dimensions.dart'; import '../common/rank_ribbon.dart'; import '../common/ripple_effect.dart'; +import 'board_game_name.dart'; class BoardGameTile extends StatefulWidget { const BoardGameTile({ @@ -22,6 +22,7 @@ class BoardGameTile extends StatefulWidget { this.onTap, this.heroTag = AnimationTags.boardGameHeroTag, this.elevation, + this.borderRadius = AppTheme.defaultBorderRadius, }) : super(key: key); final String id; @@ -32,17 +33,18 @@ class BoardGameTile extends StatefulWidget { final Future Function()? onTap; final String heroTag; final double? elevation; + final BorderRadiusGeometry borderRadius; @override - State createState() => _BoardGameSearchItemWidget(); + State createState() => _BoardGameTileState(); } -class _BoardGameSearchItemWidget extends State { +class _BoardGameTileState extends State { @override Widget build(BuildContext context) { return Material( elevation: widget.elevation ?? 0, - borderRadius: AppTheme.defaultBoxRadius, + borderRadius: widget.borderRadius, shadowColor: AppColors.primaryColor, child: Stack( children: [ @@ -52,22 +54,22 @@ class _BoardGameSearchItemWidget extends State { imageUrl: widget.imageUrl, imageBuilder: (context, imageProvider) => Container( decoration: BoxDecoration( - borderRadius: AppTheme.defaultBoxRadius, + borderRadius: widget.borderRadius, image: DecorationImage(image: imageProvider, fit: BoxFit.cover), ), ), fit: BoxFit.fitWidth, placeholder: (context, url) => Container( - decoration: const BoxDecoration( + decoration: BoxDecoration( color: AppColors.primaryColor, - borderRadius: AppTheme.defaultBoxRadius, + borderRadius: widget.borderRadius, ), ), errorWidget: (context, url, dynamic error) => Container( - decoration: const BoxDecoration( - borderRadius: AppTheme.defaultBoxRadius, + decoration: BoxDecoration( + borderRadius: widget.borderRadius, color: AppColors.primaryColor, - image: DecorationImage( + image: const DecorationImage( alignment: Alignment.center, image: AssetImage('assets/icons/logo.png'), ), @@ -76,65 +78,18 @@ class _BoardGameSearchItemWidget extends State { ), ), if (widget.name.isNotNullOrBlank) - _Name( - name: widget.name!, - fontSize: widget.nameFontSize, + Align( + alignment: Alignment.bottomCenter, + child: BoardGameName(name: widget.name!, fontSize: widget.nameFontSize), ), if (widget.rank != null && widget.rank! < Constants.top100) - Positioned( - top: 0, - right: 12, - child: RankRibbon(rank: widget.rank!), - ), + Positioned(top: 0, right: 12, child: RankRibbon(rank: widget.rank!)), Positioned.fill( - child: RippleEffect(borderRadius: AppTheme.defaultBoxRadius, onTap: widget.onTap), + child: + RippleEffect(borderRadius: widget.borderRadius.resolve(null), onTap: widget.onTap), ) ], ), ); } } - -class _Name extends StatelessWidget { - const _Name({ - Key? key, - required this.name, - required this.fontSize, - }) : super(key: key); - - final String name; - final double fontSize; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only( - bottom: Dimensions.standardSpacing, - left: Dimensions.halfStandardSpacing, - right: Dimensions.standardSpacing, - ), - child: Align( - alignment: Alignment.bottomCenter, - child: Container( - decoration: BoxDecoration( - color: AppColors.accentColor.withAlpha(AppStyles.opacity70Percent), - borderRadius: const BorderRadius.all( - Radius.circular(AppStyles.defaultCornerRadius), - ), - ), - child: Padding( - padding: const EdgeInsets.all(Dimensions.halfStandardSpacing), - child: Text( - name, - textAlign: TextAlign.center, - style: TextStyle( - color: AppColors.defaultTextColor, - fontSize: fontSize, - ), - ), - ), - ), - ), - ); - } -} diff --git a/board_games_companion/lib/widgets/common/app_bar/app_bar_bottom_tab.dart b/board_games_companion/lib/widgets/common/app_bar/app_bar_bottom_tab.dart new file mode 100644 index 00000000..25d18adb --- /dev/null +++ b/board_games_companion/lib/widgets/common/app_bar/app_bar_bottom_tab.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +import '../../../common/app_colors.dart'; +import '../../../common/app_theme.dart'; +import '../../../common/dimensions.dart'; + +class AppBarBottomTab extends StatelessWidget { + const AppBarBottomTab( + this.title, + this.icon, { + this.isSelected = true, + Key? key, + }) : super(key: key); + + final String title; + final IconData icon; + final bool isSelected; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Tab( + icon: Icon( + icon, + color: isSelected ? AppColors.selectedTabIconColor : AppColors.deselectedTabIconColor, + ), + iconMargin: const EdgeInsets.only(bottom: Dimensions.halfStandardSpacing), + child: Text( + title, + style: AppTheme.titleTextStyle.copyWith( + fontSize: Dimensions.standardFontSize, + color: isSelected ? AppColors.defaultTextColor : AppColors.deselectedTabIconColor, + ), + ), + ), + ], + ); + } +} diff --git a/board_games_companion/lib/widgets/common/board_game/board_game_property.dart b/board_games_companion/lib/widgets/common/board_game/board_game_property.dart new file mode 100644 index 00000000..518371ba --- /dev/null +++ b/board_games_companion/lib/widgets/common/board_game/board_game_property.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +import '../../../common/app_colors.dart'; +import '../../../common/app_theme.dart'; +import '../../../common/dimensions.dart'; + +class BoardGameProperty extends StatelessWidget { + const BoardGameProperty({ + Key? key, + required this.icon, + required this.iconWidth, + required this.propertyName, + this.fontSize = Dimensions.standardFontSize, + }) : super(key: key); + + final Widget icon; + final String propertyName; + final double fontSize; + + /// Reuquired to specify because widget can be anything and might not behave as an icon (e.g. [RatingHexagon]) + final double iconWidth; + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: iconWidth, child: Center(child: icon)), + const SizedBox(width: Dimensions.standardSpacing), + Text( + propertyName, + overflow: TextOverflow.ellipsis, + style: AppTheme.subTitleTextStyle.copyWith( + color: AppColors.whiteColor, + fontSize: fontSize, + ), + ), + ], + ); + } +} diff --git a/board_games_companion/lib/widgets/common/collection_toggle_button.dart b/board_games_companion/lib/widgets/common/collection_toggle_button.dart new file mode 100644 index 00000000..fb8df6e6 --- /dev/null +++ b/board_games_companion/lib/widgets/common/collection_toggle_button.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; + +import '../../common/app_colors.dart'; +import '../../common/dimensions.dart'; + +class CollectionToggleButton extends StatelessWidget { + const CollectionToggleButton({ + required this.icon, + required this.title, + required this.isSelected, + Key? key, + }) : super(key: key); + + final IconData icon; + final String title; + final bool isSelected; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: Dimensions.standardSpacing, + vertical: Dimensions.halfStandardSpacing, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon), + Text( + title, + style: TextStyle( + color: isSelected ? AppColors.whiteColor : AppColors.deselectedTabIconColor, + ), + ), + ], + ), + ); + } +} diff --git a/board_games_companion/lib/widgets/common/custom_icon_button.dart b/board_games_companion/lib/widgets/common/custom_icon_button.dart index f4ed6666..8ef2b303 100644 --- a/board_games_companion/lib/widgets/common/custom_icon_button.dart +++ b/board_games_companion/lib/widgets/common/custom_icon_button.dart @@ -19,9 +19,7 @@ class CustomIconButton extends StatelessWidget { return InkWell( onTap: onTap, child: Padding( - padding: const EdgeInsets.all( - Dimensions.halfStandardSpacing, - ), + padding: const EdgeInsets.all(Dimensions.halfStandardSpacing), child: ClipRRect( borderRadius: const BorderRadius.all( Radius.circular(AppStyles.defaultCornerRadius), diff --git a/board_games_companion/lib/widgets/common/panel_container.dart b/board_games_companion/lib/widgets/common/panel_container.dart index aa0ad972..2735cc94 100644 --- a/board_games_companion/lib/widgets/common/panel_container.dart +++ b/board_games_companion/lib/widgets/common/panel_container.dart @@ -1,7 +1,6 @@ import 'package:board_games_companion/common/app_theme.dart'; import 'package:flutter/material.dart'; -import '../../common/app_colors.dart'; import '../../common/app_styles.dart'; import '../elevated_container.dart'; @@ -19,22 +18,13 @@ class PanelContainer extends StatelessWidget { Widget build(BuildContext context) { return ElevatedContainer( elevation: AppStyles.defaultElevation, - borderRadius: AppTheme.defaultBoxRadius, + borderRadius: AppTheme.defaultBorderRadius, child: Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - stops: [0.2, 0.5, 0.9], - colors: [ - AppColors.endDefaultPageBackgroundColorGradient, - AppColors.startDefaultPageBackgroundColorGradient, - AppColors.endDefaultPageBackgroundColorGradient, - ], - ), - borderRadius: AppTheme.defaultBoxRadius, + decoration: AppStyles.tileGradientBoxDecoration, + child: Material( + color: Colors.transparent, + child: child, ), - child: child, ), ); } diff --git a/board_games_companion/lib/widgets/playthrough/calendar_card.dart b/board_games_companion/lib/widgets/playthrough/calendar_card.dart index 1eb848e8..77cbae01 100644 --- a/board_games_companion/lib/widgets/playthrough/calendar_card.dart +++ b/board_games_companion/lib/widgets/playthrough/calendar_card.dart @@ -26,10 +26,10 @@ class CalendarCard extends StatelessWidget { width: _width, child: ElevatedContainer( elevation: AppStyles.defaultElevation, - borderRadius: AppTheme.defaultBoxRadius, + borderRadius: AppTheme.defaultBorderRadius, child: RippleEffect( backgroundColor: AppColors.whiteColor, - borderRadius: AppTheme.defaultBoxRadius, + borderRadius: AppTheme.defaultBorderRadius, onTap: onTap, child: Column( mainAxisSize: MainAxisSize.min, diff --git a/board_games_companion/pubspec.lock b/board_games_companion/pubspec.lock index 269b79e5..b22eac09 100644 --- a/board_games_companion/pubspec.lock +++ b/board_games_companion/pubspec.lock @@ -169,6 +169,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.16.0" + confetti: + dependency: "direct main" + description: + name: confetti + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0" convert: dependency: transitive description: @@ -449,7 +456,7 @@ packages: name: font_awesome_flutter url: "https://pub.dartlang.org" source: hosted - version: "10.2.1" + version: "10.3.0" freezed: dependency: "direct main" description: diff --git a/board_games_companion/pubspec.yaml b/board_games_companion/pubspec.yaml index 6f7cebc5..21cd6cd1 100644 --- a/board_games_companion/pubspec.yaml +++ b/board_games_companion/pubspec.yaml @@ -14,7 +14,7 @@ description: A new Flutter project. version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" dependencies: animations: ^2.0.1 @@ -23,7 +23,8 @@ dependencies: basics: ^0.9.0 cached_network_image: ^3.2.1 collection: ^1.15.0 - convex_bottom_bar: ^3.0.0 + confetti: ^0.7.0 + convex_bottom_bar: ^3.0.0 dio: ^4.0.6 dio_cache_interceptor: ^3.3.0 dio_cache_interceptor_hive_store: ^3.2.0 @@ -33,13 +34,13 @@ dependencies: firebase_core: ^1.7.0 firebase_crashlytics: ^2.2.2 firebase_analytics: ^8.3.3 - fl_chart: ^0.55.2 + fl_chart: ^0.55.2 flutter_mobx: ^2.0.6+1 flutter_polygon: ^0.2.0 flutter_slidable: ^2.0.0 flutter_speed_dial: 6.2.0 flutter_svg: ^0.22.0 - font_awesome_flutter: ^10.2.1 + font_awesome_flutter: ^10.3.0 freezed: ^2.1.0+1 freezed_annotation: ^2.1.0 google_fonts: ^3.0.1 @@ -55,7 +56,7 @@ dependencies: mobx: ^2.0.7+4 numberpicker: ^2.1.1 path: ^1.8.0 - path_provider: ^2.0.5 + path_provider: ^2.0.5 package_info: ^2.0.2 retry: ^3.1.0 share_plus: ^4.0.10+1