Skip to content

Commit

Permalink
Merge pull request #317 from lichess-org/account_preferences
Browse files Browse the repository at this point in the history
Account game preferences
  • Loading branch information
veloce authored Oct 2, 2023
2 parents 14b8e76 + 694a7a0 commit 731afd4
Show file tree
Hide file tree
Showing 24 changed files with 1,622 additions and 293 deletions.
319 changes: 319 additions & 0 deletions lib/src/model/account/account_preferences.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
import 'package:flutter/widgets.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:result_extensions/result_extensions.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';

import 'package:lichess_mobile/src/utils/l10n_context.dart';
import 'package:lichess_mobile/src/model/auth/auth_session.dart';

import 'account_repository.dart';

part 'account_preferences.g.dart';

typedef AccountPrefState = ({
BooleanPref premove,
AutoQueen autoQueen,
AutoThreefold autoThreefold,
Takeback takeback,
Moretime moretime,
BooleanPref confirmResign,
SubmitMove submitMove,
});

/// Get the account preferences for the current user.
///
/// The result is cached for the lifetime of the app, until refreshed.
/// If the server returns an error, default values are returned.
@Riverpod(keepAlive: true)
class AccountPreferences extends _$AccountPreferences {
@override
Future<AccountPrefState?> build() async {
final session = ref.watch(authSessionProvider);

if (session == null) {
return null;
}

return _repo.getPreferences().fold(
(value) => value,
(_, __) => (
premove: const BooleanPref(true),
autoQueen: AutoQueen.premove,
autoThreefold: AutoThreefold.always,
takeback: Takeback.always,
moretime: Moretime.always,
confirmResign: const BooleanPref(true),
submitMove: SubmitMove({
SubmitMoveChoice.correspondence,
}),
),
);
}

Future<void> setPremove(BooleanPref value) => _setPref('premove', value);
Future<void> setTakeback(Takeback value) => _setPref('takeback', value);
Future<void> setAutoQueen(AutoQueen value) => _setPref('autoQueen', value);
Future<void> setAutoThreefold(AutoThreefold value) =>
_setPref('autoThreefold', value);
Future<void> setMoretime(Moretime value) => _setPref('moretime', value);
Future<void> setConfirmResign(BooleanPref value) =>
_setPref('confirmResign', value);
Future<void> setSubmitMove(SubmitMove value) => _setPref('submitMove', value);

Future<void> _setPref<T>(String key, AccountPref<T> value) async {
await _repo.setPreference(key, value);
ref.invalidateSelf();
}

AccountRepository get _repo => ref.read(accountRepositoryProvider);
}

abstract class AccountPref<T> {
T get value;
String get toFormData;
}

class BooleanPref implements AccountPref<bool> {
const BooleanPref(this.value);

@override
final bool value;

@override
String get toFormData => value ? '1' : '0';

static BooleanPref fromInt(int value) {
switch (value) {
case 1:
return const BooleanPref(true);
case 0:
return const BooleanPref(false);
default:
throw Exception('Invalid value for BooleanPref');
}
}
}

enum AutoQueen implements AccountPref<int> {
never(1),
premove(2),
always(3);

const AutoQueen(this.value);

@override
final int value;

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

String label(BuildContext context) {
switch (this) {
case AutoQueen.never:
return context.l10n.never;
case AutoQueen.premove:
return context.l10n.preferencesWhenPremoving;
case AutoQueen.always:
return context.l10n.always;
}
}

static AutoQueen fromInt(int value) {
switch (value) {
case 1:
return AutoQueen.never;
case 2:
return AutoQueen.premove;
case 3:
return AutoQueen.always;
default:
throw Exception('Invalid value for AutoQueen');
}
}
}

enum AutoThreefold implements AccountPref<int> {
never(1),
time(2),
always(3);

const AutoThreefold(this.value);

@override
final int value;

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

String label(BuildContext context) {
switch (this) {
case AutoThreefold.never:
return context.l10n.never;
case AutoThreefold.time:
return context.l10n.preferencesWhenTimeRemainingLessThanThirtySeconds;
case AutoThreefold.always:
return context.l10n.always;
}
}

static AutoThreefold fromInt(int value) {
switch (value) {
case 1:
return AutoThreefold.never;
case 2:
return AutoThreefold.time;
case 3:
return AutoThreefold.always;
default:
throw Exception('Invalid value for AutoThreefold');
}
}
}

enum Takeback implements AccountPref<int> {
never(1),
casual(2),
always(3);

const Takeback(this.value);

@override
final int value;

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

String label(BuildContext context) {
switch (this) {
case Takeback.never:
return context.l10n.never;
case Takeback.casual:
return context.l10n.preferencesInCasualGamesOnly;
case Takeback.always:
return context.l10n.always;
}
}

static Takeback fromInt(int value) {
switch (value) {
case 1:
return Takeback.never;
case 2:
return Takeback.casual;
case 3:
return Takeback.always;
default:
throw Exception('Invalid value for Takeback');
}
}
}

