Skip to content

Commit

Permalink
feat: add support for the new "hide rating except in game" option
Browse files Browse the repository at this point in the history
  • Loading branch information
tom-anders committed Dec 20, 2024
1 parent 37158c2 commit 79c5961
Show file tree
Hide file tree
Showing 14 changed files with 256 additions and 113 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 @@ -15,7 +15,7 @@ typedef AccountPrefState =
// game display
Zen zenMode,
PieceNotation pieceNotation,
BooleanPref showRatings,
ShowRatings showRatings,
// game behavior
BooleanPref premove,
AutoQueen autoQueen,
Expand All @@ -33,9 +33,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 @@ -58,7 +58,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 @@ -95,7 +95,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 @@ -179,6 +179,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 @@ -115,7 +115,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
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,
};
}
}
138 changes: 68 additions & 70 deletions lib/src/view/analysis/analysis_share_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,80 +108,78 @@ class _EditPgnTagsFormState extends ConsumerState<_EditPgnTagsForm> {
}
}

return showRatingAsync.maybeWhen(
data: (showRatings) {
return SafeArea(
child: Padding(
padding: Styles.verticalBodyPadding,
child: ListView(
children: [
Column(
children:
pgnHeaders.entries
.where((e) => showRatings || !_ratingHeaders.contains(e.key))
.mapIndexed((index, e) {
return _EditablePgnField(
entry: e,
controller: _controllers[e.key]!,
focusNode: _focusNodes[e.key]!,
onTap: () {
_controllers[e.key]!.selection = TextSelection(
baseOffset: 0,
extentOffset: _controllers[e.key]!.text.length,
return switch (showRatingAsync) {
AsyncData(:final value) => SafeArea(
child: Padding(
padding: Styles.verticalBodyPadding,
child: ListView(
children: [
Column(
children:
pgnHeaders.entries
.where((e) => value != ShowRatings.no || !_ratingHeaders.contains(e.key))
.mapIndexed((index, e) {
return _EditablePgnField(
entry: e,
controller: _controllers[e.key]!,
focusNode: _focusNodes[e.key]!,
onTap: () {
_controllers[e.key]!.selection = TextSelection(
baseOffset: 0,
extentOffset: _controllers[e.key]!.text.length,
);
if (e.key == 'Result') {
_showResultChoicePicker(
e,
context: context,
onEntryChanged: () {
focusAndSelectNextField(index, pgnHeaders);
},
);
if (e.key == 'Result') {
_showResultChoicePicker(
e,
context: context,
onEntryChanged: () {
focusAndSelectNextField(index, pgnHeaders);
},
);
} else if (e.key == 'Date') {
_showDatePicker(
e,
context: context,
onEntryChanged: () {
focusAndSelectNextField(index, pgnHeaders);
},
);
}
},
onSubmitted: (value) {
ref.read(ctrlProvider.notifier).updatePgnHeader(e.key, value);
focusAndSelectNextField(index, pgnHeaders);
},
);
})
.toList(),
),
Padding(
padding: const EdgeInsets.all(24.0),
child: Builder(
builder: (context) {
return FatButton(
semanticsLabel: 'Share PGN',
onPressed: () {
launchShareDialog(
context,
text:
ref
.read(analysisControllerProvider(widget.options).notifier)
.makeExportPgn(),
} else if (e.key == 'Date') {
_showDatePicker(
e,
context: context,
onEntryChanged: () {
focusAndSelectNextField(index, pgnHeaders);
},
);
}
},
onSubmitted: (value) {
ref.read(ctrlProvider.notifier).updatePgnHeader(e.key, value);
focusAndSelectNextField(index, pgnHeaders);
},
);
},
child: Text(context.l10n.mobileShareGamePGN),
);
},
),
})
.toList(),
),
Padding(
padding: const EdgeInsets.all(24.0),
child: Builder(
builder: (context) {
return FatButton(
semanticsLabel: 'Share PGN',
onPressed: () {
launchShareDialog(
context,
text:
ref
.read(analysisControllerProvider(widget.options).notifier)
.makeExportPgn(),
);
},
child: Text(context.l10n.mobileShareGamePGN),
);
},
),
],
),
),
],
),
);
},
orElse: () => const SizedBox.shrink(),
);
),
),
_ => const SizedBox.shrink(),
};
}

Future<void> _showDatePicker(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ class _BodyState extends ConsumerState<_Body> {

final black = GamePlayer(
player: game.black,
isActiveGameOfCurrentUser: !game.finished,
materialDiff: materialDifference.visible ? game.materialDiffAt(stepCursor, Side.black) : null,
materialDifferenceFormat: materialDifference,
shouldLinkToUserProfile: false,
Expand All @@ -153,6 +154,7 @@ class _BodyState extends ConsumerState<_Body> {
);
final white = GamePlayer(
player: game.white,
isActiveGameOfCurrentUser: !game.finished,
materialDiff: materialDifference.visible ? game.materialDiffAt(stepCursor, Side.white) : null,
materialDifferenceFormat: materialDifference,
shouldLinkToUserProfile: false,
Expand Down
14 changes: 12 additions & 2 deletions lib/src/view/game/archived_game_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,16 @@ class _BoardBody extends ConsumerWidget {

final isBoardTurned = ref.watch(isBoardTurnedProvider);
final gameCursor = ref.watch(gameCursorProvider(gameData.id));
final black = GamePlayer(key: const ValueKey('black-player'), player: gameData.black);
final white = GamePlayer(key: const ValueKey('white-player'), player: gameData.white);
final black = GamePlayer(
key: const ValueKey('black-player'),
player: gameData.black,
isActiveGameOfCurrentUser: false,
);
final white = GamePlayer(
key: const ValueKey('white-player'),
player: gameData.white,
isActiveGameOfCurrentUser: false,
);
final topPlayer = orientation == Side.white ? black : white;
final bottomPlayer = orientation == Side.white ? white : black;
final loadingBoard = BoardTable(
Expand All @@ -205,12 +213,14 @@ class _BoardBody extends ConsumerWidget {
final black = GamePlayer(
key: const ValueKey('black-player'),
player: gameData.black,
isActiveGameOfCurrentUser: false,
clock: blackClock != null ? Clock(timeLeft: blackClock) : null,
materialDiff: game.materialDiffAt(cursor, Side.black),
);
final white = GamePlayer(
key: const ValueKey('white-player'),
player: gameData.white,
isActiveGameOfCurrentUser: false,
clock: whiteClock != null ? Clock(timeLeft: whiteClock) : null,
materialDiff: game.materialDiffAt(cursor, Side.white),
);
Expand Down
2 changes: 2 additions & 0 deletions lib/src/view/game/game_body.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ class GameBody extends ConsumerWidget {

final black = GamePlayer(
player: gameState.game.black,
isActiveGameOfCurrentUser: !gameState.game.finished,
materialDiff:
boardPreferences.materialDifferenceFormat.visible
? gameState.game.materialDiffAt(gameState.stepCursor, Side.black)
Expand Down Expand Up @@ -159,6 +160,7 @@ class GameBody extends ConsumerWidget {
);
final white = GamePlayer(
player: gameState.game.white,
isActiveGameOfCurrentUser: !gameState.game.finished,
materialDiff:
boardPreferences.materialDifferenceFormat.visible
? gameState.game.materialDiffAt(gameState.stepCursor, Side.white)
Expand Down
6 changes: 6 additions & 0 deletions lib/src/view/game/game_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import 'package:lichess_mobile/src/widgets/buttons.dart';
class GamePlayer extends StatelessWidget {
const GamePlayer({
required this.player,
required this.isActiveGameOfCurrentUser,
this.clock,
this.materialDiff,
this.materialDifferenceFormat,
Expand All @@ -40,6 +41,10 @@ class GamePlayer extends StatelessWidget {
});

final Player player;

/// Whether we're displaying this player as part of an active game of the current user.
/// This might influence whether their rating is shown or not, based on the [ShowRatings] setting.
final bool isActiveGameOfCurrentUser;
final Widget? clock;
final MaterialDiffSide? materialDiff;
final MaterialDifferenceFormat? materialDifferenceFormat;
Expand Down Expand Up @@ -119,6 +124,7 @@ class GamePlayer extends StatelessWidget {
],
if (player.rating != null)
RatingPrefAware(
isActiveGameOfCurrentUser: isActiveGameOfCurrentUser,
child: Text.rich(
TextSpan(
text: ' ${player.rating}${player.provisional == true ? '?' : ''}',
Expand Down
1 change: 1 addition & 0 deletions lib/src/view/over_the_board/over_the_board_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ class _Player extends ConsumerWidget {
return RotatedBox(
quarterTurns: upsideDown ? 2 : 0,
child: GamePlayer(
isActiveGameOfCurrentUser: true,
player: Player(
onGame: true,
user: LightUser(id: UserId(side.name), name: side.name.capitalize()),
Expand Down
Loading

0 comments on commit 79c5961

Please sign in to comment.