Skip to content

Commit fdac26b

Browse files
committed
More work on game bookmark
1 parent 52c9fce commit fdac26b

8 files changed

+107
-44
lines changed

lib/src/model/game/archived_game.dart

+2
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ class LightArchivedGame with _$LightArchivedGame {
116116
@MoveConverter() Move? lastMove,
117117
Side? winner,
118118
ClockData? clock,
119+
bool? bookmarked,
119120
}) = _ArchivedGameData;
120121

121122
factory LightArchivedGame.fromServerJson(Map<String, dynamic> json) {
@@ -240,6 +241,7 @@ LightArchivedGame _lightArchivedGameFromPick(RequiredPick pick) {
240241
lastMove: pick('lastMove').asUciMoveOrNull(),
241242
clock: pick('clock').letOrNull(_clockDataFromPick),
242243
opening: pick('opening').letOrNull(_openingFromPick),
244+
bookmarked: pick('bookmarked').asBoolOrNull(),
243245
);
244246
}
245247

lib/src/model/game/game_history.dart

+8-2
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,10 @@ Future<IList<LightArchivedGameWithPov>> myRecentGames(Ref ref) async {
6060
/// A provider that fetches the recent games from the server for a given user.
6161
@riverpod
6262
Future<IList<LightArchivedGameWithPov>> userRecentGames(Ref ref, {required UserId userId}) {
63+
final isLoggedIn = ref.watch(authSessionProvider) != null;
64+
6365
return ref.withClientCacheFor(
64-
(client) => GameRepository(client).getUserGames(userId),
66+
(client) => GameRepository(client).getUserGames(userId, withBookmarked: isLoggedIn),
6567
// cache is important because the associated widget is in a [ListView] and
6668
// the provider may be instanciated multiple times in a short period of time
6769
// (e.g. when scrolling)
@@ -119,7 +121,11 @@ class UserGameHistory extends _$UserGameHistory {
119121
final id = userId ?? session?.user.id;
120122
final recentGames =
121123
id != null && online
122-
? ref.withClient((client) => GameRepository(client).getUserGames(id, filter: filter))
124+
? ref.withClient(
125+
(client) => GameRepository(
126+
client,
127+
).getUserGames(id, filter: filter, withBookmarked: session != null),
128+
)
123129
: storage
124130
.page(userId: id, filter: filter)
125131
.then(

lib/src/model/game/game_repository.dart

+8-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class GameRepository {
3434
int max = 20,
3535
DateTime? until,
3636
GameFilterState filter = const GameFilterState(),
37+
bool withBookmarked = false,
3738
}) {
3839
assert(!filter.perfs.contains(Perf.fromPosition));
3940
assert(!filter.perfs.contains(Perf.puzzle));
@@ -53,6 +54,7 @@ class GameRepository {
5354
if (filter.perfs.isNotEmpty)
5455
'perfType': filter.perfs.map((perf) => perf.name).join(','),
5556
if (filter.side != null) 'color': filter.side!.name,
57+
if (withBookmarked) 'withBookmarked': 'true',
5658
},
5759
),
5860
headers: {'Accept': 'application/x-ndjson'},
@@ -92,11 +94,13 @@ class GameRepository {
9294
);
9395
}
9496

95-
Future<void> bookmark(GameId id, {int v = -1}) async {
96-
// if v is -1, toggle the bookmark value on server
97-
// otherwise explicitly set the new value
97+
Future<void> bookmark(GameId id, {int? v}) async {
98+
// if v is not set, toggle the bookmark value on server
99+
// otherwise explicitly set the new value with 0 to not
100+
// bookmark the game and 1 to bookmark it
101+
assert(v == null || v == 0 || v == 1);
98102
final uri =
99-
v == -1
103+
v == null
100104
? Uri(path: '/bookmark/$id')
101105
: Uri(path: '/bookmark/$id', queryParameters: {'v': v.toString()});
102106
final response = await client.post(uri);

lib/src/view/analysis/analysis_screen.dart

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
55
import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
66
import 'package:lichess_mobile/src/model/analysis/analysis_preferences.dart';
77
import 'package:lichess_mobile/src/model/analysis/opening_service.dart';
8+
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
89
import 'package:lichess_mobile/src/model/common/chess.dart';
910
import 'package:lichess_mobile/src/model/game/game_share_service.dart';
1011
import 'package:lichess_mobile/src/network/http.dart';
@@ -74,12 +75,13 @@ class _AnalysisScreenState extends ConsumerState<AnalysisScreen>
7475
final ctrlProvider = analysisControllerProvider(widget.options);
7576
final asyncState = ref.watch(ctrlProvider);
7677
final prefs = ref.watch(analysisPreferencesProvider);
78+
final isLoggedIn = ref.watch(authSessionProvider) != null;
7779

7880
final appBarActions = [
7981
if (prefs.enableComputerAnalysis)
8082
EngineDepth(defaultEval: asyncState.valueOrNull?.currentNode.eval),
8183
AppBarAnalysisTabIndicator(tabs: tabs, controller: _tabController),
82-
if (widget.options.gameId != null) BookmarkButton(id: widget.options.gameId!),
84+
if (widget.options.gameId != null && isLoggedIn) BookmarkButton(id: widget.options.gameId!),
8385
AppBarIconButton(
8486
onPressed: () {
8587
pushPlatformRoute(

lib/src/view/game/archived_game_screen.dart

+35-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
44
import 'package:flutter_riverpod/flutter_riverpod.dart';
55
import 'package:intl/intl.dart';
66
import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
7+
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
78
import 'package:lichess_mobile/src/model/common/id.dart';
89
import 'package:lichess_mobile/src/model/game/archived_game.dart';
910
import 'package:lichess_mobile/src/model/game/game.dart';
@@ -44,8 +45,15 @@ class ArchivedGameScreen extends ConsumerWidget {
4445

4546
@override
4647
Widget build(BuildContext context, WidgetRef ref) {
48+
final isLoggedIn = ref.watch(authSessionProvider) != null;
49+
4750
if (gameData != null) {
48-
return _Body(gameData: gameData, orientation: orientation, initialCursor: initialCursor);
51+
return _Body(
52+
gameData: gameData,
53+
orientation: orientation,
54+
isLoggedIn: isLoggedIn,
55+
initialCursor: initialCursor,
56+
);
4957
} else {
5058
return _LoadGame(gameId: gameId!, orientation: orientation, initialCursor: initialCursor);
5159
}
@@ -62,25 +70,40 @@ class _LoadGame extends ConsumerWidget {
6270
@override
6371
Widget build(BuildContext context, WidgetRef ref) {
6472
final game = ref.watch(archivedGameProvider(id: gameId));
73+
final isLoggedIn = ref.watch(authSessionProvider) != null;
74+
6575
return game.when(
6676
data: (game) {
67-
return _Body(gameData: game.data, orientation: orientation, initialCursor: initialCursor);
77+
return _Body(
78+
gameData: game.data,
79+
orientation: orientation,
80+
isLoggedIn: isLoggedIn,
81+
initialCursor: initialCursor,
82+
);
6883
},
69-
loading: () => _Body(gameData: null, orientation: orientation, initialCursor: initialCursor),
84+
loading:
85+
() => _Body(
86+
gameData: null,
87+
orientation: orientation,
88+
isLoggedIn: isLoggedIn,
89+
initialCursor: initialCursor,
90+
),
7091
error: (error, stackTrace) {
7192
debugPrint('SEVERE: [ArchivedGameScreen] could not load game; $error\n$stackTrace');
7293
switch (error) {
7394
case ServerException _ when error.statusCode == 404:
7495
return _Body(
7596
gameData: null,
7697
orientation: orientation,
98+
isLoggedIn: isLoggedIn,
7799
initialCursor: initialCursor,
78100
error: 'Game not found.',
79101
);
80102
default:
81103
return _Body(
82104
gameData: null,
83105
orientation: orientation,
106+
isLoggedIn: isLoggedIn,
84107
initialCursor: initialCursor,
85108
error: error,
86109
);
@@ -91,10 +114,17 @@ class _LoadGame extends ConsumerWidget {
91114
}
92115

93116
class _Body extends StatelessWidget {
94-
const _Body({required this.gameData, required this.orientation, this.initialCursor, this.error});
117+
const _Body({
118+
required this.gameData,
119+
required this.orientation,
120+
required this.isLoggedIn,
121+
this.initialCursor,
122+
this.error,
123+
});
95124

96125
final LightArchivedGame? gameData;
97126
final Object? error;
127+
final bool isLoggedIn;
98128
final Side orientation;
99129
final int? initialCursor;
100130

@@ -105,7 +135,7 @@ class _Body extends StatelessWidget {
105135
title: gameData != null ? _GameTitle(gameData: gameData!) : const SizedBox.shrink(),
106136
actions: [
107137
if (gameData == null && error == null) const PlatformAppBarLoadingIndicator(),
108-
if (gameData != null) BookmarkButton(id: gameData!.id),
138+
if (gameData != null && isLoggedIn) BookmarkButton(id: gameData!.id),
109139
const ToggleSoundButton(),
110140
],
111141
),

lib/src/view/game/game_common_widgets.dart

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart';
44
import 'package:flutter/material.dart';
55
import 'package:flutter_riverpod/flutter_riverpod.dart';
66
import 'package:intl/intl.dart';
7+
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
78
import 'package:lichess_mobile/src/model/challenge/challenge.dart';
89
import 'package:lichess_mobile/src/model/common/id.dart';
910
import 'package:lichess_mobile/src/model/common/time_increment.dart';
@@ -81,6 +82,7 @@ class GameAppBar extends ConsumerWidget {
8182
Widget build(BuildContext context, WidgetRef ref) {
8283
final shouldPreventGoingBackAsync =
8384
id != null ? ref.watch(shouldPreventGoingBackProvider(id!)) : const AsyncValue.data(true);
85+
final isLoggedIn = ref.watch(authSessionProvider) != null;
8486

8587
return PlatformAppBar(
8688
leading: shouldPreventGoingBackAsync.maybeWhen<Widget?>(
@@ -97,7 +99,10 @@ class GameAppBar extends ConsumerWidget {
9799
: const SizedBox.shrink(),
98100
actions: [
99101
const ToggleSoundButton(),
100-
if (id != null) ...[BookmarkButton(id: id!.gameId), _SettingButton(id: id!)],
102+
if (id != null) ...[
103+
if (isLoggedIn) BookmarkButton(id: id!.gameId),
104+
_SettingButton(id: id!),
105+
],
101106
],
102107
);
103108
}

lib/src/view/game/game_list_tile.dart

+12-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
44
import 'package:flutter_riverpod/flutter_riverpod.dart';
55
import 'package:intl/intl.dart';
66
import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
7+
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
78
import 'package:lichess_mobile/src/model/game/archived_game.dart';
89
import 'package:lichess_mobile/src/model/game/game_repository.dart';
910
import 'package:lichess_mobile/src/model/game/game_share_service.dart';
@@ -111,6 +112,8 @@ class _ContextMenu extends ConsumerWidget {
111112

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

115+
final isLoggedIn = ref.watch(authSessionProvider) != null;
116+
114117
return BottomSheetScrollableContainer(
115118
children: [
116119
Padding(
@@ -234,14 +237,15 @@ class _ContextMenu extends ConsumerWidget {
234237
closeOnPressed: false,
235238
child: Text(context.l10n.mobileShareGameURL),
236239
),
237-
BottomSheetContextMenuAction(
238-
onPressed: () {
239-
ref.withClient((client) => GameRepository(client).bookmark(game.id, v: 1));
240-
},
241-
icon: Icons.star_border_rounded,
242-
closeOnPressed: false,
243-
child: const Text('Bookmark Game'),
244-
),
240+
if (isLoggedIn)
241+
BottomSheetContextMenuAction(
242+
onPressed: () {
243+
ref.withClient((client) => GameRepository(client).bookmark(game.id, v: 1));
244+
},
245+
icon: Icons.star_border_rounded,
246+
closeOnPressed: false,
247+
child: Text(context.l10n.bookmarkThisGame),
248+
),
245249
// Builder is used to retrieve the context immediately surrounding the
246250
// BottomSheetContextMenuAction
247251
// This is necessary to get the correct context for the iPad share dialog

lib/src/view/user/game_history_screen.dart

+33-23
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ class _BodyState extends ConsumerState<_Body> {
141141
final gameListState = ref.watch(
142142
userGameHistoryProvider(widget.user?.id, isOnline: widget.isOnline, filter: gameFilterState),
143143
);
144+
final isLoggedIn = ref.watch(authSessionProvider) != null;
144145

145146
return gameListState.when(
146147
data: (state) {
@@ -173,30 +174,39 @@ class _BodyState extends ConsumerState<_Body> {
173174
);
174175
}
175176

176-
return Slidable(
177-
endActionPane: ActionPane(
178-
motion: const ScrollMotion(),
179-
children: [
180-
SlidableAction(
181-
onPressed: (BuildContext context) {
182-
ref.withClient(
183-
(client) => GameRepository(client).bookmark(list[index].game.id, v: 1),
184-
);
185-
},
186-
icon: Icons.star_outline_rounded,
187-
label: 'Bookmark',
188-
),
189-
],
190-
),
191-
child: ExtendedGameListTile(
192-
item: list[index],
193-
// see: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/cupertino/list_tile.dart#L30 for horizontal padding value
194-
padding:
195-
Theme.of(context).platform == TargetPlatform.iOS
196-
? const EdgeInsets.symmetric(horizontal: 14.0, vertical: 12.0)
197-
: null,
198-
),
177+
final gameTile = ExtendedGameListTile(
178+
item: list[index],
179+
// see: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/cupertino/list_tile.dart#L30 for horizontal padding value
180+
padding:
181+
Theme.of(context).platform == TargetPlatform.iOS
182+
? const EdgeInsets.symmetric(horizontal: 14.0, vertical: 12.0)
183+
: null,
199184
);
185+
186+
final game = list[index].game;
187+
188+
return isLoggedIn
189+
? Slidable(
190+
endActionPane: ActionPane(
191+
motion: const ScrollMotion(),
192+
children: [
193+
SlidableAction(
194+
onPressed: (BuildContext context) {
195+
ref.withClient(
196+
(client) => GameRepository(client).bookmark(game.id, v: 1),
197+
);
198+
},
199+
icon:
200+
(game.bookmarked ?? false)
201+
? Icons.stars_rounded
202+
: Icons.star_outline_rounded,
203+
label: context.l10n.bookmarkThisGame,
204+
),
205+
],
206+
),
207+
child: gameTile,
208+
)
209+
: gameTile;
200210
},
201211
);
202212
},

0 commit comments

Comments
 (0)