enum Moretime implements AccountPref<int> {
never(1),
casual(2),
always(3);

const Moretime(this.value);

@override
final int value;

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

String label(BuildContext context) {
switch (this) {
case Moretime.never:
return context.l10n.never;
case Moretime.casual:
return context.l10n.preferencesInCasualGamesOnly;
case Moretime.always:
return context.l10n.always;
}
}

static Moretime fromInt(int value) {
switch (value) {
case 1:
return Moretime.never;
case 2:
return Moretime.casual;
case 3:
return Moretime.always;
default:
throw Exception('Invalid value for Moretime');
}
}
}

class SubmitMove implements AccountPref<int> {
SubmitMove(Iterable<SubmitMoveChoice> choices)
: choices = ISet(choices.toSet());

final ISet<SubmitMoveChoice> choices;

@override
int get value => choices.fold(0, (acc, choice) => acc | choice.value);

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

String label(BuildContext context) {
if (choices.isEmpty) {
return context.l10n.never;
}

return choices.map((choice) => choice.label(context)).join(', ');
}

factory SubmitMove.fromInt(int value) => SubmitMove(
SubmitMoveChoice.values
.where((choice) => _bitPresent(value, choice.value)),
);
}

enum SubmitMoveChoice {
unlimited(1),
correspondence(2),
classical(4),
rapid(8),
blitz(16);

const SubmitMoveChoice(this.value);

final int value;

String label(BuildContext context) {
switch (this) {
case SubmitMoveChoice.unlimited:
return context.l10n.unlimited;
case SubmitMoveChoice.correspondence:
return context.l10n.correspondence;
case SubmitMoveChoice.classical:
return context.l10n.classical;
case SubmitMoveChoice.rapid:
return context.l10n.rapid;
case SubmitMoveChoice.blitz:
return 'Blitz';
}
}

static SubmitMoveChoice fromInt(int value) {
switch (value) {
case 1:
return SubmitMoveChoice.unlimited;
case 2:
return SubmitMoveChoice.correspondence;
case 4:
return SubmitMoveChoice.classical;
case 8:
return SubmitMoveChoice.rapid;
case 16:
return SubmitMoveChoice.blitz;
default:
throw Exception('Invalid value for SubmitMoveChoice');
}
}
}

bool _bitPresent(int anInt, int bit) => (anInt & bit) == bit;
52 changes: 52 additions & 0 deletions lib/src/model/account/account_repository.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:logging/logging.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:result_extensions/result_extensions.dart';
import 'package:deep_pick/deep_pick.dart';

import 'package:lichess_mobile/src/constants.dart';
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
Expand All @@ -9,6 +10,8 @@ import 'package:lichess_mobile/src/model/user/user.dart';
import 'package:lichess_mobile/src/utils/json.dart';
import 'package:lichess_mobile/src/utils/riverpod.dart';

import 'account_preferences.dart';

part 'account_repository.g.dart';

@Riverpod(keepAlive: true)
Expand Down Expand Up @@ -56,4 +59,53 @@ class AccountRepository {
),
);
}

FutureResult<AccountPrefState> getPreferences() {
return _apiClient
.get(Uri.parse('$kLichessHost/api/account/preferences'))
.then(
(result) => result.flatMap(
(response) => readJsonObject(
response,
mapper: (Map<String, dynamic> json) {
return _accountPreferencesFromPick(
pick(json, 'prefs').required(),
);
},
logger: _log,
),
),
);
}

FutureResult<void> setPreference<T>(String prefKey, AccountPref<T> pref) {
return _apiClient.post(
Uri.parse('$kLichessHost/api/account/preferences/$prefKey'),
body: {prefKey: pref.toFormData},
);
}
}

AccountPrefState _accountPreferencesFromPick(RequiredPick pick) {
return (
premove: BooleanPref(pick('premove').asBoolOrThrow()),
autoQueen: AutoQueen.fromInt(
pick('autoQueen').asIntOrThrow(),
),
autoThreefold: AutoThreefold.fromInt(
pick('autoThreefold').asIntOrThrow(),
),
takeback: Takeback.fromInt(
pick('takeback').asIntOrThrow(),
),
moretime: Moretime.fromInt(
pick('moretime').asIntOrThrow(),
),
confirmResign: BooleanPref.fromInt(
pick('confirmResign').asIntOrThrow(),
),
submitMove: SubmitMove.fromInt(
pick('submitMove').asIntOrThrow(),
),
);
}
Loading

0 comments on commit 731afd4

Please sign in to comment.