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

Filter games #877

Merged
merged 40 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
2b46014
feat: create provider for game filters
Mauritz8 Jul 20, 2024
6f6e18a
feat: create filter in ui
Mauritz8 Jul 20, 2024
af2bcee
feat: apply filter to search
Mauritz8 Jul 21, 2024
b7d536e
refactor: pass game filters object instead of individual params
Mauritz8 Jul 21, 2024
2d9fd9c
feat: filter with multiple perfs
Mauritz8 Jul 21, 2024
6452a4f
feat: indicate how many filters in use
Mauritz8 Jul 21, 2024
fa1bfc0
refactor: separate widget for multiple choice filter
Mauritz8 Jul 21, 2024
9b6d104
fix: do not show amount of games found
Mauritz8 Jul 21, 2024
35f4c98
feat: show message if no games found
Mauritz8 Jul 21, 2024
304b0c3
fix: only allow valid Perf enum values
Mauritz8 Jul 21, 2024
d97afa9
feat: highlight button if filter is used
Mauritz8 Jul 21, 2024
4dcc8eb
fix: make text readable on all themes
Mauritz8 Jul 21, 2024
af1c3b0
fix: sync paginator with filters
Mauritz8 Jul 21, 2024
9026b10
feat: use chips from material3
Mauritz8 Jul 22, 2024
c830b57
refactor: use ISet instead of Set
Mauritz8 Jul 22, 2024
1febfbf
feat: add btn colors depending on state
Mauritz8 Jul 22, 2024
43ade6e
refactor: rename variable
Mauritz8 Jul 25, 2024
19be3a6
feat: update menu button label immediately when changed
Mauritz8 Jul 25, 2024
ae8de79
fix: minor style fixes
Mauritz8 Jul 25, 2024
8d5a7a7
feat: use gridview instead of menu for layout
Mauritz8 Jul 25, 2024
10b9f56
feat: calculate height of dialog
Mauritz8 Jul 25, 2024
faf0361
feat: change layout on tablet
Mauritz8 Jul 25, 2024
e2f97f3
feat: change layout
Mauritz8 Jul 26, 2024
b98f4a6
fix: shorter filter name
Mauritz8 Jul 26, 2024
0af623b
feat: move filter to app bar
Mauritz8 Jul 26, 2024
7e99865
feat: show line separator between groups
Mauritz8 Jul 26, 2024
b296a52
feat: btn opens all filters in a bottom sheet
Mauritz8 Jul 27, 2024
f13f007
fix: remove incorrect expanded widget
Mauritz8 Jul 27, 2024
69c4c4d
feat: add filter for color
Mauritz8 Jul 27, 2024
e428e01
feat: make bottom sheet scrollable
Mauritz8 Jul 27, 2024
e5e966a
feat: add a chip indicating number of used filters
Mauritz8 Jul 28, 2024
3801986
feat: icons before filter name
Mauritz8 Jul 28, 2024
2fa7ee3
feat: include username in appbar title
Mauritz8 Jul 28, 2024
b590d9c
feat: sort variants and remove unplayed ones
Mauritz8 Jul 28, 2024
477807d
fix: ui adjustments + extract variable + format
Mauritz8 Jul 28, 2024
14c1f57
refactor: address a few pr comments
Mauritz8 Aug 5, 2024
d5e8581
feat: if no filter is used, set title to number of games
Mauritz8 Aug 5, 2024
d418bd8
refactor: make UserGameHistory not depend on recent games providers
Mauritz8 Aug 5, 2024
09e1251
feat: support filtering games offline
Mauritz8 Aug 5, 2024
680b223
Merge branch 'main' of github.com:lichess-org/mobile into filter-games
Mauritz8 Aug 5, 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
38 changes: 38 additions & 0 deletions lib/src/model/game/game_filter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:dartchess/dartchess.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:lichess_mobile/src/model/common/perf.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'game_filter.freezed.dart';
part 'game_filter.g.dart';

@riverpod
class GameFilter extends _$GameFilter {
@override
GameFilterState build({GameFilterState? filter}) {
return filter ?? const GameFilterState();
}

void setFilter(GameFilterState filter) => state = state.copyWith(
perfs: filter.perfs,
side: filter.side,
);
}

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

const factory GameFilterState({
@Default(ISet<Perf>.empty()) ISet<Perf> perfs,
Side? side,
}) = _GameFilterState;

