Skip to content

Commit

Permalink
More work on game bookmark
Browse files Browse the repository at this point in the history
  • Loading branch information
julien4215 committed Dec 26, 2024
1 parent 52c9fce commit 50ccaf2
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 46 deletions.
2 changes: 2 additions & 0 deletions lib/src/model/game/archived_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class LightArchivedGame with _$LightArchivedGame {
@MoveConverter() Move? lastMove,
Side? winner,
ClockData? clock,
bool? bookmarked,
}) = _ArchivedGameData;

factory LightArchivedGame.fromServerJson(Map<String, dynamic> json) {
Expand Down Expand Up @@ -240,6 +241,7 @@ LightArchivedGame _lightArchivedGameFromPick(RequiredPick pick) {
lastMove: pick('lastMove').asUciMoveOrNull(),
clock: pick('clock').letOrNull(_clockDataFromPick),
opening: pick('opening').letOrNull(_openingFromPick),
bookmarked: pick('bookmarked').asBoolOrFalse(),
);
}

Expand Down
10 changes: 8 additions & 2 deletions lib/src/model/game/game_history.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ Future<IList<LightArchivedGameWithPov>> myRecentGames(Ref ref) async {
/// A provider that fetches the recent games from the server for a given user.
@riverpod
Future<IList<LightArchivedGameWithPov>> userRecentGames(Ref ref, {required UserId userId}) {
final isLoggedIn = ref.watch(authSessionProvider) != null;

return ref.withClientCacheFor(
(client) => GameRepository(client).getUserGames(userId),
(client) => GameRepository(client).getUserGames(userId, withBookmarked: isLoggedIn),
// cache is important because the associated widget is in a [ListView] and
// the provider may be instanciated multiple times in a short period of time
// (e.g. when scrolling)
Expand Down Expand Up @@ -119,7 +121,11 @@ class UserGameHistory extends _$UserGameHistory {
final id = userId ?? session?.user.id;
final recentGames =
id != null && online
? ref.withClient((client) => GameRepository(client).getUserGames(id, filter: filter))
? ref.withClient(
(client) => GameRepository(
client,
).getUserGames(id, filter: filter, withBookmarked: session != null),
)
: storage
.page(userId: id, filter: filter)
.then(
Expand Down
12 changes: 8 additions & 4 deletions lib/src/model/game/game_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class GameRepository {
int max = 20,
DateTime? until,
GameFilterState filter = const GameFilterState(),
bool withBookmarked = false,
}) {
assert(!filter.perfs.contains(Perf.fromPosition));
assert(!filter.perfs.contains(Perf.puzzle));
Expand All @@ -53,6 +54,7 @@ class GameRepository {
if (filter.perfs.isNotEmpty)
'perfType': filter.perfs.map((perf) => perf.name).join(','),
if (filter.side != null) 'color': filter.side!.name,
if (withBookmarked) 'withBookmarked': 'true',
},
),
headers: {'Accept': 'application/x-ndjson'},
Expand Down Expand Up @@ -92,11 +94,13 @@ class GameRepository {
);
}

Future<void> bookmark(GameId id, {int v = -1}) async {
// if v is -1, toggle the bookmark value on server
// otherwise explicitly set the new value
Future<void> bookmark(GameId id, {int? v}) async {
// if v is not set, toggle the bookmark value on server
// otherwise explicitly set the new value with 0 to not
// bookmark the game and 1 to bookmark it
assert(v == null || v == 0 || v == 1);
final uri =
v == -1
v == null
? Uri(path: '/bookmark/$id')
: Uri(path: '/bookmark/$id', queryParameters: {'v': v.toString()});
final response = await client.post(uri);
Expand Down
4 changes: 3 additions & 1 deletion lib/src/view/analysis/analysis_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
import 'package:lichess_mobile/src/model/analysis/analysis_preferences.dart';
import 'package:lichess_mobile/src/model/analysis/opening_service.dart';
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
import 'package:lichess_mobile/src/model/common/chess.dart';
import 'package:lichess_mobile/src/model/game/game_share_service.dart';
import 'package:lichess_mobile/src/network/http.dart';
Expand Down Expand Up @@ -74,12 +75,13 @@ class _AnalysisScreenState extends ConsumerState<AnalysisScreen>
final ctrlProvider = analysisControllerProvider(widget.options);
final asyncState = ref.watch(ctrlProvider);
final prefs = ref.watch(analysisPreferencesProvider);
final isLoggedIn = ref.watch(authSessionProvider) != null;

final appBarActions = [
if (prefs.enableComputerAnalysis)
EngineDepth(defaultEval: asyncState.valueOrNull?.currentNode.eval),
AppBarAnalysisTabIndicator(tabs: tabs, controller: _tabController),
if (widget.options.gameId != null) BookmarkButton(id: widget.options.gameId!),
if (widget.options.gameId != null && isLoggedIn) BookmarkButton(id: widget.options.gameId!),
AppBarIconButton(
onPressed: () {
pushPlatformRoute(
Expand Down
41 changes: 36 additions & 5 deletions lib/src/view/game/archived_game_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/model/game/archived_game.dart';
import 'package:lichess_mobile/src/model/game/game.dart';
Expand Down Expand Up @@ -44,8 +45,15 @@ class ArchivedGameScreen extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final isLoggedIn = ref.watch(authSessionProvider) != null;

if (gameData != null) {
return _Body(gameData: gameData, orientation: orientation, initialCursor: initialCursor);
return _Body(
gameData: gameData,
orientation: orientation,
isLoggedIn: isLoggedIn,
initialCursor: initialCursor,
);
} else {
return _LoadGame(gameId: gameId!, orientation: orientation, initialCursor: initialCursor);
}
Expand All @@ -62,25 +70,40 @@ class _LoadGame extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final game = ref.watch(archivedGameProvider(id: gameId));
final isLoggedIn = ref.watch(authSessionProvider) != null;

return game.when(
data: (game) {
return _Body(gameData: game.data, orientation: orientation, initialCursor: initialCursor);
return _Body(
gameData: game.data,
orientation: orientation,
isLoggedIn: isLoggedIn,
initialCursor: initialCursor,
);
},
loading: () => _Body(gameData: null, orientation: orientation, initialCursor: initialCursor),
loading:
() => _Body(
gameData: null,
orientation: orientation,
isLoggedIn: isLoggedIn,
initialCursor: initialCursor,
),
error: (error, stackTrace) {
debugPrint('SEVERE: [ArchivedGameScreen] could not load game; $error\n$stackTrace');
switch (error) {
case ServerException _ when error.statusCode == 404:
return _Body(
gameData: null,
orientation: orientation,
isLoggedIn: isLoggedIn,
initialCursor: initialCursor,
error: 'Game not found.',
);
default:
return _Body(
gameData: null,
orientation: orientation,
isLoggedIn: isLoggedIn,
initialCursor: initialCursor,
error: error,
);
Expand All @@ -91,10 +114,17 @@ class _LoadGame extends ConsumerWidget {
}

class _Body extends StatelessWidget {
const _Body({required this.gameData, required this.orientation, this.initialCursor, this.error});
const _Body({
required this.gameData,
required this.orientation,
required this.isLoggedIn,
this.initialCursor,
this.error,
});

final LightArchivedGame? gameData;
final Object? error;
final bool isLoggedIn;
final Side orientation;
final int? initialCursor;

Expand All @@ -105,7 +135,8 @@ class _Body extends StatelessWidget {
title: gameData != null ? _GameTitle(gameData: gameData!) : const SizedBox.shrink(),
actions: [
if (gameData == null && error == null) const PlatformAppBarLoadingIndicator(),
if (gameData != null) BookmarkButton(id: gameData!.id),
if (gameData != null && isLoggedIn)
BookmarkButton(id: gameData!.id, bookmarked: gameData!.bookmarked!),
const ToggleSoundButton(),
],
),
Expand Down
16 changes: 13 additions & 3 deletions lib/src/view/game/game_common_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
import 'package:lichess_mobile/src/model/challenge/challenge.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/model/common/time_increment.dart';
Expand All @@ -26,23 +27,28 @@ import 'game_settings.dart';
import 'ping_rating.dart';

class BookmarkButton extends ConsumerWidget {
const BookmarkButton({required this.id});
const BookmarkButton({required this.id, this.bookmarked = false});

final GameId id;
final bool bookmarked;

@override
Widget build(BuildContext context, WidgetRef ref) {
return AppBarIconButton(
onPressed: () {
ref.withClient((client) => GameRepository(client).bookmark(id, v: 1));
},
semanticsLabel: context.l10n.bookmarkThisGame,
icon: const Icon(Icons.star_border_rounded),
icon: Icon(bookmarked ? Icons.star : Icons.star_outline_rounded),
);
}
}

class _SettingButton extends ConsumerWidget {
const _SettingButton({required this.id});

final GameFullId id;

@override
Widget build(BuildContext context, WidgetRef ref) {
return AppBarIconButton(
Expand Down Expand Up @@ -81,6 +87,7 @@ class GameAppBar extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final shouldPreventGoingBackAsync =
id != null ? ref.watch(shouldPreventGoingBackProvider(id!)) : const AsyncValue.data(true);
final isLoggedIn = ref.watch(authSessionProvider) != null;

return PlatformAppBar(
leading: shouldPreventGoingBackAsync.maybeWhen<Widget?>(
Expand All @@ -97,7 +104,10 @@ class GameAppBar extends ConsumerWidget {
: const SizedBox.shrink(),
actions: [
const ToggleSoundButton(),
if (id != null) ...[BookmarkButton(id: id!.gameId), _SettingButton(id: id!)],
if (id != null) ...[
if (isLoggedIn) BookmarkButton(id: id!.gameId),
_SettingButton(id: id!),
],
],
);
}
Expand Down
20 changes: 12 additions & 8 deletions lib/src/view/game/game_list_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
import 'package:lichess_mobile/src/model/game/archived_game.dart';
import 'package:lichess_mobile/src/model/game/game_repository.dart';
import 'package:lichess_mobile/src/model/game/game_share_service.dart';
Expand Down Expand Up @@ -111,6 +112,8 @@ class _ContextMenu extends ConsumerWidget {

final customColors = Theme.of(context).extension<CustomColors>();

final isLoggedIn = ref.watch(authSessionProvider) != null;

return BottomSheetScrollableContainer(
children: [
Padding(
Expand Down Expand Up @@ -234,14 +237,15 @@ class _ContextMenu extends ConsumerWidget {
closeOnPressed: false,
child: Text(context.l10n.mobileShareGameURL),
),
BottomSheetContextMenuAction(
onPressed: () {
ref.withClient((client) => GameRepository(client).bookmark(game.id, v: 1));
},
icon: Icons.star_border_rounded,
closeOnPressed: false,
child: const Text('Bookmark Game'),
),
if (isLoggedIn)
BottomSheetContextMenuAction(
onPressed: () {
ref.withClient((client) => GameRepository(client).bookmark(game.id, v: 1));
},
icon: (game.bookmarked!) ? Icons.star : Icons.star_outline_rounded,
closeOnPressed: false,
child: Text(context.l10n.bookmarkThisGame),
),
// Builder is used to retrieve the context immediately surrounding the
// BottomSheetContextMenuAction
// This is necessary to get the correct context for the iPad share dialog
Expand Down
53 changes: 30 additions & 23 deletions lib/src/view/user/game_history_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ class _BodyState extends ConsumerState<_Body> {
final gameListState = ref.watch(
userGameHistoryProvider(widget.user?.id, isOnline: widget.isOnline, filter: gameFilterState),
);
final isLoggedIn = ref.watch(authSessionProvider) != null;

return gameListState.when(
data: (state) {
Expand Down Expand Up @@ -173,30 +174,36 @@ class _BodyState extends ConsumerState<_Body> {
);
}

return Slidable(
endActionPane: ActionPane(
motion: const ScrollMotion(),
children: [
SlidableAction(
onPressed: (BuildContext context) {
ref.withClient(
(client) => GameRepository(client).bookmark(list[index].game.id, v: 1),
);
},
icon: Icons.star_outline_rounded,
label: 'Bookmark',
),
],
),
child: ExtendedGameListTile(
item: list[index],
// see: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/cupertino/list_tile.dart#L30 for horizontal padding value
padding:
Theme.of(context).platform == TargetPlatform.iOS
? const EdgeInsets.symmetric(horizontal: 14.0, vertical: 12.0)
: null,
),
final gameTile = ExtendedGameListTile(
item: list[index],
// see: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/cupertino/list_tile.dart#L30 for horizontal padding value
padding:
Theme.of(context).platform == TargetPlatform.iOS
? const EdgeInsets.symmetric(horizontal: 14.0, vertical: 12.0)
: null,
);

final game = list[index].game;

return isLoggedIn
? Slidable(
endActionPane: ActionPane(
motion: const ScrollMotion(),
children: [
SlidableAction(
onPressed: (BuildContext context) {
ref.withClient(
(client) => GameRepository(client).bookmark(game.id, v: 1),
);
},
icon: (game.bookmarked!) ? Icons.star : Icons.star_outline_rounded,
label: context.l10n.bookmarkThisGame,
),
],
),
child: gameTile,
)
: gameTile;
},
);
},
Expand Down

0 comments on commit 50ccaf2

Please sign in to comment.