diff --git a/board_games_companion/ios/Podfile.lock b/board_games_companion/ios/Podfile.lock index 02a01dff..10d6acf2 100644 --- a/board_games_companion/ios/Podfile.lock +++ b/board_games_companion/ios/Podfile.lock @@ -272,7 +272,7 @@ SPEC CHECKSUMS: PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 SDWebImage: 2aea163b50bfcb569a2726b6a754c54a4506fcf6 - share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 + share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 diff --git a/board_games_companion/lib/common/app_text.dart b/board_games_companion/lib/common/app_text.dart index 231798f0..2c68f8af 100644 --- a/board_games_companion/lib/common/app_text.dart +++ b/board_games_companion/lib/common/app_text.dart @@ -71,7 +71,7 @@ class AppText { static const boardGameDetailsPaboutGameNoComments = 'No comments'; static const boardGameDetailsPaboutGameCommentsFormat = '%s comments'; - static const hotBoardGamesPageTitle = 'Hot Board Games'; + static const hotBoardGamesPageTitle = 'Hot board games'; static const playthroughsStatisticsPageNoStatsTitle = "You haven't logged any games yet"; static const playthroughsStatisticsPageNoStatsSubtitle = @@ -256,14 +256,15 @@ class AppText { static const playersPageSearchPlayerButtonText = 'Search'; static const playersPageDeletePlayersButtonText = 'Delete players'; static const playersPageNoPlayersTitle = "You don't have any players"; + static const playersPageNoDeletedPlayersTitle = "You don't have any deleted players"; static const playersPageNoPlayersInstructions = "Create players to log games you've played with your family or friends"; static const playersPageConfirmationTitle = 'Delete players'; static const playersPageConfirmationDialogDeletePlayersButtonText = 'Delete'; static const playersPageConfirmationDialogContent = 'Are you sure you want to delete selected players?'; - static const playersPageDeletedPlayersSectionTitle = 'Deleted players'; - static const playersPageActivePlayersSectionTitle = 'Active players'; + static const playersPageDeletedPlayersSectionTitle = 'Deleted'; + static const playersPageActivePlayersSectionTitle = 'Active'; static const playerPageSearchClearSaerch = 'Clear search'; static const playerPageSearchNoSearchResults = 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 a8a8b073..f824fcf8 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 @@ -72,7 +72,8 @@ class BoardGamesDetailsPageState extends BasePageState { )); return PopScope( - onPopInvoked: (_) async => _handleOnWillPop(context), + canPop: false, + onPopInvoked: (didPop) async => _handleOnPop(context, didPop), child: Scaffold( body: SafeArea( child: PageContainer( @@ -102,15 +103,19 @@ class BoardGamesDetailsPageState extends BasePageState { ); } - Future _handleOnWillPop(BuildContext context) async { + Future _handleOnPop(BuildContext context, bool didPop) async { + if (didPop) { + return; + } + final boardGamesStore = getIt(); if (!boardGamesStore.allBoardGamesInCollectionsMap.containsKey(widget.viewModel.boardGame.id) && widget.navigatingFromType == PlaythroughsPage) { Navigator.popUntil(context, ModalRoute.withName(HomePage.pageRoute)); - return false; + return; } - return true; + Navigator.of(context).pop(); } Future _navigateToCreateBoardGamePage() async { diff --git a/board_games_companion/lib/pages/board_game_details/board_game_details_view_model.dart b/board_games_companion/lib/pages/board_game_details/board_game_details_view_model.dart index 44701edc..d8d18cdc 100644 --- a/board_games_companion/lib/pages/board_game_details/board_game_details_view_model.dart +++ b/board_games_companion/lib/pages/board_game_details/board_game_details_view_model.dart @@ -110,6 +110,7 @@ abstract class _BoardGameDetailsViewModel with Store { Future _loadBoardGameDetails() async { try { if (isCreatedByUser) { + visualState = BoardGameDetailsVisualState.detailsLoaded(boardGameDetails: boardGame); return; } diff --git a/board_games_companion/lib/pages/collections/collections_page.dart b/board_games_companion/lib/pages/collections/collections_page.dart index e7de1f3b..bd8d41b1 100644 --- a/board_games_companion/lib/pages/collections/collections_page.dart +++ b/board_games_companion/lib/pages/collections/collections_page.dart @@ -16,6 +16,7 @@ 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 '../../common/enums/collection_type.dart'; import '../../common/enums/games_tab.dart'; @@ -259,7 +260,9 @@ class _Collection extends StatelessWidget { ] ] ], - const SliverPadding(padding: EdgeInsets.all(8.0)), + const SliverToBoxAdapter( + child: SizedBox(height: Dimensions.floatingActionButtonBottomSpacing), + ), ], ); } @@ -309,9 +312,10 @@ class _AppBarState extends State<_AppBar> { elevation: Dimensions.defaultElevation, titleSpacing: Dimensions.standardSpacing, foregroundColor: AppColors.accentColor, + centerTitle: false, title: const Text( AppText.collectionsPageTitle, - style: TextStyle(color: AppColors.whiteColor), + style: AppTheme.titleTextStyle, ), actions: [ Observer( @@ -481,6 +485,9 @@ class _Empty extends StatelessWidget { ), ), ), + SliverToBoxAdapter( + child: SizedBox(height: Dimensions.floatingActionButtonBottomSpacing), + ), ], ); } @@ -544,7 +551,7 @@ class _EmptyCollection extends StatelessWidget { @override Widget build(BuildContext context) { - return SliverFillRemaining( + return SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(horizontal: Dimensions.doubleStandardSpacing), child: Column( diff --git a/board_games_companion/lib/pages/create_board_game/create_board_game_page.dart b/board_games_companion/lib/pages/create_board_game/create_board_game_page.dart index 0db30a5d..edcefa83 100644 --- a/board_games_companion/lib/pages/create_board_game/create_board_game_page.dart +++ b/board_games_companion/lib/pages/create_board_game/create_board_game_page.dart @@ -81,7 +81,8 @@ class _CreateBoardGamePageState extends State { @override Widget build(BuildContext context) => PopScope( - onPopInvoked: (_) => _handleOnWillPop(), + canPop: false, + onPopInvoked: (didPop) => _handleOnPop(context, didPop), child: Scaffold( body: SafeArea( child: PageContainer( @@ -127,37 +128,44 @@ class _CreateBoardGamePageState extends State { ), ); - Future _handleOnWillPop() async { - if (widget.viewModel.hasUnsavedChanges) { - final shouldNavigateAway = await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text(AppText.createNewGameUnsavedChangesDialogTitle), - content: const Text(AppText.createNewGameUnsavedChangesDialogContent), - elevation: Dimensions.defaultElevation, - actions: [ - TextButton( - child: const Text(AppText.cancel), - onPressed: () => Navigator.of(context).pop(false), - ), - TextButton( - style: TextButton.styleFrom(backgroundColor: AppColors.redColor), - onPressed: () => Navigator.of(context).pop(true), - child: const Text( - AppText.navigateAway, - style: TextStyle(color: AppColors.defaultTextColor), - ), - ), - ], - ); - }, - ); + Future _handleOnPop(BuildContext context, bool didPop) async { + if (didPop) { + return; + } - return shouldNavigateAway ?? true; + if (!widget.viewModel.hasUnsavedChanges) { + Navigator.of(context).pop(); + return; } - return true; + final shouldNavigateAway = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text(AppText.createNewGameUnsavedChangesDialogTitle), + content: const Text(AppText.createNewGameUnsavedChangesDialogContent), + elevation: Dimensions.defaultElevation, + actions: [ + TextButton( + child: const Text(AppText.cancel), + onPressed: () => Navigator.of(context).pop(false), + ), + TextButton( + style: TextButton.styleFrom(backgroundColor: AppColors.redColor), + onPressed: () => Navigator.of(context).pop(true), + child: const Text( + AppText.navigateAway, + style: TextStyle(color: AppColors.defaultTextColor), + ), + ), + ], + ); + }, + ); + + if (context.mounted && (shouldNavigateAway ?? false)) { + Navigator.of(context).pop(); + } } Future _handlePickingAndSavingBoardGameImage(ImageSource imageSource) async { diff --git a/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_page.dart b/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_page.dart index e6e3b955..3f417d37 100644 --- a/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_page.dart +++ b/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_page.dart @@ -55,14 +55,18 @@ class EditPlaythroughPage extends StatefulWidget { class EditPlaythroughPageState extends State with EnterScoreDialogMixin { @override Widget build(BuildContext context) => PopScope( - onPopInvoked: (_) async => _handleOnWillPop(context), + canPop: false, + onPopInvoked: (didPop) async => _handleOnPop(context, didPop: didPop), child: Scaffold( appBar: AppBar( automaticallyImplyLeading: false, centerTitle: true, title: const Text(AppText.editPlaythroughPageTitle), actions: [ - IconButton(icon: const Icon(Icons.close), onPressed: () => _close(context)), + IconButton( + icon: const Icon(Icons.close), + onPressed: () => _close(context), + ), ], ), body: SafeArea( @@ -200,12 +204,7 @@ class EditPlaythroughPageState extends State with EnterScor } Future _close(BuildContext context) async { - // ignore: use_build_context_synchronously - if (await _handleOnWillPop(context)) { - if (context.mounted) { - Navigator.pop(context); - } - } + _handleOnPop(context, didPop: false); } Future _showDeletePlaythroughDialog(BuildContext context) async { @@ -239,9 +238,14 @@ class EditPlaythroughPageState extends State with EnterScor ); } - Future _handleOnWillPop(BuildContext context) async { + Future _handleOnPop(BuildContext context, {required bool didPop}) async { + if (didPop) { + return; + } + if (!widget.viewModel.isDirty) { - return true; + Navigator.of(context).pop(); + return; } await showDialog( @@ -270,8 +274,6 @@ class EditPlaythroughPageState extends State with EnterScor ); }, ); - - return false; } Future _addNote() async { diff --git a/board_games_companion/lib/pages/home/home_page.dart b/board_games_companion/lib/pages/home/home_page.dart index fa13f178..29a0be6b 100644 --- a/board_games_companion/lib/pages/home/home_page.dart +++ b/board_games_companion/lib/pages/home/home_page.dart @@ -79,6 +79,7 @@ class HomePageState extends BasePageState with SingleTickerProviderSta @override void dispose() { _tabController.dispose(); + widget.viewModel.dispose(); super.dispose(); } 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 b27d40c4..02a950c5 100644 --- a/board_games_companion/lib/pages/home/home_view_model.dart +++ b/board_games_companion/lib/pages/home/home_view_model.dart @@ -53,13 +53,16 @@ abstract class _HomeViewModelBase with Store { searchResultsStream = ObservableStream>(_searchResultsStreamController.stream); // MK When restoring a backup, reload all of the data - reaction((_) => _appStore.backupRestored, (bool? backupRestored) { + _backupRestoredReactionDisposer = + reaction((_) => _appStore.backupRestored, (bool? backupRestored) { if (backupRestored ?? false) { loadData(); } }); } + late final ReactionDisposer _backupRestoredReactionDisposer; + final AnalyticsService analyticsService; final RateAndReviewService rateAndReviewService; final PlayersViewModel playersViewModel; @@ -231,4 +234,9 @@ abstract class _HomeViewModelBase with Store { ), ); } + + void dispose() { + _backupRestoredReactionDisposer(); + playersViewModel.dispose(); + } } diff --git a/board_games_companion/lib/pages/hot_board_games/hot_board_games_page.dart b/board_games_companion/lib/pages/hot_board_games/hot_board_games_page.dart index c6920c78..a26a5c75 100644 --- a/board_games_companion/lib/pages/hot_board_games/hot_board_games_page.dart +++ b/board_games_companion/lib/pages/hot_board_games/hot_board_games_page.dart @@ -80,7 +80,11 @@ class _AppBar extends StatelessWidget { forceElevated: true, elevation: Dimensions.defaultElevation, foregroundColor: AppColors.accentColor, - title: Text(AppText.hotBoardGamesPageTitle, style: AppTheme.titleTextStyle), + centerTitle: false, + title: Text( + AppText.hotBoardGamesPageTitle, + style: AppTheme.titleTextStyle, + ), ); } } diff --git a/board_games_companion/lib/pages/player/player_page.dart b/board_games_companion/lib/pages/player/player_page.dart index dbf66eb4..accd37fd 100644 --- a/board_games_companion/lib/pages/player/player_page.dart +++ b/board_games_companion/lib/pages/player/player_page.dart @@ -62,7 +62,8 @@ class PlayerPageState extends BasePageState { @override Widget build(BuildContext context) => PopScope( - onPopInvoked: (_) async => _handleOnWillPop(context), + canPop: false, + onPopInvoked: (didPop) async => _handleWillPop(context, didPop: didPop), child: Scaffold( appBar: AppBar( title: Observer( @@ -213,9 +214,14 @@ class PlayerPageState extends BasePageState { ); } - Future _handleOnWillPop(BuildContext context) async { + Future _handleWillPop(BuildContext context, {required bool didPop}) async { + if (didPop) { + return; + } + if (!widget.viewModel.hasUnsavedChanges) { - return true; + Navigator.of(context).pop(); + return; } await showDialog( @@ -234,12 +240,9 @@ class PlayerPageState extends BasePageState { ), TextButton( style: TextButton.styleFrom(backgroundColor: AppColors.redColor), - onPressed: () { - // MK Pop the dialog - Navigator.of(context).pop(); - // MK Go back - Navigator.of(context).pop(); - }, + onPressed: () => Navigator.of(context).popUntil( + ModalRoute.withName(HomePage.pageRoute), + ), child: const Text( AppText.playerPageNavigateAway, style: TextStyle(color: AppColors.defaultTextColor), @@ -249,8 +252,6 @@ class PlayerPageState extends BasePageState { ); }, ); - - return false; } Future _createOrUpdatePlayer(BuildContext context) async { diff --git a/board_games_companion/lib/pages/players/players_page.dart b/board_games_companion/lib/pages/players/players_page.dart index 280362be..cd7c6240 100644 --- a/board_games_companion/lib/pages/players/players_page.dart +++ b/board_games_companion/lib/pages/players/players_page.dart @@ -1,9 +1,9 @@ import 'package:basics/basics.dart'; +import 'package:board_games_companion/pages/players/players_visual_state.dart'; import 'package:board_games_companion/widgets/common/slivers/bgc_sliver_title_header_delegate.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; -import 'package:mobx/mobx.dart'; import 'package:sliver_tools/sliver_tools.dart'; import '../../common/animation_tags.dart'; @@ -16,6 +16,7 @@ import '../../models/hive/player.dart'; import '../../models/navigation/player_page_arguments.dart'; import '../../widgets/common/default_icon.dart'; import '../../widgets/common/elevated_icon_button.dart'; +import '../../widgets/common/generic_error_message_widget.dart'; import '../../widgets/common/loading_indicator_widget.dart'; import '../../widgets/common/text/item_property_title_widget.dart'; import '../../widgets/elevated_container.dart'; @@ -49,137 +50,99 @@ class PlayersPageState extends State { } @override - void dispose() { - widget.viewModel.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) => Observer( - builder: (context) { - switch (widget.viewModel.futureLoadPlayers?.status ?? FutureStatus.pending) { - case FutureStatus.pending: - case FutureStatus.rejected: - return CustomScrollView( - slivers: [ - _AppBar( - hasAnyDeletedPlayers: widget.viewModel.hasAnyDeletedPlayers, - onToggleShowDeletedPlayers: () => widget.viewModel.toggleShowDeletePlayers(), - isShowingAllPlayers: widget.viewModel.visualState.isShowingAllPlayers, + Widget build(BuildContext context) => Stack( + children: [ + Observer( + builder: (context) { + return switch (widget.viewModel.visualState) { + LoadingPlayers() => CustomScrollView( + slivers: [ + _AppBar( + onToggleShowDeletedPlayers: () => + widget.viewModel.toggleShowDeletedPlayers(), + ), + const SliverFillRemaining(child: LoadingIndicator()), + ], ), - const SliverFillRemaining(child: LoadingIndicator()), - ], - ); - case FutureStatus.fulfilled: - return Stack( - children: [ - CustomScrollView( + LoadingFailed() => const CustomScrollView( slivers: [ - if (widget.viewModel.hasAnyActivePlayers) ...[ - _AppBar( - hasAnyDeletedPlayers: widget.viewModel.hasAnyDeletedPlayers, - onToggleShowDeletedPlayers: () => - widget.viewModel.toggleShowDeletePlayers(), - isShowingAllPlayers: widget.viewModel.visualState.isShowingAllPlayers, - ), - Observer( - builder: (_) { - return widget.viewModel.visualState.maybeWhen( - activePlayers: (activePlayers) => _Players( - players: activePlayers, - isDeletePlayersMode: false, - onPlayerTap: (player, isChecked) => - _playerTapped(widget.viewModel, player, isChecked), - onPlayerLongPress: (player) => - widget.viewModel.toggleDeletePlayersMode(), - ), - deletePlayers: (activePlayers) => _Players( - players: activePlayers, - isDeletePlayersMode: true, - onPlayerTap: (player, isChecked) => - _playerTapped(widget.viewModel, player, isChecked), - onPlayerLongPress: (player) => - widget.viewModel.toggleDeletePlayersMode(), - ), - allPlayersPlayers: (activePlayers, deletedPlayers) => _AllPlayers( - activePlayers: activePlayers, - deletedPlayers: deletedPlayers, - onPlayerTap: (player, isChecked) => - _playerTapped(widget.viewModel, player, isChecked), - ), - orElse: () => const SliverToBoxAdapter(child: SizedBox.shrink()), - ); - }, - ), - ] else ...[ - _AppBar( - hasAnyDeletedPlayers: widget.viewModel.hasAnyDeletedPlayers, - onToggleShowDeletedPlayers: () => - widget.viewModel.toggleShowDeletePlayers(), - isShowingAllPlayers: widget.viewModel.visualState.isShowingAllPlayers, + SliverFillRemaining( + child: Padding( + padding: EdgeInsets.all(Dimensions.doubleStandardSpacing), + child: Center(child: GenericErrorMessage()), ), - const _NoPlayers(), - ] + ), ], ), - Positioned( - bottom: Dimensions.bottomTabTopHeight, - right: Dimensions.standardSpacing, - child: widget.viewModel.visualState.isDeletePlayersMode - ? ElevatedIconButton( - title: AppText.playersPageDeletePlayersButtonText, - icon: const DefaultIcon(Icons.delete), - color: AppColors.redColor, - onPressed: () async { - if (await _showDeletePlayersDialog(context) ?? false) { - setState(() {}); - } - }, - ) - : SpeedDial( - icon: Icons.menu, - backgroundColor: AppColors.accentColor, - activeBackgroundColor: AppColors.redColor, - overlayColor: AppColors.dialogBackgroundColor, - activeIcon: Icons.close, - children: [ - SpeedDialChild( - child: const Icon(Icons.create), - backgroundColor: AppColors.accentColor, - foregroundColor: Colors.white, - label: AppText.playersPageCreatePlayerButtonText, - labelBackgroundColor: AppColors.accentColor, - shape: const CircleBorder(), - onTap: () async => Navigator.pushNamed( - context, - PlayerPage.pageRoute, - arguments: const PlayerPageArguments(player: null), - ), - ), - if (widget.viewModel.hasAnyActivePlayers) - SpeedDialChild( - child: const Icon(Icons.search), - backgroundColor: AppColors.greenColor, - foregroundColor: Colors.white, - label: AppText.playersPageSearchPlayerButtonText, - labelBackgroundColor: AppColors.greenColor, - shape: const CircleBorder(), - onTap: () async => showSearch( - context: context, - delegate: _PlayersSerach( - players: widget.viewModel.activePlayers, - onResultTap: (player) => - _navigateToPlayerPage(context, player), - ), - ), - ), - ], - ), + NoPlayers() => CustomScrollView( + slivers: [ + _AppBar( + onToggleShowDeletedPlayers: () => + widget.viewModel.toggleShowDeletedPlayers(), + ), + const _NoPlayers(), + ], ), - ], - ); - } - }, + NoActivePlayers() => CustomScrollView( + slivers: [ + _AppBar( + onToggleShowDeletedPlayers: () => + widget.viewModel.toggleShowDeletedPlayers(), + ), + const _NoPlayers(), + ], + ), + _ => CustomScrollView( + slivers: [ + _AppBar( + onToggleShowDeletedPlayers: () => + widget.viewModel.toggleShowDeletedPlayers(), + ), + Observer( + builder: (_) { + return widget.viewModel.visualState.maybeWhen( + activePlayers: (activePlayers) => _Players( + players: activePlayers, + isDeletePlayersMode: false, + onPlayerTap: (player, isChecked) => + _playerTapped(widget.viewModel, player, isChecked), + onPlayerLongPress: (player) => + widget.viewModel.toggleDeletePlayersMode(), + ), + deletePlayers: (activePlayers) => _Players( + players: activePlayers, + isDeletePlayersMode: true, + onPlayerTap: (player, isChecked) => + _playerTapped(widget.viewModel, player, isChecked), + onPlayerLongPress: (player) => + widget.viewModel.toggleDeletePlayersMode(), + ), + allPlayersPlayers: (activePlayers, deletedPlayers) => _AllPlayers( + activePlayers: activePlayers, + deletedPlayers: deletedPlayers, + onPlayerTap: (player, isChecked) => + _playerTapped(widget.viewModel, player, isChecked), + ), + orElse: () => const SliverToBoxAdapter(child: SizedBox.shrink()), + ); + }, + ), + ], + ) + }; + }, + ), + Observer( + builder: (context) => _FloatingActionButton( + isDeletePlayersMode: widget.viewModel.visualState.isDeletePlayersMode, + confirmDeletePlayersCallback: () async => + await _showDeletePlayersDialog(context) ?? false, + hasAnyActivePlayers: widget.viewModel.hasAnyActivePlayers, + activePlayers: widget.viewModel.activePlayers, + onSearchResultTapped: (player) => _navigateToPlayerPage(context, player), + ), + ), + ], ); Future _navigateToPlayerPage(BuildContext context, Player player) async { @@ -243,14 +206,10 @@ class PlayersPageState extends State { class _AppBar extends StatelessWidget { const _AppBar({ - required this.hasAnyDeletedPlayers, required this.onToggleShowDeletedPlayers, - required this.isShowingAllPlayers, }); - final bool hasAnyDeletedPlayers; final VoidCallback onToggleShowDeletedPlayers; - final bool isShowingAllPlayers; @override Widget build(BuildContext context) { @@ -261,16 +220,13 @@ class _AppBar extends StatelessWidget { elevation: Dimensions.defaultElevation, titleSpacing: Dimensions.standardSpacing, foregroundColor: AppColors.accentColor, + centerTitle: false, title: const Text(AppText.playersPageTitle, style: AppTheme.titleTextStyle), actions: [ - if (hasAnyDeletedPlayers) - IconButton( - icon: Icon(Icons.delete_sweep, - color: isShowingAllPlayers - ? AppColors.enabledIconIconColor - : AppColors.disabledIconIconColor), - onPressed: onToggleShowDeletedPlayers, - ), + IconButton( + icon: const Icon(Icons.delete_sweep, color: AppColors.enabledIconIconColor), + onPressed: onToggleShowDeletedPlayers, + ), ], ); } @@ -312,6 +268,35 @@ class _NoPlayers extends StatelessWidget { } } +class _NoDeletedPlayers extends StatelessWidget { + const _NoDeletedPlayers(); + + @override + Widget build(BuildContext context) { + return const SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.all(Dimensions.doubleStandardSpacing), + child: Column( + children: [ + Center( + child: Text( + AppText.playersPageNoDeletedPlayersTitle, + style: TextStyle(fontSize: Dimensions.extraLargeFontSize), + ), + ), + SizedBox(height: Dimensions.doubleStandardSpacing), + Icon( + Icons.delete_sweep, + size: Dimensions.emptyPageTitleIconSize, + color: AppColors.primaryColor, + ), + ], + ), + ), + ); + } +} + class _AllPlayers extends StatelessWidget { const _AllPlayers({ required this.activePlayers, @@ -325,26 +310,34 @@ class _AllPlayers extends StatelessWidget { @override Widget build(BuildContext context) { - return MultiSliver(children: [ - SliverPersistentHeader( - delegate: BgcSliverTitleHeaderDelegate.title( - primaryTitle: AppText.playersPageDeletedPlayersSectionTitle, + return MultiSliver( + children: [ + SliverPersistentHeader( + delegate: BgcSliverTitleHeaderDelegate.title( + primaryTitle: AppText.playersPageDeletedPlayersSectionTitle, + ), ), - ), - _Players( - players: deletedPlayers, - onPlayerTap: onPlayerTap, - ), - SliverPersistentHeader( - delegate: BgcSliverTitleHeaderDelegate.title( - primaryTitle: AppText.playersPageActivePlayersSectionTitle, + if (deletedPlayers.isEmpty) + const _NoDeletedPlayers() + else + _Players( + players: deletedPlayers, + onPlayerTap: onPlayerTap, + ), + SliverPersistentHeader( + delegate: BgcSliverTitleHeaderDelegate.title( + primaryTitle: AppText.playersPageActivePlayersSectionTitle, + ), ), - ), - _Players( - players: activePlayers, - onPlayerTap: onPlayerTap, - ) - ]); + if (activePlayers.isEmpty) + const _NoPlayers() + else + _Players( + players: activePlayers, + onPlayerTap: onPlayerTap, + ) + ], + ); } } @@ -661,3 +654,82 @@ class _NoSearchResults extends StatelessWidget { ); } } + +class _FloatingActionButton extends StatefulWidget { + const _FloatingActionButton({ + required this.isDeletePlayersMode, + required this.hasAnyActivePlayers, + required this.activePlayers, + required this.confirmDeletePlayersCallback, + required this.onSearchResultTapped, + }); + + final bool isDeletePlayersMode; + final bool hasAnyActivePlayers; + final List activePlayers; + final Future Function() confirmDeletePlayersCallback; + final void Function(Player) onSearchResultTapped; + + @override + State<_FloatingActionButton> createState() => _FloatingActionButtonState(); +} + +class _FloatingActionButtonState extends State<_FloatingActionButton> { + @override + Widget build(BuildContext context) { + return Positioned( + bottom: Dimensions.bottomTabTopHeight, + right: Dimensions.standardSpacing, + child: widget.isDeletePlayersMode + ? ElevatedIconButton( + title: AppText.playersPageDeletePlayersButtonText, + icon: const DefaultIcon(Icons.delete), + color: AppColors.redColor, + onPressed: () async { + final deleteConfirmed = await widget.confirmDeletePlayersCallback(); + if (deleteConfirmed) { + setState(() {}); + } + }, + ) + : SpeedDial( + icon: Icons.menu, + backgroundColor: AppColors.accentColor, + activeBackgroundColor: AppColors.redColor, + overlayColor: AppColors.dialogBackgroundColor, + activeIcon: Icons.close, + children: [ + SpeedDialChild( + child: const Icon(Icons.create), + backgroundColor: AppColors.accentColor, + foregroundColor: Colors.white, + label: AppText.playersPageCreatePlayerButtonText, + labelBackgroundColor: AppColors.accentColor, + shape: const CircleBorder(), + onTap: () async => Navigator.pushNamed( + context, + PlayerPage.pageRoute, + arguments: const PlayerPageArguments(player: null), + ), + ), + if (widget.hasAnyActivePlayers) + SpeedDialChild( + child: const Icon(Icons.search), + backgroundColor: AppColors.greenColor, + foregroundColor: Colors.white, + label: AppText.playersPageSearchPlayerButtonText, + labelBackgroundColor: AppColors.greenColor, + shape: const CircleBorder(), + onTap: () async => showSearch( + context: context, + delegate: _PlayersSerach( + players: widget.activePlayers, + onResultTap: (player) => widget.onSearchResultTapped(player), + ), + ), + ), + ], + ), + ); + } +} diff --git a/board_games_companion/lib/pages/players/players_view_model.dart b/board_games_companion/lib/pages/players/players_view_model.dart index 3bc814f2..b97ea992 100644 --- a/board_games_companion/lib/pages/players/players_view_model.dart +++ b/board_games_companion/lib/pages/players/players_view_model.dart @@ -17,14 +17,22 @@ abstract class _PlayersViewModel with Store { _PlayersViewModel(this._playersStore) { // MK React to any updates on the players collection (e.g. delete or restore) _playersReactionDisposer = reaction((_) => _playersStore.players, (_) { + // MK If user is in [ActivePlayers] or [AllPlayers] state, update it. + // If it's any other, update it based on the players collection (i.e. [_updatePlayersViauslState]) switch (visualState) { case ActivePlayers(): - visualState = PlayersVisualState.activePlayers(activePlayers: activePlayers); + if (!hasAnyActivePlayers) { + visualState = const PlayersVisualState.noPlayers(); + } else { + visualState = PlayersVisualState.activePlayers(activePlayers: activePlayers); + } case AllPlayers(): visualState = PlayersVisualState.allPlayersPlayers( activePlayers: activePlayers, deletedPlayers: deletedPlayers, ); + default: + _updatePlayersVisualState(); } }); } @@ -51,10 +59,10 @@ abstract class _PlayersViewModel with Store { bool get hasAnyActivePlayers => activePlayers.isNotEmpty; @computed - bool get hasAnyDeletedPlayers => activePlayers.isNotEmpty; + bool get hasAnyDeletedPlayers => deletedPlayers.isNotEmpty; @computed - bool get hasAnyPlayers => activePlayers.isNotEmpty && deletedPlayers.isNotEmpty; + bool get hasAnyPlayers => hasAnyActivePlayers || hasAnyDeletedPlayers; String? searchPhrase; @@ -76,7 +84,7 @@ abstract class _PlayersViewModel with Store { } @action - void toggleShowDeletePlayers() { + void toggleShowDeletedPlayers() { switch (visualState) { case AllPlayers(): visualState = PlayersVisualState.activePlayers(activePlayers: activePlayers); @@ -96,7 +104,7 @@ abstract class _PlayersViewModel with Store { await _playersStore.deletePlayer(playerId); } - toggleDeletePlayersMode(); + _selectedPlayers.clear(); } catch (e, stack) { FirebaseCrashlytics.instance.recordError(e, stack); } @@ -110,12 +118,23 @@ abstract class _PlayersViewModel with Store { try { visualState = const PlayersVisualState.loadingPlayers(); await _playersStore.loadPlayers(); - visualState = PlayersVisualState.activePlayers(activePlayers: activePlayers); + _updatePlayersVisualState(); } catch (e, stack) { + visualState = const PlayersVisualState.loadingFailed(); FirebaseCrashlytics.instance.recordError(e, stack); } } + void _updatePlayersVisualState() { + if (!hasAnyPlayers) { + visualState = const PlayersVisualState.noPlayers(); + } else if (!hasAnyActivePlayers) { + visualState = const PlayersVisualState.noActivePlayers(); + } else { + visualState = PlayersVisualState.activePlayers(activePlayers: activePlayers); + } + } + void dispose() { _playersReactionDisposer(); } diff --git a/board_games_companion/lib/pages/players/players_view_model.g.dart b/board_games_companion/lib/pages/players/players_view_model.g.dart index 8fccb234..a02847fc 100644 --- a/board_games_companion/lib/pages/players/players_view_model.g.dart +++ b/board_games_companion/lib/pages/players/players_view_model.g.dart @@ -112,11 +112,11 @@ mixin _$PlayersViewModel on _PlayersViewModel, Store { } @override - void toggleShowDeletePlayers() { + void toggleShowDeletedPlayers() { final _$actionInfo = _$_PlayersViewModelActionController.startAction( - name: '_PlayersViewModel.toggleShowDeletePlayers'); + name: '_PlayersViewModel.toggleShowDeletedPlayers'); try { - return super.toggleShowDeletePlayers(); + return super.toggleShowDeletedPlayers(); } finally { _$_PlayersViewModelActionController.endAction(_$actionInfo); } diff --git a/board_games_companion/lib/pages/players/players_visual_state.dart b/board_games_companion/lib/pages/players/players_visual_state.dart index 9b67d863..4b4de37e 100644 --- a/board_games_companion/lib/pages/players/players_visual_state.dart +++ b/board_games_companion/lib/pages/players/players_visual_state.dart @@ -7,6 +7,13 @@ part 'players_visual_state.freezed.dart'; @freezed class PlayersVisualState with _$PlayersVisualState { const factory PlayersVisualState.loadingPlayers() = LoadingPlayers; + const factory PlayersVisualState.loadingFailed() = LoadingFailed; + + /// No players + const factory PlayersVisualState.noPlayers() = NoPlayers; + + /// No active players + const factory PlayersVisualState.noActivePlayers() = NoActivePlayers; /// Show [activePlayers] only const factory PlayersVisualState.activePlayers({ @@ -19,7 +26,7 @@ class PlayersVisualState with _$PlayersVisualState { required List deletedPlayers, }) = AllPlayers; - /// A mode in which users can check players on the screen and delete + /// A mode in which users can players on the screen and delete /// selected const factory PlayersVisualState.deletePlayers({ required List activePlayers, diff --git a/board_games_companion/lib/pages/players/players_visual_state.freezed.dart b/board_games_companion/lib/pages/players/players_visual_state.freezed.dart index a0dbeba2..ca573c77 100644 --- a/board_games_companion/lib/pages/players/players_visual_state.freezed.dart +++ b/board_games_companion/lib/pages/players/players_visual_state.freezed.dart @@ -19,6 +19,9 @@ mixin _$PlayersVisualState { @optionalTypeArgs TResult when({ required TResult Function() loadingPlayers, + required TResult Function() loadingFailed, + required TResult Function() noPlayers, + required TResult Function() noActivePlayers, required TResult Function(List activePlayers) activePlayers, required TResult Function( List activePlayers, List deletedPlayers) @@ -29,6 +32,9 @@ mixin _$PlayersVisualState { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? loadingPlayers, + TResult? Function()? loadingFailed, + TResult? Function()? noPlayers, + TResult? Function()? noActivePlayers, TResult? Function(List activePlayers)? activePlayers, TResult? Function(List activePlayers, List deletedPlayers)? allPlayersPlayers, @@ -38,6 +44,9 @@ mixin _$PlayersVisualState { @optionalTypeArgs TResult maybeWhen({ TResult Function()? loadingPlayers, + TResult Function()? loadingFailed, + TResult Function()? noPlayers, + TResult Function()? noActivePlayers, TResult Function(List activePlayers)? activePlayers, TResult Function(List activePlayers, List deletedPlayers)? allPlayersPlayers, @@ -48,6 +57,9 @@ mixin _$PlayersVisualState { @optionalTypeArgs TResult map({ required TResult Function(LoadingPlayers value) loadingPlayers, + required TResult Function(LoadingFailed value) loadingFailed, + required TResult Function(NoPlayers value) noPlayers, + required TResult Function(NoActivePlayers value) noActivePlayers, required TResult Function(ActivePlayers value) activePlayers, required TResult Function(AllPlayers value) allPlayersPlayers, required TResult Function(DeletePlayers value) deletePlayers, @@ -56,6 +68,9 @@ mixin _$PlayersVisualState { @optionalTypeArgs TResult? mapOrNull({ TResult? Function(LoadingPlayers value)? loadingPlayers, + TResult? Function(LoadingFailed value)? loadingFailed, + TResult? Function(NoPlayers value)? noPlayers, + TResult? Function(NoActivePlayers value)? noActivePlayers, TResult? Function(ActivePlayers value)? activePlayers, TResult? Function(AllPlayers value)? allPlayersPlayers, TResult? Function(DeletePlayers value)? deletePlayers, @@ -64,6 +79,9 @@ mixin _$PlayersVisualState { @optionalTypeArgs TResult maybeMap({ TResult Function(LoadingPlayers value)? loadingPlayers, + TResult Function(LoadingFailed value)? loadingFailed, + TResult Function(NoPlayers value)? noPlayers, + TResult Function(NoActivePlayers value)? noActivePlayers, TResult Function(ActivePlayers value)? activePlayers, TResult Function(AllPlayers value)? allPlayersPlayers, TResult Function(DeletePlayers value)? deletePlayers, @@ -129,6 +147,9 @@ class _$LoadingPlayersImpl extends LoadingPlayers { @optionalTypeArgs TResult when({ required TResult Function() loadingPlayers, + required TResult Function() loadingFailed, + required TResult Function() noPlayers, + required TResult Function() noActivePlayers, required TResult Function(List activePlayers) activePlayers, required TResult Function( List activePlayers, List deletedPlayers) @@ -142,6 +163,9 @@ class _$LoadingPlayersImpl extends LoadingPlayers { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? loadingPlayers, + TResult? Function()? loadingFailed, + TResult? Function()? noPlayers, + TResult? Function()? noActivePlayers, TResult? Function(List activePlayers)? activePlayers, TResult? Function(List activePlayers, List deletedPlayers)? allPlayersPlayers, @@ -154,6 +178,9 @@ class _$LoadingPlayersImpl extends LoadingPlayers { @optionalTypeArgs TResult maybeWhen({ TResult Function()? loadingPlayers, + TResult Function()? loadingFailed, + TResult Function()? noPlayers, + TResult Function()? noActivePlayers, TResult Function(List activePlayers)? activePlayers, TResult Function(List activePlayers, List deletedPlayers)? allPlayersPlayers, @@ -170,6 +197,9 @@ class _$LoadingPlayersImpl extends LoadingPlayers { @optionalTypeArgs TResult map({ required TResult Function(LoadingPlayers value) loadingPlayers, + required TResult Function(LoadingFailed value) loadingFailed, + required TResult Function(NoPlayers value) noPlayers, + required TResult Function(NoActivePlayers value) noActivePlayers, required TResult Function(ActivePlayers value) activePlayers, required TResult Function(AllPlayers value) allPlayersPlayers, required TResult Function(DeletePlayers value) deletePlayers, @@ -181,6 +211,9 @@ class _$LoadingPlayersImpl extends LoadingPlayers { @optionalTypeArgs TResult? mapOrNull({ TResult? Function(LoadingPlayers value)? loadingPlayers, + TResult? Function(LoadingFailed value)? loadingFailed, + TResult? Function(NoPlayers value)? noPlayers, + TResult? Function(NoActivePlayers value)? noActivePlayers, TResult? Function(ActivePlayers value)? activePlayers, TResult? Function(AllPlayers value)? allPlayersPlayers, TResult? Function(DeletePlayers value)? deletePlayers, @@ -192,6 +225,9 @@ class _$LoadingPlayersImpl extends LoadingPlayers { @optionalTypeArgs TResult maybeMap({ TResult Function(LoadingPlayers value)? loadingPlayers, + TResult Function(LoadingFailed value)? loadingFailed, + TResult Function(NoPlayers value)? noPlayers, + TResult Function(NoActivePlayers value)? noActivePlayers, TResult Function(ActivePlayers value)? activePlayers, TResult Function(AllPlayers value)? allPlayersPlayers, TResult Function(DeletePlayers value)? deletePlayers, @@ -209,6 +245,417 @@ abstract class LoadingPlayers extends PlayersVisualState { const LoadingPlayers._() : super._(); } +/// @nodoc +abstract class _$$LoadingFailedImplCopyWith<$Res> { + factory _$$LoadingFailedImplCopyWith( + _$LoadingFailedImpl value, $Res Function(_$LoadingFailedImpl) then) = + __$$LoadingFailedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingFailedImplCopyWithImpl<$Res> + extends _$PlayersVisualStateCopyWithImpl<$Res, _$LoadingFailedImpl> + implements _$$LoadingFailedImplCopyWith<$Res> { + __$$LoadingFailedImplCopyWithImpl( + _$LoadingFailedImpl _value, $Res Function(_$LoadingFailedImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$LoadingFailedImpl extends LoadingFailed { + const _$LoadingFailedImpl() : super._(); + + @override + String toString() { + return 'PlayersVisualState.loadingFailed()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$LoadingFailedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() loadingPlayers, + required TResult Function() loadingFailed, + required TResult Function() noPlayers, + required TResult Function() noActivePlayers, + required TResult Function(List activePlayers) activePlayers, + required TResult Function( + List activePlayers, List deletedPlayers) + allPlayersPlayers, + required TResult Function(List activePlayers) deletePlayers, + }) { + return loadingFailed(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? loadingPlayers, + TResult? Function()? loadingFailed, + TResult? Function()? noPlayers, + TResult? Function()? noActivePlayers, + TResult? Function(List activePlayers)? activePlayers, + TResult? Function(List activePlayers, List deletedPlayers)? + allPlayersPlayers, + TResult? Function(List activePlayers)? deletePlayers, + }) { + return loadingFailed?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? loadingPlayers, + TResult Function()? loadingFailed, + TResult Function()? noPlayers, + TResult Function()? noActivePlayers, + TResult Function(List activePlayers)? activePlayers, + TResult Function(List activePlayers, List deletedPlayers)? + allPlayersPlayers, + TResult Function(List activePlayers)? deletePlayers, + required TResult orElse(), + }) { + if (loadingFailed != null) { + return loadingFailed(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(LoadingPlayers value) loadingPlayers, + required TResult Function(LoadingFailed value) loadingFailed, + required TResult Function(NoPlayers value) noPlayers, + required TResult Function(NoActivePlayers value) noActivePlayers, + required TResult Function(ActivePlayers value) activePlayers, + required TResult Function(AllPlayers value) allPlayersPlayers, + required TResult Function(DeletePlayers value) deletePlayers, + }) { + return loadingFailed(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(LoadingPlayers value)? loadingPlayers, + TResult? Function(LoadingFailed value)? loadingFailed, + TResult? Function(NoPlayers value)? noPlayers, + TResult? Function(NoActivePlayers value)? noActivePlayers, + TResult? Function(ActivePlayers value)? activePlayers, + TResult? Function(AllPlayers value)? allPlayersPlayers, + TResult? Function(DeletePlayers value)? deletePlayers, + }) { + return loadingFailed?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(LoadingPlayers value)? loadingPlayers, + TResult Function(LoadingFailed value)? loadingFailed, + TResult Function(NoPlayers value)? noPlayers, + TResult Function(NoActivePlayers value)? noActivePlayers, + TResult Function(ActivePlayers value)? activePlayers, + TResult Function(AllPlayers value)? allPlayersPlayers, + TResult Function(DeletePlayers value)? deletePlayers, + required TResult orElse(), + }) { + if (loadingFailed != null) { + return loadingFailed(this); + } + return orElse(); + } +} + +abstract class LoadingFailed extends PlayersVisualState { + const factory LoadingFailed() = _$LoadingFailedImpl; + const LoadingFailed._() : super._(); +} + +/// @nodoc +abstract class _$$NoPlayersImplCopyWith<$Res> { + factory _$$NoPlayersImplCopyWith( + _$NoPlayersImpl value, $Res Function(_$NoPlayersImpl) then) = + __$$NoPlayersImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$NoPlayersImplCopyWithImpl<$Res> + extends _$PlayersVisualStateCopyWithImpl<$Res, _$NoPlayersImpl> + implements _$$NoPlayersImplCopyWith<$Res> { + __$$NoPlayersImplCopyWithImpl( + _$NoPlayersImpl _value, $Res Function(_$NoPlayersImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$NoPlayersImpl extends NoPlayers { + const _$NoPlayersImpl() : super._(); + + @override + String toString() { + return 'PlayersVisualState.noPlayers()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$NoPlayersImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() loadingPlayers, + required TResult Function() loadingFailed, + required TResult Function() noPlayers, + required TResult Function() noActivePlayers, + required TResult Function(List activePlayers) activePlayers, + required TResult Function( + List activePlayers, List deletedPlayers) + allPlayersPlayers, + required TResult Function(List activePlayers) deletePlayers, + }) { + return noPlayers(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? loadingPlayers, + TResult? Function()? loadingFailed, + TResult? Function()? noPlayers, + TResult? Function()? noActivePlayers, + TResult? Function(List activePlayers)? activePlayers, + TResult? Function(List activePlayers, List deletedPlayers)? + allPlayersPlayers, + TResult? Function(List activePlayers)? deletePlayers, + }) { + return noPlayers?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? loadingPlayers, + TResult Function()? loadingFailed, + TResult Function()? noPlayers, + TResult Function()? noActivePlayers, + TResult Function(List activePlayers)? activePlayers, + TResult Function(List activePlayers, List deletedPlayers)? + allPlayersPlayers, + TResult Function(List activePlayers)? deletePlayers, + required TResult orElse(), + }) { + if (noPlayers != null) { + return noPlayers(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(LoadingPlayers value) loadingPlayers, + required TResult Function(LoadingFailed value) loadingFailed, + required TResult Function(NoPlayers value) noPlayers, + required TResult Function(NoActivePlayers value) noActivePlayers, + required TResult Function(ActivePlayers value) activePlayers, + required TResult Function(AllPlayers value) allPlayersPlayers, + required TResult Function(DeletePlayers value) deletePlayers, + }) { + return noPlayers(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(LoadingPlayers value)? loadingPlayers, + TResult? Function(LoadingFailed value)? loadingFailed, + TResult? Function(NoPlayers value)? noPlayers, + TResult? Function(NoActivePlayers value)? noActivePlayers, + TResult? Function(ActivePlayers value)? activePlayers, + TResult? Function(AllPlayers value)? allPlayersPlayers, + TResult? Function(DeletePlayers value)? deletePlayers, + }) { + return noPlayers?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(LoadingPlayers value)? loadingPlayers, + TResult Function(LoadingFailed value)? loadingFailed, + TResult Function(NoPlayers value)? noPlayers, + TResult Function(NoActivePlayers value)? noActivePlayers, + TResult Function(ActivePlayers value)? activePlayers, + TResult Function(AllPlayers value)? allPlayersPlayers, + TResult Function(DeletePlayers value)? deletePlayers, + required TResult orElse(), + }) { + if (noPlayers != null) { + return noPlayers(this); + } + return orElse(); + } +} + +abstract class NoPlayers extends PlayersVisualState { + const factory NoPlayers() = _$NoPlayersImpl; + const NoPlayers._() : super._(); +} + +/// @nodoc +abstract class _$$NoActivePlayersImplCopyWith<$Res> { + factory _$$NoActivePlayersImplCopyWith(_$NoActivePlayersImpl value, + $Res Function(_$NoActivePlayersImpl) then) = + __$$NoActivePlayersImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$NoActivePlayersImplCopyWithImpl<$Res> + extends _$PlayersVisualStateCopyWithImpl<$Res, _$NoActivePlayersImpl> + implements _$$NoActivePlayersImplCopyWith<$Res> { + __$$NoActivePlayersImplCopyWithImpl( + _$NoActivePlayersImpl _value, $Res Function(_$NoActivePlayersImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$NoActivePlayersImpl extends NoActivePlayers { + const _$NoActivePlayersImpl() : super._(); + + @override + String toString() { + return 'PlayersVisualState.noActivePlayers()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$NoActivePlayersImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() loadingPlayers, + required TResult Function() loadingFailed, + required TResult Function() noPlayers, + required TResult Function() noActivePlayers, + required TResult Function(List activePlayers) activePlayers, + required TResult Function( + List activePlayers, List deletedPlayers) + allPlayersPlayers, + required TResult Function(List activePlayers) deletePlayers, + }) { + return noActivePlayers(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? loadingPlayers, + TResult? Function()? loadingFailed, + TResult? Function()? noPlayers, + TResult? Function()? noActivePlayers, + TResult? Function(List activePlayers)? activePlayers, + TResult? Function(List activePlayers, List deletedPlayers)? + allPlayersPlayers, + TResult? Function(List activePlayers)? deletePlayers, + }) { + return noActivePlayers?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? loadingPlayers, + TResult Function()? loadingFailed, + TResult Function()? noPlayers, + TResult Function()? noActivePlayers, + TResult Function(List activePlayers)? activePlayers, + TResult Function(List activePlayers, List deletedPlayers)? + allPlayersPlayers, + TResult Function(List activePlayers)? deletePlayers, + required TResult orElse(), + }) { + if (noActivePlayers != null) { + return noActivePlayers(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(LoadingPlayers value) loadingPlayers, + required TResult Function(LoadingFailed value) loadingFailed, + required TResult Function(NoPlayers value) noPlayers, + required TResult Function(NoActivePlayers value) noActivePlayers, + required TResult Function(ActivePlayers value) activePlayers, + required TResult Function(AllPlayers value) allPlayersPlayers, + required TResult Function(DeletePlayers value) deletePlayers, + }) { + return noActivePlayers(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(LoadingPlayers value)? loadingPlayers, + TResult? Function(LoadingFailed value)? loadingFailed, + TResult? Function(NoPlayers value)? noPlayers, + TResult? Function(NoActivePlayers value)? noActivePlayers, + TResult? Function(ActivePlayers value)? activePlayers, + TResult? Function(AllPlayers value)? allPlayersPlayers, + TResult? Function(DeletePlayers value)? deletePlayers, + }) { + return noActivePlayers?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(LoadingPlayers value)? loadingPlayers, + TResult Function(LoadingFailed value)? loadingFailed, + TResult Function(NoPlayers value)? noPlayers, + TResult Function(NoActivePlayers value)? noActivePlayers, + TResult Function(ActivePlayers value)? activePlayers, + TResult Function(AllPlayers value)? allPlayersPlayers, + TResult Function(DeletePlayers value)? deletePlayers, + required TResult orElse(), + }) { + if (noActivePlayers != null) { + return noActivePlayers(this); + } + return orElse(); + } +} + +abstract class NoActivePlayers extends PlayersVisualState { + const factory NoActivePlayers() = _$NoActivePlayersImpl; + const NoActivePlayers._() : super._(); +} + /// @nodoc abstract class _$$ActivePlayersImplCopyWith<$Res> { factory _$$ActivePlayersImplCopyWith( @@ -283,6 +730,9 @@ class _$ActivePlayersImpl extends ActivePlayers { @optionalTypeArgs TResult when({ required TResult Function() loadingPlayers, + required TResult Function() loadingFailed, + required TResult Function() noPlayers, + required TResult Function() noActivePlayers, required TResult Function(List activePlayers) activePlayers, required TResult Function( List activePlayers, List deletedPlayers) @@ -296,6 +746,9 @@ class _$ActivePlayersImpl extends ActivePlayers { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? loadingPlayers, + TResult? Function()? loadingFailed, + TResult? Function()? noPlayers, + TResult? Function()? noActivePlayers, TResult? Function(List activePlayers)? activePlayers, TResult? Function(List activePlayers, List deletedPlayers)? allPlayersPlayers, @@ -308,6 +761,9 @@ class _$ActivePlayersImpl extends ActivePlayers { @optionalTypeArgs TResult maybeWhen({ TResult Function()? loadingPlayers, + TResult Function()? loadingFailed, + TResult Function()? noPlayers, + TResult Function()? noActivePlayers, TResult Function(List activePlayers)? activePlayers, TResult Function(List activePlayers, List deletedPlayers)? allPlayersPlayers, @@ -324,6 +780,9 @@ class _$ActivePlayersImpl extends ActivePlayers { @optionalTypeArgs TResult map({ required TResult Function(LoadingPlayers value) loadingPlayers, + required TResult Function(LoadingFailed value) loadingFailed, + required TResult Function(NoPlayers value) noPlayers, + required TResult Function(NoActivePlayers value) noActivePlayers, required TResult Function(ActivePlayers value) activePlayers, required TResult Function(AllPlayers value) allPlayersPlayers, required TResult Function(DeletePlayers value) deletePlayers, @@ -335,6 +794,9 @@ class _$ActivePlayersImpl extends ActivePlayers { @optionalTypeArgs TResult? mapOrNull({ TResult? Function(LoadingPlayers value)? loadingPlayers, + TResult? Function(LoadingFailed value)? loadingFailed, + TResult? Function(NoPlayers value)? noPlayers, + TResult? Function(NoActivePlayers value)? noActivePlayers, TResult? Function(ActivePlayers value)? activePlayers, TResult? Function(AllPlayers value)? allPlayersPlayers, TResult? Function(DeletePlayers value)? deletePlayers, @@ -346,6 +808,9 @@ class _$ActivePlayersImpl extends ActivePlayers { @optionalTypeArgs TResult maybeMap({ TResult Function(LoadingPlayers value)? loadingPlayers, + TResult Function(LoadingFailed value)? loadingFailed, + TResult Function(NoPlayers value)? noPlayers, + TResult Function(NoActivePlayers value)? noActivePlayers, TResult Function(ActivePlayers value)? activePlayers, TResult Function(AllPlayers value)? allPlayersPlayers, TResult Function(DeletePlayers value)? deletePlayers, @@ -463,6 +928,9 @@ class _$AllPlayersImpl extends AllPlayers { @optionalTypeArgs TResult when({ required TResult Function() loadingPlayers, + required TResult Function() loadingFailed, + required TResult Function() noPlayers, + required TResult Function() noActivePlayers, required TResult Function(List activePlayers) activePlayers, required TResult Function( List activePlayers, List deletedPlayers) @@ -476,6 +944,9 @@ class _$AllPlayersImpl extends AllPlayers { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? loadingPlayers, + TResult? Function()? loadingFailed, + TResult? Function()? noPlayers, + TResult? Function()? noActivePlayers, TResult? Function(List activePlayers)? activePlayers, TResult? Function(List activePlayers, List deletedPlayers)? allPlayersPlayers, @@ -488,6 +959,9 @@ class _$AllPlayersImpl extends AllPlayers { @optionalTypeArgs TResult maybeWhen({ TResult Function()? loadingPlayers, + TResult Function()? loadingFailed, + TResult Function()? noPlayers, + TResult Function()? noActivePlayers, TResult Function(List activePlayers)? activePlayers, TResult Function(List activePlayers, List deletedPlayers)? allPlayersPlayers, @@ -504,6 +978,9 @@ class _$AllPlayersImpl extends AllPlayers { @optionalTypeArgs TResult map({ required TResult Function(LoadingPlayers value) loadingPlayers, + required TResult Function(LoadingFailed value) loadingFailed, + required TResult Function(NoPlayers value) noPlayers, + required TResult Function(NoActivePlayers value) noActivePlayers, required TResult Function(ActivePlayers value) activePlayers, required TResult Function(AllPlayers value) allPlayersPlayers, required TResult Function(DeletePlayers value) deletePlayers, @@ -515,6 +992,9 @@ class _$AllPlayersImpl extends AllPlayers { @optionalTypeArgs TResult? mapOrNull({ TResult? Function(LoadingPlayers value)? loadingPlayers, + TResult? Function(LoadingFailed value)? loadingFailed, + TResult? Function(NoPlayers value)? noPlayers, + TResult? Function(NoActivePlayers value)? noActivePlayers, TResult? Function(ActivePlayers value)? activePlayers, TResult? Function(AllPlayers value)? allPlayersPlayers, TResult? Function(DeletePlayers value)? deletePlayers, @@ -526,6 +1006,9 @@ class _$AllPlayersImpl extends AllPlayers { @optionalTypeArgs TResult maybeMap({ TResult Function(LoadingPlayers value)? loadingPlayers, + TResult Function(LoadingFailed value)? loadingFailed, + TResult Function(NoPlayers value)? noPlayers, + TResult Function(NoActivePlayers value)? noActivePlayers, TResult Function(ActivePlayers value)? activePlayers, TResult Function(AllPlayers value)? allPlayersPlayers, TResult Function(DeletePlayers value)? deletePlayers, @@ -625,6 +1108,9 @@ class _$DeletePlayersImpl extends DeletePlayers { @optionalTypeArgs TResult when({ required TResult Function() loadingPlayers, + required TResult Function() loadingFailed, + required TResult Function() noPlayers, + required TResult Function() noActivePlayers, required TResult Function(List activePlayers) activePlayers, required TResult Function( List activePlayers, List deletedPlayers) @@ -638,6 +1124,9 @@ class _$DeletePlayersImpl extends DeletePlayers { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? loadingPlayers, + TResult? Function()? loadingFailed, + TResult? Function()? noPlayers, + TResult? Function()? noActivePlayers, TResult? Function(List activePlayers)? activePlayers, TResult? Function(List activePlayers, List deletedPlayers)? allPlayersPlayers, @@ -650,6 +1139,9 @@ class _$DeletePlayersImpl extends DeletePlayers { @optionalTypeArgs TResult maybeWhen({ TResult Function()? loadingPlayers, + TResult Function()? loadingFailed, + TResult Function()? noPlayers, + TResult Function()? noActivePlayers, TResult Function(List activePlayers)? activePlayers, TResult Function(List activePlayers, List deletedPlayers)? allPlayersPlayers, @@ -666,6 +1158,9 @@ class _$DeletePlayersImpl extends DeletePlayers { @optionalTypeArgs TResult map({ required TResult Function(LoadingPlayers value) loadingPlayers, + required TResult Function(LoadingFailed value) loadingFailed, + required TResult Function(NoPlayers value) noPlayers, + required TResult Function(NoActivePlayers value) noActivePlayers, required TResult Function(ActivePlayers value) activePlayers, required TResult Function(AllPlayers value) allPlayersPlayers, required TResult Function(DeletePlayers value) deletePlayers, @@ -677,6 +1172,9 @@ class _$DeletePlayersImpl extends DeletePlayers { @optionalTypeArgs TResult? mapOrNull({ TResult? Function(LoadingPlayers value)? loadingPlayers, + TResult? Function(LoadingFailed value)? loadingFailed, + TResult? Function(NoPlayers value)? noPlayers, + TResult? Function(NoActivePlayers value)? noActivePlayers, TResult? Function(ActivePlayers value)? activePlayers, TResult? Function(AllPlayers value)? allPlayersPlayers, TResult? Function(DeletePlayers value)? deletePlayers, @@ -688,6 +1186,9 @@ class _$DeletePlayersImpl extends DeletePlayers { @optionalTypeArgs TResult maybeMap({ TResult Function(LoadingPlayers value)? loadingPlayers, + TResult Function(LoadingFailed value)? loadingFailed, + TResult Function(NoPlayers value)? noPlayers, + TResult Function(NoActivePlayers value)? noActivePlayers, TResult Function(ActivePlayers value)? activePlayers, TResult Function(AllPlayers value)? allPlayersPlayers, TResult Function(DeletePlayers value)? deletePlayers, diff --git a/board_games_companion/lib/pages/plays/plays_page.dart b/board_games_companion/lib/pages/plays/plays_page.dart index 3b747a30..7900dbb6 100644 --- a/board_games_companion/lib/pages/plays/plays_page.dart +++ b/board_games_companion/lib/pages/plays/plays_page.dart @@ -1825,6 +1825,7 @@ class _AppBar extends StatelessWidget { elevation: Dimensions.defaultElevation, titleSpacing: Dimensions.standardSpacing, foregroundColor: AppColors.accentColor, + centerTitle: false, title: const Text(AppText.playsPageTitle, style: AppTheme.titleTextStyle), bottom: PreferredSize( preferredSize: const Size.fromHeight(74), diff --git a/board_games_companion/lib/pages/playthroughs/playthrough_migration_page.dart b/board_games_companion/lib/pages/playthroughs/playthrough_migration_page.dart index ccf4df2c..34d1c4f1 100644 --- a/board_games_companion/lib/pages/playthroughs/playthrough_migration_page.dart +++ b/board_games_companion/lib/pages/playthroughs/playthrough_migration_page.dart @@ -63,7 +63,8 @@ class _PlaythroughMigrationPageState extends State { @override Widget build(BuildContext context) => PopScope( - onPopInvoked: (_) async => _handleOnWillPop(context), + canPop: false, + onPopInvoked: (didPop) async => _handleOnWillPop(context, didPop: didPop), child: Scaffold( appBar: AppBar( title: const Text( @@ -140,9 +141,14 @@ class _PlaythroughMigrationPageState extends State { ); } - Future _handleOnWillPop(BuildContext context) async { + Future _handleOnWillPop(BuildContext context, {required bool didPop}) async { + if (didPop) { + return; + } + if (!widget.viewModel.isDirty) { - return true; + Navigator.of(context).pop(); + return; } await showDialog( @@ -170,8 +176,6 @@ class _PlaythroughMigrationPageState extends State { ); }, ); - - return false; } } diff --git a/board_games_companion/lib/pages/settings/settings_page.dart b/board_games_companion/lib/pages/settings/settings_page.dart index 36cc1339..06cd1968 100644 --- a/board_games_companion/lib/pages/settings/settings_page.dart +++ b/board_games_companion/lib/pages/settings/settings_page.dart @@ -43,8 +43,12 @@ class SettingsPageState extends State { return Observer( builder: (_) { final scaffold = Scaffold( - appBar: - AppBar(title: const Text(AppText.settingsPageTitle, style: AppTheme.titleTextStyle)), + appBar: AppBar( + title: const Text( + AppText.settingsPageTitle, + style: AppTheme.titleTextStyle, + ), + ), body: SafeArea( child: PageContainer( child: Column( diff --git a/board_games_companion/lib/utilities/screenshot_generator.dart b/board_games_companion/lib/utilities/screenshot_generator.dart index fe924b24..32f27844 100644 --- a/board_games_companion/lib/utilities/screenshot_generator.dart +++ b/board_games_companion/lib/utilities/screenshot_generator.dart @@ -294,9 +294,7 @@ class _Logo extends StatelessWidget { return const Padding( padding: EdgeInsets.only( top: Dimensions.doubleStandardSpacing, - // MK Bottom is reduced because the entire container has padding - // which sums up to [Dimensions.doubleStandardSpacing] - bottom: Dimensions.standardSpacing, + bottom: Dimensions.doubleStandardSpacing, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/board_games_companion/lib/widgets/common/generic_error_message_widget.dart b/board_games_companion/lib/widgets/common/generic_error_message_widget.dart index 03bccf64..3b48dd8e 100644 --- a/board_games_companion/lib/widgets/common/generic_error_message_widget.dart +++ b/board_games_companion/lib/widgets/common/generic_error_message_widget.dart @@ -6,6 +6,7 @@ class GenericErrorMessage extends StatelessWidget { @override Widget build(BuildContext context) { return const Text( - 'Oops, we ran into an issue. Please contact support at feedback@progrunning.net'); + 'Oops, we ran into an issue. Please contact support at feedback@progrunning.net', + ); } }