int get count {
final fields = [perfs, side];
return fields
.where((field) => field is Iterable ? field.isNotEmpty : field != null)
.length;
}
}
39 changes: 24 additions & 15 deletions lib/src/model/game/game_history.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import 'package:lichess_mobile/src/model/account/account_repository.dart';
import 'package:lichess_mobile/src/model/auth/auth_session.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/common/perf.dart';
import 'package:lichess_mobile/src/model/game/archived_game.dart';
import 'package:lichess_mobile/src/model/game/game_filter.dart';
import 'package:lichess_mobile/src/model/game/game_repository.dart';
import 'package:lichess_mobile/src/model/game/game_storage.dart';
import 'package:lichess_mobile/src/model/user/user.dart';
Expand Down Expand Up @@ -64,10 +64,9 @@ Future<IList<LightArchivedGameWithPov>> myRecentGames(
Future<IList<LightArchivedGameWithPov>> userRecentGames(
UserRecentGamesRef ref, {
required UserId userId,
Perf? perf,
}) {
return ref.withClientCacheFor(
(client) => GameRepository(client).getUserGames(userId, perfType: perf),
(client) => GameRepository(client).getUserGames(userId),
// 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 @@ -116,23 +115,33 @@ class UserGameHistory extends _$UserGameHistory {
/// server. If this is false, the provider will fetch the games from the
/// local storage.
required bool isOnline,
Perf? perf,
GameFilterState filter = const GameFilterState(),
}) async {
ref.cacheFor(const Duration(minutes: 5));
ref.onDispose(() {
_list.clear();
});

final session = ref.watch(authSessionProvider);
final online = await ref
.watch(connectivityChangesProvider.selectAsync((c) => c.isOnline));
final storage = ref.watch(gameStorageProvider);

final recentGames = userId != null
? ref.read(
userRecentGamesProvider(
userId: userId,
perf: perf,
).future,
final id = userId ?? session?.user.id;
final recentGames = id != null && online
? ref.withClient(
(client) => GameRepository(client).getUserGames(id, filter: filter),
)
: ref.read(myRecentGamesProvider.future);
: storage.page(userId: id, filter: filter).then(
(value) => value
// we can assume that `youAre` is not null either for logged
// in users or for anonymous users
.map(
(e) =>
(game: e.game.data, pov: e.game.youAre ?? Side.white),
)
.toIList(),
);

_list.addAll(await recentGames);

Expand All @@ -142,7 +151,7 @@ class UserGameHistory extends _$UserGameHistory {
hasMore: true,
hasError: false,
online: isOnline,
perfType: perf,
filter: filter,
session: session,
);
}
Expand All @@ -160,7 +169,7 @@ class UserGameHistory extends _$UserGameHistory {
userId!,
max: _nbPerPage,
until: _list.last.game.createdAt,
perfType: currentVal.perfType,
filter: currentVal.filter,
),
)
: currentVal.online && currentVal.session != null
Expand All @@ -169,7 +178,7 @@ class UserGameHistory extends _$UserGameHistory {
currentVal.session!.user.id,
max: _nbPerPage,
until: _list.last.game.createdAt,
perfType: currentVal.perfType,
filter: currentVal.filter,
),
)
: ref
Expand Down Expand Up @@ -219,7 +228,7 @@ class UserGameHistoryState with _$UserGameHistoryState {
const factory UserGameHistoryState({
required IList<LightArchivedGameWithPov> gameList,
required bool isLoading,
Perf? perfType,
required GameFilterState filter,
required bool hasMore,
required bool hasError,
required bool online,
Expand Down
15 changes: 9 additions & 6 deletions lib/src/model/game/game_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:lichess_mobile/src/model/common/http.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/model/common/perf.dart';
import 'package:lichess_mobile/src/model/game/archived_game.dart';
import 'package:lichess_mobile/src/model/game/game_filter.dart';
import 'package:lichess_mobile/src/model/game/playable_game.dart';

class GameRepository {
Expand Down Expand Up @@ -40,12 +41,12 @@ class GameRepository {
UserId userId, {
int max = 20,
DateTime? until,
Perf? perfType,
GameFilterState filter = const GameFilterState(),
}) {
assert(
![Perf.fromPosition, Perf.puzzle, Perf.storm, Perf.streak]
.contains(perfType),
);
assert(!filter.perfs.contains(Perf.fromPosition));
assert(!filter.perfs.contains(Perf.puzzle));
assert(!filter.perfs.contains(Perf.storm));
assert(!filter.perfs.contains(Perf.streak));
return client
.readNdJsonList(
Uri(
Expand All @@ -54,11 +55,13 @@ class GameRepository {
'max': max.toString(),
if (until != null)
'until': until.millisecondsSinceEpoch.toString(),
if (perfType != null) 'perfType': perfType.name,
'moves': 'false',
'lastFen': 'true',
'accuracy': 'true',
'opening': 'true',
if (filter.perfs.isNotEmpty)
'perfType': filter.perfs.map((perf) => perf.name).join(','),
if (filter.side != null) 'color': filter.side!.name,
},
),
headers: {'Accept': 'application/x-ndjson'},
Expand Down
37 changes: 23 additions & 14 deletions lib/src/model/game/game_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:lichess_mobile/src/db/database.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_filter.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sqflite/sqflite.dart';

Expand Down Expand Up @@ -44,6 +45,7 @@ class GameStorage {
UserId? userId,
DateTime? until,
int max = 20,
GameFilterState filter = const GameFilterState(),
}) async {
final list = await _db.query(
kGameStorageTable,
Expand All @@ -59,20 +61,27 @@ class GameStorage {
limit: max,
);

return list.map((e) {
final raw = e['data']! as String;
final json = jsonDecode(raw);
if (json is! Map<String, dynamic>) {
throw const FormatException(
'[GameStorage] cannot fetch game: expected an object',
);
}
return (
userId: UserId(e['userId']! as String),
lastModified: DateTime.parse(e['lastModified']! as String),
game: ArchivedGame.fromJson(json),
);
}).toIList();
return list
.map((e) {
final raw = e['data']! as String;
final json = jsonDecode(raw);
if (json is! Map<String, dynamic>) {
throw const FormatException(
'[GameStorage] cannot fetch game: expected an object',
);
}
return (
userId: UserId(e['userId']! as String),
lastModified: DateTime.parse(e['lastModified']! as String),
game: ArchivedGame.fromJson(json),
);
})
.where(
(e) =>
filter.perfs.isEmpty || filter.perfs.contains(e.game.meta.perf),
)
.where((e) => filter.side == null || filter.side == e.game.youAre)
.toIList();
}

Future<ArchivedGame?> fetch({
Expand Down
Loading