Skip to content

Commit

Permalink
Merge pull request #1285 from tom-anders/showRatingExceptInGame
Browse files Browse the repository at this point in the history
feat: add support for the new "hide rating except in game" option
  • Loading branch information
veloce authored Dec 27, 2024
2 parents 47123c2 + a957f6e commit b873582
Show file tree
Hide file tree
Showing 24 changed files with 316 additions and 203 deletions.
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

0 comments on commit b873582

Please sign in to comment.