Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User full game list #572

Merged
merged 39 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5db8481
WIP on user full game list
ZTL-UwU Feb 24, 2024
7c94aae
Add bookmark button
ZTL-UwU Feb 24, 2024
8505629
Fix analysis
ZTL-UwU Feb 24, 2024
78ff233
Add pagination
ZTL-UwU Feb 25, 2024
dc9852a
Fix loading indicator behavior
ZTL-UwU Feb 25, 2024
326aabf
Merge branch 'main' into full-game-list
ZTL-UwU Apr 12, 2024
3ed4ffc
fix conflicts
ZTL-UwU Apr 12, 2024
3dd513c
remove gradle.properties proxy
ZTL-UwU Apr 12, 2024
024af03
fix analyze
ZTL-UwU Apr 12, 2024
1ff450d
use flutter_slidable
ZTL-UwU Apr 15, 2024
1887dbc
remove unnecessary import
ZTL-UwU Apr 15, 2024
bb11551
Merge branch 'main' into full-game-list
ZTL-UwU May 16, 2024
3b02bf2
Refactor game histroy
ZTL-UwU May 16, 2024
1e03c9b
Clear engine suggestion arrows after reaching checkmate or stalemate
timmcca-be May 27, 2024
03d12f5
Merge branch 'main' into full-game-list
ZTL-UwU May 28, 2024
4348ef8
Add side for game history & add comment for ExtendedGameListTile & fi…
ZTL-UwU May 28, 2024
78d936a
Merge pull request #724 from timmcca-be/m0-fix
veloce May 28, 2024
ad69b56
Replace getRecentGame with getUserGames
ZTL-UwU May 28, 2024
b66e02f
Add more missing context.mounted checks
veloce May 28, 2024
f339fc2
Fix format
veloce May 28, 2024
1f626f9
Add a doc comment
veloce May 28, 2024
3a45d3b
Improve LichessClient tests and types
veloce May 28, 2024
b9dd630
Use gameStorage when offline
ZTL-UwU May 28, 2024
854ce06
Remove duplicate method
veloce May 28, 2024
6a72d1c
Remove unused imports
ZTL-UwU May 28, 2024
0ab7560
Remove unused imports
ZTL-UwU May 28, 2024
5acb1a6
Remove comment
veloce May 28, 2024
10ed05e
Merge branch 'full-game-list' of https://github.com/ZTL-UwU/mobile in…
veloce May 28, 2024
bcbc2d5
Refactor recent game providers
veloce May 28, 2024
562b9ef
Show total number of games
veloce May 29, 2024
fb276e9
Retrict context menu analysis access to supported variants
veloce May 29, 2024
15ac0d5
Remove bookmark action for now, refactor
veloce May 29, 2024
5cb6b54
Use correct game count
veloce May 29, 2024
bd75349
Make max parameter mandatory
veloce May 29, 2024
cbe7b4d
Remove unused import
veloce May 29, 2024
b37150a
Only show more button if number of games > recent games
veloce May 29, 2024
7b6fb8f
Add missing trailing comma
veloce May 29, 2024
c3a082e
Tweak comment
veloce May 30, 2024
d29afcc
Comment out game count fields that we don't use for now
veloce May 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ android.useAndroidX=true
android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
android.nonFinalResIds=false
ZTL-UwU marked this conversation as resolved.
Show resolved Hide resolved
13 changes: 13 additions & 0 deletions lib/src/model/account/account_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ Future<IList<LightArchivedGame>> accountRecentGames(
);
}

@riverpod
Future<FullGamePaginator?> accountFullGames(
AccountFullGamesRef ref,
int page,
) async {
final session = ref.watch(authSessionProvider);
if (session == null) return null;
return ref.withClientCacheFor(
(client) => GameRepository(client).getFullGames(session.user.id, page),
const Duration(hours: 1),
);
}

