Skip to content

Commit

Permalink
Merge branch 'filter-games' of https://github.com/Mauritz8/mobile int…
Browse files Browse the repository at this point in the history
…o Mauritz8-filter-games
  • Loading branch information
veloce committed Aug 5, 2024
2 parents 48328a8 + 680b223 commit 993ceae
Show file tree
Hide file tree
Showing 7 changed files with 459 additions and 105 deletions.
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

0 comments on commit 993ceae

Please sign in to comment.