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

feat: add support for the new "hide rating except in game" option #1285

Merged
merged 9 commits into from
Dec 27, 2024
48 changes: 43 additions & 5 deletions lib/src/model/account/account_preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ typedef AccountPrefState =
// game display
Zen zenMode,
PieceNotation pieceNotation,
BooleanPref showRatings,
ShowRatings showRatings,
// game behavior
BooleanPref premove,
AutoQueen autoQueen,
Expand All @@ -32,9 +32,9 @@ typedef AccountPrefState =

/// A provider that tells if the user wants to see ratings in the app.
@Riverpod(keepAlive: true)
Future<bool> showRatingsPref(Ref ref) async {
Future<ShowRatings> showRatingsPref(Ref ref) async {
return ref.watch(
accountPreferencesProvider.selectAsync((state) => state?.showRatings.value ?? true),
accountPreferencesProvider.selectAsync((state) => state?.showRatings ?? ShowRatings.yes),
);
}

Expand All @@ -57,7 +57,7 @@ Future<PieceNotation> pieceNotation(Ref ref) async {
final defaultAccountPreferences = (
zenMode: Zen.no,
pieceNotation: PieceNotation.symbol,
showRatings: const BooleanPref(true),
showRatings: ShowRatings.yes,
premove: const BooleanPref(true),
autoQueen: AutoQueen.premove,
autoThreefold: AutoThreefold.always,
Expand Down Expand Up @@ -94,7 +94,7 @@ class AccountPreferences extends _$AccountPreferences {

Future<void> setZen(Zen value) => _setPref('zen', value);
Future<void> setPieceNotation(PieceNotation value) => _setPref('pieceNotation', value);
Future<void> setShowRatings(BooleanPref value) => _setPref('ratings', value);
Future<void> setShowRatings(ShowRatings value) => _setPref('ratings', value);

Future<void> setPremove(BooleanPref value) => _setPref('premove', value);
Future<void> setTakeback(Takeback value) => _setPref('takeback', value);
Expand Down Expand Up @@ -178,6 +178,44 @@ enum Zen implements AccountPref<int> {
}
}

enum ShowRatings implements AccountPref<int> {
no(0),
yes(1),
exceptInGame(2);

const ShowRatings(this.value);

@override
final int value;

@override
String get toFormData => value.toString();

String label(BuildContext context) {
switch (this) {
case ShowRatings.no:
return context.l10n.no;
case ShowRatings.yes:
return context.l10n.yes;
case ShowRatings.exceptInGame:
return context.l10n.preferencesExceptInGame;
}
}

static ShowRatings fromInt(int value) {
switch (value) {
case 0:
return ShowRatings.no;
case 1:
return ShowRatings.yes;
case 2:
return ShowRatings.exceptInGame;
default:
throw Exception('Invalid value for ShowRatings');
}
}
}

enum PieceNotation implements AccountPref<int> {
symbol(0),
letter(1);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/model/account/account_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ AccountPrefState _accountPreferencesFromPick(RequiredPick pick) {
return (
zenMode: Zen.fromInt(pick('zen').asIntOrThrow()),
pieceNotation: PieceNotation.fromInt(pick('pieceNotation').asIntOrThrow()),
showRatings: BooleanPref.fromInt(pick('ratings').asIntOrThrow()),
showRatings: ShowRatings.fromInt(pick('ratings').asIntOrThrow()),
premove: BooleanPref(pick('premove').asBoolOrThrow()),
autoQueen: AutoQueen.fromInt(pick('autoQueen').asIntOrThrow()),
autoThreefold: AutoThreefold.fromInt(pick('autoThreefold').asIntOrThrow()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class CorrespondenceGameStorage {
Future<void> save(OfflineCorrespondenceGame game) async {
try {
await _db.insert(kCorrespondenceStorageTable, {
'userId': game.me.user?.id.toString() ?? kCorrespondenceStorageAnonId,
'userId': game.me?.user?.id.toString() ?? kCorrespondenceStorageAnonId,
'gameId': game.id.toString(),
'lastModified': DateTime.now().toIso8601String(),
'data': jsonEncode(game.toJson()),
Expand Down
2 changes: 1 addition & 1 deletion lib/src/model/correspondence/correspondence_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ class CorrespondenceService {
perf: game.meta.perf,
white: game.white,
black: game.black,
youAre: game.youAre!,
youAre: game.youAre,
daysPerTurn: game.meta.daysPerTurn,
clock: game.correspondenceClock,
winner: game.winner,
Expand Down
16 changes: 6 additions & 10 deletions lib/src/model/correspondence/offline_correspondence_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ part 'offline_correspondence_game.freezed.dart';
part 'offline_correspondence_game.g.dart';

/// An offline correspondence game.
///
/// This is always a game of the current user, so [youAre], [me] and [opponent]
/// are always guaranteed to be non-null.
@Freezed(fromJson: true, toJson: true)
class OfflineCorrespondenceGame
with _$OfflineCorrespondenceGame, BaseGame, IndexableSteps
Expand All @@ -35,7 +38,7 @@ class OfflineCorrespondenceGame
required Perf perf,
required Player white,
required Player black,
required Side youAre,
required Side? youAre,
int? daysPerTurn,
Side? winner,
bool? isThreefoldRepetition,
Expand All @@ -45,22 +48,19 @@ class OfflineCorrespondenceGame
factory OfflineCorrespondenceGame.fromJson(Map<String, dynamic> json) =>
_$OfflineCorrespondenceGameFromJson(json);

Side get orientation => youAre;
Side get orientation => youAre!;

@override
IList<Duration>? get clocks => null;

@override
IList<ExternalEval>? get evals => null;

Player get me => youAre == Side.white ? white : black;
Player get opponent => youAre == Side.white ? black : white;

Side get sideToMove => lastPosition.turn;

bool get isMyTurn => sideToMove == youAre;

Duration? myTimeLeft(DateTime lastModifiedTime) => estimatedTimeLeft(youAre, lastModifiedTime);
Duration? myTimeLeft(DateTime lastModifiedTime) => estimatedTimeLeft(youAre!, lastModifiedTime);

Duration? estimatedTimeLeft(Side side, DateTime lastModifiedTime) {
final timeLeft = side == Side.white ? clock?.white : clock?.black;
Expand All @@ -69,8 +69,4 @@ class OfflineCorrespondenceGame
}
return null;
}

bool get playable => status.value < GameStatus.aborted.value;
bool get playing => status.value > GameStatus.started.value;
bool get finished => status.value >= GameStatus.mate.value;
}
16 changes: 0 additions & 16 deletions lib/src/model/game/archived_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,6 @@ class ArchivedGame with _$ArchivedGame, BaseGame, IndexableSteps implements Base

/// Create an archived game from a local storage JSON.
factory ArchivedGame.fromJson(Map<String, dynamic> json) => _$ArchivedGameFromJson(json);

/// Player point of view. Null if spectating.
Player? get me =>
youAre == null
? null
: youAre == Side.white
? white
: black;

/// Opponent point of view. Null if spectating.
Player? get opponent =>
youAre == null
? null
: youAre == Side.white
? black
: white;
}

/// A [LightArchivedGame] associated with a point of view of a player.
Expand Down
27 changes: 27 additions & 0 deletions lib/src/model/game/game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,40 @@ abstract mixin class BaseGame {

GameStatus get status;

/// Whether the game is properly finished (not aborted).
bool get finished => status.value >= GameStatus.mate.value;
bool get aborted => status == GameStatus.aborted;

/// Whether the game is still playable (not finished or aborted and not imported).
bool get playable => status.value < GameStatus.aborted.value;

/// This field is null if the game is being watched as a spectator, and
/// represents the side that the current player is playing as otherwise.
Side? get youAre;

Side? get winner;

bool? get isThreefoldRepetition;

Player get white;
Player get black;

/// Player of the playing point of view. Null if spectating.
Player? get me =>
youAre == null
? null
: youAre == Side.white
? white
: black;

/// Opponent from the playing point of view. Null if spectating.
Player? get opponent =>
youAre == null
? null
: youAre == Side.white
? black
: white;

Position get lastPosition;

Side? playerSideOf(UserId id) {
Expand Down
19 changes: 14 additions & 5 deletions lib/src/model/game/over_the_board_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:lichess_mobile/src/model/common/eval.dart';
import 'package:lichess_mobile/src/model/common/id.dart';

import 'package:lichess_mobile/src/model/game/game.dart';
import 'package:lichess_mobile/src/model/game/game_status.dart';
import 'package:lichess_mobile/src/model/game/player.dart';
import 'package:lichess_mobile/src/model/user/user.dart';
import 'package:lichess_mobile/src/utils/string.dart';

part 'over_the_board_game.freezed.dart';
part 'over_the_board_game.g.dart';
Expand All @@ -19,9 +20,19 @@ abstract class OverTheBoardGame with _$OverTheBoardGame, BaseGame, IndexableStep
const OverTheBoardGame._();

@override
Player get white => const Player();
Player get white => Player(
onGame: true,
user: LightUser(id: UserId(Side.white.name), name: Side.white.name.capitalize()),
);

@override
Player get black => const Player();
Player get black => Player(
onGame: true,
user: LightUser(id: UserId(Side.black.name), name: Side.black.name.capitalize()),
);

@override
Side? get youAre => null;

@override
IList<ExternalEval>? get evals => null;
Expand All @@ -40,6 +51,4 @@ abstract class OverTheBoardGame with _$OverTheBoardGame, BaseGame, IndexableStep
Side? winner,
bool? isThreefoldRepetition,
}) = _OverTheBoardGame;

bool get finished => status.value >= GameStatus.mate.value;
}
22 changes: 0 additions & 22 deletions lib/src/model/game/playable_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,22 +72,6 @@ class PlayableGame with _$PlayableGame, BaseGame, IndexableSteps implements Base
return _playableGameFromPick(pick(json).required());
}

/// Player of the playing point of view. Null if spectating.
Player? get me =>
youAre == null
? null
: youAre == Side.white
? white
: black;

/// Opponent from the playing point of view. Null if spectating.
Player? get opponent =>
youAre == null
? null
: youAre == Side.white
? black
: white;

Side get sideToMove => lastPosition.turn;

bool get hasAI => white.isAI || black.isAI;
Expand All @@ -97,12 +81,6 @@ class PlayableGame with _$PlayableGame, BaseGame, IndexableSteps implements Base
/// Whether it is the current player's turn.
bool get isMyTurn => lastPosition.turn == youAre;

/// Whether the game is properly finished (not aborted).
bool get finished => status.value >= GameStatus.mate.value;
bool get aborted => status == GameStatus.aborted;

/// Whether the game is still playable (not finished or aborted and not imported).
bool get playable => status.value < GameStatus.aborted.value && !imported;
bool get abortable =>
playable &&
lastPosition.fullmoves <= 1 &&
Expand Down
31 changes: 19 additions & 12 deletions lib/src/view/account/rating_pref_aware.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,33 @@ import 'package:lichess_mobile/src/model/account/account_preferences.dart';

/// A widget that knows if the user has enabled ratings in their settings.
class RatingPrefAware extends ConsumerWidget {
/// Creates a widget that displays its child only if the user has enabled ratings
/// Creates a widget that displays its [child] only if the user has enabled ratings
/// in their settings.
///
/// Optionally, a different [orElse] widget can be displayed if ratings are disabled.
const RatingPrefAware({required this.child, this.orElse, super.key});
///
/// If the user has chosen [ShowRatings.exceptInGame] in their settings,
/// the [child] will be hidden if and only if [isActiveGameOfCurrentUser] is true.
/// Set this to `true` if the widget is being used in a screen that shows an active game.
const RatingPrefAware({
required this.child,
this.orElse = const SizedBox.shrink(),
this.isActiveGameOfCurrentUser = false,
super.key,
});

final Widget child;
final Widget? orElse;
final Widget orElse;
final bool isActiveGameOfCurrentUser;

@override
Widget build(BuildContext context, WidgetRef ref) {
final showRatingAsync = ref.watch(showRatingsPrefProvider);
return showRatingAsync.maybeWhen(
data: (showRatings) {
if (!showRatings) {
return orElse ?? const SizedBox.shrink();
}
return child;
},
orElse: () => orElse ?? const SizedBox.shrink(),
);

return switch (showRatingAsync) {
AsyncData(value: ShowRatings.yes) => child,
AsyncData(value: ShowRatings.exceptInGame) => isActiveGameOfCurrentUser ? orElse : child,
_ => orElse,
};
}
}
Loading
Loading