Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
16 changes: 9 additions & 7 deletions lib/src/view/game/game_loading_board.dart
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,10 @@ class LoadingPlayerWidget extends StatelessWidget {
}

class LoadGameError extends StatelessWidget {
const LoadGameError(this.errorMessage);
const LoadGameError(this.errorMessage, {this.showBottomBar = true});

final String errorMessage;
final bool showBottomBar;

@override
Widget build(BuildContext context) {
Expand All @@ -309,12 +310,13 @@ class LoadGameError extends StatelessWidget {
),
BottomBar(
children: [
BottomBarButton(
onTap: () => Navigator.of(context).pop(),
label: context.l10n.cancel,
icon: CupertinoIcons.xmark,
showLabel: true,
),
if (showBottomBar)
BottomBarButton(
onTap: () => Navigator.of(context).pop(),
label: context.l10n.cancel,
icon: CupertinoIcons.xmark,
showLabel: true,
),
],
),
],
Expand Down
58 changes: 57 additions & 1 deletion lib/src/view/game/game_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:lichess_mobile/src/model/settings/general_preferences.dart';
import 'package:lichess_mobile/src/network/http.dart';
import 'package:lichess_mobile/src/utils/duration.dart';
import 'package:lichess_mobile/src/utils/gestures_exclusion.dart';
import 'package:lichess_mobile/src/utils/immersive_mode.dart';
import 'package:lichess_mobile/src/utils/l10n_context.dart';
import 'package:lichess_mobile/src/utils/navigation.dart';
import 'package:lichess_mobile/src/view/game/game_body.dart';
Expand Down Expand Up @@ -68,12 +69,67 @@ class _GameScreenState extends ConsumerState<GameScreen> {
final _whiteClockKey = GlobalKey(debugLabel: 'whiteClockOnGameScreen');
final _blackClockKey = GlobalKey(debugLabel: 'blackClockOnGameScreen');
final _boardKey = GlobalKey(debugLabel: 'boardOnGameScreen');
AppLifecycleListener? _appLifecycleListener;

@override
void initState() {
super.initState();
// Cancel pending seek or challenges when app goes to background to prevent games being created
// while the user is away from the app.
_appLifecycleListener = AppLifecycleListener(onPause: _cancelSeekOrChallenge);
}

@override
void dispose() {
_appLifecycleListener?.dispose();
super.dispose();
}

Future<void> _cancelSeekOrChallenge() async {
if (!mounted) return;
final loader = ref.read(gameScreenLoaderProvider(widget.source));
// Only cancel if we are still seeking or waiting for challenge to be accepted
if (loader is! AsyncLoading<GameScreenState>) {
return;
}
switch (widget.source) {
case LobbySource():
await ref.read(gameScreenLoaderProvider(widget.source).notifier).cancelSeek();
case UserChallengeSource():
await ref.read(gameScreenLoaderProvider(widget.source).notifier).cancelChallenge();
case ExistingGameSource():
break;
}
}

@override
Widget build(BuildContext context) {
final boardPreferences = ref.watch(boardPreferencesProvider);

switch (ref.watch(gameScreenLoaderProvider(widget.source))) {
case AsyncData(value: SeekCancelledState()):
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
leading: const BackButton(),
Comment thread
HaonRekcef marked this conversation as resolved.
Outdated
title: switch (widget.source) {
LobbySource(:final seek) => _LobbyGameTitle(seek: seek),
_ => const SizedBox.shrink(),
},
),
body: const LoadGameError('The game search was cancelled.', showBottomBar: false),
);
case AsyncData(value: ChallengeCancelledState()):
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
leading: const BackButton(),
title: _ChallengeGameTitle(
challenge: (widget.source as UserChallengeSource).challengeRequest,
),
),
body: const LoadGameError('The challenge was cancelled.', showBottomBar: false),
);
case AsyncData(
value: ChallengeDeclinedState(
response: ChallengeResponseDeclined(:final challenge, :final declineReason),
Expand Down Expand Up @@ -206,7 +262,7 @@ class _GameScreenState extends ConsumerState<GameScreen> {
_ => const SizedBox.shrink(),
},
),
body: PopScope(canPop: false, child: loadingBoard),
body: PopScope(canPop: false, child: WakelockWidget(child: loadingBoard)),
);
}
}
Expand Down
28 changes: 28 additions & 0 deletions lib/src/view/game/game_screen_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ part 'game_screen_providers.freezed.dart';
/// It can be:
/// - [GameCreatedState]: A game has been created or loaded.
/// - [ChallengeDeclinedState]: A real time challenge has been declined.
/// - [SeekCancelledState]: A game seek has been cancelled.
/// - [ChallengeCancelledState]: A real time challenge has been cancelled.
sealed class GameScreenState {}

/// Game screen state when a game has been created or loaded.
Expand All @@ -43,6 +45,22 @@ sealed class ChallengeDeclinedState with _$ChallengeDeclinedState implements Gam
_ChallengeDeclinedState;
}

/// A game seek has been cancelled.
@freezed
sealed class SeekCancelledState with _$SeekCancelledState implements GameScreenState {
const SeekCancelledState._();

const factory SeekCancelledState() = _SeekCancelledState;
}

/// A real time challenge has been cancelled.
@freezed
sealed class ChallengeCancelledState with _$ChallengeCancelledState implements GameScreenState {
const ChallengeCancelledState._();

const factory ChallengeCancelledState() = _ChallengeCancelledState;
}

/// The source from which the [GameScreen] was opened.
///
/// It can be:
Expand Down Expand Up @@ -113,6 +131,16 @@ class GameScreenLoader extends _$GameScreenLoader {
void loadGame(GameFullId id) {
state = AsyncValue.data(GameCreatedState(id));
}

Future<void> cancelSeek() async {
state = const AsyncValue.data(SeekCancelledState());
await ref.read(createGameServiceProvider).cancelSeek();
Comment thread
HaonRekcef marked this conversation as resolved.
Outdated
}

Future<void> cancelChallenge() async {
state = const AsyncValue.data(ChallengeCancelledState());
await ref.read(createGameServiceProvider).cancelChallenge();
}
}

@riverpod
Expand Down