From 0063ae83566584952b906567e93d1fb1c8c52c2b Mon Sep 17 00:00:00 2001 From: Mikolaj Kieres Date: Sun, 20 Nov 2022 18:35:40 +1100 Subject: [PATCH] 1.7.3 - Adding ability to edit playthrough from the history page (#150) * changing android's hosted agent to ubuntu * adding ability to navigate to edit playthrough from the history list --- board_games_companion/lib/app.dart | 9 +- .../lib/common/app_styles.dart | 5 +- .../lib/injectable.config.dart | 37 ++-- .../edit_playthrough_page_arguments.dart | 14 +- ...it_playthrough_page_arguments.freezed.dart | 180 ++++++++++++++++++ .../lib/models/player_score.dart | 1 + .../edit_playthrough_page.dart | 19 +- .../edit_playthrough_view_model.dart | 24 ++- .../edit_playthrough_view_model.g.dart | 11 ++ .../playthrough_note_view_model.dart | 10 +- .../lib/pages/games/games_page.dart | 5 +- .../lib/pages/games/games_view_model.dart | 5 + .../lib/pages/players/players_page.dart | 2 + .../lib/pages/players/players_view_model.dart | 2 +- .../pages/players/players_view_model.g.dart | 8 +- .../playthrough_statistics_view_model.dart | 4 +- ...playthroughs_game_settings_view_model.dart | 14 +- .../playthroughs_history_page.dart | 6 +- .../playthroughs_history_view_model.dart | 12 +- .../playthroughs_log_game_view_model.dart | 4 +- .../playthroughs/playthroughs_view_model.dart | 36 ++-- .../playthroughs_history_page.dart | 102 +++++++--- .../playthroughs_history_view_model.dart | 4 +- .../search_board_games_page.dart | 2 + ...t => game_playthroughs_details_store.dart} | 33 ++-- ...=> game_playthroughs_details_store.g.dart} | 45 +++-- .../lib/stores/players_store.dart | 6 +- .../lib/stores/players_store.g.dart | 8 + .../lib/stores/playthroughs_store.dart | 2 +- .../lib/widgets/common/panel_container.dart | 2 +- board_games_companion/pubspec.lock | 2 +- board_games_companion/pubspec.yaml | 2 +- pipelines/mobile_app/deploy-pipeline.yml | 15 +- 33 files changed, 457 insertions(+), 174 deletions(-) create mode 100644 board_games_companion/lib/models/navigation/edit_playthrough_page_arguments.freezed.dart rename board_games_companion/lib/stores/{game_playthroughs_store.dart => game_playthroughs_details_store.dart} (79%) rename board_games_companion/lib/stores/{game_playthroughs_store.g.dart => game_playthroughs_details_store.g.dart} (68%) diff --git a/board_games_companion/lib/app.dart b/board_games_companion/lib/app.dart index e771d447..a4503b86 100644 --- a/board_games_companion/lib/app.dart +++ b/board_games_companion/lib/app.dart @@ -109,11 +109,16 @@ class BoardGamesCompanionAppState extends State { case EditPlaythroughPage.pageRoute: final arguments = routeSettings.arguments as EditPlaythroughPageArguments; final viewModel = getIt(); + viewModel.setBoardGameId(arguments.boardGameId); viewModel.setPlaythroughId(arguments.playthroughId); return MaterialPageRoute( - settings: routeSettings, - builder: (BuildContext context) => EditPlaythroughPage(viewModel: viewModel)); + settings: routeSettings, + builder: (BuildContext context) => EditPlaythroughPage( + viewModel: viewModel, + goBackPageRoute: arguments.goBackPageRoute, + ), + ); case AboutPage.pageRoute: return MaterialPageRoute( diff --git a/board_games_companion/lib/common/app_styles.dart b/board_games_companion/lib/common/app_styles.dart index 05120c2a..ea90fbf2 100644 --- a/board_games_companion/lib/common/app_styles.dart +++ b/board_games_companion/lib/common/app_styles.dart @@ -4,11 +4,12 @@ class AppStyles { static const double _maxOpacityDecimal = 255; static const double _hundredPercent = 100; - static const double defaultShadowRadius = defaultCornerRadius; static const double defaultCornerRadius = 5; + static const double defaultShadowRadius = defaultCornerRadius; static const Offset defaultShadowOffset = Offset(defaultCornerRadius / 2, defaultCornerRadius / 2); static const double defaultBottomSheetCornerRadius = defaultCornerRadius * 6; + static const double defaultElevation = 4; static const double boardGameTileImageCircularRadius = 15; static const double boardGameTileImageShadowBlur = 1.5; @@ -33,5 +34,5 @@ class AppStyles { fontSize: 32, ); - static const double defaultElevation = 4; + static const double panelContainerCornerRadius = defaultCornerRadius * 3; } diff --git a/board_games_companion/lib/injectable.config.dart b/board_games_companion/lib/injectable.config.dart index ffa1a88b..eda95c1c 100644 --- a/board_games_companion/lib/injectable.config.dart +++ b/board_games_companion/lib/injectable.config.dart @@ -41,7 +41,7 @@ import 'services/user_service.dart' as _i16; import 'stores/app_store.dart' as _i3; import 'stores/board_games_filters_store.dart' as _i19; import 'stores/board_games_store.dart' as _i26; -import 'stores/game_playthroughs_store.dart' as _i28; +import 'stores/game_playthroughs_details_store.dart' as _i28; import 'stores/players_store.dart' as _i10; import 'stores/playthroughs_store.dart' as _i24; import 'stores/scores_store.dart' as _i15; @@ -98,26 +98,27 @@ _i1.GetIt $initGetIt(_i1.GetIt get, get<_i3.AppStore>())); gh.factory<_i27.CollectionSearchResultViewModel>( () => _i27.CollectionSearchResultViewModel(get<_i26.BoardGamesStore>())); - gh.singleton<_i28.GamePlaythroughsStore>(_i28.GamePlaythroughsStore( - get<_i24.PlaythroughsStore>(), - get<_i15.ScoresStore>(), - get<_i9.PlayerService>())); + gh.singleton<_i28.GamePlaythroughsDetailsStore>( + _i28.GamePlaythroughsDetailsStore(get<_i24.PlaythroughsStore>(), + get<_i15.ScoresStore>(), get<_i10.PlayersStore>())); gh.factory<_i29.GamesViewModel>(() => _i29.GamesViewModel( get<_i17.UserStore>(), get<_i26.BoardGamesStore>(), get<_i19.BoardGamesFiltersStore>(), get<_i15.ScoresStore>(), - get<_i24.PlaythroughsStore>())); - gh.factory<_i30.PlaythroughNoteViewModel>( - () => _i30.PlaythroughNoteViewModel(get<_i28.GamePlaythroughsStore>())); + get<_i24.PlaythroughsStore>(), + get<_i10.PlayersStore>())); + gh.factory<_i30.PlaythroughNoteViewModel>(() => + _i30.PlaythroughNoteViewModel(get<_i28.GamePlaythroughsDetailsStore>())); gh.singleton<_i31.PlaythroughStatisticsViewModel>( _i31.PlaythroughStatisticsViewModel(get<_i9.PlayerService>(), - get<_i15.ScoresStore>(), get<_i28.GamePlaythroughsStore>())); + get<_i15.ScoresStore>(), get<_i28.GamePlaythroughsDetailsStore>())); gh.factory<_i32.PlaythroughsGameSettingsViewModel>(() => - _i32.PlaythroughsGameSettingsViewModel( - get<_i26.BoardGamesStore>(), get<_i28.GamePlaythroughsStore>())); + _i32.PlaythroughsGameSettingsViewModel(get<_i26.BoardGamesStore>(), + get<_i28.GamePlaythroughsDetailsStore>())); gh.factory<_i33.PlaythroughsHistoryViewModel>(() => - _i33.PlaythroughsHistoryViewModel(get<_i28.GamePlaythroughsStore>())); + _i33.PlaythroughsHistoryViewModel( + get<_i28.GamePlaythroughsDetailsStore>())); gh.factory<_i34.PlaythroughsHistoryViewModel>(() => _i34.PlaythroughsHistoryViewModel( get<_i24.PlaythroughsStore>(), @@ -125,10 +126,12 @@ _i1.GetIt $initGetIt(_i1.GetIt get, get<_i10.PlayersStore>(), get<_i15.ScoresStore>())); gh.factory<_i35.PlaythroughsLogGameViewModel>(() => - _i35.PlaythroughsLogGameViewModel(get<_i10.PlayersStore>(), - get<_i28.GamePlaythroughsStore>(), get<_i18.AnalyticsService>())); + _i35.PlaythroughsLogGameViewModel( + get<_i10.PlayersStore>(), + get<_i28.GamePlaythroughsDetailsStore>(), + get<_i18.AnalyticsService>())); gh.factory<_i36.PlaythroughsViewModel>(() => _i36.PlaythroughsViewModel( - get<_i28.GamePlaythroughsStore>(), + get<_i28.GamePlaythroughsDetailsStore>(), get<_i10.PlayersStore>(), get<_i18.AnalyticsService>(), get<_i21.BoardGamesService>(), @@ -152,8 +155,8 @@ _i1.GetIt $initGetIt(_i1.GetIt get, gh.factory<_i39.BoardGameDetailsViewModel>(() => _i39.BoardGameDetailsViewModel( get<_i26.BoardGamesStore>(), get<_i18.AnalyticsService>())); - gh.factory<_i40.EditPlaythoughViewModel>( - () => _i40.EditPlaythoughViewModel(get<_i28.GamePlaythroughsStore>())); + gh.factory<_i40.EditPlaythoughViewModel>(() => _i40.EditPlaythoughViewModel( + get<_i28.GamePlaythroughsDetailsStore>(), get<_i26.BoardGamesStore>())); gh.factory<_i41.HomeViewModel>(() => _i41.HomeViewModel( get<_i18.AnalyticsService>(), get<_i13.RateAndReviewService>(), diff --git a/board_games_companion/lib/models/navigation/edit_playthrough_page_arguments.dart b/board_games_companion/lib/models/navigation/edit_playthrough_page_arguments.dart index 4bb90518..9e3e1c37 100644 --- a/board_games_companion/lib/models/navigation/edit_playthrough_page_arguments.dart +++ b/board_games_companion/lib/models/navigation/edit_playthrough_page_arguments.dart @@ -1,6 +1,12 @@ -class EditPlaythroughPageArguments { - const EditPlaythroughPageArguments(this.playthroughId, this.boardGameId); +import 'package:freezed_annotation/freezed_annotation.dart'; - final String playthroughId; - final String boardGameId; +part 'edit_playthrough_page_arguments.freezed.dart'; + +@freezed +class EditPlaythroughPageArguments with _$EditPlaythroughPageArguments { + const factory EditPlaythroughPageArguments({ + required String playthroughId, + required String boardGameId, + required String goBackPageRoute, + }) = _EditPlaythroughPageArguments; } diff --git a/board_games_companion/lib/models/navigation/edit_playthrough_page_arguments.freezed.dart b/board_games_companion/lib/models/navigation/edit_playthrough_page_arguments.freezed.dart new file mode 100644 index 00000000..89a7a1c5 --- /dev/null +++ b/board_games_companion/lib/models/navigation/edit_playthrough_page_arguments.freezed.dart @@ -0,0 +1,180 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target + +part of 'edit_playthrough_page_arguments.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 _$EditPlaythroughPageArguments { + String get playthroughId => throw _privateConstructorUsedError; + String get boardGameId => throw _privateConstructorUsedError; + String get goBackPageRoute => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $EditPlaythroughPageArgumentsCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $EditPlaythroughPageArgumentsCopyWith<$Res> { + factory $EditPlaythroughPageArgumentsCopyWith( + EditPlaythroughPageArguments value, + $Res Function(EditPlaythroughPageArguments) then) = + _$EditPlaythroughPageArgumentsCopyWithImpl<$Res>; + $Res call({String playthroughId, String boardGameId, String goBackPageRoute}); +} + +/// @nodoc +class _$EditPlaythroughPageArgumentsCopyWithImpl<$Res> + implements $EditPlaythroughPageArgumentsCopyWith<$Res> { + _$EditPlaythroughPageArgumentsCopyWithImpl(this._value, this._then); + + final EditPlaythroughPageArguments _value; + // ignore: unused_field + final $Res Function(EditPlaythroughPageArguments) _then; + + @override + $Res call({ + Object? playthroughId = freezed, + Object? boardGameId = freezed, + Object? goBackPageRoute = freezed, + }) { + return _then(_value.copyWith( + playthroughId: playthroughId == freezed + ? _value.playthroughId + : playthroughId // ignore: cast_nullable_to_non_nullable + as String, + boardGameId: boardGameId == freezed + ? _value.boardGameId + : boardGameId // ignore: cast_nullable_to_non_nullable + as String, + goBackPageRoute: goBackPageRoute == freezed + ? _value.goBackPageRoute + : goBackPageRoute // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +abstract class _$$_EditPlaythroughPageArgumentsCopyWith<$Res> + implements $EditPlaythroughPageArgumentsCopyWith<$Res> { + factory _$$_EditPlaythroughPageArgumentsCopyWith( + _$_EditPlaythroughPageArguments value, + $Res Function(_$_EditPlaythroughPageArguments) then) = + __$$_EditPlaythroughPageArgumentsCopyWithImpl<$Res>; + @override + $Res call({String playthroughId, String boardGameId, String goBackPageRoute}); +} + +/// @nodoc +class __$$_EditPlaythroughPageArgumentsCopyWithImpl<$Res> + extends _$EditPlaythroughPageArgumentsCopyWithImpl<$Res> + implements _$$_EditPlaythroughPageArgumentsCopyWith<$Res> { + __$$_EditPlaythroughPageArgumentsCopyWithImpl( + _$_EditPlaythroughPageArguments _value, + $Res Function(_$_EditPlaythroughPageArguments) _then) + : super(_value, (v) => _then(v as _$_EditPlaythroughPageArguments)); + + @override + _$_EditPlaythroughPageArguments get _value => + super._value as _$_EditPlaythroughPageArguments; + + @override + $Res call({ + Object? playthroughId = freezed, + Object? boardGameId = freezed, + Object? goBackPageRoute = freezed, + }) { + return _then(_$_EditPlaythroughPageArguments( + playthroughId: playthroughId == freezed + ? _value.playthroughId + : playthroughId // ignore: cast_nullable_to_non_nullable + as String, + boardGameId: boardGameId == freezed + ? _value.boardGameId + : boardGameId // ignore: cast_nullable_to_non_nullable + as String, + goBackPageRoute: goBackPageRoute == freezed + ? _value.goBackPageRoute + : goBackPageRoute // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$_EditPlaythroughPageArguments implements _EditPlaythroughPageArguments { + const _$_EditPlaythroughPageArguments( + {required this.playthroughId, + required this.boardGameId, + required this.goBackPageRoute}); + + @override + final String playthroughId; + @override + final String boardGameId; + @override + final String goBackPageRoute; + + @override + String toString() { + return 'EditPlaythroughPageArguments(playthroughId: $playthroughId, boardGameId: $boardGameId, goBackPageRoute: $goBackPageRoute)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_EditPlaythroughPageArguments && + const DeepCollectionEquality() + .equals(other.playthroughId, playthroughId) && + const DeepCollectionEquality() + .equals(other.boardGameId, boardGameId) && + const DeepCollectionEquality() + .equals(other.goBackPageRoute, goBackPageRoute)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(playthroughId), + const DeepCollectionEquality().hash(boardGameId), + const DeepCollectionEquality().hash(goBackPageRoute)); + + @JsonKey(ignore: true) + @override + _$$_EditPlaythroughPageArgumentsCopyWith<_$_EditPlaythroughPageArguments> + get copyWith => __$$_EditPlaythroughPageArgumentsCopyWithImpl< + _$_EditPlaythroughPageArguments>(this, _$identity); +} + +abstract class _EditPlaythroughPageArguments + implements EditPlaythroughPageArguments { + const factory _EditPlaythroughPageArguments( + {required final String playthroughId, + required final String boardGameId, + required final String goBackPageRoute}) = _$_EditPlaythroughPageArguments; + + @override + String get playthroughId; + @override + String get boardGameId; + @override + String get goBackPageRoute; + @override + @JsonKey(ignore: true) + _$$_EditPlaythroughPageArgumentsCopyWith<_$_EditPlaythroughPageArguments> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/board_games_companion/lib/models/player_score.dart b/board_games_companion/lib/models/player_score.dart index e47b8c1d..1f73325c 100644 --- a/board_games_companion/lib/models/player_score.dart +++ b/board_games_companion/lib/models/player_score.dart @@ -7,6 +7,7 @@ export '../extensions/player_score_extensions.dart'; part 'player_score.freezed.dart'; +/// Model containing [Player] and their [Score] @freezed class PlayerScore with _$PlayerScore { const factory PlayerScore({ 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 ee3365f2..d4451bba 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 @@ -23,18 +23,19 @@ import '../../widgets/common/slivers/bgc_sliver_header_delegate.dart'; import '../../widgets/player/player_avatar.dart'; import '../../widgets/playthrough/calendar_card.dart'; import '../enter_score/enter_score_view_model.dart'; -import '../playthroughs/playthroughs_page.dart'; import 'edit_playthrough_view_model.dart'; class EditPlaythroughPage extends StatefulWidget { const EditPlaythroughPage({ required this.viewModel, + required this.goBackPageRoute, Key? key, }) : super(key: key); static const String pageRoute = '/editPlaythrough'; final EditPlaythoughViewModel viewModel; + final String goBackPageRoute; @override EditPlaythroughPageState createState() => EditPlaythroughPageState(); @@ -183,9 +184,7 @@ class EditPlaythroughPageState extends State with EnterScor actions: [ TextButton( child: const Text(AppText.cancel, style: TextStyle(color: AppColors.accentColor)), - onPressed: () { - Navigator.of(context).pop(); - }, + onPressed: () => Navigator.of(context).pop(), ), TextButton( style: TextButton.styleFrom(backgroundColor: AppColors.redColor), @@ -195,7 +194,7 @@ class EditPlaythroughPageState extends State with EnterScor return; } - Navigator.of(context).popUntil(ModalRoute.withName(PlaythroughsPage.pageRoute)); + Navigator.of(context).popUntil(ModalRoute.withName(widget.goBackPageRoute)); }, child: const Text(AppText.delete, style: TextStyle(color: AppColors.defaultTextColor)), @@ -221,14 +220,12 @@ class EditPlaythroughPageState extends State with EnterScor actions: [ TextButton( child: const Text(AppText.cancel, style: TextStyle(color: AppColors.accentColor)), - onPressed: () { - Navigator.of(context).pop(); - }, + onPressed: () => Navigator.of(context).pop(), ), TextButton( style: TextButton.styleFrom(backgroundColor: AppColors.redColor), onPressed: () async { - Navigator.of(context).popUntil(ModalRoute.withName(PlaythroughsPage.pageRoute)); + Navigator.of(context).popUntil(ModalRoute.withName(widget.goBackPageRoute)); }, child: const Text( AppText.editPlaythroughPageUnsavedChangesActionButtonText, @@ -246,9 +243,7 @@ class EditPlaythroughPageState extends State with EnterScor Future _addNote() async { await Navigator.of(context).pushNamed( PlaythroughNotePage.pageRoute, - arguments: PlaythroughNotePageArguments( - widget.viewModel.playthrough, - ), + arguments: PlaythroughNotePageArguments(widget.viewModel.playthrough), ); widget.viewModel.refreshNotes(); } diff --git a/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_view_model.dart b/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_view_model.dart index a2bafacb..2f386994 100644 --- a/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_view_model.dart +++ b/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_view_model.dart @@ -4,13 +4,14 @@ import 'package:board_games_companion/models/hive/playthrough.dart'; import 'package:board_games_companion/models/hive/playthrough_note.dart'; import 'package:board_games_companion/models/player_score.dart'; import 'package:board_games_companion/models/playthrough_details.dart'; -import 'package:board_games_companion/stores/game_playthroughs_store.dart'; +import 'package:board_games_companion/stores/game_playthroughs_details_store.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:injectable/injectable.dart'; import 'package:mobx/mobx.dart'; import '../../common/enums/playthrough_status.dart'; +import '../../stores/board_games_store.dart'; part 'edit_playthrough_view_model.g.dart'; @@ -18,10 +19,12 @@ part 'edit_playthrough_view_model.g.dart'; class EditPlaythoughViewModel = _EditPlaythoughViewModel with _$EditPlaythoughViewModel; abstract class _EditPlaythoughViewModel with Store { - _EditPlaythoughViewModel(this._playthroughsStore); + _EditPlaythoughViewModel(this._gamePlaythroughsDetailsStore, this._boardGamesStore); + + final GamePlaythroughsDetailsStore _gamePlaythroughsDetailsStore; + final BoardGamesStore _boardGamesStore; late final String _playthroughId; - final GamePlaythroughsStore _playthroughsStore; ValueNotifier isSpeedDialContextMenuOpen = ValueNotifier(false); @@ -32,7 +35,7 @@ abstract class _EditPlaythoughViewModel with Store { @computed PlaythroughDetails get playthroughDetails => - _playthroughsStore.playthroughsDetails.firstWhere((pd) => pd.id == _playthroughId); + _gamePlaythroughsDetailsStore.playthroughsDetails.firstWhere((pd) => pd.id == _playthroughId); @computed Playthrough get playthrough => playthroughDetailsWorkingCopy.playthrough; @@ -71,6 +74,13 @@ abstract class _EditPlaythoughViewModel with Store { _playthroughDetailsWorkingCopy = playthroughDetails.copyWith(); } + @action + void setBoardGameId(String boardGameId) { + _gamePlaythroughsDetailsStore.setBoardGame( + _boardGamesStore.allBoardGames.firstWhere((element) => element.id == boardGameId)); + _gamePlaythroughsDetailsStore.loadPlaythroughsDetails(); + } + @action Future stopPlaythrough() async { final updatedPlaythrough = playthrough.copyWith( @@ -79,13 +89,13 @@ abstract class _EditPlaythoughViewModel with Store { ); _playthroughDetailsWorkingCopy = _playthroughDetailsWorkingCopy?.copyWith(playthrough: updatedPlaythrough); - await _playthroughsStore.updatePlaythrough(_playthroughDetailsWorkingCopy); + await _gamePlaythroughsDetailsStore.updatePlaythrough(_playthroughDetailsWorkingCopy); } @action Future saveChanges() async { if (isDirty) { - await _playthroughsStore.updatePlaythrough(playthroughDetailsWorkingCopy); + await _gamePlaythroughsDetailsStore.updatePlaythrough(playthroughDetailsWorkingCopy); } } @@ -135,7 +145,7 @@ abstract class _EditPlaythoughViewModel with Store { @action Future deletePlaythrough() async { - await _playthroughsStore.deletePlaythrough(playthroughDetails.id); + await _gamePlaythroughsDetailsStore.deletePlaythrough(playthroughDetails.id); } @action diff --git a/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_view_model.g.dart b/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_view_model.g.dart index b10ac4dd..118dba98 100644 --- a/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_view_model.g.dart +++ b/board_games_companion/lib/pages/edit_playthrough/edit_playthrough_view_model.g.dart @@ -123,6 +123,17 @@ mixin _$EditPlaythoughViewModel on _EditPlaythoughViewModel, Store { } } + @override + void setBoardGameId(String boardGameId) { + final _$actionInfo = _$_EditPlaythoughViewModelActionController.startAction( + name: '_EditPlaythoughViewModel.setBoardGameId'); + try { + return super.setBoardGameId(boardGameId); + } finally { + _$_EditPlaythoughViewModelActionController.endAction(_$actionInfo); + } + } + @override void updateStartDate(DateTime newStartDate) { final _$actionInfo = _$_EditPlaythoughViewModelActionController.startAction( diff --git a/board_games_companion/lib/pages/edit_playthrough/playthrough_note_view_model.dart b/board_games_companion/lib/pages/edit_playthrough/playthrough_note_view_model.dart index 39248607..3fce8221 100644 --- a/board_games_companion/lib/pages/edit_playthrough/playthrough_note_view_model.dart +++ b/board_games_companion/lib/pages/edit_playthrough/playthrough_note_view_model.dart @@ -3,7 +3,7 @@ import 'package:basics/basics.dart'; import 'package:board_games_companion/models/hive/playthrough.dart'; import 'package:board_games_companion/models/playthrough_details.dart'; -import 'package:board_games_companion/stores/game_playthroughs_store.dart'; +import 'package:board_games_companion/stores/game_playthroughs_details_store.dart'; import 'package:injectable/injectable.dart'; import 'package:mobx/mobx.dart'; import 'package:uuid/uuid.dart'; @@ -17,11 +17,11 @@ part 'playthrough_note_view_model.g.dart'; class PlaythroughNoteViewModel = _PlaythroughNoteViewModel with _$PlaythroughNoteViewModel; abstract class _PlaythroughNoteViewModel with Store { - _PlaythroughNoteViewModel(this._playthroughsStore); + _PlaythroughNoteViewModel(this._gamePlaythroughsDetailsStore); - final GamePlaythroughsStore _playthroughsStore; + final GamePlaythroughsDetailsStore _gamePlaythroughsDetailsStore; - PlaythroughDetails get _playthroughDetails => _playthroughsStore.playthroughsDetails + PlaythroughDetails get _playthroughDetails => _gamePlaythroughsDetailsStore.playthroughsDetails .firstWhere((playthroughDetails) => playthroughDetails.id == _playthrough!.id); String? _noteId; @@ -84,6 +84,6 @@ abstract class _PlaythroughNoteViewModel with Store { _playthrough = _playthrough!.copyWith(notes: playthroughNotes); final updatedPlaythroughDetails = _playthroughDetails.copyWith(playthrough: _playthrough!); - await _playthroughsStore.updatePlaythrough(updatedPlaythroughDetails); + await _gamePlaythroughsDetailsStore.updatePlaythrough(updatedPlaythroughDetails); } } diff --git a/board_games_companion/lib/pages/games/games_page.dart b/board_games_companion/lib/pages/games/games_page.dart index 960a9b79..21956083 100644 --- a/board_games_companion/lib/pages/games/games_page.dart +++ b/board_games_companion/lib/pages/games/games_page.dart @@ -240,9 +240,10 @@ class _AppBar extends StatefulWidget { class _AppBarState extends State<_AppBar> { @override Widget build(BuildContext context) => SliverAppBar( - pinned: false, + pinned: true, floating: true, - elevation: 0, + forceElevated: true, + elevation: Dimensions.defaultElevation, titleSpacing: Dimensions.standardSpacing, foregroundColor: AppColors.accentColor, title: const Text( diff --git a/board_games_companion/lib/pages/games/games_view_model.dart b/board_games_companion/lib/pages/games/games_view_model.dart index 09b4355a..281a7a5f 100644 --- a/board_games_companion/lib/pages/games/games_view_model.dart +++ b/board_games_companion/lib/pages/games/games_view_model.dart @@ -24,6 +24,7 @@ import '../../extensions/int_extensions.dart'; import '../../extensions/string_extensions.dart'; import '../../models/hive/board_game_details.dart'; import '../../stores/board_games_store.dart'; +import '../../stores/players_store.dart'; part 'games_view_model.g.dart'; @@ -37,6 +38,7 @@ abstract class _GamesViewModel with Store { this._boardGamesFiltersStore, this._scoresStore, this._playthroughsStore, + this._playersStore, ); final UserStore _userStore; @@ -44,6 +46,7 @@ abstract class _GamesViewModel with Store { final BoardGamesFiltersStore _boardGamesFiltersStore; final ScoresStore _scoresStore; final PlaythroughsStore _playthroughsStore; + final PlayersStore _playersStore; @computed List get allMainGames => @@ -259,11 +262,13 @@ abstract class _GamesViewModel with Store { Future _loadBoardGames() async { try { // TODO MK Think about if we could potentially load all of the data once and then use it across the app + // What impact on the startup time and memory usage would it have? await _userStore.loadUser(); await _boardGamesStore.loadBoardGames(); await _boardGamesFiltersStore.loadFilterPreferences(); await _playthroughsStore.loadPlaythroughs(); await _scoresStore.loadScores(); + await _playersStore.loadPlayers(); } catch (e, stack) { FirebaseCrashlytics.instance.recordError(e, stack); } diff --git a/board_games_companion/lib/pages/players/players_page.dart b/board_games_companion/lib/pages/players/players_page.dart index 7c70e3a8..f87fdfc6 100644 --- a/board_games_companion/lib/pages/players/players_page.dart +++ b/board_games_companion/lib/pages/players/players_page.dart @@ -189,6 +189,8 @@ class _AppBar extends StatelessWidget { return SliverAppBar( pinned: true, floating: true, + forceElevated: true, + elevation: Dimensions.defaultElevation, titleSpacing: Dimensions.standardSpacing, foregroundColor: AppColors.accentColor, title: const Text(AppText.playersPageTitle, style: AppTheme.titleTextStyle), 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 e2d3fc87..f8586f72 100644 --- a/board_games_companion/lib/pages/players/players_view_model.dart +++ b/board_games_companion/lib/pages/players/players_view_model.dart @@ -26,7 +26,7 @@ abstract class _PlayersViewModel with Store { bool isEditMode = false; @computed - ObservableList get players => _playersStore.players; + List get players => _playersStore.activePlayers; @computed bool get hasAnyPlayers => players.isNotEmpty; 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 199ebbab..e15993e6 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 @@ -9,13 +9,13 @@ part of 'players_view_model.dart'; // ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers mixin _$PlayersViewModel on _PlayersViewModel, Store { - Computed>? _$playersComputed; + Computed>? _$playersComputed; @override - ObservableList get players => (_$playersComputed ??= - Computed>(() => super.players, + List get players => + (_$playersComputed ??= Computed>(() => super.players, name: '_PlayersViewModel.players')) - .value; + .value; Computed? _$hasAnyPlayersComputed; @override diff --git a/board_games_companion/lib/pages/playthroughs/playthrough_statistics_view_model.dart b/board_games_companion/lib/pages/playthroughs/playthrough_statistics_view_model.dart index 91f571cf..19071147 100644 --- a/board_games_companion/lib/pages/playthroughs/playthrough_statistics_view_model.dart +++ b/board_games_companion/lib/pages/playthroughs/playthrough_statistics_view_model.dart @@ -1,7 +1,7 @@ // ignore_for_file: library_private_types_in_public_api import 'package:board_games_companion/common/enums/game_winning_condition.dart'; -import 'package:board_games_companion/stores/game_playthroughs_store.dart'; +import 'package:board_games_companion/stores/game_playthroughs_details_store.dart'; import 'package:board_games_companion/stores/scores_store.dart'; import 'package:collection/collection.dart'; import 'package:fimber/fimber.dart'; @@ -32,7 +32,7 @@ abstract class _PlaythroughStatisticsViewModel with Store { final PlayerService _playerService; final ScoresStore _scoresStore; - final GamePlaythroughsStore _gamePlaythroughsStore; + final GamePlaythroughsDetailsStore _gamePlaythroughsStore; static const int _maxNumberOfTopScoresToDisplay = 5; diff --git a/board_games_companion/lib/pages/playthroughs/playthroughs_game_settings_view_model.dart b/board_games_companion/lib/pages/playthroughs/playthroughs_game_settings_view_model.dart index a5392f8d..c13a5d1f 100644 --- a/board_games_companion/lib/pages/playthroughs/playthroughs_game_settings_view_model.dart +++ b/board_games_companion/lib/pages/playthroughs/playthroughs_game_settings_view_model.dart @@ -1,27 +1,27 @@ import 'package:board_games_companion/common/enums/game_winning_condition.dart'; import 'package:board_games_companion/models/hive/board_game_settings.dart'; -import 'package:board_games_companion/stores/game_playthroughs_store.dart'; +import 'package:board_games_companion/stores/game_playthroughs_details_store.dart'; import 'package:injectable/injectable.dart'; import '../../stores/board_games_store.dart'; @injectable class PlaythroughsGameSettingsViewModel { - PlaythroughsGameSettingsViewModel(this._boardGamesStore, this._playthroughsStore); + PlaythroughsGameSettingsViewModel(this._boardGamesStore, this._gamePlaythroughsStore); final BoardGamesStore _boardGamesStore; - final GamePlaythroughsStore _playthroughsStore; + final GamePlaythroughsDetailsStore _gamePlaythroughsStore; - GameWinningCondition get winningCondition => _playthroughsStore.gameWinningCondition; + GameWinningCondition get winningCondition => _gamePlaythroughsStore.gameWinningCondition; Future updateWinningCondition(GameWinningCondition winningCondition) async { - final boardGame = - _boardGamesStore.allBoardGames.firstWhere((bg) => bg.id == _playthroughsStore.boardGameId); + final boardGame = _boardGamesStore.allBoardGames + .firstWhere((bg) => bg.id == _gamePlaythroughsStore.boardGameId); final updatedBoardGame = boardGame.copyWith( settings: (boardGame.settings ?? const BoardGameSettings()) .copyWith(winningCondition: winningCondition)); await _boardGamesStore.addOrUpdateBoardGame(updatedBoardGame); - _playthroughsStore.setBoardGame(updatedBoardGame); + _gamePlaythroughsStore.setBoardGame(updatedBoardGame); } } diff --git a/board_games_companion/lib/pages/playthroughs/playthroughs_history_page.dart b/board_games_companion/lib/pages/playthroughs/playthroughs_history_page.dart index a5b9a018..0a731532 100644 --- a/board_games_companion/lib/pages/playthroughs/playthroughs_history_page.dart +++ b/board_games_companion/lib/pages/playthroughs/playthroughs_history_page.dart @@ -22,6 +22,7 @@ import '../../widgets/playthrough/calendar_card.dart'; import '../../widgets/playthrough/player_score_rank_avatar.dart'; import '../edit_playthrough/edit_playthrough_page.dart'; import 'playthroughs_history_view_model.dart'; +import 'playthroughs_page.dart'; class PlaythroughsHistoryPage extends StatefulWidget { const PlaythroughsHistoryPage({Key? key}) : super(key: key); @@ -179,8 +180,9 @@ class _PlaythroughPlayersStats extends StatelessWidget { context, EditPlaythroughPage.pageRoute, arguments: EditPlaythroughPageArguments( - playthroughDetails.id, - playthroughDetails.boardGameId, + boardGameId: playthroughDetails.boardGameId, + playthroughId: playthroughDetails.id, + goBackPageRoute: PlaythroughsPage.pageRoute, ), ), ), diff --git a/board_games_companion/lib/pages/playthroughs/playthroughs_history_view_model.dart b/board_games_companion/lib/pages/playthroughs/playthroughs_history_view_model.dart index 62048f45..c395a26f 100644 --- a/board_games_companion/lib/pages/playthroughs/playthroughs_history_view_model.dart +++ b/board_games_companion/lib/pages/playthroughs/playthroughs_history_view_model.dart @@ -1,6 +1,6 @@ // ignore_for_file: library_private_types_in_public_api -import 'package:board_games_companion/stores/game_playthroughs_store.dart'; +import 'package:board_games_companion/stores/game_playthroughs_details_store.dart'; import 'package:injectable/injectable.dart'; import 'package:mobx/mobx.dart'; @@ -13,26 +13,26 @@ class PlaythroughsHistoryViewModel = _PlaythroughsHistoryViewModel with _$PlaythroughsHistoryViewModel; abstract class _PlaythroughsHistoryViewModel with Store { - _PlaythroughsHistoryViewModel(this._playthroughsStore); + _PlaythroughsHistoryViewModel(this._gamePlaythroughsStore); - final GamePlaythroughsStore _playthroughsStore; + final GamePlaythroughsDetailsStore _gamePlaythroughsStore; @observable ObservableFuture? futureloadPlaythroughs; @computed ObservableList get playthroughs { - final sortedPlaythrough = List.of(_playthroughsStore.playthroughsDetails, growable: false); + final sortedPlaythrough = List.of(_gamePlaythroughsStore.playthroughsDetails, growable: false); return ObservableList.of(sortedPlaythrough..sort((a, b) => b.startDate.compareTo(a.startDate))); } @computed - bool get hasAnyPlaythroughs => _playthroughsStore.playthroughsDetails.isNotEmpty; + bool get hasAnyPlaythroughs => _gamePlaythroughsStore.playthroughsDetails.isNotEmpty; @action void loadPlaythroughs() => futureloadPlaythroughs = ObservableFuture(_loadPlaythroughs()); Future _loadPlaythroughs() async { - await _playthroughsStore.loadPlaythroughs(); + _gamePlaythroughsStore.loadPlaythroughsDetails(); } } diff --git a/board_games_companion/lib/pages/playthroughs/playthroughs_log_game_view_model.dart b/board_games_companion/lib/pages/playthroughs/playthroughs_log_game_view_model.dart index e5c73453..3f54285a 100644 --- a/board_games_companion/lib/pages/playthroughs/playthroughs_log_game_view_model.dart +++ b/board_games_companion/lib/pages/playthroughs/playthroughs_log_game_view_model.dart @@ -1,6 +1,6 @@ // ignore_for_file: library_private_types_in_public_api -import 'package:board_games_companion/stores/game_playthroughs_store.dart'; +import 'package:board_games_companion/stores/game_playthroughs_details_store.dart'; import 'package:board_games_companion/stores/players_store.dart'; import 'package:injectable/injectable.dart'; import 'package:mobx/mobx.dart'; @@ -29,7 +29,7 @@ abstract class _PlaythroughsLogGameViewModel with Store { ); final PlayersStore _playersStore; - final GamePlaythroughsStore _gamePlaythroughsStore; + final GamePlaythroughsDetailsStore _gamePlaythroughsStore; final AnalyticsService _analyticsService; @observable diff --git a/board_games_companion/lib/pages/playthroughs/playthroughs_view_model.dart b/board_games_companion/lib/pages/playthroughs/playthroughs_view_model.dart index 045f57ff..030aaf08 100644 --- a/board_games_companion/lib/pages/playthroughs/playthroughs_view_model.dart +++ b/board_games_companion/lib/pages/playthroughs/playthroughs_view_model.dart @@ -2,8 +2,6 @@ import 'package:basics/basics.dart'; import 'package:board_games_companion/models/import_result.dart'; -import 'package:board_games_companion/models/playthrough_details.dart'; -import 'package:board_games_companion/stores/game_playthroughs_store.dart'; import 'package:board_games_companion/stores/user_store.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:injectable/injectable.dart'; @@ -20,6 +18,7 @@ import '../../models/player_score.dart'; import '../../models/playthrough_player.dart'; import '../../services/analytics_service.dart'; import '../../services/board_games_service.dart'; +import '../../stores/game_playthroughs_details_store.dart'; import '../../stores/players_store.dart'; part 'playthroughs_view_model.g.dart'; @@ -29,7 +28,7 @@ class PlaythroughsViewModel = _PlaythroughsViewModel with _$PlaythroughsViewMode abstract class _PlaythroughsViewModel with Store { _PlaythroughsViewModel( - this._playthroughsStore, + this._gamePlaythroughsDetailsStore, this._playersStore, this._analyticsService, this._boardGamesService, @@ -47,11 +46,12 @@ abstract class _PlaythroughsViewModel with Store { }; final PlayersStore _playersStore; - final GamePlaythroughsStore _playthroughsStore; final AnalyticsService _analyticsService; final BoardGamesService _boardGamesService; final UserStore _userStore; + final GamePlaythroughsDetailsStore _gamePlaythroughsDetailsStore; + late final BoardGameDetails _boardGameDetails; late String _boardGameImageHeroId; List? _playthroughPlayers; @@ -62,10 +62,10 @@ abstract class _PlaythroughsViewModel with Store { String get boardGameImageHeroId => _boardGameImageHeroId; @computed - String get boardGameId => _playthroughsStore.boardGameId; + String get boardGameId => _boardGameDetails.id; @computed - String get boardGameName => _playthroughsStore.boardGameName; + String get boardGameName => _boardGameDetails.name; @computed bool get hasUser => _userStore.hasUser; @@ -77,7 +77,10 @@ abstract class _PlaythroughsViewModel with Store { String get gamePlaylistUrl => '$melodicePlaylistUrl/$boardGameId'; @action - void setBoardGame(BoardGameDetails boardGame) => _playthroughsStore.setBoardGame(boardGame); + void setBoardGame(BoardGameDetails boardGame) { + _boardGameDetails = boardGame; + _gamePlaythroughsDetailsStore.setBoardGame(boardGame); + } void setBoardGameImageHeroId(String boardGameImageHeroId) => _boardGameImageHeroId = boardGameImageHeroId; @@ -109,30 +112,27 @@ abstract class _PlaythroughsViewModel with Store { // TODO Consider using isolates to parse and iterate over the results for (final bggPlay in bggPlaysImportResult.data!) { - final bggPlayExists = _playthroughsStore.playthroughsDetails - .any((PlaythroughDetails playthrough) => playthrough.bggPlayId == bggPlay.id); + final bggPlayExists = _gamePlaythroughsDetailsStore.playthroughs + .any((playthrough) => playthrough.bggPlayId == bggPlay.id); if (bggPlayExists) { continue; } - // TODO MK Players should be loaded by the time this method is called. - // This is to fix a bug in production that casues duplicate players to be created because players are not populated. - await _playersStore.loadPlayers(); - final List playthroughPlayers = []; final Map playerScores = {}; for (final bggPlayer in bggPlay.players) { String playerId; if (bggPlayer.playerBggUserId == null) { - playerId = - _playersStore.players.firstWhereOrNull((p) => p.name == bggPlayer.playerName)?.id ?? - const Uuid().v4(); + playerId = _playersStore.activePlayers + .firstWhereOrNull((p) => p.name == bggPlayer.playerName) + ?.id ?? + const Uuid().v4(); } else { playerId = bggPlayer.playerBggUserId.toString(); } final bool newPlayer = - _playersStore.players.firstWhereOrNull((p) => p.id == playerId) != null; + _playersStore.activePlayers.firstWhereOrNull((p) => p.id == playerId) != null; final Player player = Player( id: playerId, name: bggPlayer.playerName, @@ -156,7 +156,7 @@ abstract class _PlaythroughsViewModel with Store { } } - final newPlaythrough = await _playthroughsStore.createPlaythrough( + final newPlaythrough = await _gamePlaythroughsDetailsStore.createPlaythrough( bggPlay.boardGameId, playthroughPlayers, playerScores, diff --git a/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_page.dart b/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_page.dart index fb7a3dd7..a1046de1 100644 --- a/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_page.dart +++ b/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_page.dart @@ -1,4 +1,5 @@ import 'package:board_games_companion/extensions/int_extensions.dart'; +import 'package:board_games_companion/pages/edit_playthrough/edit_playthrough_page.dart'; import 'package:board_games_companion/pages/playthroughs/playthroughs_page.dart'; import 'package:board_games_companion/pages/playthroughs_history/board_game_playthrough.dart'; import 'package:board_games_companion/pages/playthroughs_history/grouped_board_game_playthroughs.dart'; @@ -9,16 +10,19 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:mobx/mobx.dart'; import '../../common/app_colors.dart'; +import '../../common/app_styles.dart'; import '../../common/app_text.dart'; import '../../common/app_theme.dart'; import '../../common/dimensions.dart'; import '../../models/navigation/board_game_details_page_arguments.dart'; +import '../../models/navigation/edit_playthrough_page_arguments.dart'; import '../../models/navigation/playthroughs_page_arguments.dart'; import '../../widgets/board_games/board_game_tile.dart'; import '../../widgets/common/loading_indicator_widget.dart'; import '../../widgets/common/panel_container.dart'; import '../../widgets/common/slivers/bgc_sliver_header_delegate.dart'; import '../board_game_details/board_game_details_page.dart'; +import '../home/home_page.dart'; class PlaythroughsHistoryPage extends StatefulWidget { const PlaythroughsHistoryPage({ @@ -66,6 +70,11 @@ class _PlaythroughsHistoryPageState extends State { _PlaythroughGroupListSliver( groupedBoardGamePlaythroughs: groupedBoardGamePlaythroughs, ), + if (groupedBoardGamePlaythroughs == + widget.viewModel.finishedBoardGamePlaythroughs.last) + const SliverToBoxAdapter( + child: SizedBox(height: Dimensions.bottomTabTopHeight), + ), ] ], ); @@ -88,44 +97,58 @@ class _PlaythroughGroupListSliver extends StatelessWidget { delegate: SliverChildBuilderDelegate( (_, index) { final boardGamePlaythrough = groupedBoardGamePlaythroughs.boardGamePlaythroughs[index]; + final isFirst = index == 0; return Padding( - padding: const EdgeInsets.only( - top: Dimensions.standardSpacing, + padding: EdgeInsets.only( + top: isFirst ? Dimensions.standardSpacing : 0, bottom: Dimensions.standardSpacing, left: Dimensions.standardSpacing, right: Dimensions.standardSpacing, ), child: PanelContainer( - child: Padding( - padding: const EdgeInsets.all(Dimensions.standardSpacing), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - IntrinsicHeight( - child: Row( - children: [ - SizedBox( - height: Dimensions.collectionSearchResultBoardGameImageHeight, - width: Dimensions.collectionSearchResultBoardGameImageWidth, - child: BoardGameTile( - id: boardGamePlaythrough.id, - imageUrl: boardGamePlaythrough.boardGameDetails.thumbnailUrl ?? '', - ), - ), - const SizedBox(width: Dimensions.standardSpacing), - Expanded( - child: _PlaythroughDetails(boardGamePlaythrough: boardGamePlaythrough), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(AppStyles.panelContainerCornerRadius), + onTap: () => _navigateToEditPlaythrough( + context, + boardGamePlaythrough.boardGameDetails.id, + boardGamePlaythrough.playthrough.id, + ), + child: Padding( + padding: const EdgeInsets.all(Dimensions.standardSpacing), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IntrinsicHeight( + child: Row( + children: [ + SizedBox( + height: Dimensions.collectionSearchResultBoardGameImageHeight, + width: Dimensions.collectionSearchResultBoardGameImageWidth, + child: BoardGameTile( + id: boardGamePlaythrough.id, + imageUrl: + boardGamePlaythrough.boardGameDetails.thumbnailUrl ?? '', + ), + ), + const SizedBox(width: Dimensions.standardSpacing), + Expanded( + child: + _PlaythroughDetails(boardGamePlaythrough: boardGamePlaythrough), + ), + _PlaythroughActions( + onTapBoardGameDetails: () => + _navigateToBoardGameDetails(context, boardGamePlaythrough), + onTapPlaythroughs: () => + _navigateToPlaythrough(context, boardGamePlaythrough), + ), + ], ), - _PlaythroughActions( - onTapBoardGameDetails: () => - _navigateToBoardGameDetails(context, boardGamePlaythrough), - onTapPlaythroughs: () => - _navigateToPlaythrough(context, boardGamePlaythrough), - ), - ], - ), - ) - ], + ) + ], + ), + ), ), ), ), @@ -164,6 +187,22 @@ class _PlaythroughGroupListSliver extends StatelessWidget { ), ); } + + void _navigateToEditPlaythrough( + BuildContext context, + String boardGameId, + String playthroughId, + ) { + Navigator.pushNamed( + context, + EditPlaythroughPage.pageRoute, + arguments: EditPlaythroughPageArguments( + boardGameId: boardGameId, + playthroughId: playthroughId, + goBackPageRoute: HomePage.pageRoute, + ), + ); + } } class _PlaythroughDetails extends StatelessWidget { @@ -332,6 +371,7 @@ class _AppBar extends StatelessWidget { return const SliverAppBar( pinned: true, floating: true, + forceElevated: true, elevation: Dimensions.defaultElevation, titleSpacing: Dimensions.standardSpacing, foregroundColor: AppColors.accentColor, diff --git a/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_view_model.dart b/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_view_model.dart index c0816ff2..b2453e26 100644 --- a/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_view_model.dart +++ b/board_games_companion/lib/pages/playthroughs_history/playthroughs_history_view_model.dart @@ -99,8 +99,8 @@ abstract class _PlaythroughsHistoryViewModel with Store { futureLoadGamesPlaythroughs = ObservableFuture(_loadGamesPlaythroughs()); Future _loadGamesPlaythroughs() async { - await _playthroughsStore.loadPlaythroughs(); - await _playersStore.loadPlayers(); await _scoreStore.loadScores(); + await _playersStore.loadPlayers(); + await _playthroughsStore.loadPlaythroughs(); } } diff --git a/board_games_companion/lib/pages/search_board_games/search_board_games_page.dart b/board_games_companion/lib/pages/search_board_games/search_board_games_page.dart index 45efd7c1..3b2ad077 100644 --- a/board_games_companion/lib/pages/search_board_games/search_board_games_page.dart +++ b/board_games_companion/lib/pages/search_board_games/search_board_games_page.dart @@ -130,6 +130,8 @@ class _SearchBarState extends State<_SearchBar> { Widget build(BuildContext context) { return SliverAppBar( titleSpacing: 0, + forceElevated: true, + elevation: Dimensions.defaultElevation, foregroundColor: AppColors.accentColor, title: Padding( padding: const EdgeInsets.symmetric( diff --git a/board_games_companion/lib/stores/game_playthroughs_store.dart b/board_games_companion/lib/stores/game_playthroughs_details_store.dart similarity index 79% rename from board_games_companion/lib/stores/game_playthroughs_store.dart rename to board_games_companion/lib/stores/game_playthroughs_details_store.dart index 1f2db7c4..24914cf7 100644 --- a/board_games_companion/lib/stores/game_playthroughs_store.dart +++ b/board_games_companion/lib/stores/game_playthroughs_details_store.dart @@ -15,19 +15,22 @@ import '../models/hive/playthrough.dart'; import '../models/hive/score.dart'; import '../models/playthrough_details.dart'; import '../models/playthrough_player.dart'; -import '../services/player_service.dart'; +import 'players_store.dart'; -part 'game_playthroughs_store.g.dart'; +part 'game_playthroughs_details_store.g.dart'; +/// Store that loads [PlaythroughDetails] for a particular [BoardGameDetails]. +/// [Playthrough]s are retrieved using [PlaythroughsStore] and filtered by board game id. @singleton -class GamePlaythroughsStore = _GamePlaythroughsStore with _$GamePlaythroughsStore; +class GamePlaythroughsDetailsStore = _GamePlaythroughsDetailsStore + with _$GamePlaythroughsDetailsStore; -abstract class _GamePlaythroughsStore with Store { - _GamePlaythroughsStore(this._playthroughsStore, this._scoresStore, this._playerService); +abstract class _GamePlaythroughsDetailsStore with Store { + _GamePlaythroughsDetailsStore(this._playthroughsStore, this._scoresStore, this._playerStore); final PlaythroughsStore _playthroughsStore; final ScoresStore _scoresStore; - final PlayerService _playerService; + final PlayersStore _playerStore; @observable BoardGameDetails? _boardGame; @@ -60,13 +63,13 @@ abstract class _GamePlaythroughsStore with Store { GameWinningCondition get gameWinningCondition => _boardGame!.settings?.winningCondition ?? GameWinningCondition.HighestScore; + /// Ensure that [setBoardGame] is called before loading playthoughs details and that [Playthrough]s in the [PlaythroughsStore] are loaded as well. @action - Future loadPlaythroughs() async { + void loadPlaythroughsDetails() { try { final loadedPlaythroughDetails = []; - await _playthroughsStore.loadPlaythroughs(); for (final playthrough in playthroughs) { - final playthroughDetails = await createPlaythroughDetails(playthrough); + final playthroughDetails = createPlaythroughDetails(playthrough); loadedPlaythroughDetails.add(playthroughDetails); } @@ -100,7 +103,7 @@ abstract class _GamePlaythroughsStore with Store { return null; } - final newPlaythroughDetails = await createPlaythroughDetails(newPlaythrough); + final newPlaythroughDetails = createPlaythroughDetails(newPlaythrough); playthroughsDetails.add(newPlaythroughDetails); return newPlaythroughDetails; } @@ -118,7 +121,7 @@ abstract class _GamePlaythroughsStore with Store { await _scoresStore.addOrUpdateScore(playerScore.score); } - await loadPlaythroughs(); + loadPlaythroughsDetails(); } } catch (e, stack) { FirebaseCrashlytics.instance.recordError(e, stack); @@ -140,15 +143,13 @@ abstract class _GamePlaythroughsStore with Store { return false; } - Future createPlaythroughDetails(Playthrough playthrough) async { + PlaythroughDetails createPlaythroughDetails(Playthrough playthrough) { final scores = _scoresStore.scores.where((score) => score.playthroughId == playthrough.id).toList() ..sortByScore(gameWinningCondition) ..toList(); - final players = await _playerService.retrievePlayers( - playerIds: playthrough.playerIds, - includeDeleted: true, - ); + final players = + _playerStore.players.where((player) => playthrough.playerIds.contains(player.id)).toList(); final playerScores = scores.mapIndexed((int index, Score score) { final player = players.firstWhereOrNull((Player p) => score.playerId == p.id); diff --git a/board_games_companion/lib/stores/game_playthroughs_store.g.dart b/board_games_companion/lib/stores/game_playthroughs_details_store.g.dart similarity index 68% rename from board_games_companion/lib/stores/game_playthroughs_store.g.dart rename to board_games_companion/lib/stores/game_playthroughs_details_store.g.dart index 32535176..ed490812 100644 --- a/board_games_companion/lib/stores/game_playthroughs_store.g.dart +++ b/board_games_companion/lib/stores/game_playthroughs_details_store.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'game_playthroughs_store.dart'; +part of 'game_playthroughs_details_store.dart'; // ************************************************************************** // StoreGenerator @@ -8,13 +8,13 @@ part of 'game_playthroughs_store.dart'; // ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers -mixin _$GamePlaythroughsStore on _GamePlaythroughsStore, Store { +mixin _$GamePlaythroughsDetailsStore on _GamePlaythroughsDetailsStore, Store { Computed>? _$playthroughsComputed; @override List get playthroughs => (_$playthroughsComputed ??= Computed>(() => super.playthroughs, - name: '_GamePlaythroughsStore.playthroughs')) + name: '_GamePlaythroughsDetailsStore.playthroughs')) .value; Computed>? _$finishedPlaythroughsComputed; @@ -22,28 +22,28 @@ mixin _$GamePlaythroughsStore on _GamePlaythroughsStore, Store { List get finishedPlaythroughs => (_$finishedPlaythroughsComputed ??= Computed>( () => super.finishedPlaythroughs, - name: '_GamePlaythroughsStore.finishedPlaythroughs')) + name: '_GamePlaythroughsDetailsStore.finishedPlaythroughs')) .value; Computed? _$boardGameNameComputed; @override String get boardGameName => (_$boardGameNameComputed ??= Computed(() => super.boardGameName, - name: '_GamePlaythroughsStore.boardGameName')) + name: '_GamePlaythroughsDetailsStore.boardGameName')) .value; Computed? _$boardGameIdComputed; @override String get boardGameId => (_$boardGameIdComputed ??= Computed(() => super.boardGameId, - name: '_GamePlaythroughsStore.boardGameId')) + name: '_GamePlaythroughsDetailsStore.boardGameId')) .value; Computed? _$boardGameImageUrlComputed; @override String? get boardGameImageUrl => (_$boardGameImageUrlComputed ??= Computed(() => super.boardGameImageUrl, - name: '_GamePlaythroughsStore.boardGameImageUrl')) + name: '_GamePlaythroughsDetailsStore.boardGameImageUrl')) .value; Computed? _$gameWinningConditionComputed; @@ -51,11 +51,11 @@ mixin _$GamePlaythroughsStore on _GamePlaythroughsStore, Store { GameWinningCondition get gameWinningCondition => (_$gameWinningConditionComputed ??= Computed( () => super.gameWinningCondition, - name: '_GamePlaythroughsStore.gameWinningCondition')) + name: '_GamePlaythroughsDetailsStore.gameWinningCondition')) .value; late final _$_boardGameAtom = - Atom(name: '_GamePlaythroughsStore._boardGame', context: context); + Atom(name: '_GamePlaythroughsDetailsStore._boardGame', context: context); @override BoardGameDetails? get _boardGame { @@ -71,7 +71,8 @@ mixin _$GamePlaythroughsStore on _GamePlaythroughsStore, Store { } late final _$playthroughsDetailsAtom = Atom( - name: '_GamePlaythroughsStore.playthroughsDetails', context: context); + name: '_GamePlaythroughsDetailsStore.playthroughsDetails', + context: context); @override ObservableList get playthroughsDetails { @@ -86,25 +87,29 @@ mixin _$GamePlaythroughsStore on _GamePlaythroughsStore, Store { }); } - late final _$loadPlaythroughsAsyncAction = - AsyncAction('_GamePlaythroughsStore.loadPlaythroughs', context: context); + late final _$_GamePlaythroughsDetailsStoreActionController = + ActionController(name: '_GamePlaythroughsDetailsStore', context: context); @override - Future loadPlaythroughs() { - return _$loadPlaythroughsAsyncAction.run(() => super.loadPlaythroughs()); + void loadPlaythroughsDetails() { + final _$actionInfo = + _$_GamePlaythroughsDetailsStoreActionController.startAction( + name: '_GamePlaythroughsDetailsStore.loadPlaythroughsDetails'); + try { + return super.loadPlaythroughsDetails(); + } finally { + _$_GamePlaythroughsDetailsStoreActionController.endAction(_$actionInfo); + } } - late final _$_GamePlaythroughsStoreActionController = - ActionController(name: '_GamePlaythroughsStore', context: context); - @override void setBoardGame(BoardGameDetails boardGame) { - final _$actionInfo = _$_GamePlaythroughsStoreActionController.startAction( - name: '_GamePlaythroughsStore.setBoardGame'); + final _$actionInfo = _$_GamePlaythroughsDetailsStoreActionController + .startAction(name: '_GamePlaythroughsDetailsStore.setBoardGame'); try { return super.setBoardGame(boardGame); } finally { - _$_GamePlaythroughsStoreActionController.endAction(_$actionInfo); + _$_GamePlaythroughsDetailsStoreActionController.endAction(_$actionInfo); } } diff --git a/board_games_companion/lib/stores/players_store.dart b/board_games_companion/lib/stores/players_store.dart index 553c28ec..b72733aa 100644 --- a/board_games_companion/lib/stores/players_store.dart +++ b/board_games_companion/lib/stores/players_store.dart @@ -31,6 +31,10 @@ abstract class _PlayersStore with Store { @observable ObservableList players = ObservableList.of([]); + @computed + List get activePlayers => + players.where((player) => !(player.isDeleted ?? false)).toList(); + @computed Map get playersById => {for (final player in players) player.id: player}; @@ -41,7 +45,7 @@ abstract class _PlayersStore with Store { } try { - players = ObservableList.of(await _playerService.retrievePlayers()); + players = ObservableList.of(await _playerService.retrievePlayers(includeDeleted: true)); } catch (e, stack) { FirebaseCrashlytics.instance.recordError(e, stack); } diff --git a/board_games_companion/lib/stores/players_store.g.dart b/board_games_companion/lib/stores/players_store.g.dart index 286965cf..bbe98639 100644 --- a/board_games_companion/lib/stores/players_store.g.dart +++ b/board_games_companion/lib/stores/players_store.g.dart @@ -9,6 +9,13 @@ part of 'players_store.dart'; // ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers mixin _$PlayersStore on _PlayersStore, Store { + Computed>? _$activePlayersComputed; + + @override + List get activePlayers => (_$activePlayersComputed ??= + Computed>(() => super.activePlayers, + name: '_PlayersStore.activePlayers')) + .value; Computed>? _$playersByIdComputed; @override @@ -62,6 +69,7 @@ mixin _$PlayersStore on _PlayersStore, Store { String toString() { return ''' players: ${players}, +activePlayers: ${activePlayers}, playersById: ${playersById} '''; } diff --git a/board_games_companion/lib/stores/playthroughs_store.dart b/board_games_companion/lib/stores/playthroughs_store.dart index 5918492a..ff8612e8 100644 --- a/board_games_companion/lib/stores/playthroughs_store.dart +++ b/board_games_companion/lib/stores/playthroughs_store.dart @@ -35,7 +35,7 @@ abstract class _PlaythroughsStore with Store { Future loadPlaythroughs() async { try { - playthroughs = ObservableList.of(await _playthroughService.retrievePlaythroughs()); + playthroughs = (await _playthroughService.retrievePlaythroughs()).asObservable(); } catch (e, stack) { FirebaseCrashlytics.instance.recordError(e, stack); } diff --git a/board_games_companion/lib/widgets/common/panel_container.dart b/board_games_companion/lib/widgets/common/panel_container.dart index 3fce93b3..aa0ad972 100644 --- a/board_games_companion/lib/widgets/common/panel_container.dart +++ b/board_games_companion/lib/widgets/common/panel_container.dart @@ -8,7 +8,7 @@ import '../elevated_container.dart'; class PanelContainer extends StatelessWidget { const PanelContainer({ required this.child, - this.borderRadius = AppStyles.defaultCornerRadius * 3, + this.borderRadius = AppStyles.panelContainerCornerRadius, Key? key, }) : super(key: key); diff --git a/board_games_companion/pubspec.lock b/board_games_companion/pubspec.lock index 52b344e8..269b79e5 100644 --- a/board_games_companion/pubspec.lock +++ b/board_games_companion/pubspec.lock @@ -425,7 +425,7 @@ packages: name: flutter_speed_dial url: "https://pub.dartlang.org" source: hosted - version: "6.0.0" + version: "6.2.0" flutter_svg: dependency: "direct main" description: diff --git a/board_games_companion/pubspec.yaml b/board_games_companion/pubspec.yaml index 46ed4d0b..6f7cebc5 100644 --- a/board_games_companion/pubspec.yaml +++ b/board_games_companion/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: flutter_mobx: ^2.0.6+1 flutter_polygon: ^0.2.0 flutter_slidable: ^2.0.0 - flutter_speed_dial: 6.0.0 + flutter_speed_dial: 6.2.0 flutter_svg: ^0.22.0 font_awesome_flutter: ^10.2.1 freezed: ^2.1.0+1 diff --git a/pipelines/mobile_app/deploy-pipeline.yml b/pipelines/mobile_app/deploy-pipeline.yml index dabd0f9a..c13f2cdf 100644 --- a/pipelines/mobile_app/deploy-pipeline.yml +++ b/pipelines/mobile_app/deploy-pipeline.yml @@ -43,11 +43,11 @@ stages: condition: or(eq('${{ parameters.publishAndroid }}', 'true'), in(variables['Build.Reason'], 'BatchedCI', 'IndividualCI')) timeoutInMinutes: 20 pool: - vmImage: "macOS-12" + vmImage: "ubuntu-22.04" steps: - task: JavaToolInstaller@0 - displayName: Java Installer + displayName: "Install Java 11" inputs: versionSpec: "11" jdkArchitectureOption: x64 @@ -86,7 +86,7 @@ stages: TargetFolder: "$(Build.SourcesDirectory)/$(appDirectoryName)/android/app" - task: FlutterBuild@0 - displayName: "Build Android" + displayName: Build Android inputs: target: "aab" projectDirectory: "$(Build.SourcesDirectory)/$(appDirectoryName)" @@ -94,14 +94,14 @@ stages: buildName: "$(appVersion)" - task: DeleteFiles@1 - displayName: "Deleting key.properties file" + displayName: Deleting key.properties file condition: always() inputs: SourceFolder: "$(Build.SourcesDirectory)/android/" Contents: "$(keyPropertiesFileName)" - task: DeleteFiles@1 - displayName: "Deleting keystore file" + displayName: Deleting keystore file condition: always() inputs: SourceFolder: "$(Build.SourcesDirectory)/$(appDirectoryName)/android/app" @@ -109,7 +109,7 @@ stages: - task: PublishPipelineArtifact@1 condition: always() - displayName: "Publishing aab artifacts" + displayName: Publishing aab artifacts inputs: targetPath: "$(Build.SourcesDirectory)/$(appDirectoryName)/build/app/outputs/bundle/release/app-release.aab" artifact: "drop-droid" @@ -122,10 +122,11 @@ stages: condition: eq(stageDependencies.CdBuildAndroid.BuildAndroid.result, 'Succeeded') timeoutInMinutes: 5 pool: - vmImage: "macOS-12" + vmImage: "ubuntu-22.04" steps: - task: DownloadPipelineArtifact@2 + displayName: "Download pipeline artifacts" inputs: buildType: "current" artifactName: "drop-droid"