@riverpod
Future<IList<OngoingGame>> ongoingGames(OngoingGamesRef ref) async {
final session = ref.watch(authSessionProvider);
Expand Down
41 changes: 38 additions & 3 deletions lib/src/model/game/archived_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,25 @@ class LightArchivedGame with _$LightArchivedGame {
}
}

@freezed
class FullGamePaginator with _$FullGamePaginator {
const FullGamePaginator._();

const factory FullGamePaginator({
int? currentPage,
int? maxPerPage,
int? previousPage,
int? nextPage,
int? nbResults,
int? nbPages,
required IList<LightArchivedGame> games,
}) = _FullGamePaginator;

factory FullGamePaginator.fromServerJson(Map<String, dynamic> json) {
return _fullGamePaginatorFromPick(pick(json, 'paginator').required());
}
}

IList<ExternalEval>? gameEvalsFromPick(RequiredPick pick) {
return pick('analysis')
.asListOrNull<ExternalEval>(
Expand Down Expand Up @@ -198,20 +217,36 @@ LightArchivedGame _lightArchivedGameFromPick(RequiredPick pick) {
rated: pick('rated').asBoolOrThrow(),
speed: pick('speed').asSpeedOrThrow(),
perf: pick('perf').asPerfOrThrow(),
createdAt: pick('createdAt').asDateTimeFromMillisecondsOrThrow(),
lastMoveAt: pick('lastMoveAt').asDateTimeFromMillisecondsOrThrow(),
createdAt: pick('timestamp').asDateTimeFromMillisecondsOrNull() ??
pick('createdAt').asDateTimeFromMillisecondsOrThrow(),
lastMoveAt: pick('timestamp').asDateTimeFromMillisecondsOrNull() ??
pick('lastMoveAt').asDateTimeFromMillisecondsOrThrow(),
status: pick('status').asGameStatusOrThrow(),
white: pick('players', 'white').letOrThrow(_playerFromUserGamePick),
black: pick('players', 'black').letOrThrow(_playerFromUserGamePick),
winner: pick('winner').asSideOrNull(),
variant: pick('variant').asVariantOrThrow(),
lastFen: pick('lastFen').asStringOrNull(),
lastFen: pick('lastFen').asStringOrNull() ?? pick('fen').asStringOrNull(),
lastMove: pick('lastMove').asUciMoveOrNull(),
clock: pick('clock').letOrNull(_clockDataFromPick),
opening: pick('opening').letOrNull(_openingFromPick),
);
}

FullGamePaginator _fullGamePaginatorFromPick(RequiredPick pick) {
return FullGamePaginator(
currentPage: pick('currentPage').asIntOrNull(),
maxPerPage: pick('maxPerPage').asIntOrNull(),
previousPage: pick('previousPage').asIntOrNull(),
nextPage: pick('nextPage').asIntOrNull(),
nbResults: pick('nbResults').asIntOrNull(),
nbPages: pick('nbPages').asIntOrNull(),
games: IList(
pick('currentPageResults').asListOrThrow(_lightArchivedGameFromPick),
),
);
}

LightOpening _openingFromPick(RequiredPick pick) {
return LightOpening(
eco: pick('eco').asStringOrThrow(),
Expand Down
11 changes: 11 additions & 0 deletions lib/src/model/game/game_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ class GameRepository {
);
}

Future<FullGamePaginator> getFullGames(
UserId userId,
int page,
) {
return client.readJson(
Uri.parse('$kLichessHost/@/$userId/all?page=$page'),
ZTL-UwU marked this conversation as resolved.
Show resolved Hide resolved
headers: {'Accept': 'application/json'},
mapper: FullGamePaginator.fromServerJson,
);
}

/// Returns the games of the current user, given a list of ids.
Future<IList<PlayableGame>> getMyGamesByIds(ISet<GameId> ids) {
if (ids.isEmpty) {
Expand Down
105 changes: 105 additions & 0 deletions lib/src/view/game/game_list_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,27 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lichess_mobile/src/constants.dart';
import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
import 'package:lichess_mobile/src/model/common/http.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_repository.dart';
import 'package:lichess_mobile/src/model/game/game_share_service.dart';
import 'package:lichess_mobile/src/model/game/game_status.dart';
import 'package:lichess_mobile/src/styles/lichess_colors.dart';
import 'package:lichess_mobile/src/styles/styles.dart';
import 'package:lichess_mobile/src/utils/chessground_compat.dart';
import 'package:lichess_mobile/src/utils/l10n_context.dart';
import 'package:lichess_mobile/src/utils/navigation.dart';
import 'package:lichess_mobile/src/utils/share.dart';
import 'package:lichess_mobile/src/view/analysis/analysis_screen.dart';
import 'package:lichess_mobile/src/view/game/archived_game_screen.dart';
import 'package:lichess_mobile/src/view/game/standalone_game_screen.dart';
import 'package:lichess_mobile/src/view/game/status_l10n.dart';
import 'package:lichess_mobile/src/widgets/adaptive_bottom_sheet.dart';
import 'package:lichess_mobile/src/widgets/board_thumbnail.dart';
import 'package:lichess_mobile/src/widgets/feedback.dart';
import 'package:lichess_mobile/src/widgets/list.dart';
import 'package:lichess_mobile/src/widgets/user_full_name.dart';
import 'package:timeago/timeago.dart' as timeago;

/// A list tile that shows game info.
class GameListTile extends StatelessWidget {
Expand Down Expand Up @@ -444,3 +451,101 @@ class _ContextMenu extends ConsumerWidget {
);
}
}

class ExtendedGameListTile extends StatelessWidget {
ZTL-UwU marked this conversation as resolved.
Show resolved Hide resolved
const ExtendedGameListTile({required this.game, this.userId});

final LightArchivedGame game;
final UserId? userId;

@override
Widget build(BuildContext context) {
final mySide = game.white.user?.id == userId ? Side.white : Side.black;
final me = game.white.user?.id == userId ? game.white : game.black;
final opponent = game.white.user?.id == userId ? game.black : game.white;

Widget getResultIcon(LightArchivedGame game, Side mySide) {
if (game.status == GameStatus.aborted ||
game.status == GameStatus.noStart) {
return const Icon(
CupertinoIcons.xmark_square_fill,
color: LichessColors.grey,
);
} else {
return game.winner == null
? Icon(
CupertinoIcons.equal_square_fill,
color: context.lichessColors.brag,
)
: game.winner == mySide
? Icon(
CupertinoIcons.plus_square_fill,
color: context.lichessColors.good,
)
: Icon(
CupertinoIcons.minus_square_fill,
color: context.lichessColors.error,
);
}
}

return GameListTile(
game: game,
mySide: userId == game.white.user?.id ? Side.white : Side.black,
onTap: game.variant.isSupported
? () {
pushPlatformRoute(
context,
rootNavigator: true,
builder: (context) => game.fullId != null
? StandaloneGameScreen(
params: InitialStandaloneGameParams(
id: game.fullId!,
),
)
: ArchivedGameScreen(
gameData: game,
orientation: userId == game.white.user?.id
? Side.white
: Side.black,
),
);
}
: null,
icon: game.perf.icon,
playerTitle: UserFullNameWidget.player(
user: opponent.user,
aiLevel: opponent.aiLevel,
rating: opponent.rating,
),
subtitle: Text(
timeago.format(game.lastMoveAt),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (me.analysis != null) ...[
Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
CupertinoIcons.chart_bar_alt_fill,
color: textShade(context, 0.5),
),
Text(
me.analysis!.accuracy.toString(),
style: TextStyle(
fontSize: 10,
color: textShade(context, Styles.subtitleOpacity),
),
),
],
),
const SizedBox(width: 5),
],
getResultIcon(game, mySide),
],
),
);
}
}
Loading