mirror of
https://github.com/lichess-org/mobile.git
synced 2026-05-26 13:50:52 +00:00
refactor: use @freezed unions to improve readability of GameScreen code
This commit is contained in:
@@ -9,6 +9,7 @@ targets:
|
||||
generate_for:
|
||||
- lib/src/model/**/*.dart
|
||||
- lib/src/**/*_models.dart
|
||||
- lib/src/**/*_providers.dart
|
||||
options:
|
||||
from_json: false
|
||||
to_json: false
|
||||
|
||||
@@ -16,8 +16,10 @@ import 'package:lichess_mobile/src/styles/styles.dart';
|
||||
import 'package:lichess_mobile/src/tab_scaffold.dart' show currentNavigatorKeyProvider;
|
||||
import 'package:lichess_mobile/src/utils/l10n_context.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:lichess_mobile/src/view/user/challenge_requests_screen.dart';
|
||||
import 'package:lichess_mobile/src/widgets/adaptive_action_sheet.dart';
|
||||
import 'package:lichess_mobile/src/widgets/feedback.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:stream_transform/stream_transform.dart';
|
||||
|
||||
@@ -111,8 +113,7 @@ class ChallengeService {
|
||||
|
||||
switch (actionid) {
|
||||
case 'accept':
|
||||
final fullId = await acceptChallenge(challengeId);
|
||||
_goToGameScreen(fullId);
|
||||
await _acceptChallenge(challengeId);
|
||||
|
||||
case 'decline':
|
||||
final context = ref.read(currentNavigatorKeyProvider).currentContext;
|
||||
@@ -139,10 +140,16 @@ class ChallengeService {
|
||||
return await challengeRepo.show(id).then((challenge) => challenge.gameFullId);
|
||||
}
|
||||
|
||||
void _goToGameScreen(GameFullId? fullId) {
|
||||
Future<void> _acceptChallenge(ChallengeId id) async {
|
||||
final fullId = await acceptChallenge(id);
|
||||
|
||||
final context = ref.read(currentNavigatorKeyProvider).currentContext;
|
||||
if (context == null || !context.mounted) return;
|
||||
|
||||
if (fullId == null) {
|
||||
return showSnackBar(context, 'Failed to accept challenge', type: SnackBarType.error);
|
||||
}
|
||||
|
||||
final rootNavState = Navigator.of(context, rootNavigator: true);
|
||||
if (rootNavState.canPop()) {
|
||||
rootNavState.popUntil((route) => route.isFirst);
|
||||
@@ -151,7 +158,7 @@ class ChallengeService {
|
||||
Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).push(GameScreen.buildRoute(context, initialGameId: fullId));
|
||||
).push(GameScreen.buildRoute(context, source: ExistingGameSource(fullId)));
|
||||
}
|
||||
|
||||
void _showDeclineDialog(BuildContext context, ChallengeId id) {
|
||||
@@ -187,10 +194,7 @@ class ChallengeService {
|
||||
makeLabel: (context) => Text(context.l10n.accept),
|
||||
leading: Icon(Icons.check, color: context.lichessColors.good),
|
||||
isDefaultAction: true,
|
||||
onPressed: () async {
|
||||
final fullId = await acceptChallenge(challenge.id);
|
||||
_goToGameScreen(fullId);
|
||||
},
|
||||
onPressed: () async => await _acceptChallenge(challenge.id),
|
||||
),
|
||||
BottomSheetAction(
|
||||
makeLabel: (context) => Text(context.l10n.decline),
|
||||
|
||||
@@ -22,6 +22,7 @@ import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:lichess_mobile/src/tab_scaffold.dart' show currentNavigatorKeyProvider;
|
||||
import 'package:lichess_mobile/src/view/game/game_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -87,7 +88,7 @@ class CorrespondenceService {
|
||||
Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).push(GameScreen.buildRoute(context, initialGameId: fullId));
|
||||
).push(GameScreen.buildRoute(context, source: ExistingGameSource(fullId)));
|
||||
}
|
||||
|
||||
/// Syncs offline correspondence games with the server.
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:deep_pick/deep_pick.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/account/account_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/challenge/challenge.dart';
|
||||
import 'package:lichess_mobile/src/model/challenge/challenge_repository.dart';
|
||||
@@ -16,12 +17,19 @@ import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'create_game_service.g.dart';
|
||||
part 'create_game_service.freezed.dart';
|
||||
|
||||
typedef ChallengeResponse = ({
|
||||
GameFullId? gameFullId,
|
||||
Challenge? challenge,
|
||||
ChallengeDeclineReason? declineReason,
|
||||
});
|
||||
@freezed
|
||||
sealed class ChallengeResponse with _$ChallengeResponse {
|
||||
const ChallengeResponse._();
|
||||
|
||||
factory ChallengeResponse.accepted({required GameFullId gameFullId}) = ChallengeAcceptedResponse;
|
||||
|
||||
factory ChallengeResponse.declined({
|
||||
required Challenge challenge,
|
||||
required ChallengeDeclineReason? declineReason,
|
||||
}) = ChallengeDeclinedResponse;
|
||||
}
|
||||
|
||||
/// A provider for the [CreateGameService].
|
||||
@riverpod
|
||||
@@ -159,17 +167,16 @@ class CreateGameService {
|
||||
try {
|
||||
final updatedChallenge = await challengeRepository.show(challenge.id);
|
||||
if (updatedChallenge.gameFullId != null) {
|
||||
completer.complete((
|
||||
gameFullId: updatedChallenge.gameFullId,
|
||||
challenge: null,
|
||||
declineReason: null,
|
||||
));
|
||||
completer.complete(
|
||||
ChallengeResponse.accepted(gameFullId: updatedChallenge.gameFullId!),
|
||||
);
|
||||
} else if (updatedChallenge.status == ChallengeStatus.declined) {
|
||||
completer.complete((
|
||||
gameFullId: null,
|
||||
challenge: challenge,
|
||||
declineReason: updatedChallenge.declineReason,
|
||||
));
|
||||
completer.complete(
|
||||
ChallengeResponse.declined(
|
||||
challenge: challenge,
|
||||
declineReason: updatedChallenge.declineReason,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
_log.warning('Failed to reload challenge', e);
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:lichess_mobile/src/model/puzzle/puzzle_angle.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_theme.dart';
|
||||
import 'package:lichess_mobile/src/tab_scaffold.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:lichess_mobile/src/view/puzzle/puzzle_screen.dart';
|
||||
import 'package:quick_actions/quick_actions.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -50,7 +51,7 @@ class QuickActionService {
|
||||
Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).push(GameScreen.buildRoute(context, seek: recentSeeks[index]));
|
||||
).push(GameScreen.buildRoute(context, source: LobbySource(recentSeeks[index])));
|
||||
}
|
||||
} else if (shortcutType == 'play_puzzles') {
|
||||
Navigator.of(
|
||||
|
||||
@@ -36,6 +36,8 @@ import 'package:lichess_mobile/src/widgets/game_layout.dart';
|
||||
import 'package:lichess_mobile/src/widgets/platform_alert_dialog.dart';
|
||||
import 'package:lichess_mobile/src/widgets/yes_no_dialog.dart';
|
||||
|
||||
typedef LoadingPosition = ({String? fen, Move? lastMove, Side? orientation});
|
||||
|
||||
/// Game body for the [GameScreen].
|
||||
///
|
||||
/// This widget is responsible for displaying the board, the clocks, the players,
|
||||
@@ -47,7 +49,8 @@ import 'package:lichess_mobile/src/widgets/yes_no_dialog.dart';
|
||||
/// prevent the user from going back to the previous screen.
|
||||
class GameBody extends ConsumerWidget {
|
||||
const GameBody({
|
||||
required this.loadedGame,
|
||||
required this.gameId,
|
||||
this.loadingPosition,
|
||||
required this.whiteClockKey,
|
||||
required this.blackClockKey,
|
||||
required this.onLoadGameCallback,
|
||||
@@ -55,7 +58,9 @@ class GameBody extends ConsumerWidget {
|
||||
required this.boardKey,
|
||||
});
|
||||
|
||||
final LoadedGame loadedGame;
|
||||
final GameFullId gameId;
|
||||
|
||||
final LoadingPosition? loadingPosition;
|
||||
|
||||
/// [GlobalKey] for the white clock.
|
||||
///
|
||||
@@ -84,7 +89,7 @@ class GameBody extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final ctrlProvider = gameControllerProvider(loadedGame.gameId);
|
||||
final ctrlProvider = gameControllerProvider(gameId);
|
||||
|
||||
ref.listen(
|
||||
ctrlProvider,
|
||||
@@ -280,7 +285,7 @@ class GameBody extends ConsumerWidget {
|
||||
},
|
||||
zenMode: gameState.isZenModeActive,
|
||||
userActionsBar: _GameBottomBar(
|
||||
id: loadedGame.gameId,
|
||||
id: gameId,
|
||||
onLoadGameCallback: onLoadGameCallback,
|
||||
onNewOpponentCallback: onNewOpponentCallback,
|
||||
),
|
||||
@@ -290,22 +295,22 @@ class GameBody extends ConsumerWidget {
|
||||
|
||||
case AsyncData(:final value, isRefreshing: true):
|
||||
return StandaloneGameLoadingContent(
|
||||
fen: value.game.lastPosition.fen,
|
||||
lastMove: value.game.moveAt(value.stepCursor) as NormalMove?,
|
||||
orientation: value.game.youAre,
|
||||
position: (
|
||||
fen: value.game.lastPosition.fen,
|
||||
lastMove: value.game.moveAt(value.stepCursor),
|
||||
orientation: value.game.youAre,
|
||||
),
|
||||
userActionsBar: _GameBottomBar(
|
||||
id: loadedGame.gameId,
|
||||
id: gameId,
|
||||
onLoadGameCallback: onLoadGameCallback,
|
||||
onNewOpponentCallback: onNewOpponentCallback,
|
||||
),
|
||||
);
|
||||
case final _:
|
||||
return StandaloneGameLoadingContent(
|
||||
fen: loadedGame.lastFen,
|
||||
lastMove: loadedGame.lastMove,
|
||||
orientation: loadedGame.side,
|
||||
position: loadingPosition,
|
||||
userActionsBar: _GameBottomBar(
|
||||
id: loadedGame.gameId,
|
||||
id: gameId,
|
||||
onLoadGameCallback: onLoadGameCallback,
|
||||
onNewOpponentCallback: onNewOpponentCallback,
|
||||
),
|
||||
@@ -325,7 +330,7 @@ class GameBody extends ConsumerWidget {
|
||||
if (context.mounted) {
|
||||
// when Zen mode is disabled, reload chat data
|
||||
ref
|
||||
.read(gameControllerProvider(loadedGame.gameId).notifier)
|
||||
.read(gameControllerProvider(gameId).notifier)
|
||||
.onToggleChat(state.requireValue.chatOptions != null);
|
||||
}
|
||||
}
|
||||
@@ -339,10 +344,8 @@ class GameBody extends ConsumerWidget {
|
||||
if (context.mounted) {
|
||||
showAdaptiveDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => GameResultDialog(
|
||||
id: loadedGame.gameId,
|
||||
onNewOpponentCallback: onNewOpponentCallback,
|
||||
),
|
||||
builder: (context) =>
|
||||
GameResultDialog(id: gameId, onNewOpponentCallback: onNewOpponentCallback),
|
||||
barrierDismissible: true,
|
||||
);
|
||||
}
|
||||
@@ -372,7 +375,7 @@ class GameBody extends ConsumerWidget {
|
||||
if (context.mounted) {
|
||||
showAdaptiveDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => _ClaimWinDialog(id: loadedGame.gameId),
|
||||
builder: (context) => _ClaimWinDialog(id: gameId),
|
||||
barrierDismissible: true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:lichess_mobile/src/utils/l10n_context.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/game_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:lichess_mobile/src/widgets/feedback.dart';
|
||||
import 'package:lichess_mobile/src/widgets/platform_context_menu_button.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
@@ -32,10 +33,12 @@ void openGameScreen(
|
||||
game.fullId != null
|
||||
? GameScreen.buildRoute(
|
||||
context,
|
||||
initialGameId: game.fullId,
|
||||
loadingOrientation: orientation,
|
||||
loadingFen: loadingFen,
|
||||
loadingLastMove: loadingLastMove,
|
||||
source: ExistingGameSource(game.fullId!),
|
||||
loadingPosition: (
|
||||
fen: loadingFen,
|
||||
lastMove: loadingLastMove,
|
||||
orientation: orientation,
|
||||
),
|
||||
lastMoveAt: lastMoveAt,
|
||||
)
|
||||
: AnalysisScreen.buildRoute(
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:lichess_mobile/src/model/lobby/lobby_numbers.dart';
|
||||
import 'package:lichess_mobile/src/utils/l10n_context.dart';
|
||||
import 'package:lichess_mobile/src/utils/string.dart';
|
||||
import 'package:lichess_mobile/src/view/account/rating_pref_aware.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_body.dart';
|
||||
import 'package:lichess_mobile/src/widgets/bottom_bar.dart';
|
||||
import 'package:lichess_mobile/src/widgets/feedback.dart';
|
||||
import 'package:lichess_mobile/src/widgets/game_layout.dart';
|
||||
@@ -237,17 +238,9 @@ class _ChallengeLoadingContentState extends State<ChallengeLoadingContent> {
|
||||
}
|
||||
|
||||
class StandaloneGameLoadingContent extends StatelessWidget {
|
||||
const StandaloneGameLoadingContent({
|
||||
this.fen,
|
||||
this.lastMove,
|
||||
this.orientation,
|
||||
this.userActionsBar,
|
||||
super.key,
|
||||
});
|
||||
const StandaloneGameLoadingContent({this.position, this.userActionsBar, super.key});
|
||||
|
||||
final String? fen;
|
||||
final Side? orientation;
|
||||
final Move? lastMove;
|
||||
final LoadingPosition? position;
|
||||
final Widget? userActionsBar;
|
||||
|
||||
@override
|
||||
@@ -255,9 +248,9 @@ class StandaloneGameLoadingContent extends StatelessWidget {
|
||||
return Shimmer(
|
||||
child: SafeArea(
|
||||
child: GameLayout(
|
||||
orientation: orientation ?? Side.white,
|
||||
fen: fen ?? kEmptyFen,
|
||||
lastMove: lastMove as NormalMove?,
|
||||
orientation: position?.orientation ?? Side.white,
|
||||
fen: position?.fen ?? kEmptyFen,
|
||||
lastMove: position?.lastMove as NormalMove?,
|
||||
topTable: const LoadingPlayerWidget(),
|
||||
bottomTable: const LoadingPlayerWidget(),
|
||||
moves: const [],
|
||||
|
||||
@@ -33,68 +33,30 @@ import 'package:lichess_mobile/src/widgets/shimmer.dart';
|
||||
/// Screen to play a game, or to show a challenge or to show current user's past games.
|
||||
///
|
||||
/// The screen can be created in three ways:
|
||||
/// - From the lobby, to play a game with a random opponent: using a [GameSeek] as [seek].
|
||||
/// - From a challenge, to accept or decline a challenge: using a [ChallengeRequest] as [challenge].
|
||||
/// - From a game id, to show a game that is already in progress: using a [GameFullId] as [initialGameId].
|
||||
/// - From the lobby, to play a game with a random opponent: using [CurrentGameSource.lobby].
|
||||
/// - From a challenge, to accept or decline a challenge: using a [CurrentGameSource.userChallenge].
|
||||
/// - From a game id, to show a game that is already in progress: using [CurrentGameSource.loadedGame].
|
||||
///
|
||||
/// The screen will show a loading board while the game is being created.
|
||||
class GameScreen extends ConsumerStatefulWidget {
|
||||
const GameScreen({
|
||||
this.seek,
|
||||
this.initialGameId,
|
||||
this.challenge,
|
||||
this.loadingFen,
|
||||
this.loadingLastMove,
|
||||
this.loadingOrientation,
|
||||
this.lastMoveAt,
|
||||
super.key,
|
||||
}) : assert(
|
||||
initialGameId != null || seek != null || challenge != null,
|
||||
'Either a seek, a challenge or an initial game id must be provided.',
|
||||
);
|
||||
const GameScreen({required this.source, this.loadingPosition, this.lastMoveAt, super.key});
|
||||
|
||||
final GameSeek? seek;
|
||||
final GameFullId? initialGameId;
|
||||
final ChallengeRequest? challenge;
|
||||
final GameScreenSource source;
|
||||
|
||||
final String? loadingFen;
|
||||
final Move? loadingLastMove;
|
||||
final Side? loadingOrientation;
|
||||
final LoadingPosition? loadingPosition;
|
||||
|
||||
/// The date of the last move played in the game. If null, the game is in progress.
|
||||
final DateTime? lastMoveAt;
|
||||
|
||||
_GameSource get source {
|
||||
if (initialGameId != null) {
|
||||
return _GameSource.game;
|
||||
} else if (challenge != null) {
|
||||
return _GameSource.challenge;
|
||||
} else {
|
||||
return _GameSource.lobby;
|
||||
}
|
||||
}
|
||||
|
||||
static Route<dynamic> buildRoute(
|
||||
BuildContext context, {
|
||||
GameSeek? seek,
|
||||
GameFullId? initialGameId,
|
||||
ChallengeRequest? challenge,
|
||||
String? loadingFen,
|
||||
Move? loadingLastMove,
|
||||
Side? loadingOrientation,
|
||||
required GameScreenSource source,
|
||||
LoadingPosition? loadingPosition,
|
||||
DateTime? lastMoveAt,
|
||||
}) {
|
||||
return buildScreenRoute(
|
||||
context,
|
||||
screen: GameScreen(
|
||||
seek: seek,
|
||||
initialGameId: initialGameId,
|
||||
challenge: challenge,
|
||||
loadingFen: loadingFen,
|
||||
loadingLastMove: loadingLastMove,
|
||||
loadingOrientation: loadingOrientation,
|
||||
lastMoveAt: lastMoveAt,
|
||||
),
|
||||
screen: GameScreen(source: source, loadingPosition: loadingPosition, lastMoveAt: lastMoveAt),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,8 +64,6 @@ class GameScreen extends ConsumerStatefulWidget {
|
||||
ConsumerState<GameScreen> createState() => _GameScreenState();
|
||||
}
|
||||
|
||||
enum _GameSource { lobby, challenge, game }
|
||||
|
||||
class _GameScreenState extends ConsumerState<GameScreen> {
|
||||
final _whiteClockKey = GlobalKey(debugLabel: 'whiteClockOnGameScreen');
|
||||
final _blackClockKey = GlobalKey(debugLabel: 'blackClockOnGameScreen');
|
||||
@@ -111,31 +71,34 @@ class _GameScreenState extends ConsumerState<GameScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final provider = currentGameProvider(
|
||||
seek: widget.seek,
|
||||
challenge: widget.challenge,
|
||||
game: widget.initialGameId != null
|
||||
? (
|
||||
gameId: widget.initialGameId!,
|
||||
lastFen: widget.loadingFen,
|
||||
lastMove: widget.loadingLastMove,
|
||||
side: widget.loadingOrientation,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
final provider = currentGameProvider(widget.source);
|
||||
final boardPreferences = ref.watch(boardPreferencesProvider);
|
||||
|
||||
switch (ref.watch(provider)) {
|
||||
case AsyncData(:final value):
|
||||
final (game: loadedGame, challenge: challenge, declineReason: declineReason) = value;
|
||||
case AsyncData(
|
||||
value: ChallengeDeclinedState(
|
||||
response: ChallengeDeclinedResponse(:final challenge, :final declineReason),
|
||||
),
|
||||
):
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
title: _ChallengeGameTitle(
|
||||
challenge: (widget.source as UserChallengeSource).challengeRequest,
|
||||
),
|
||||
),
|
||||
body: ChallengeDeclinedBoard(
|
||||
challenge: challenge,
|
||||
declineReason: declineReason != null
|
||||
? declineReason.label(context.l10n)
|
||||
: ChallengeDeclineReason.generic.label(context.l10n),
|
||||
),
|
||||
);
|
||||
case AsyncData(value: GameCreatedState(:final createdGameId)):
|
||||
final isRealTimePlayingGame =
|
||||
(loadedGame != null
|
||||
? ref.watch(isRealTimePlayableGameProvider(loadedGame.gameId))
|
||||
: const AsyncValue.data(true))
|
||||
.valueOrNull ??
|
||||
false;
|
||||
ref.watch(isRealTimePlayableGameProvider(createdGameId)).valueOrNull ?? false;
|
||||
|
||||
final socketUri = loadedGame != null ? GameController.socketUri(loadedGame.gameId) : null;
|
||||
final socketUri = GameController.socketUri(createdGameId);
|
||||
|
||||
final body = PopScope(
|
||||
canPop: isRealTimePlayingGame != true,
|
||||
@@ -143,41 +106,38 @@ class _GameScreenState extends ConsumerState<GameScreen> {
|
||||
// view padding can change on Android when immersive mode is enabled, so to prevent any
|
||||
// board vertical shift, we set `maintainBottomViewPadding` to true.
|
||||
maintainBottomViewPadding: true,
|
||||
child: loadedGame != null
|
||||
? GameBody(
|
||||
loadedGame: loadedGame,
|
||||
whiteClockKey: _whiteClockKey,
|
||||
blackClockKey: _blackClockKey,
|
||||
boardKey: _boardKey,
|
||||
onLoadGameCallback: (id) {
|
||||
if (mounted) {
|
||||
ref.read(provider.notifier).loadGame(id);
|
||||
}
|
||||
},
|
||||
onNewOpponentCallback: (game) {
|
||||
if (!mounted) return;
|
||||
child: GameBody(
|
||||
gameId: createdGameId,
|
||||
// Only show the initial loading position if this is still the game that the GameScreen
|
||||
// was created for. This will not be the case when searching for a new opponent after the game.
|
||||
loadingPosition: switch (widget.source) {
|
||||
ExistingGameSource(:final id) when id == createdGameId => widget.loadingPosition,
|
||||
_ => null,
|
||||
},
|
||||
whiteClockKey: _whiteClockKey,
|
||||
blackClockKey: _blackClockKey,
|
||||
boardKey: _boardKey,
|
||||
onLoadGameCallback: (id) {
|
||||
if (mounted) {
|
||||
ref.read(provider.notifier).loadGame(id);
|
||||
}
|
||||
},
|
||||
onNewOpponentCallback: (game) {
|
||||
if (!mounted) return;
|
||||
|
||||
if (widget.source == _GameSource.lobby) {
|
||||
ref.read(provider.notifier).newOpponent();
|
||||
} else {
|
||||
final savedSetup = ref.read(gameSetupPreferencesProvider);
|
||||
Navigator.of(context, rootNavigator: true).pushReplacement(
|
||||
GameScreen.buildRoute(
|
||||
context,
|
||||
seek: GameSeek.newOpponentFromGame(game, savedSetup),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
: widget.challenge != null && challenge != null
|
||||
? ChallengeDeclinedBoard(
|
||||
challenge: challenge,
|
||||
declineReason: declineReason != null
|
||||
? declineReason.label(context.l10n)
|
||||
: ChallengeDeclineReason.generic.label(context.l10n),
|
||||
)
|
||||
: const LoadGameError('Could not create the game.'),
|
||||
if (widget.source is LobbySource) {
|
||||
ref.read(provider.notifier).newOpponent();
|
||||
} else {
|
||||
final savedSetup = ref.read(gameSetupPreferencesProvider);
|
||||
Navigator.of(context, rootNavigator: true).pushReplacement(
|
||||
GameScreen.buildRoute(
|
||||
context,
|
||||
source: LobbySource(GameSeek.newOpponentFromGame(game, savedSetup)),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -185,15 +145,8 @@ class _GameScreenState extends ConsumerState<GameScreen> {
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
leading: isRealTimePlayingGame ? SocketPingRatingIcon(socketUri: socketUri) : null,
|
||||
title: loadedGame != null
|
||||
? _StandaloneGameTitle(id: loadedGame.gameId, lastMoveAt: widget.lastMoveAt)
|
||||
: widget.seek != null
|
||||
? _LobbyGameTitle(seek: widget.seek!)
|
||||
: widget.challenge != null
|
||||
? _ChallengeGameTitle(challenge: widget.challenge!)
|
||||
: const SizedBox.shrink(),
|
||||
|
||||
actions: [if (loadedGame != null) _GameMenu(gameId: loadedGame.gameId)],
|
||||
title: _StandaloneGameTitle(id: createdGameId, lastMoveAt: widget.lastMoveAt),
|
||||
actions: [_GameMenu(gameId: createdGameId)],
|
||||
),
|
||||
body: Theme.of(context).platform == TargetPlatform.android
|
||||
? AndroidGesturesExclusionWidget(
|
||||
@@ -216,36 +169,43 @@ class _GameScreenState extends ConsumerState<GameScreen> {
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
leading: const SocketPingRatingIcon(),
|
||||
title: widget.seek != null
|
||||
? _LobbyGameTitle(seek: widget.seek!)
|
||||
: widget.challenge != null
|
||||
? _ChallengeGameTitle(challenge: widget.challenge!)
|
||||
: const SizedBox.shrink(),
|
||||
title: switch (widget.source) {
|
||||
LobbySource(:final seek) => _LobbyGameTitle(seek: seek),
|
||||
UserChallengeSource(:final challengeRequest) => _ChallengeGameTitle(
|
||||
challenge: challengeRequest,
|
||||
),
|
||||
_ => const SizedBox.shrink(),
|
||||
},
|
||||
),
|
||||
body: PopScope(child: message),
|
||||
);
|
||||
case _:
|
||||
final loadingBoard = widget.seek != null
|
||||
? LobbyScreenLoadingContent(
|
||||
widget.seek!,
|
||||
() => ref.read(createGameServiceProvider).cancelSeek(),
|
||||
)
|
||||
: widget.challenge != null
|
||||
? ChallengeLoadingContent(
|
||||
widget.challenge!,
|
||||
() => ref.read(createGameServiceProvider).cancelChallenge(),
|
||||
)
|
||||
: const StandaloneGameLoadingContent(userActionsBar: BottomBar.empty());
|
||||
final loadingBoard = switch (widget.source) {
|
||||
LobbySource(:final seek) => LobbyScreenLoadingContent(
|
||||
seek,
|
||||
() => ref.read(createGameServiceProvider).cancelSeek(),
|
||||
),
|
||||
UserChallengeSource(:final challengeRequest) => ChallengeLoadingContent(
|
||||
challengeRequest,
|
||||
() => ref.read(createGameServiceProvider).cancelChallenge(),
|
||||
),
|
||||
ExistingGameSource() => StandaloneGameLoadingContent(
|
||||
position: widget.loadingPosition,
|
||||
userActionsBar: const BottomBar.empty(),
|
||||
),
|
||||
};
|
||||
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
leading: const SocketPingRatingIcon(),
|
||||
title: widget.seek != null
|
||||
? _LobbyGameTitle(seek: widget.seek!)
|
||||
: widget.challenge != null
|
||||
? _ChallengeGameTitle(challenge: widget.challenge!)
|
||||
: const SizedBox.shrink(),
|
||||
title: switch (widget.source) {
|
||||
LobbySource(:final seek) => _LobbyGameTitle(seek: seek),
|
||||
UserChallengeSource(:final challengeRequest) => _ChallengeGameTitle(
|
||||
challenge: challengeRequest,
|
||||
),
|
||||
_ => const SizedBox.shrink(),
|
||||
},
|
||||
),
|
||||
body: PopScope(canPop: false, child: loadingBoard),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:dartchess/dartchess.dart' show Move, Side;
|
||||
import 'package:dartchess/dartchess.dart' show Side;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/challenge/challenge.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/common/speed.dart';
|
||||
@@ -10,13 +11,52 @@ import 'package:lichess_mobile/src/model/lobby/game_seek.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'game_screen_providers.g.dart';
|
||||
part 'game_screen_providers.freezed.dart';
|
||||
|
||||
typedef LoadedGame = ({GameFullId gameId, String? lastFen, Move? lastMove, Side? side});
|
||||
typedef CurrentGameState = ({
|
||||
LoadedGame? game,
|
||||
Challenge? challenge,
|
||||
ChallengeDeclineReason? declineReason,
|
||||
});
|
||||
sealed class CurrentGameState {}
|
||||
|
||||
/// This is used in the following cases:
|
||||
/// - A game that had already been created is loaded.
|
||||
/// - A game has been created from a lobby seek.
|
||||
/// - A challenge has been accepted and a game has been created from it.
|
||||
@freezed
|
||||
sealed class GameCreatedState with _$GameCreatedState implements CurrentGameState {
|
||||
const GameCreatedState._();
|
||||
|
||||
const factory GameCreatedState(GameFullId createdGameId) = _GameCreatedState;
|
||||
}
|
||||
|
||||
/// A real time challenge has been declined.
|
||||
@freezed
|
||||
sealed class ChallengeDeclinedState with _$ChallengeDeclinedState implements CurrentGameState {
|
||||
const ChallengeDeclinedState._();
|
||||
|
||||
const factory ChallengeDeclinedState(ChallengeDeclinedResponse response) =
|
||||
_ChallengeDeclinedState;
|
||||
}
|
||||
|
||||
sealed class GameScreenSource {}
|
||||
|
||||
@freezed
|
||||
sealed class ExistingGameSource with _$ExistingGameSource implements GameScreenSource {
|
||||
const ExistingGameSource._();
|
||||
|
||||
const factory ExistingGameSource(GameFullId id) = _ExistingGameSource;
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class LobbySource with _$LobbySource implements GameScreenSource {
|
||||
const LobbySource._();
|
||||
|
||||
const factory LobbySource(GameSeek seek) = _LobbySource;
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class UserChallengeSource with _$UserChallengeSource implements GameScreenSource {
|
||||
const UserChallengeSource._();
|
||||
|
||||
const factory UserChallengeSource(ChallengeRequest challengeRequest) = _UserChallengeSource;
|
||||
}
|
||||
|
||||
/// A provider that returns the currently loaded [GameFullId] for the [GameScreen].
|
||||
///
|
||||
@@ -25,71 +65,36 @@ typedef CurrentGameState = ({
|
||||
@riverpod
|
||||
class CurrentGame extends _$CurrentGame {
|
||||
@override
|
||||
Future<CurrentGameState> build({
|
||||
GameSeek? seek,
|
||||
ChallengeRequest? challenge,
|
||||
({GameFullId gameId, String? lastFen, Move? lastMove, Side? side})? game,
|
||||
}) {
|
||||
assert(
|
||||
game != null || seek != null || challenge != null,
|
||||
'Either a seek, challenge or a game id must be provided.',
|
||||
);
|
||||
|
||||
Future<CurrentGameState> build(GameScreenSource source) {
|
||||
final service = ref.watch(createGameServiceProvider);
|
||||
|
||||
if (seek != null) {
|
||||
return service
|
||||
.newLobbyGame(seek)
|
||||
.then(
|
||||
(id) => (
|
||||
game: (gameId: id, lastFen: null, lastMove: null, side: null),
|
||||
challenge: null,
|
||||
declineReason: null,
|
||||
return switch (source) {
|
||||
LobbySource(:final seek) => service.newLobbyGame(seek).then((id) => GameCreatedState(id)),
|
||||
UserChallengeSource(:final challengeRequest) =>
|
||||
service
|
||||
.newRealTimeChallenge(challengeRequest)
|
||||
.then(
|
||||
(data) => switch (data) {
|
||||
ChallengeAcceptedResponse(:final gameFullId) => GameCreatedState(gameFullId),
|
||||
ChallengeDeclinedResponse() => ChallengeDeclinedState(data),
|
||||
},
|
||||
),
|
||||
);
|
||||
} else if (challenge != null) {
|
||||
return service
|
||||
.newRealTimeChallenge(challenge)
|
||||
.then(
|
||||
(data) => (
|
||||
game: data.gameFullId != null
|
||||
? (gameId: data.gameFullId!, lastFen: null, lastMove: null, side: null)
|
||||
: null,
|
||||
challenge: data.challenge,
|
||||
declineReason: data.declineReason,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Future.value((game: game!, challenge: null, declineReason: null));
|
||||
ExistingGameSource(:final id) => Future.value(GameCreatedState(id)),
|
||||
};
|
||||
}
|
||||
|
||||
/// Search for a new opponent (lobby only).
|
||||
Future<void> newOpponent() async {
|
||||
if (seek != null) {
|
||||
if (source case LobbySource(:final seek)) {
|
||||
final service = ref.read(createGameServiceProvider);
|
||||
state = const AsyncValue.loading();
|
||||
state = AsyncValue.data(
|
||||
await service
|
||||
.newLobbyGame(seek!)
|
||||
.then(
|
||||
(id) => (
|
||||
game: (gameId: id, lastFen: null, lastMove: null, side: null),
|
||||
challenge: null,
|
||||
declineReason: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
state = AsyncValue.data(await service.newLobbyGame(seek).then((id) => GameCreatedState(id)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a game from its id.
|
||||
void loadGame(GameFullId id) {
|
||||
state = AsyncValue.data((
|
||||
game: (gameId: id, lastFen: null, lastMove: null, side: null),
|
||||
challenge: null,
|
||||
declineReason: null,
|
||||
));
|
||||
state = AsyncValue.data(GameCreatedState(id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import 'package:lichess_mobile/src/view/account/account_drawer.dart';
|
||||
import 'package:lichess_mobile/src/view/account/profile_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/correspondence/offline_correspondence_game_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:lichess_mobile/src/view/game/offline_correspondence_games_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/home/games_carousel.dart';
|
||||
import 'package:lichess_mobile/src/view/play/ongoing_games_screen.dart';
|
||||
@@ -683,10 +684,12 @@ class _OngoingGamesCarousel extends ConsumerWidget {
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
GameScreen.buildRoute(
|
||||
context,
|
||||
initialGameId: game.fullId,
|
||||
loadingFen: game.fen,
|
||||
loadingOrientation: game.orientation,
|
||||
loadingLastMove: game.lastMove,
|
||||
source: ExistingGameSource(game.fullId),
|
||||
loadingPosition: (
|
||||
fen: game.fen,
|
||||
orientation: game.orientation,
|
||||
lastMove: game.lastMove,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:lichess_mobile/src/styles/styles.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_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:lichess_mobile/src/widgets/board_thumbnail.dart';
|
||||
import 'package:lichess_mobile/src/widgets/non_linear_slider.dart';
|
||||
|
||||
@@ -269,17 +270,19 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> {
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
GameScreen.buildRoute(
|
||||
context,
|
||||
challenge: ChallengeRequest(
|
||||
destUser: widget.bot,
|
||||
variant: Variant.fromPosition,
|
||||
timeControl: ChallengeTimeControlType.clock,
|
||||
clock: (
|
||||
time: Duration(seconds: seconds),
|
||||
increment: Duration(seconds: incrementSeconds),
|
||||
source: UserChallengeSource(
|
||||
ChallengeRequest(
|
||||
destUser: widget.bot,
|
||||
variant: Variant.fromPosition,
|
||||
timeControl: ChallengeTimeControlType.clock,
|
||||
clock: (
|
||||
time: Duration(seconds: seconds),
|
||||
increment: Duration(seconds: incrementSeconds),
|
||||
),
|
||||
rated: false,
|
||||
sideChoice: sideChoice,
|
||||
initialFen: fen,
|
||||
),
|
||||
rated: false,
|
||||
sideChoice: sideChoice,
|
||||
initialFen: fen,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:lichess_mobile/src/network/socket.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_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:lichess_mobile/src/view/play/challenge_list_item.dart';
|
||||
import 'package:lichess_mobile/src/view/play/create_correspondence_game_bottom_sheet.dart';
|
||||
import 'package:lichess_mobile/src/widgets/adaptive_action_sheet.dart';
|
||||
@@ -51,7 +52,7 @@ class _ChallengesBodyState extends ConsumerState<CorrespondenceChallengesScreen>
|
||||
Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).push(GameScreen.buildRoute(context, initialGameId: gameFullId));
|
||||
).push(GameScreen.buildRoute(context, source: ExistingGameSource(gameFullId)));
|
||||
}
|
||||
|
||||
case 'reload_seeks':
|
||||
|
||||
@@ -17,6 +17,7 @@ import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:lichess_mobile/src/styles/styles.dart';
|
||||
import 'package:lichess_mobile/src/utils/l10n_context.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:lichess_mobile/src/widgets/adaptive_bottom_sheet.dart';
|
||||
import 'package:lichess_mobile/src/widgets/adaptive_choice_picker.dart';
|
||||
import 'package:lichess_mobile/src/widgets/board_preview.dart';
|
||||
@@ -321,11 +322,13 @@ class _CreateChallengeBottomSheetState extends ConsumerState<CreateChallengeBott
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
GameScreen.buildRoute(
|
||||
context,
|
||||
challenge: preferences.makeRequest(
|
||||
widget.user,
|
||||
preferences.variant != Variant.fromPosition
|
||||
? null
|
||||
: fromPositionFenInput,
|
||||
source: UserChallengeSource(
|
||||
preferences.makeRequest(
|
||||
widget.user,
|
||||
preferences.variant != Variant.fromPosition
|
||||
? null
|
||||
: fromPositionFenInput,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:lichess_mobile/src/model/lobby/game_setup_preferences.dart';
|
||||
import 'package:lichess_mobile/src/network/connectivity.dart';
|
||||
import 'package:lichess_mobile/src/utils/l10n_context.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:lichess_mobile/src/view/play/common_play_widgets.dart';
|
||||
import 'package:lichess_mobile/src/view/play/time_control_modal.dart';
|
||||
import 'package:lichess_mobile/src/widgets/adaptive_bottom_sheet.dart';
|
||||
@@ -165,10 +166,12 @@ class CreateGameWidget extends ConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).push(GameScreen.buildRoute(context, seek: GameSeek.custom(playPrefs, account)));
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
GameScreen.buildRoute(
|
||||
context,
|
||||
source: LobbySource(GameSeek.custom(playPrefs, account)),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: Text(context.l10n.createAGame),
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:lichess_mobile/src/utils/l10n.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_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:lichess_mobile/src/widgets/board_preview.dart';
|
||||
import 'package:lichess_mobile/src/widgets/user_full_name.dart';
|
||||
|
||||
@@ -89,10 +90,12 @@ class OngoingGamePreview extends ConsumerWidget {
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
GameScreen.buildRoute(
|
||||
context,
|
||||
initialGameId: game.fullId,
|
||||
loadingFen: game.fen,
|
||||
loadingOrientation: game.orientation,
|
||||
loadingLastMove: game.lastMove,
|
||||
source: ExistingGameSource(game.fullId),
|
||||
loadingPosition: (
|
||||
fen: game.fen,
|
||||
orientation: game.orientation,
|
||||
lastMove: game.lastMove,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:lichess_mobile/src/network/connectivity.dart';
|
||||
import 'package:lichess_mobile/src/styles/styles.dart';
|
||||
import 'package:lichess_mobile/src/utils/l10n_context.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:lichess_mobile/src/view/play/play_bottom_sheet.dart';
|
||||
import 'package:lichess_mobile/src/view/play/playban.dart';
|
||||
|
||||
@@ -95,7 +96,7 @@ class _SectionChoices extends ConsumerWidget {
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
GameScreen.buildRoute(
|
||||
context,
|
||||
seek: GameSeek.fastPairing(choice, session),
|
||||
source: LobbySource(GameSeek.fastPairing(choice, session)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import 'package:lichess_mobile/src/utils/share.dart';
|
||||
import 'package:lichess_mobile/src/view/analysis/analysis_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/chat/chat_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:lichess_mobile/src/view/user/user_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/watch/tv_screen.dart';
|
||||
import 'package:lichess_mobile/src/widgets/board_thumbnail.dart';
|
||||
@@ -102,7 +103,7 @@ class _TournamentScreenState extends ConsumerState<TournamentScreen> with RouteA
|
||||
Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).push(GameScreen.buildRoute(context, initialGameId: currentGameId));
|
||||
).push(GameScreen.buildRoute(context, source: ExistingGameSource(currentGameId)));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:lichess_mobile/src/styles/styles.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_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:lichess_mobile/src/view/play/challenge_list_item.dart';
|
||||
import 'package:lichess_mobile/src/widgets/adaptive_action_sheet.dart';
|
||||
import 'package:lichess_mobile/src/widgets/feedback.dart';
|
||||
@@ -85,10 +86,13 @@ class _ChallengeListItem extends ConsumerWidget {
|
||||
Future<void> acceptChallenge() async {
|
||||
final fullId = await ref.read(challengeServiceProvider).acceptChallenge(challenge.id);
|
||||
if (!context.mounted) return;
|
||||
if (fullId == null) {
|
||||
return showSnackBar(context, 'Failed to accept challenge', type: SnackBarType.error);
|
||||
}
|
||||
Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).push(GameScreen.buildRoute(context, initialGameId: fullId));
|
||||
).push(GameScreen.buildRoute(context, source: ExistingGameSource(fullId)));
|
||||
}
|
||||
|
||||
Future<void> declineChallenge(ChallengeDeclineReason? reason) async {
|
||||
|
||||
@@ -21,6 +21,7 @@ import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:lichess_mobile/src/styles/lichess_icons.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen.dart';
|
||||
import 'package:lichess_mobile/src/view/game/game_screen_providers.dart';
|
||||
import 'package:lichess_mobile/src/widgets/bottom_bar.dart';
|
||||
import 'package:lichess_mobile/src/widgets/clock.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
@@ -47,7 +48,7 @@ void main() {
|
||||
testWidgets('a game directly with initialGameId', (WidgetTester tester) async {
|
||||
final app = await makeTestProviderScopeApp(
|
||||
tester,
|
||||
home: const GameScreen(initialGameId: testGameFullId),
|
||||
home: const GameScreen(source: ExistingGameSource(testGameFullId)),
|
||||
overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))],
|
||||
);
|
||||
await tester.pumpWidget(app);
|
||||
@@ -95,7 +96,9 @@ void main() {
|
||||
final app = await makeTestProviderScopeApp(
|
||||
tester,
|
||||
home: const GameScreen(
|
||||
seek: GameSeek(clock: (Duration(minutes: 3), Duration(seconds: 2)), rated: true),
|
||||
source: LobbySource(
|
||||
GameSeek(clock: (Duration(minutes: 3), Duration(seconds: 2)), rated: true),
|
||||
),
|
||||
),
|
||||
overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))],
|
||||
);
|
||||
@@ -334,7 +337,7 @@ void main() {
|
||||
testWidgets('displays tournament info', (WidgetTester tester) async {
|
||||
final app = await makeTestProviderScopeApp(
|
||||
tester,
|
||||
home: const GameScreen(initialGameId: GameFullId('qVChCOTcHSeW')),
|
||||
home: const GameScreen(source: ExistingGameSource(GameFullId('qVChCOTcHSeW'))),
|
||||
overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))],
|
||||
);
|
||||
await tester.pumpWidget(app);
|
||||
@@ -356,7 +359,7 @@ void main() {
|
||||
testWidgets('supports berserking', (WidgetTester tester) async {
|
||||
final app = await makeTestProviderScopeApp(
|
||||
tester,
|
||||
home: const GameScreen(initialGameId: GameFullId('qVChCOTcHSeW')),
|
||||
home: const GameScreen(source: ExistingGameSource(GameFullId('qVChCOTcHSeW'))),
|
||||
overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))],
|
||||
);
|
||||
await tester.pumpWidget(app);
|
||||
@@ -690,7 +693,7 @@ void main() {
|
||||
|
||||
final app = await makeTestProviderScopeApp(
|
||||
tester,
|
||||
home: const GameScreen(initialGameId: gameFullId),
|
||||
home: const GameScreen(source: ExistingGameSource(gameFullId)),
|
||||
overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(mockClient, ref))],
|
||||
);
|
||||
await tester.pumpWidget(app);
|
||||
@@ -854,7 +857,7 @@ Future<void> createTestGame(
|
||||
const gameFullId = GameFullId('qVChCOTcHSeW');
|
||||
final app = await makeTestProviderScopeApp(
|
||||
tester,
|
||||
home: const GameScreen(initialGameId: gameFullId),
|
||||
home: const GameScreen(source: ExistingGameSource(gameFullId)),
|
||||
defaultPreferences: defaultPreferences,
|
||||
overrides: [
|
||||
lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)),
|
||||
@@ -897,7 +900,7 @@ Future<void> loadFinishedTestGame(
|
||||
final gameFullId = GameFullId('${gameId.value}test');
|
||||
final app = await makeTestProviderScopeApp(
|
||||
tester,
|
||||
home: GameScreen(initialGameId: gameFullId),
|
||||
home: GameScreen(source: ExistingGameSource(gameFullId)),
|
||||
overrides: [
|
||||
lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)),
|
||||
...?overrides,
|
||||
|
||||
Reference in New Issue
Block a user