diff --git a/backend/BGC.SearchApi/Program.cs b/backend/BGC.SearchApi/Program.cs index f31e4539..8443b114 100644 --- a/backend/BGC.SearchApi/Program.cs +++ b/backend/BGC.SearchApi/Program.cs @@ -28,6 +28,7 @@ .ValidateDataAnnotations() .ValidateOnStart(); +builder.Services.AddHealthChecks(); builder.Services.AddApplicationInsightsTelemetry(); builder.Services.Configure(config => { @@ -78,6 +79,7 @@ await Results.Problem(statusCode: statusCodeContext.HttpContext.Response.StatusCode) .ExecuteAsync(statusCodeContext.HttpContext); }); +app.MapHealthChecks("/health"); app.MapGet("api/search", [Authorize] ([FromQuery] string query, ISearchService searchService) => searchService.Search(query, CancellationToken.None)) .WithOpenApi(); diff --git a/board_games_companion/lib/common/app_text.dart b/board_games_companion/lib/common/app_text.dart index 13a473b2..aff77bf1 100644 --- a/board_games_companion/lib/common/app_text.dart +++ b/board_games_companion/lib/common/app_text.dart @@ -55,12 +55,18 @@ class AppText { static const aboutPageCommunityJoinDiscord = "Tap on the below logo to join the BGC's Discord server."; - static const boardGameDetailsPaboutGeneralTitle = 'General'; - static const boardGameDetailsPaboutLinksTitle = 'Links'; - static const boardGameDetailsPaboutCreditsTitle = 'Credits'; - static const boardGameDetailsPaboutCategoriesTitle = 'Categories'; - static const boardGameDetailsPaboutDescriptionTitle = 'Description'; - static const boardGameDetailsPaboutCollectionsTitle = 'Collections'; + static const boardGameDetailsPageGeneralTitle = 'General'; + static const boardGameDetailsPagetLinksTitle = 'Links'; + static const boardGameDetailsPageCreditsTitle = 'Credits'; + static const boardGameDetailsPageCategoriesTitle = 'Categories'; + static const boardGameDetailsPageDescriptionTitle = 'Description'; + static const boardGameDetailsPageCollectionsTitle = 'Collections'; + + static const boardGameDetailsPaboutGameNotRanked = 'Not ranked'; + static const boardGameDetailsPaboutGameNotRated = 'Not rated'; + static const boardGameDetailsPaboutGameRatingFormat = '%s ratings'; + static const boardGameDetailsPaboutGameNoComments = 'No comments'; + static const boardGameDetailsPaboutGameCommentsFormat = '%s comments'; static const hotBoardGamesPageTitle = 'Hot Board Games'; @@ -113,6 +119,11 @@ class AppText { static const searchBoardGamesSearchRetry = 'Retry'; static const searchBoardGamesCreateGame = 'Create game'; + static const searchBoardGamesErrorTitle = 'Sorry, we ran into a problem'; + static const searchBoardGamesTimeoutError = + 'Unfortunately our services timed out. Please try searching again.'; + static const searchBoardGamesGenericError = 'Check your internet connectivity and try again.'; + static const gamesPageMainGamesSliverSectionTitleFormat = 'Main Games (%s)'; static const gamesPageExpansionsSliverSectionTitleFormat = '%s Expansions (%s)'; 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 63e49179..c70939bc 100644 --- a/board_games_companion/lib/models/hive/board_game_details.dart +++ b/board_games_companion/lib/models/hive/board_game_details.dart @@ -147,20 +147,33 @@ class BoardGameDetails with _$BoardGameDetails { return sprintf(AppText.gamePlayersRangeFormat, [minPlayers, maxPlayers]); } - String? get rankFormatted { + String get rankFormatted { if (rank != null) { return rank.toString(); } else if (ranks.isNotEmpty) { final overallRank = ranks.first.rank; - return overallRank?.toString() ?? 'Not Ranked'; + return overallRank?.toString() ?? AppText.boardGameDetailsPaboutGameNotRanked; } - return null; + return AppText.boardGameDetailsPaboutGameNotRanked; } - String? get votesFormatted => _formatNumber(votes); + String get votesNumberFormatted { + if (votes == null) { + return AppText.boardGameDetailsPaboutGameNotRated; + } + + return sprintf(AppText.boardGameDetailsPaboutGameRatingFormat, [_formatNumber(votes)]); + } - String? get commentsNumberFormatted => _formatNumber(commentsNumber); + String get commentsNumberFormatted { + if (votes == null) { + return AppText.boardGameDetailsPaboutGameNoComments; + } + + return sprintf( + AppText.boardGameDetailsPaboutGameCommentsFormat, [_formatNumber(commentsNumber)]); + } bool get hasIncompleteDetails => (isBggSynced ?? false) && 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 32a19293..d0fc80a3 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 @@ -239,8 +239,8 @@ We couldn't retrieve any board games. Check your Internet connectivity and try a delegate: SliverChildListDelegate.fixed( [ SectionHeader.titles( - primaryTitle: AppText.boardGameDetailsPaboutGeneralTitle, - secondaryTitle: AppText.boardGameDetailsPaboutCollectionsTitle, + primaryTitle: AppText.boardGameDetailsPageGeneralTitle, + secondaryTitle: AppText.boardGameDetailsPageCollectionsTitle, ), const SizedBox(height: _sectionTopSpacing), _GeneralAndCollections(viewModel: viewModel), @@ -260,15 +260,15 @@ We couldn't retrieve any board games. Check your Internet connectivity and try a ), const SizedBox(height: _halfSpacingBetweenSecions), if (!viewModel.isCreatedByUser) ...[ - SectionHeader.title(title: AppText.boardGameDetailsPaboutLinksTitle), + SectionHeader.title(title: AppText.boardGameDetailsPagetLinksTitle), _Links(boardGameDetailsStore: viewModel), const SizedBox(height: _halfSpacingBetweenSecions), - SectionHeader.title(title: AppText.boardGameDetailsPaboutCreditsTitle), + SectionHeader.title(title: AppText.boardGameDetailsPageCreditsTitle), const SizedBox(height: _sectionTopSpacing), _Credits(boardGameDetails: viewModel.boardGame), const SizedBox(height: _halfSpacingBetweenSecions), SectionHeader.title( - title: AppText.boardGameDetailsPaboutCategoriesTitle, + title: AppText.boardGameDetailsPageCategoriesTitle, ), _Categories(categories: viewModel.boardGame.categories!), if (viewModel.isMainGame && viewModel.hasExpansions) ...[ @@ -286,7 +286,7 @@ We couldn't retrieve any board games. Check your Internet connectivity and try a const SizedBox(height: _halfSpacingBetweenSecions), ], SectionHeader.title( - title: AppText.boardGameDetailsPaboutDescriptionTitle, + title: AppText.boardGameDetailsPageDescriptionTitle, ), const SizedBox(height: _sectionTopSpacing), Padding( @@ -607,21 +607,21 @@ class _GeneralAndCollections extends StatelessWidget { BoardGameProperty( icon: const Icon(Icons.tag, size: _iconSize), iconWidth: _iconSize, - propertyName: '${viewModel.boardGame.rankFormatted}', + propertyName: viewModel.boardGame.rankFormatted, fontSize: Dimensions.mediumFontSize, ), const SizedBox(height: Dimensions.halfStandardSpacing), BoardGameProperty( icon: const Icon(Icons.how_to_vote, size: _iconSize), iconWidth: _iconSize, - propertyName: '${viewModel.boardGame.votesFormatted} ratings', + propertyName: viewModel.boardGame.votesNumberFormatted, fontSize: Dimensions.mediumFontSize, ), const SizedBox(height: Dimensions.halfStandardSpacing), BoardGameProperty( icon: const Icon(Icons.comment, size: _iconSize), iconWidth: _iconSize, - propertyName: '${viewModel.boardGame.commentsNumberFormatted} comments', + propertyName: viewModel.boardGame.commentsNumberFormatted, fontSize: Dimensions.mediumFontSize, ), const SizedBox(height: Dimensions.halfStandardSpacing), @@ -717,14 +717,14 @@ class _SecondRowGeneralInfoPanels extends StatelessWidget { ), ), const SizedBox(width: Dimensions.standardSpacing), - if (avgWeight != null) + if (avgWeight != null && avgWeight != 0) Expanded( child: _InfoPanel( icon: const FaIcon(FontAwesomeIcons.scaleUnbalanced), title: '${avgWeight!.toStringAsFixed(2)} / 5', ), ), - if (avgWeight == null) const Spacer() + if (avgWeight == null || avgWeight == 0) const Spacer() ], ), ), 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 cc30d599..9e81a484 100644 --- a/board_games_companion/lib/pages/home/home_view_model.dart +++ b/board_games_companion/lib/pages/home/home_view_model.dart @@ -2,7 +2,10 @@ import 'dart:async'; +import 'package:async/async.dart'; +import 'package:board_games_companion/models/api/search/board_game_search_dto.dart'; import 'package:board_games_companion/services/board_games_search_service.dart'; +import 'package:board_games_companion/widgets/search/board_game_search_error.dart'; import 'package:collection/collection.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/material.dart'; @@ -84,6 +87,8 @@ abstract class _HomeViewModelBase with Store { String? _searchQuery; String? _previousSearchQuery; + CancelableOperation>? _searchBoardGamesOperation; + @observable ObservableStream> searchResultsStream = ObservableStream(Stream.value([])); @@ -177,6 +182,8 @@ abstract class _HomeViewModelBase with Store { return; } + await _searchBoardGamesOperation?.cancel(); + if (_previousSearchQuery == _searchQuery && (searchResultsStream.value?.isNotEmpty ?? false)) { // Refresh data in the stream, otherwise the [ConnectionState] won't change from waiting _searchResultsStreamController.add(searchResultsStream.value!); @@ -185,29 +192,36 @@ abstract class _HomeViewModelBase with Store { await _captureSearchDetailsEvent(_searchQuery!); - try { - _searchResults.clear(); - final searchResultBoardGames = await _boardGameSearchServive.search(_searchQuery); - for (final searchResultBoardGame in searchResultBoardGames) { - // Enrich game details, if game details are available. - // Otherwise add the game to the store. - final boardGameDetails = BoardGameDetails.fromSearchResult(searchResultBoardGame); - if (_boardGamesStore.allBoardGamesMap.containsKey(boardGameDetails.id)) { - _searchResults.add(_boardGamesStore.allBoardGamesMap[boardGameDetails.id]!); - continue; - } - - await _boardGamesStore.addOrUpdateBoardGame(boardGameDetails); - _searchResults.add(boardGameDetails); + _searchResults.clear(); + _searchBoardGamesOperation = CancelableOperation>.fromFuture( + _boardGameSearchServive.search(_searchQuery)); + _searchBoardGamesOperation!.value.onError((error, stackTrace) { + FirebaseCrashlytics.instance.recordError(error, stackTrace); + if (error is TimeoutException) { + _searchResultsStreamController.addError(const BoardGameSearchError.timout()); + } else { + _searchResultsStreamController.addError(const BoardGameSearchError.generic()); } - _previousSearchQuery = _searchQuery; - _searchResultsStreamController.add(_searchResults..sortBy(searchSelectedSortBy)); - } catch (e, stack) { - FirebaseCrashlytics.instance.recordError(e, stack); - _searchResultsStreamController.addError(e); - rethrow; + return Future.value([]); + }); + + final searchResultBoardGames = await _searchBoardGamesOperation!.value; + for (final searchResultBoardGame in searchResultBoardGames) { + // Enrich game details, if game details are available. + // Otherwise add the game to the store. + final boardGameDetails = BoardGameDetails.fromSearchResult(searchResultBoardGame); + if (_boardGamesStore.allBoardGamesMap.containsKey(boardGameDetails.id)) { + _searchResults.add(_boardGamesStore.allBoardGamesMap[boardGameDetails.id]!); + continue; + } + + await _boardGamesStore.addOrUpdateBoardGame(boardGameDetails); + _searchResults.add(boardGameDetails); } + + _previousSearchQuery = _searchQuery; + _searchResultsStreamController.add(_searchResults..sortBy(searchSelectedSortBy)); } Future _captureSearchDetailsEvent(String query) async { diff --git a/board_games_companion/lib/pages/plays/plays_page.dart b/board_games_companion/lib/pages/plays/plays_page.dart index c6d1e7b8..3314dee8 100644 --- a/board_games_companion/lib/pages/plays/plays_page.dart +++ b/board_games_companion/lib/pages/plays/plays_page.dart @@ -124,17 +124,15 @@ class _PlaysPageState extends State with SingleTickerProviderStateMix Observer( builder: (_) { return widget.viewModel.visualState?.when( - history: (tab, historicalPlaythroughs) { - if (historicalPlaythroughs.isEmpty) { - return const _NoPlaythroughsSliver(); - } else { - return _HistoricalPlaythroughSliverList( - historicalPlaythroughs: historicalPlaythroughs, + history: () => Observer( + builder: (_) { + return _HistoryTab( + historicalPlaythroughs: widget.viewModel.historicalPlaythroughs, ); - } - }, - statistics: (tab) => const SliverToBoxAdapter(), - selectGame: (tab, shuffledBoardGames) { + }, + ), + statistics: () => const SliverToBoxAdapter(), + selectGame: () { if (!widget.viewModel.hasAnyBoardGames) { return const _NoBoardGamesSliver(); } @@ -146,7 +144,7 @@ class _PlaysPageState extends State with SingleTickerProviderStateMix if (widget.viewModel.hasAnyBoardGamesToShuffle) _GameSpinnerSliver( scrollController: _scrollController, - shuffledBoardGames: shuffledBoardGames, + shuffledBoardGames: widget.viewModel.shuffledBoardGames, onSpin: () => _spin(), onGameSelected: () => _selectGame(), ), @@ -1214,7 +1212,7 @@ class _AppBar extends StatelessWidget { AppBarBottomTab( AppText.playsPageHistoryTabTitle, Icons.history, - isSelected: tabVisualState?.playsTab == PlaysTab.history, + isSelected: tabVisualState == const PlaysPageVisualState.history(), ), // TODO Add stats page // AppBarBottomTab( @@ -1225,7 +1223,7 @@ class _AppBar extends StatelessWidget { AppBarBottomTab( AppText.playsPageSelectGameTabTitle, Icons.shuffle, - isSelected: tabVisualState?.playsTab == PlaysTab.selectGame, + isSelected: tabVisualState == const PlaysPageVisualState.selectGame(), ), ], indicatorColor: AppColors.accentColor, @@ -1236,6 +1234,25 @@ class _AppBar extends StatelessWidget { } } +class _HistoryTab extends StatelessWidget { + const _HistoryTab({ + required this.historicalPlaythroughs, + }); + + final List historicalPlaythroughs; + + @override + Widget build(BuildContext context) { + if (historicalPlaythroughs.isEmpty) { + return const _NoPlaythroughsSliver(); + } else { + return _HistoricalPlaythroughSliverList( + historicalPlaythroughs: historicalPlaythroughs, + ); + } + } +} + class _FilterPlaytime extends BgcSegmentedButton { _FilterPlaytime.time({ required bool isSelected, 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 index 5ba595fc..0c8c5082 100644 --- a/board_games_companion/lib/pages/plays/plays_page_visual_states.dart +++ b/board_games_companion/lib/pages/plays/plays_page_visual_states.dart @@ -1,20 +1,10 @@ -import 'package:board_games_companion/pages/plays/historical_playthrough.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import '../../common/enums/plays_tab.dart'; -import '../../models/hive/board_game_details.dart'; - part 'plays_page_visual_states.freezed.dart'; @freezed class PlaysPageVisualState with _$PlaysPageVisualState { - const factory PlaysPageVisualState.history( - PlaysTab playsTab, - List historicalPlaythroughs, - ) = _History; - const factory PlaysPageVisualState.statistics(PlaysTab playsTab) = _Statistics; - const factory PlaysPageVisualState.selectGame( - PlaysTab playsTab, - List shuffledBoardGames, - ) = _SelectGame; + const factory PlaysPageVisualState.history() = _History; + const factory PlaysPageVisualState.statistics() = _Statistics; + const factory PlaysPageVisualState.selectGame() = _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 index 8d1afa3e..63c95d1f 100644 --- 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 @@ -16,38 +16,25 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$PlaysPageVisualState { - PlaysTab get playsTab => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function(PlaysTab playsTab, - List historicalPlaythroughs) - history, - required TResult Function(PlaysTab playsTab) statistics, - required TResult Function( - PlaysTab playsTab, List shuffledBoardGames) - selectGame, + required TResult Function() history, + required TResult Function() statistics, + required TResult Function() selectGame, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(PlaysTab playsTab, - List historicalPlaythroughs)? - history, - TResult? Function(PlaysTab playsTab)? statistics, - TResult? Function( - PlaysTab playsTab, List shuffledBoardGames)? - selectGame, + TResult? Function()? history, + TResult? Function()? statistics, + TResult? Function()? selectGame, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function(PlaysTab playsTab, - List historicalPlaythroughs)? - history, - TResult Function(PlaysTab playsTab)? statistics, - TResult Function( - PlaysTab playsTab, List shuffledBoardGames)? - selectGame, + TResult Function()? history, + TResult Function()? statistics, + TResult Function()? selectGame, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -73,10 +60,6 @@ mixin _$PlaysPageVisualState { required TResult orElse(), }) => throw _privateConstructorUsedError; - - @JsonKey(ignore: true) - $PlaysPageVisualStateCopyWith get copyWith => - throw _privateConstructorUsedError; } /// @nodoc @@ -84,8 +67,6 @@ abstract class $PlaysPageVisualStateCopyWith<$Res> { factory $PlaysPageVisualStateCopyWith(PlaysPageVisualState value, $Res Function(PlaysPageVisualState) then) = _$PlaysPageVisualStateCopyWithImpl<$Res, PlaysPageVisualState>; - @useResult - $Res call({PlaysTab playsTab}); } /// @nodoc @@ -98,31 +79,13 @@ class _$PlaysPageVisualStateCopyWithImpl<$Res, final $Val _value; // ignore: unused_field final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? playsTab = null, - }) { - return _then(_value.copyWith( - playsTab: null == playsTab - ? _value.playsTab - : playsTab // ignore: cast_nullable_to_non_nullable - as PlaysTab, - ) as $Val); - } } /// @nodoc -abstract class _$$_HistoryCopyWith<$Res> - implements $PlaysPageVisualStateCopyWith<$Res> { +abstract class _$$_HistoryCopyWith<$Res> { factory _$$_HistoryCopyWith( _$_History value, $Res Function(_$_History) then) = __$$_HistoryCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {PlaysTab playsTab, List historicalPlaythroughs}); } /// @nodoc @@ -131,112 +94,57 @@ class __$$_HistoryCopyWithImpl<$Res> implements _$$_HistoryCopyWith<$Res> { __$$_HistoryCopyWithImpl(_$_History _value, $Res Function(_$_History) _then) : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? playsTab = null, - Object? historicalPlaythroughs = null, - }) { - return _then(_$_History( - null == playsTab - ? _value.playsTab - : playsTab // ignore: cast_nullable_to_non_nullable - as PlaysTab, - null == historicalPlaythroughs - ? _value._historicalPlaythroughs - : historicalPlaythroughs // ignore: cast_nullable_to_non_nullable - as List, - )); - } } /// @nodoc class _$_History implements _History { - const _$_History( - this.playsTab, final List historicalPlaythroughs) - : _historicalPlaythroughs = historicalPlaythroughs; - - @override - final PlaysTab playsTab; - final List _historicalPlaythroughs; - @override - List get historicalPlaythroughs { - if (_historicalPlaythroughs is EqualUnmodifiableListView) - return _historicalPlaythroughs; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_historicalPlaythroughs); - } + const _$_History(); @override String toString() { - return 'PlaysPageVisualState.history(playsTab: $playsTab, historicalPlaythroughs: $historicalPlaythroughs)'; + return 'PlaysPageVisualState.history()'; } @override bool operator ==(dynamic other) { return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$_History && - (identical(other.playsTab, playsTab) || - other.playsTab == playsTab) && - const DeepCollectionEquality().equals( - other._historicalPlaythroughs, _historicalPlaythroughs)); + (other.runtimeType == runtimeType && other is _$_History); } @override - int get hashCode => Object.hash(runtimeType, playsTab, - const DeepCollectionEquality().hash(_historicalPlaythroughs)); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$_HistoryCopyWith<_$_History> get copyWith => - __$$_HistoryCopyWithImpl<_$_History>(this, _$identity); + int get hashCode => runtimeType.hashCode; @override @optionalTypeArgs TResult when({ - required TResult Function(PlaysTab playsTab, - List historicalPlaythroughs) - history, - required TResult Function(PlaysTab playsTab) statistics, - required TResult Function( - PlaysTab playsTab, List shuffledBoardGames) - selectGame, + required TResult Function() history, + required TResult Function() statistics, + required TResult Function() selectGame, }) { - return history(playsTab, historicalPlaythroughs); + return history(); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(PlaysTab playsTab, - List historicalPlaythroughs)? - history, - TResult? Function(PlaysTab playsTab)? statistics, - TResult? Function( - PlaysTab playsTab, List shuffledBoardGames)? - selectGame, + TResult? Function()? history, + TResult? Function()? statistics, + TResult? Function()? selectGame, }) { - return history?.call(playsTab, historicalPlaythroughs); + return history?.call(); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(PlaysTab playsTab, - List historicalPlaythroughs)? - history, - TResult Function(PlaysTab playsTab)? statistics, - TResult Function( - PlaysTab playsTab, List shuffledBoardGames)? - selectGame, + TResult Function()? history, + TResult Function()? statistics, + TResult Function()? selectGame, required TResult orElse(), }) { if (history != null) { - return history(playsTab, historicalPlaythroughs); + return history(); } return orElse(); } @@ -277,27 +185,14 @@ class _$_History implements _History { } abstract class _History implements PlaysPageVisualState { - const factory _History(final PlaysTab playsTab, - final List historicalPlaythroughs) = _$_History; - - @override - PlaysTab get playsTab; - List get historicalPlaythroughs; - @override - @JsonKey(ignore: true) - _$$_HistoryCopyWith<_$_History> get copyWith => - throw _privateConstructorUsedError; + const factory _History() = _$_History; } /// @nodoc -abstract class _$$_StatisticsCopyWith<$Res> - implements $PlaysPageVisualStateCopyWith<$Res> { +abstract class _$$_StatisticsCopyWith<$Res> { factory _$$_StatisticsCopyWith( _$_Statistics value, $Res Function(_$_Statistics) then) = __$$_StatisticsCopyWithImpl<$Res>; - @override - @useResult - $Res call({PlaysTab playsTab}); } /// @nodoc @@ -307,94 +202,57 @@ class __$$_StatisticsCopyWithImpl<$Res> __$$_StatisticsCopyWithImpl( _$_Statistics _value, $Res Function(_$_Statistics) _then) : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? playsTab = null, - }) { - return _then(_$_Statistics( - null == playsTab - ? _value.playsTab - : playsTab // ignore: cast_nullable_to_non_nullable - as PlaysTab, - )); - } } /// @nodoc class _$_Statistics implements _Statistics { - const _$_Statistics(this.playsTab); - - @override - final PlaysTab playsTab; + const _$_Statistics(); @override String toString() { - return 'PlaysPageVisualState.statistics(playsTab: $playsTab)'; + return 'PlaysPageVisualState.statistics()'; } @override bool operator ==(dynamic other) { return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$_Statistics && - (identical(other.playsTab, playsTab) || - other.playsTab == playsTab)); + (other.runtimeType == runtimeType && other is _$_Statistics); } @override - int get hashCode => Object.hash(runtimeType, playsTab); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$_StatisticsCopyWith<_$_Statistics> get copyWith => - __$$_StatisticsCopyWithImpl<_$_Statistics>(this, _$identity); + int get hashCode => runtimeType.hashCode; @override @optionalTypeArgs TResult when({ - required TResult Function(PlaysTab playsTab, - List historicalPlaythroughs) - history, - required TResult Function(PlaysTab playsTab) statistics, - required TResult Function( - PlaysTab playsTab, List shuffledBoardGames) - selectGame, + required TResult Function() history, + required TResult Function() statistics, + required TResult Function() selectGame, }) { - return statistics(playsTab); + return statistics(); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(PlaysTab playsTab, - List historicalPlaythroughs)? - history, - TResult? Function(PlaysTab playsTab)? statistics, - TResult? Function( - PlaysTab playsTab, List shuffledBoardGames)? - selectGame, + TResult? Function()? history, + TResult? Function()? statistics, + TResult? Function()? selectGame, }) { - return statistics?.call(playsTab); + return statistics?.call(); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(PlaysTab playsTab, - List historicalPlaythroughs)? - history, - TResult Function(PlaysTab playsTab)? statistics, - TResult Function( - PlaysTab playsTab, List shuffledBoardGames)? - selectGame, + TResult Function()? history, + TResult Function()? statistics, + TResult Function()? selectGame, required TResult orElse(), }) { if (statistics != null) { - return statistics(playsTab); + return statistics(); } return orElse(); } @@ -435,25 +293,14 @@ class _$_Statistics implements _Statistics { } 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; + const factory _Statistics() = _$_Statistics; } /// @nodoc -abstract class _$$_SelectGameCopyWith<$Res> - implements $PlaysPageVisualStateCopyWith<$Res> { +abstract class _$$_SelectGameCopyWith<$Res> { factory _$$_SelectGameCopyWith( _$_SelectGame value, $Res Function(_$_SelectGame) then) = __$$_SelectGameCopyWithImpl<$Res>; - @override - @useResult - $Res call({PlaysTab playsTab, List shuffledBoardGames}); } /// @nodoc @@ -463,112 +310,57 @@ class __$$_SelectGameCopyWithImpl<$Res> __$$_SelectGameCopyWithImpl( _$_SelectGame _value, $Res Function(_$_SelectGame) _then) : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? playsTab = null, - Object? shuffledBoardGames = null, - }) { - return _then(_$_SelectGame( - null == playsTab - ? _value.playsTab - : playsTab // ignore: cast_nullable_to_non_nullable - as PlaysTab, - null == shuffledBoardGames - ? _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 { - if (_shuffledBoardGames is EqualUnmodifiableListView) - return _shuffledBoardGames; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_shuffledBoardGames); - } + const _$_SelectGame(); @override String toString() { - return 'PlaysPageVisualState.selectGame(playsTab: $playsTab, shuffledBoardGames: $shuffledBoardGames)'; + return 'PlaysPageVisualState.selectGame()'; } @override bool operator ==(dynamic other) { return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$_SelectGame && - (identical(other.playsTab, playsTab) || - other.playsTab == playsTab) && - const DeepCollectionEquality() - .equals(other._shuffledBoardGames, _shuffledBoardGames)); + (other.runtimeType == runtimeType && other is _$_SelectGame); } @override - int get hashCode => Object.hash(runtimeType, playsTab, - const DeepCollectionEquality().hash(_shuffledBoardGames)); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$_SelectGameCopyWith<_$_SelectGame> get copyWith => - __$$_SelectGameCopyWithImpl<_$_SelectGame>(this, _$identity); + int get hashCode => runtimeType.hashCode; @override @optionalTypeArgs TResult when({ - required TResult Function(PlaysTab playsTab, - List historicalPlaythroughs) - history, - required TResult Function(PlaysTab playsTab) statistics, - required TResult Function( - PlaysTab playsTab, List shuffledBoardGames) - selectGame, + required TResult Function() history, + required TResult Function() statistics, + required TResult Function() selectGame, }) { - return selectGame(playsTab, shuffledBoardGames); + return selectGame(); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(PlaysTab playsTab, - List historicalPlaythroughs)? - history, - TResult? Function(PlaysTab playsTab)? statistics, - TResult? Function( - PlaysTab playsTab, List shuffledBoardGames)? - selectGame, + TResult? Function()? history, + TResult? Function()? statistics, + TResult? Function()? selectGame, }) { - return selectGame?.call(playsTab, shuffledBoardGames); + return selectGame?.call(); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(PlaysTab playsTab, - List historicalPlaythroughs)? - history, - TResult Function(PlaysTab playsTab)? statistics, - TResult Function( - PlaysTab playsTab, List shuffledBoardGames)? - selectGame, + TResult Function()? history, + TResult Function()? statistics, + TResult Function()? selectGame, required TResult orElse(), }) { if (selectGame != null) { - return selectGame(playsTab, shuffledBoardGames); + return selectGame(); } return orElse(); } @@ -609,14 +401,5 @@ class _$_SelectGame implements _SelectGame { } 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; + const factory _SelectGame() = _$_SelectGame; } diff --git a/board_games_companion/lib/pages/plays/plays_view_model.dart b/board_games_companion/lib/pages/plays/plays_view_model.dart index 394ae6ef..f7de8007 100644 --- a/board_games_companion/lib/pages/plays/plays_view_model.dart +++ b/board_games_companion/lib/pages/plays/plays_view_model.dart @@ -88,8 +88,8 @@ abstract class _PlaysViewModel with Store { final playthroughsGrouped = groupBy( finishedPlaythroughs ..sort((playthroughA, playthroughB) => - playthroughB.endDate!.compareTo(playthroughA.endDate!)), - (Playthrough playthrough) => historicalPlaythroughDateFormat.format(playthrough.endDate!)); + playthroughB.startDate.compareTo(playthroughA.startDate)), + (Playthrough playthrough) => historicalPlaythroughDateFormat.format(playthrough.startDate)); for (final playthroughsEntry in playthroughsGrouped.entries) { result.add( @@ -177,15 +177,15 @@ abstract class _PlaysViewModel with Store { void setSelectTab(PlaysTab selectedTab) { switch (selectedTab) { case PlaysTab.history: - visualState = PlaysPageVisualState.history(PlaysTab.history, historicalPlaythroughs); + visualState = const PlaysPageVisualState.history(); break; case PlaysTab.statistics: - visualState = const PlaysPageVisualState.statistics(PlaysTab.statistics); + visualState = const PlaysPageVisualState.statistics(); break; case PlaysTab.selectGame: - visualState = PlaysPageVisualState.selectGame(PlaysTab.selectGame, shuffledBoardGames); + visualState = const PlaysPageVisualState.selectGame(); break; default: @@ -206,7 +206,7 @@ abstract class _PlaysViewModel with Store { ); } - visualState = PlaysPageVisualState.selectGame(PlaysTab.selectGame, shuffledBoardGames); + visualState = const PlaysPageVisualState.selectGame(); } @action @@ -215,21 +215,21 @@ abstract class _PlaysViewModel with Store { includeExpansions: includeExpansions ?? false, ); - visualState = PlaysPageVisualState.selectGame(PlaysTab.selectGame, shuffledBoardGames); + visualState = const PlaysPageVisualState.selectGame(); } @action void updateNumberOfPlayersNumberFilter(NumberOfPlayersFilter numberOfPlayersFilter) { gameSpinnerFilters = gameSpinnerFilters.copyWith(numberOfPlayersFilter: numberOfPlayersFilter); - visualState = PlaysPageVisualState.selectGame(PlaysTab.selectGame, shuffledBoardGames); + visualState = const PlaysPageVisualState.selectGame(); } @action void updatePlaytimeFilter(PlaytimeFilter playtimeFilter) { gameSpinnerFilters = gameSpinnerFilters.copyWith(playtimeFilter: playtimeFilter); - visualState = PlaysPageVisualState.selectGame(PlaysTab.selectGame, shuffledBoardGames); + visualState = const PlaysPageVisualState.selectGame(); } Future trackTabChange(int tabIndex) async { @@ -252,11 +252,7 @@ abstract class _PlaysViewModel with Store { .toList() ..shuffle(); _setupGameSpinnerFilters(); - visualState = PlaysPageVisualState.history( - PlaysTab.history, - historicalPlaythroughs, - // finishedBoardGamePlaythroughs.take(2).toList(), - ); + visualState = const PlaysPageVisualState.history(); } void _setupGameSpinnerFilters() { diff --git a/board_games_companion/lib/services/board_games_search_service.dart b/board_games_companion/lib/services/board_games_search_service.dart index 3fc50e32..cbc28f33 100644 --- a/board_games_companion/lib/services/board_games_search_service.dart +++ b/board_games_companion/lib/services/board_games_search_service.dart @@ -1,6 +1,8 @@ +import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; +import 'package:http/retry.dart'; import 'package:injectable/injectable.dart'; import '../models/api/search/board_game_search_dto.dart'; @@ -8,23 +10,29 @@ import 'environment_service.dart'; @singleton class BoardGamesSearchService { - BoardGamesSearchService(this._environmentService); + BoardGamesSearchService(this._environmentService) { + _httpClient = RetryClient(http.Client()); + } final EnvironmentService _environmentService; + /// An arbitrary number of seconds to wait until a request should timeout + final Duration _searchTimeout = const Duration(seconds: 10); + + late RetryClient _httpClient; + Future> search(String? searchPhrase) async { if (searchPhrase?.isEmpty ?? true) { return []; } final url = '${_environmentService.searchBoardGamesApiBaseUrl}/api/search?query=$searchPhrase'; - // TODO Add retry policy https://stackoverflow.com/a/65585101/510627? - final response = await http.get( + final response = await _httpClient.get( Uri.parse(url), headers: { 'Ocp-Apim-Subscription-Key': _environmentService.searchBoardGamesApiSubscriptionKey, }, - ); + ).timeout(_searchTimeout); final boardGamesMap = jsonDecode(response.body) as List; return boardGamesMap 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 7649093b..c2263157 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 @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:basics/basics.dart'; +import 'package:board_games_companion/widgets/common/bgc_shimmer.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; @@ -154,10 +155,12 @@ class _WebImage extends StatelessWidget { ), ), fit: BoxFit.fitWidth, - placeholder: (context, url) => Container( - decoration: BoxDecoration( - color: AppColors.primaryColor, - borderRadius: borderRadius, + placeholder: (context, url) => BgcShimmer( + child: Container( + decoration: BoxDecoration( + color: AppColors.primaryColor, + borderRadius: borderRadius, + ), ), ), errorWidget: (context, url, dynamic error) => _NoImage( diff --git a/board_games_companion/lib/widgets/common/bgc_shimmer.dart b/board_games_companion/lib/widgets/common/bgc_shimmer.dart new file mode 100644 index 00000000..5baea069 --- /dev/null +++ b/board_games_companion/lib/widgets/common/bgc_shimmer.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +import '../../common/app_colors.dart'; + +class BgcShimmer extends StatelessWidget { + const BgcShimmer({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + return Shimmer.fromColors( + baseColor: AppColors.primaryColor, + highlightColor: AppColors.primaryColorLight, + enabled: true, + child: child, + ); + } +} diff --git a/board_games_companion/lib/widgets/common/search/search_result_game_details.dart b/board_games_companion/lib/widgets/common/search/search_result_game_details.dart index 80c3e42a..af6e5e63 100644 --- a/board_games_companion/lib/widgets/common/search/search_result_game_details.dart +++ b/board_games_companion/lib/widgets/common/search/search_result_game_details.dart @@ -18,7 +18,6 @@ class SearchResultGameDetails extends StatelessWidget { final BoardGameDetails boardGame; - static const double _gameStatIconSize = 16; static const double _gamePropertyIconSize = 20; @override @@ -33,7 +32,7 @@ class SearchResultGameDetails extends StatelessWidget { if (boardGame.minPlayers != null) ...[ const SizedBox(height: Dimensions.standardSpacing), BoardGameProperty( - icon: const Icon(Icons.people, size: _gameStatIconSize), + icon: const Icon(Icons.people, size: Dimensions.smallButtonIconSize), iconWidth: _gamePropertyIconSize, propertyName: boardGame.playersFormatted, ), @@ -41,7 +40,7 @@ class SearchResultGameDetails extends StatelessWidget { if (boardGame.minPlaytime != null && boardGame.minPlaytime != 0) ...[ const SizedBox(height: Dimensions.standardSpacing), BoardGameProperty( - icon: const Icon(Icons.hourglass_bottom, size: _gameStatIconSize), + icon: const Icon(Icons.hourglass_bottom, size: Dimensions.smallButtonIconSize), iconWidth: _gamePropertyIconSize, propertyName: boardGame.playtimeFormatted, ), @@ -49,7 +48,8 @@ class SearchResultGameDetails extends StatelessWidget { if (boardGame.avgWeight != null && boardGame.avgWeight != 0) ...[ const SizedBox(height: Dimensions.standardSpacing), BoardGameProperty( - icon: const FaIcon(FontAwesomeIcons.scaleUnbalanced, size: _gameStatIconSize), + icon: const FaIcon(FontAwesomeIcons.scaleUnbalanced, + size: Dimensions.smallButtonIconSize), iconWidth: _gamePropertyIconSize, propertyName: sprintf( AppText.gamesPageSearchResultComplexityGameStatFormat, @@ -60,7 +60,8 @@ class SearchResultGameDetails extends StatelessWidget { if (boardGame.rating != null) ...[ const SizedBox(height: Dimensions.standardSpacing), BoardGameProperty( - icon: const RatingHexagon(width: _gameStatIconSize, height: _gameStatIconSize), + icon: const RatingHexagon( + width: Dimensions.smallButtonIconSize, height: Dimensions.smallButtonIconSize), iconWidth: _gamePropertyIconSize, propertyName: boardGame.rating!.toStringAsFixed(Constants.boardGameRatingNumberOfDecimalPlaces), @@ -69,7 +70,7 @@ class SearchResultGameDetails extends StatelessWidget { if (boardGame.yearPublished != null && boardGame.yearPublished != 0) ...[ const SizedBox(height: Dimensions.standardSpacing), BoardGameProperty( - icon: const Icon(Icons.event, size: _gameStatIconSize), + icon: const Icon(Icons.event, size: Dimensions.smallButtonIconSize), iconWidth: _gamePropertyIconSize, propertyName: '${boardGame.yearPublished}', ), diff --git a/board_games_companion/lib/widgets/search/bgg_search.dart b/board_games_companion/lib/widgets/search/bgg_search.dart index a4da7a34..2c62b0d5 100644 --- a/board_games_companion/lib/widgets/search/bgg_search.dart +++ b/board_games_companion/lib/widgets/search/bgg_search.dart @@ -18,11 +18,12 @@ import '../../widgets/common/default_icon.dart'; import '../../widgets/common/elevated_icon_button.dart'; import '../../widgets/common/page_container.dart'; import '../../widgets/common/panel_container.dart'; +import '../common/bgc_shimmer.dart'; import '../common/empty_page_information_panel.dart'; -import '../common/loading_indicator_widget.dart'; import '../common/search/search_result_game_details.dart'; import '../common/slivers/bgc_sliver_title_header_delegate.dart'; import '../common/sorting/sort_by_chip.dart'; +import 'board_game_search_error.dart'; /// [SearchDelegate] for the online (i.e. BGG) search. /// Controller by the [HomeViewModel]. @@ -91,11 +92,21 @@ class BggSearch extends SearchDelegate { switch (snapshot.connectionState) { case ConnectionState.none: case ConnectionState.waiting: - return const LoadingIndicator(); + return Padding( + padding: const EdgeInsets.all(Dimensions.standardSpacing), + child: ListView.separated( + itemBuilder: (BuildContext context, int index) => const _SearchResultShimmer(), + separatorBuilder: (BuildContext context, int index) => + const SizedBox(height: Dimensions.standardSpacing), + itemCount: 10, + ), + ); case ConnectionState.active: case ConnectionState.done: if (snapshot.hasError) { - return const _SearchError(); + return _SearchError( + error: snapshot.error as BoardGameSearchError, + ); } final foundGames = snapshot.data; @@ -191,6 +202,94 @@ class BggSearch extends SearchDelegate { } } +class _SearchResultShimmer extends StatelessWidget { + const _SearchResultShimmer(); + + @override + Widget build(BuildContext context) { + return PanelContainer( + child: BgcShimmer( + child: Padding( + padding: const EdgeInsets.all(Dimensions.standardSpacing), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: Dimensions.collectionSearchResultBoardGameImageHeight, + width: Dimensions.collectionSearchResultBoardGameImageWidth, + decoration: const BoxDecoration( + borderRadius: AppTheme.defaultBorderRadius, + color: AppColors.primaryColor, + ), + ), + const SizedBox(width: Dimensions.standardSpacing), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + height: Dimensions.largeFontSize, + color: AppColors.primaryColor, + ), + const SizedBox(height: Dimensions.standardSpacing), + Row( + children: [ + Container( + height: Dimensions.smallButtonIconSize, + width: Dimensions.smallButtonIconSize, + color: AppColors.primaryColor, + ), + const SizedBox(width: Dimensions.standardSpacing), + Container( + height: Dimensions.mediumFontSize, + width: 80, + color: AppColors.primaryColor, + ), + ], + ), + const SizedBox(height: Dimensions.standardSpacing), + Row( + children: [ + Container( + height: Dimensions.smallButtonIconSize, + width: Dimensions.smallButtonIconSize, + color: AppColors.primaryColor, + ), + const SizedBox(width: Dimensions.standardSpacing), + Container( + height: Dimensions.mediumFontSize, + width: 110, + color: AppColors.primaryColor, + ), + ], + ), + const SizedBox(height: Dimensions.standardSpacing), + Row( + children: [ + Container( + height: Dimensions.smallButtonIconSize, + width: Dimensions.smallButtonIconSize, + color: AppColors.primaryColor, + ), + const SizedBox(width: Dimensions.standardSpacing), + Container( + height: Dimensions.mediumFontSize, + width: 60, + color: AppColors.primaryColor, + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} + class _SearchFilters extends StatelessWidget { const _SearchFilters({ required this.sortByOptions, @@ -309,7 +408,12 @@ class _SearchResultGame extends StatelessWidget { } class _SearchError extends StatelessWidget { - const _SearchError({Key? key}) : super(key: key); + const _SearchError({ + required this.error, + Key? key, + }) : super(key: key); + + final BoardGameSearchError error; @override Widget build(BuildContext context) { @@ -320,18 +424,21 @@ class _SearchError extends StatelessWidget { ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: const [ - SizedBox(height: Dimensions.emptyPageTitleTopSpacing), + children: [ + const SizedBox(height: Dimensions.emptyPageTitleTopSpacing), EmptyPageInformationPanel( - title: 'Sorry, we ran into a problem', - icon: Icon( + title: AppText.searchBoardGamesErrorTitle, + icon: const Icon( FontAwesomeIcons.faceSadTear, size: Dimensions.emptyPageTitleIconSize, color: AppColors.primaryColor, ), - subtitle: 'Check your internet connectivity and try again.', + subtitle: error.when( + timout: () => AppText.searchBoardGamesTimeoutError, + generic: () => AppText.searchBoardGamesGenericError, + ), ), - SizedBox(height: Dimensions.doubleStandardSpacing), + const SizedBox(height: Dimensions.doubleStandardSpacing), ], ), ); diff --git a/board_games_companion/lib/widgets/search/board_game_search_error.dart b/board_games_companion/lib/widgets/search/board_game_search_error.dart new file mode 100644 index 00000000..7a6c6fa5 --- /dev/null +++ b/board_games_companion/lib/widgets/search/board_game_search_error.dart @@ -0,0 +1,9 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'board_game_search_error.freezed.dart'; + +@freezed +class BoardGameSearchError with _$BoardGameSearchError{ + const factory BoardGameSearchError.timout() = _timout; + const factory BoardGameSearchError.generic() = _generic; +} \ No newline at end of file diff --git a/board_games_companion/lib/widgets/search/board_game_search_error.freezed.dart b/board_games_companion/lib/widgets/search/board_game_search_error.freezed.dart new file mode 100644 index 00000000..4a19aabc --- /dev/null +++ b/board_games_companion/lib/widgets/search/board_game_search_error.freezed.dart @@ -0,0 +1,277 @@ +// 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, unnecessary_question_mark + +part of 'board_game_search_error.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 _$BoardGameSearchError { + @optionalTypeArgs + TResult when({ + required TResult Function() timout, + required TResult Function() generic, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? timout, + TResult? Function()? generic, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? timout, + TResult Function()? generic, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_timout value) timout, + required TResult Function(_generic value) generic, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_timout value)? timout, + TResult? Function(_generic value)? generic, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_timout value)? timout, + TResult Function(_generic value)? generic, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BoardGameSearchErrorCopyWith<$Res> { + factory $BoardGameSearchErrorCopyWith(BoardGameSearchError value, + $Res Function(BoardGameSearchError) then) = + _$BoardGameSearchErrorCopyWithImpl<$Res, BoardGameSearchError>; +} + +/// @nodoc +class _$BoardGameSearchErrorCopyWithImpl<$Res, + $Val extends BoardGameSearchError> + implements $BoardGameSearchErrorCopyWith<$Res> { + _$BoardGameSearchErrorCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$_timoutCopyWith<$Res> { + factory _$$_timoutCopyWith(_$_timout value, $Res Function(_$_timout) then) = + __$$_timoutCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$_timoutCopyWithImpl<$Res> + extends _$BoardGameSearchErrorCopyWithImpl<$Res, _$_timout> + implements _$$_timoutCopyWith<$Res> { + __$$_timoutCopyWithImpl(_$_timout _value, $Res Function(_$_timout) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$_timout implements _timout { + const _$_timout(); + + @override + String toString() { + return 'BoardGameSearchError.timout()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$_timout); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() timout, + required TResult Function() generic, + }) { + return timout(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? timout, + TResult? Function()? generic, + }) { + return timout?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? timout, + TResult Function()? generic, + required TResult orElse(), + }) { + if (timout != null) { + return timout(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_timout value) timout, + required TResult Function(_generic value) generic, + }) { + return timout(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_timout value)? timout, + TResult? Function(_generic value)? generic, + }) { + return timout?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_timout value)? timout, + TResult Function(_generic value)? generic, + required TResult orElse(), + }) { + if (timout != null) { + return timout(this); + } + return orElse(); + } +} + +abstract class _timout implements BoardGameSearchError { + const factory _timout() = _$_timout; +} + +/// @nodoc +abstract class _$$_genericCopyWith<$Res> { + factory _$$_genericCopyWith( + _$_generic value, $Res Function(_$_generic) then) = + __$$_genericCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$_genericCopyWithImpl<$Res> + extends _$BoardGameSearchErrorCopyWithImpl<$Res, _$_generic> + implements _$$_genericCopyWith<$Res> { + __$$_genericCopyWithImpl(_$_generic _value, $Res Function(_$_generic) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$_generic implements _generic { + const _$_generic(); + + @override + String toString() { + return 'BoardGameSearchError.generic()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$_generic); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() timout, + required TResult Function() generic, + }) { + return generic(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? timout, + TResult? Function()? generic, + }) { + return generic?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? timout, + TResult Function()? generic, + required TResult orElse(), + }) { + if (generic != null) { + return generic(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_timout value) timout, + required TResult Function(_generic value) generic, + }) { + return generic(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_timout value)? timout, + TResult? Function(_generic value)? generic, + }) { + return generic?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_timout value)? timout, + TResult Function(_generic value)? generic, + required TResult orElse(), + }) { + if (generic != null) { + return generic(this); + } + return orElse(); + } +} + +abstract class _generic implements BoardGameSearchError { + const factory _generic() = _$_generic; +} diff --git a/board_games_companion/pubspec.lock b/board_games_companion/pubspec.lock index 1d3d2a20..7684fa15 100644 --- a/board_games_companion/pubspec.lock +++ b/board_games_companion/pubspec.lock @@ -1160,6 +1160,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter diff --git a/board_games_companion/pubspec.yaml b/board_games_companion/pubspec.yaml index 06528d6c..ef4fcf00 100644 --- a/board_games_companion/pubspec.yaml +++ b/board_games_companion/pubspec.yaml @@ -61,6 +61,7 @@ dependencies: package_info: ^2.0.2 retry: ^3.1.0 share_plus: ^4.0.10+1 + shimmer: ^3.0.0 sliver_tools: 0.2.8 sprintf: ^6.0.0 tuple: ^2.0.0