Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 13 additions & 2 deletions lib/src/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,23 @@ class _AppState extends ConsumerState<Application> {

AppLifecycleListener? _appLifecycleListener;

DateTime? _pausedAt;

@override
void initState() {
_appLifecycleListener = AppLifecycleListener(
onResume: () async {
onPause: () {
_pausedAt = DateTime.now();
},
onRestart: () async {
// Invalidate ongoing games if the app was paused for more than an hour.
// In theory we shouldn't need to do this, because correspondence games are updated by
// fcm messages, but in practice it's not always reliable.
// See also: [CorrespondenceService].
final online = await isOnline(ref.read(defaultClientProvider));
if (online) {
if (online &&
_pausedAt != null &&
DateTime.now().difference(_pausedAt!) >= const Duration(hours: 1)) {
ref.invalidate(ongoingGamesProvider);
}
},
Expand Down
46 changes: 39 additions & 7 deletions lib/src/model/account/account_repository.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:dartchess/dartchess.dart';
import 'package:deep_pick/deep_pick.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
Expand All @@ -9,6 +10,7 @@ import 'package:lichess_mobile/src/model/common/chess.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/common/speed.dart';
import 'package:lichess_mobile/src/model/game/playable_game.dart';
import 'package:lichess_mobile/src/model/user/user.dart';
import 'package:lichess_mobile/src/model/user/user_repository.dart';
import 'package:lichess_mobile/src/network/http.dart';
Expand Down Expand Up @@ -43,15 +45,45 @@ Future<IList<UserActivity>> accountActivity(Ref ref) async {
);
}

/// A provider that fetches the ongoing games for the current user.
@riverpod
Future<IList<OngoingGame>> ongoingGames(Ref ref) async {
final session = ref.watch(authSessionProvider);
if (session == null) return IList();
class OngoingGames extends _$OngoingGames {
@override
Future<IList<OngoingGame>> build() {
final session = ref.watch(authSessionProvider);
if (session == null) return Future.value(IList());

return ref.withClientCacheFor(
(client) => AccountRepository(client).getOngoingGames(nb: 50),
// cache is useful to reduce the number of requests to the server because this is used by
// both the correspondence service to sync games and the home screen to display ongoing games
const Duration(hours: 1),
);
}

return ref.withClientCacheFor(
(client) => AccountRepository(client).getOngoingGames(nb: 20),
const Duration(hours: 1),
);
/// Update the game with the given `id` with the new `game` data.
void updateGame(GameFullId id, PlayableGame game) {
if (!state.hasValue) return;
final index = state.requireValue.indexWhere((e) => e.fullId == id);
if (index == -1) return;
final me = game.youAre ?? Side.white;
final newGame = OngoingGame(
id: game.id,
fullId: id,
orientation: me,
fen: game.lastPosition.fen,
perf: game.meta.perf,
speed: game.meta.speed,
variant: game.meta.variant,
opponent: game.opponent?.user,
isMyTurn: game.isMyTurn,
opponentRating: game.opponent?.rating,
opponentAiLevel: game.opponent?.aiLevel,
lastMove: game.lastMove,
secondsLeft: game.clock?.forSide(me).inSeconds,
);
state = AsyncData(state.requireValue.replace(index, newGame));
}
}

class AccountRepository {
Expand Down
19 changes: 6 additions & 13 deletions lib/src/model/correspondence/correspondence_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ CorrespondenceService correspondenceService(Ref ref) {
return service;
}

/// Services that manages correspondence games.
/// Service that manages correspondence games.
class CorrespondenceService {
CorrespondenceService(this._log, {required this.ref});

Expand Down Expand Up @@ -104,11 +104,8 @@ class CorrespondenceService {

ref.withClient((client) async {
try {
final accountRepository = AccountRepository(client);
final gameRepository = GameRepository(client);
// user can have more than 50 ongoing games, but we only sync the 50 most
// recent ones
final ongoingGames = await accountRepository.getOngoingGames(nb: 50);
final ongoingGames = await ref.read(ongoingGamesProvider.future);
for (final sg in storedOngoingGames) {
final game = ongoingGames.firstWhereOrNull((e) => e.id == sg.$2.id);
if (game == null) {
Expand Down Expand Up @@ -227,18 +224,14 @@ class CorrespondenceService {
PlayableGame game, {
required bool fromBackground,
}) async {
if (!fromBackground) {
// opponent just played, invalidate ongoing games
if (game.sideToMove == game.youAre) {
ref.invalidate(ongoingGamesProvider);
}
}

await updateGame(fullId, game);
}

/// Updates a stored correspondence game.
/// Updates a correspondence game.
///
/// Will update the game in the ongoing games provider and save it to the storage.
Future<void> updateGame(GameFullId fullId, PlayableGame game) async {
ref.read(ongoingGamesProvider.notifier).updateGame(fullId, game);
return (await ref.read(correspondenceGameStorageProvider.future)).save(
OfflineCorrespondenceGame(
id: game.id,
Expand Down
4 changes: 0 additions & 4 deletions lib/src/model/game/game_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:lichess_mobile/src/model/account/account_preferences.dart';
import 'package:lichess_mobile/src/model/account/account_repository.dart';
import 'package:lichess_mobile/src/model/account/account_service.dart';
import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
import 'package:lichess_mobile/src/model/clock/chess_clock.dart';
Expand Down Expand Up @@ -93,7 +92,6 @@ class GameController extends _$GameController {

if (fullEvent.game.finished) {
if (fullEvent.game.meta.speed == Speed.correspondence) {
ref.invalidate(ongoingGamesProvider);
ref.read(correspondenceServiceProvider).updateGame(gameFullId, fullEvent.game);
}

Expand Down Expand Up @@ -647,7 +645,6 @@ class GameController extends _$GameController {
}

if (curState.game.meta.speed == Speed.correspondence) {
ref.invalidate(ongoingGamesProvider);
ref.read(correspondenceServiceProvider).updateGame(gameFullId, newState.game);
}

Expand Down Expand Up @@ -699,7 +696,6 @@ class GameController extends _$GameController {
}

if (curState.game.meta.speed == Speed.correspondence) {
ref.invalidate(ongoingGamesProvider);
ref.read(correspondenceServiceProvider).updateGame(gameFullId, newState.game);
}

Expand Down
4 changes: 4 additions & 0 deletions lib/src/model/game/playable_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ class PlayableGame with _$PlayableGame, BaseGame, IndexableSteps implements Base

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

const factory PlayableClockData({
required bool running,
required Duration white,
Expand All @@ -161,6 +163,8 @@ class PlayableClockData with _$PlayableClockData {
/// The time when the clock event was received.
required DateTime at,
}) = _PlayableClockData;

Duration forSide(Side side) => side == Side.white ? white : black;
}

PlayableGame _playableGameFromPick(RequiredPick pick) {
Expand Down
Loading