mirror of
https://github.com/lichess-org/mobile.git
synced 2026-05-26 13:50:52 +00:00
Riverpod 3 (#2381)
This commit is contained in:
committed by
GitHub
parent
abfb523cd3
commit
696817c887
@@ -16,6 +16,7 @@ analyzer:
|
||||
strict-raw-types: true
|
||||
exclude:
|
||||
- "**/*.g.dart"
|
||||
- "**/*.freezed.dart"
|
||||
- lib/src/styles/lichess_icons.dart
|
||||
- lib/src/styles/social_icons.dart
|
||||
- lib/firebase_options.dart
|
||||
|
||||
@@ -13,11 +13,3 @@ targets:
|
||||
options:
|
||||
from_json: false
|
||||
to_json: false
|
||||
riverpod_generator:
|
||||
generate_for:
|
||||
- lib/src/localizations.dart
|
||||
- lib/src/model/**/*.dart
|
||||
- lib/src/network/*.dart
|
||||
- lib/src/db/*.dart
|
||||
- lib/src/quick_actions.dart
|
||||
- lib/src/**/*_providers.dart
|
||||
|
||||
@@ -129,9 +129,6 @@ PODS:
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- PromisesObjC (2.4.0)
|
||||
- PromisesSwift (2.4.0):
|
||||
- PromisesObjC (= 2.4.0)
|
||||
@@ -171,7 +168,6 @@ DEPENDENCIES:
|
||||
- multistockfish_variant (from `.symlinks/plugins/multistockfish_variant/ios`)
|
||||
- objective_c (from `.symlinks/plugins/objective_c/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- quick_actions_ios (from `.symlinks/plugins/quick_actions_ios/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
@@ -235,8 +231,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/objective_c/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
quick_actions_ios:
|
||||
:path: ".symlinks/plugins/quick_actions_ios/ios"
|
||||
share_plus:
|
||||
@@ -284,7 +278,6 @@ SPEC CHECKSUMS:
|
||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
||||
quick_actions_ios: 500fcc11711d9f646739093395c4ae8eec25f779
|
||||
|
||||
@@ -5,9 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
part 'database.g.dart';
|
||||
|
||||
const kLichessDatabaseName = 'lichess_mobile.db';
|
||||
|
||||
@@ -21,14 +19,14 @@ const kStorageAnonId = '**anonymous**';
|
||||
|
||||
final _logger = Logger('Database');
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<Database> database(Ref ref) async {
|
||||
/// A provider for the app [Database].
|
||||
final databaseProvider = FutureProvider<Database>((Ref ref) async {
|
||||
if (Platform.isLinux) {
|
||||
databaseFactory = databaseFactoryFfi;
|
||||
}
|
||||
final dbPath = await _databasePath;
|
||||
return openAppDatabase(databaseFactory, dbPath);
|
||||
}
|
||||
}, name: 'DatabaseProvider');
|
||||
|
||||
/// Returns the database path including filename.
|
||||
Future<String> get _databasePath async {
|
||||
@@ -41,11 +39,10 @@ Future<String> get _databasePath async {
|
||||
}
|
||||
|
||||
/// Returns the sqlite version as an integer.
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<int?> sqliteVersion(Ref ref) async {
|
||||
final sqliteVersionProvider = FutureProvider<int?>((Ref ref) async {
|
||||
final db = await ref.read(databaseProvider.future);
|
||||
return _getDatabaseVersion(db);
|
||||
}
|
||||
}, name: 'SqliteVersionProvider');
|
||||
|
||||
Future<int?> _getDatabaseVersion(Database db) async {
|
||||
try {
|
||||
@@ -57,13 +54,13 @@ Future<int?> _getDatabaseVersion(Database db) async {
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<int> getDbSizeInBytes(Ref ref) async {
|
||||
/// A provider that returns the size of the database file in bytes.
|
||||
final getDbSizeInBytesProvider = FutureProvider<int>((Ref ref) async {
|
||||
final dbPath = join(await getDatabasesPath(), kLichessDatabaseName);
|
||||
final dbFile = File(dbPath);
|
||||
|
||||
return dbFile.length();
|
||||
}
|
||||
}, name: 'GetDbSizeInBytesProvider');
|
||||
|
||||
/// Opens the app database.
|
||||
Future<Database> openAppDatabase(DatabaseFactory dbFactory, String path) {
|
||||
|
||||
@@ -3,22 +3,19 @@ import 'dart:io';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
part 'openings_database.g.dart';
|
||||
|
||||
// The dataset is from https://github.com/lichess-org/chess-openings
|
||||
// It can be updated by running the script at scripts/update_openings_db.py
|
||||
|
||||
const _kDatabaseVersion = 3;
|
||||
const _kDatabaseName = 'chess_openings$_kDatabaseVersion.db';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<Database> openingsDatabase(Ref ref) async {
|
||||
/// A provider for the openings database.
|
||||
final openingsDatabaseProvider = FutureProvider<Database>((Ref ref) async {
|
||||
final dbPath = p.join(await getDatabasesPath(), _kDatabaseName);
|
||||
return _openDb(dbPath);
|
||||
}
|
||||
}, name: 'OpeningsDatabaseProvider');
|
||||
|
||||
Future<Database> _openDb(String path) async {
|
||||
final exists = await databaseExists(path);
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/l10n/l10n.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/general_preferences.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'localizations.g.dart';
|
||||
|
||||
typedef ActiveLocalizations = ({Locale locale, AppLocalizations strings});
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class Localizations extends _$Localizations {
|
||||
/// A provider for [Localizations].
|
||||
final localizationsProvider = NotifierProvider<Localizations, ActiveLocalizations>(
|
||||
Localizations.new,
|
||||
name: 'LocalizationsProvider',
|
||||
);
|
||||
|
||||
class Localizations extends Notifier<ActiveLocalizations> {
|
||||
@override
|
||||
ActiveLocalizations build() {
|
||||
final generalPrefs = ref.watch(generalPreferencesProvider);
|
||||
|
||||
+11
-12
@@ -27,26 +27,25 @@ void setupLogging() {
|
||||
}
|
||||
}
|
||||
|
||||
class ProviderLogger extends ProviderObserver {
|
||||
final class ProviderLogger extends ProviderObserver {
|
||||
final _logger = Logger('Provider');
|
||||
|
||||
@override
|
||||
void didAddProvider(ProviderBase<Object?> provider, Object? value, ProviderContainer container) {
|
||||
_logger.info('${provider.name ?? provider.runtimeType} initialized', value);
|
||||
void didAddProvider(ProviderObserverContext context, Object? value) {
|
||||
_logger.info('${context.provider.name ?? context.provider.runtimeType} initialized', value);
|
||||
}
|
||||
|
||||
@override
|
||||
void didDisposeProvider(ProviderBase<Object?> provider, ProviderContainer container) {
|
||||
_logger.info('${provider.name ?? provider.runtimeType} disposed');
|
||||
void didDisposeProvider(ProviderObserverContext context) {
|
||||
_logger.info('${context.provider.name ?? context.provider.runtimeType} disposed');
|
||||
}
|
||||
|
||||
@override
|
||||
void providerDidFail(
|
||||
ProviderBase<Object?> provider,
|
||||
Object error,
|
||||
StackTrace stackTrace,
|
||||
ProviderContainer container,
|
||||
) {
|
||||
_logger.severe('${provider.name ?? provider.runtimeType} error', error, stackTrace);
|
||||
void providerDidFail(ProviderObserverContext context, Object error, StackTrace stackTrace) {
|
||||
_logger.severe(
|
||||
'${context.provider.name ?? context.provider.runtimeType} error',
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/l10n/l10n.dart';
|
||||
import 'package:lichess_mobile/src/model/account/account_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'account_preferences.g.dart';
|
||||
|
||||
typedef AccountPrefState = ({
|
||||
// game display
|
||||
@@ -30,28 +27,22 @@ typedef AccountPrefState = ({
|
||||
});
|
||||
|
||||
/// A provider that tells if the user wants to see ratings in the app.
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<ShowRatings> showRatingsPref(Ref ref) async {
|
||||
return ref.watch(
|
||||
accountPreferencesProvider.selectAsync((state) => state?.showRatings ?? ShowRatings.yes),
|
||||
);
|
||||
}
|
||||
final showRatingsPrefProvider = FutureProvider<ShowRatings>((Ref ref) async {
|
||||
final prefs = await ref.watch(accountPreferencesProvider.future);
|
||||
return prefs?.showRatings ?? defaultAccountPreferences.showRatings;
|
||||
});
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<bool> clockSound(Ref ref) async {
|
||||
return ref.watch(
|
||||
accountPreferencesProvider.selectAsync((state) => state?.clockSound.value ?? true),
|
||||
);
|
||||
}
|
||||
/// A provider that tells if the user wants clock sounds.
|
||||
final clockSoundProvider = FutureProvider<bool>((Ref ref) async {
|
||||
final prefs = await ref.watch(accountPreferencesProvider.future);
|
||||
return prefs?.clockSound.value ?? defaultAccountPreferences.clockSound.value;
|
||||
});
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<PieceNotation> pieceNotation(Ref ref) async {
|
||||
return ref.watch(
|
||||
accountPreferencesProvider.selectAsync(
|
||||
(state) => state?.pieceNotation ?? defaultAccountPreferences.pieceNotation,
|
||||
),
|
||||
);
|
||||
}
|
||||
/// A provider that gives the user's preferred piece notation.
|
||||
final pieceNotationProvider = FutureProvider<PieceNotation>((Ref ref) async {
|
||||
final prefs = await ref.watch(accountPreferencesProvider.future);
|
||||
return prefs?.pieceNotation ?? defaultAccountPreferences.pieceNotation;
|
||||
});
|
||||
|
||||
final defaultAccountPreferences = (
|
||||
zenMode: Zen.no,
|
||||
@@ -70,12 +61,17 @@ final defaultAccountPreferences = (
|
||||
message: Message.always,
|
||||
);
|
||||
|
||||
/// A provider that gives the account preferences for the current user.
|
||||
final accountPreferencesProvider = AsyncNotifierProvider<AccountPreferences, AccountPrefState?>(
|
||||
AccountPreferences.new,
|
||||
name: 'AccountPreferencesProvider',
|
||||
);
|
||||
|
||||
/// 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 {
|
||||
class AccountPreferences extends AsyncNotifier<AccountPrefState?> {
|
||||
@override
|
||||
Future<AccountPrefState?> build() async {
|
||||
final session = ref.watch(authSessionProvider);
|
||||
|
||||
@@ -17,10 +17,12 @@ final accountProvider = FutureProvider.autoDispose<User?>((Ref ref) {
|
||||
}, name: 'AccountProvider');
|
||||
|
||||
/// A provider that fetches the current user's kid mode status.
|
||||
final kidModeProvider = FutureProvider.autoDispose<bool>((ref) {
|
||||
return ref.watch(accountProvider.selectAsync((user) => user?.kid ?? false));
|
||||
final kidModeProvider = FutureProvider.autoDispose<bool>((ref) async {
|
||||
final account = await ref.watch(accountProvider.future);
|
||||
return account?.kid ?? false;
|
||||
}, name: 'KidModeProvider');
|
||||
|
||||
/// A provider for the [AccountRepository].
|
||||
final accountRepositoryProvider = Provider<AccountRepository>((ref) {
|
||||
final client = ref.read(lichessClientProvider);
|
||||
final aggregator = ref.read(aggregatorProvider);
|
||||
|
||||
@@ -14,18 +14,15 @@ import 'package:lichess_mobile/src/model/user/user.dart' show TemporaryBan, User
|
||||
import 'package:lichess_mobile/src/tab_scaffold.dart' show currentNavigatorKeyProvider;
|
||||
import 'package:lichess_mobile/src/view/play/playban.dart';
|
||||
import 'package:lichess_mobile/src/widgets/platform_alert_dialog.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'account_service.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
AccountService accountService(Ref ref) {
|
||||
/// A provider for [AccountService].
|
||||
final accountServiceProvider = Provider<AccountService>((Ref ref) {
|
||||
final service = AccountService(ref);
|
||||
ref.onDispose(() {
|
||||
service.dispose();
|
||||
});
|
||||
return service;
|
||||
}
|
||||
}, name: 'AccountServiceProvider');
|
||||
|
||||
class AccountService {
|
||||
AccountService(this._ref);
|
||||
@@ -48,7 +45,7 @@ class AccountService {
|
||||
final prefs = LichessBinding.instance.sharedPreferences;
|
||||
|
||||
_accountProviderSubscription = _ref.listen(accountProvider, (_, account) {
|
||||
final playban = account.valueOrNull?.playban;
|
||||
final playban = account.value?.playban;
|
||||
final storedDate = prefs.getString(_storageKey);
|
||||
final lastPlaybanNotificationDate = storedDate != null ? DateTime.parse(storedDate) : null;
|
||||
|
||||
|
||||
@@ -2,18 +2,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/utils/lichess_assets.dart';
|
||||
import 'package:lichess_mobile/src/widgets/emoji_picker/emoji_picker_models.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'flair_provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<EmojiData> flairList(Ref ref) async {
|
||||
/// A provider that fetches the list of available flairs.
|
||||
final flairListProvider = FutureProvider<EmojiData>((Ref ref) async {
|
||||
final list = await ref
|
||||
.read(defaultClientProvider)
|
||||
.read(Uri.parse(lichessAssetUrl('flair/list.txt')));
|
||||
final data = _makeEmojiData(list);
|
||||
return EmojiData.fromJson(data);
|
||||
}
|
||||
}, name: 'FlairListProvider');
|
||||
|
||||
const categories = [
|
||||
['smileys', 'Smileys'],
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/account/home_widgets.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'home_preferences.freezed.dart';
|
||||
part 'home_preferences.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class HomePreferences extends _$HomePreferences with SessionPreferencesStorage<HomePrefs> {
|
||||
final homePreferencesProvider = NotifierProvider<HomePreferences, HomePrefs>(
|
||||
HomePreferences.new,
|
||||
name: 'HomePreferencesProvider',
|
||||
);
|
||||
|
||||
class HomePreferences extends Notifier<HomePrefs> with SessionPreferencesStorage<HomePrefs> {
|
||||
@override
|
||||
@protected
|
||||
PrefCategory get prefCategory => PrefCategory.home;
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:collection/collection.dart';
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:lichess_mobile/src/model/account/account_service.dart';
|
||||
@@ -34,10 +35,8 @@ import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:lichess_mobile/src/view/engine/engine_gauge.dart';
|
||||
import 'package:lichess_mobile/src/widgets/pgn.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'analysis_controller.freezed.dart';
|
||||
part 'analysis_controller.g.dart';
|
||||
|
||||
final _dateFormat = DateFormat('yyyy.MM.dd');
|
||||
|
||||
@@ -104,10 +103,20 @@ enum AnalysisGameResult {
|
||||
};
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class AnalysisController extends _$AnalysisController
|
||||
/// A provider for [AnalysisController].
|
||||
final analysisControllerProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<AnalysisController, AnalysisState, AnalysisOptions>(
|
||||
AnalysisController.new,
|
||||
name: 'AnalysisControllerProvider',
|
||||
);
|
||||
|
||||
class AnalysisController extends AsyncNotifier<AnalysisState>
|
||||
with EngineEvaluationMixin
|
||||
implements PgnTreeNotifier {
|
||||
AnalysisController(this.options);
|
||||
|
||||
final AnalysisOptions options;
|
||||
|
||||
static final Uri socketUri = Uri(path: '/analysis/socket/v5');
|
||||
|
||||
StreamSubscription<SocketEvent>? _socketSubscription;
|
||||
@@ -143,7 +152,7 @@ class AnalysisController extends _$AnalysisController
|
||||
GameRepository get _gameRepository => ref.read(gameRepositoryProvider);
|
||||
|
||||
@override
|
||||
Future<AnalysisState> build(AnalysisOptions options) async {
|
||||
Future<AnalysisState> build() async {
|
||||
final serverAnalysisService = ref.watch(serverAnalysisServiceProvider);
|
||||
|
||||
ref.onDispose(() {
|
||||
@@ -173,7 +182,7 @@ class AnalysisController extends _$AnalysisController
|
||||
switch (options) {
|
||||
case ArchivedGame(:final gameId):
|
||||
{
|
||||
archivedGame = await ref.read(archivedGameProvider(id: gameId).future);
|
||||
archivedGame = await ref.read(archivedGameProvider(gameId).future);
|
||||
_variant = archivedGame!.meta.variant;
|
||||
if (!_variant.isReadSupported) {
|
||||
throw UnsupportedVariantException(_variant, gameId);
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/analysis/common_analysis_prefs.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'analysis_preferences.freezed.dart';
|
||||
part 'analysis_preferences.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class AnalysisPreferences extends _$AnalysisPreferences with PreferencesStorage<AnalysisPrefs> {
|
||||
final analysisPreferencesProvider = NotifierProvider<AnalysisPreferences, AnalysisPrefs>(
|
||||
AnalysisPreferences.new,
|
||||
name: 'AnalysisPreferencesProvider',
|
||||
);
|
||||
|
||||
class AnalysisPreferences extends Notifier<AnalysisPrefs> with PreferencesStorage<AnalysisPrefs> {
|
||||
@override
|
||||
@protected
|
||||
final prefCategory = PrefCategory.analysis;
|
||||
|
||||
@@ -2,11 +2,8 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/db/openings_database.dart';
|
||||
import 'package:lichess_mobile/src/model/common/chess.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
part 'opening_service.g.dart';
|
||||
|
||||
const kOpeningAllowedVariants = ISetConst({
|
||||
Variant.standard,
|
||||
Variant.kingOfTheHill,
|
||||
@@ -14,10 +11,10 @@ const kOpeningAllowedVariants = ISetConst({
|
||||
Variant.crazyhouse,
|
||||
});
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
OpeningService openingService(Ref ref) {
|
||||
/// A provider for [OpeningService].
|
||||
final openingServiceProvider = Provider<OpeningService>((Ref ref) {
|
||||
return OpeningService(ref);
|
||||
}
|
||||
}, name: 'OpeningServiceProvider');
|
||||
|
||||
class OpeningService {
|
||||
OpeningService(this._ref);
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:math';
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
|
||||
import 'package:lichess_mobile/src/model/analysis/common_analysis_state.dart';
|
||||
@@ -25,10 +26,8 @@ import 'package:lichess_mobile/src/model/game/game_repository_providers.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:lichess_mobile/src/view/engine/engine_gauge.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'retro_controller.freezed.dart';
|
||||
part 'retro_controller.g.dart';
|
||||
|
||||
typedef RetroOptions = ({GameId id, Side initialSide});
|
||||
|
||||
@@ -74,8 +73,18 @@ const double _kCorrectMovePovDiffThreshold = -0.04;
|
||||
/// Threshold for considering a move a mistake due to how it affected the evaluation.
|
||||
const double _kEvalSwingThreshold = 0.1;
|
||||
|
||||
@riverpod
|
||||
class RetroController extends _$RetroController with EngineEvaluationMixin {
|
||||
/// A provider for [RetroController].
|
||||
final retroControllerProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<RetroController, RetroState, RetroOptions>(
|
||||
RetroController.new,
|
||||
name: 'RetroControllerProvider',
|
||||
);
|
||||
|
||||
class RetroController extends AsyncNotifier<RetroState> with EngineEvaluationMixin {
|
||||
RetroController(this.options);
|
||||
|
||||
final RetroOptions options;
|
||||
|
||||
late Root _root;
|
||||
|
||||
late ExportedGame _game;
|
||||
@@ -109,7 +118,7 @@ class RetroController extends _$RetroController with EngineEvaluationMixin {
|
||||
Node get positionTree => _root;
|
||||
|
||||
@override
|
||||
Future<RetroState> build(RetroOptions options) async {
|
||||
Future<RetroState> build() async {
|
||||
final serverAnalysisService = ref.watch(serverAnalysisServiceProvider);
|
||||
|
||||
ref.onDispose(() {
|
||||
@@ -119,7 +128,7 @@ class RetroController extends _$RetroController with EngineEvaluationMixin {
|
||||
|
||||
socketClient = ref.watch(socketPoolProvider).open(AnalysisController.socketUri);
|
||||
|
||||
_game = await ref.read(archivedGameProvider(id: options.id).future);
|
||||
_game = await ref.read(archivedGameProvider(options.id).future);
|
||||
|
||||
if (engineSupportedVariants.contains(_game.meta.variant) == false) {
|
||||
throw Exception('Variant ${_game.meta.variant} is not supported for retro mode');
|
||||
@@ -270,7 +279,7 @@ class RetroController extends _$RetroController with EngineEvaluationMixin {
|
||||
}
|
||||
|
||||
void onPromotionSelection(Role? role) {
|
||||
final state = this.state.valueOrNull;
|
||||
final state = this.state.value;
|
||||
if (state == null) return;
|
||||
|
||||
if (role == null) {
|
||||
@@ -297,7 +306,7 @@ class RetroController extends _$RetroController with EngineEvaluationMixin {
|
||||
}
|
||||
|
||||
void viewSolution() {
|
||||
final currentMistake = state.valueOrNull?.currentMistake;
|
||||
final currentMistake = state.value?.currentMistake;
|
||||
if (currentMistake != null) {
|
||||
onUserMove(currentMistake.serverMove);
|
||||
state = AsyncValue.data(state.requireValue.copyWith(feedback: RetroFeedback.viewingSolution));
|
||||
@@ -352,7 +361,7 @@ class RetroController extends _$RetroController with EngineEvaluationMixin {
|
||||
/// Whether the user is navigating through the moves (as opposed to playing a move).
|
||||
bool isNavigating = false,
|
||||
}) {
|
||||
final state = this.state.valueOrNull;
|
||||
final state = this.state.value;
|
||||
if (state == null) return;
|
||||
|
||||
final pathChange = state.currentPath != path;
|
||||
|
||||
@@ -15,16 +15,13 @@ import 'package:lichess_mobile/src/model/game/game_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/game/game_socket_events.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'server_analysis_service.g.dart';
|
||||
|
||||
const Duration kMaxWaitForServerAnalysis = Duration(minutes: 1);
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
ServerAnalysisService serverAnalysisService(Ref ref) {
|
||||
/// A provider for [ServerAnalysisService].
|
||||
final serverAnalysisServiceProvider = Provider<ServerAnalysisService>((Ref ref) {
|
||||
return ServerAnalysisService(ref);
|
||||
}
|
||||
}, name: 'ServerAnalysisServiceProvider');
|
||||
|
||||
class ServerAnalysisService {
|
||||
ServerAnalysisService(this.ref);
|
||||
@@ -177,8 +174,13 @@ class ServerAnalysisService {
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class CurrentAnalysis extends _$CurrentAnalysis {
|
||||
/// A provider that exposes the current game being analyzed by the server.
|
||||
final currentAnalysisProvider = NotifierProvider.autoDispose<CurrentAnalysis, GameId?>(
|
||||
CurrentAnalysis.new,
|
||||
name: 'CurrentAnalysisProvider',
|
||||
);
|
||||
|
||||
class CurrentAnalysis extends Notifier<GameId?> {
|
||||
@override
|
||||
GameId? build() {
|
||||
final listenable = ref.watch(serverAnalysisServiceProvider).currentAnalysis;
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
import 'package:lichess_mobile/src/model/notifications/notification_service.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'auth_controller.g.dart';
|
||||
/// A provider for [AuthController].
|
||||
final authControllerProvider = AsyncNotifierProvider.autoDispose<AuthController, void>(
|
||||
AuthController.new,
|
||||
name: 'AuthControllerProvider',
|
||||
);
|
||||
|
||||
@riverpod
|
||||
class AuthController extends _$AuthController {
|
||||
class AuthController extends AsyncNotifier<void> {
|
||||
@override
|
||||
AsyncValue<void> build() {
|
||||
return const AsyncValue.data(null);
|
||||
Future<void> build() {
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
Future<void> signIn() async {
|
||||
|
||||
@@ -7,17 +7,14 @@ import 'package:lichess_mobile/src/model/auth/bearer.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'auth_repository.g.dart';
|
||||
|
||||
const redirectUri = 'org.lichess.mobile://login-callback';
|
||||
const oauthScopes = ['web:mobile'];
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
FlutterAppAuth appAuth(Ref ref) {
|
||||
/// A provider for [FlutterAppAuth].
|
||||
final appAuthProvider = Provider<FlutterAppAuth>((Ref ref) {
|
||||
return const FlutterAppAuth();
|
||||
}
|
||||
}, name: 'AppAuthProvider');
|
||||
|
||||
class AuthRepository {
|
||||
AuthRepository(LichessClient client, FlutterAppAuth appAuth)
|
||||
|
||||
@@ -3,13 +3,22 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/session_storage.dart';
|
||||
import 'package:lichess_mobile/src/model/common/preloaded_data.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'auth_session.freezed.dart';
|
||||
part 'auth_session.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class AuthSession extends _$AuthSession {
|
||||
/// A provider for [AuthSession].
|
||||
final authSessionProvider = NotifierProvider<AuthSession, AuthSessionState?>(
|
||||
AuthSession.new,
|
||||
name: 'AuthSessionProvider',
|
||||
);
|
||||
|
||||
/// A provider that indicates whether the user is logged in.
|
||||
final isLoggedInProvider = Provider.autoDispose<bool>((Ref ref) {
|
||||
return ref.watch(authSessionProvider.select((authSession) => authSession != null));
|
||||
}, name: 'IsLoggedInProvider');
|
||||
|
||||
class AuthSession extends Notifier<AuthSessionState?> {
|
||||
@override
|
||||
AuthSessionState? build() {
|
||||
return ref.read(preloadedDataProvider).requireValue.userSession;
|
||||
@@ -35,8 +44,3 @@ sealed class AuthSessionState with _$AuthSessionState {
|
||||
|
||||
factory AuthSessionState.fromJson(Map<String, dynamic> json) => _$AuthSessionStateFromJson(json);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
bool isLoggedIn(Ref ref) {
|
||||
return ref.watch(authSessionProvider.select((authSession) => authSession != null));
|
||||
}
|
||||
|
||||
@@ -4,16 +4,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/constants.dart';
|
||||
import 'package:lichess_mobile/src/db/secure_storage.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'session_storage.g.dart';
|
||||
|
||||
const kSessionStorageKey = '$kLichessHost.userSession';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
SessionStorage sessionStorage(Ref ref) {
|
||||
/// A provider for [SessionStorage].
|
||||
final sessionStorageProvider = Provider<SessionStorage>((Ref ref) {
|
||||
return const SessionStorage();
|
||||
}
|
||||
}, name: 'SessionStorageProvider');
|
||||
|
||||
class SessionStorage {
|
||||
const SessionStorage();
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
import 'package:chessground/chessground.dart';
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'board_editor_controller.freezed.dart';
|
||||
part 'board_editor_controller.g.dart';
|
||||
|
||||
@riverpod
|
||||
class BoardEditorController extends _$BoardEditorController {
|
||||
/// A provider for [BoardEditorController].
|
||||
final boardEditorControllerProvider = NotifierProvider.autoDispose
|
||||
.family<BoardEditorController, BoardEditorState, String?>(
|
||||
BoardEditorController.new,
|
||||
name: 'BoardEditorControllerProvider',
|
||||
);
|
||||
|
||||
class BoardEditorController extends Notifier<BoardEditorState> {
|
||||
BoardEditorController(this.initialFen);
|
||||
|
||||
final String? initialFen;
|
||||
|
||||
@override
|
||||
BoardEditorState build(String? initialFen) {
|
||||
BoardEditorState build() {
|
||||
final setup = Setup.parseFen(initialFen ?? kInitialFEN);
|
||||
final position = Chess.fromSetup(setup, ignoreImpossibleCheck: true);
|
||||
final pieces = readFen(initialFen ?? kInitialFEN).lock;
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:dartchess/dartchess.dart';
|
||||
import 'package:deep_pick/deep_pick.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
|
||||
import 'package:lichess_mobile/src/model/analysis/common_analysis_state.dart';
|
||||
@@ -24,15 +25,25 @@ import 'package:lichess_mobile/src/utils/json.dart';
|
||||
import 'package:lichess_mobile/src/utils/rate_limit.dart';
|
||||
import 'package:lichess_mobile/src/view/engine/engine_gauge.dart';
|
||||
import 'package:lichess_mobile/src/widgets/pgn.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'broadcast_analysis_controller.freezed.dart';
|
||||
part 'broadcast_analysis_controller.g.dart';
|
||||
|
||||
@riverpod
|
||||
class BroadcastAnalysisController extends _$BroadcastAnalysisController
|
||||
typedef BroadcastAnalysisControllerParams = ({BroadcastRoundId roundId, BroadcastGameId gameId});
|
||||
|
||||
/// A provider for [BroadcastAnalysisController].
|
||||
final broadcastAnalysisControllerProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<BroadcastAnalysisController, BroadcastAnalysisState, BroadcastAnalysisControllerParams>(
|
||||
BroadcastAnalysisController.new,
|
||||
name: 'BroadcastAnalysisControllerProvider',
|
||||
);
|
||||
|
||||
class BroadcastAnalysisController extends AsyncNotifier<BroadcastAnalysisState>
|
||||
with EngineEvaluationMixin
|
||||
implements PgnTreeNotifier {
|
||||
BroadcastAnalysisController(this.params);
|
||||
|
||||
final BroadcastAnalysisControllerParams params;
|
||||
|
||||
static Uri broadcastSocketUri(BroadcastRoundId broadcastRoundId) =>
|
||||
Uri(path: 'study/$broadcastRoundId/socket/v6');
|
||||
|
||||
@@ -75,7 +86,7 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController
|
||||
Root get positionTree => _root;
|
||||
|
||||
@override
|
||||
Future<BroadcastAnalysisState> build(BroadcastRoundId roundId, BroadcastGameId gameId) async {
|
||||
Future<BroadcastAnalysisState> build() async {
|
||||
ref.onDispose(() {
|
||||
_key = null;
|
||||
_subscription?.cancel();
|
||||
@@ -88,14 +99,14 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController
|
||||
|
||||
_socketClient = ref
|
||||
.watch(socketPoolProvider)
|
||||
.open(BroadcastAnalysisController.broadcastSocketUri(roundId));
|
||||
.open(BroadcastAnalysisController.broadcastSocketUri(params.roundId));
|
||||
|
||||
_subscription = _socketClient.stream.listen(_handleSocketEvent);
|
||||
|
||||
await _socketClient.firstConnection;
|
||||
|
||||
_socketOpenSubscription = _socketClient.connectedStream.listen((_) {
|
||||
if (state.valueOrNull?.isNewOrOngoing == true) {
|
||||
if (state.value?.isNewOrOngoing == true) {
|
||||
_syncDebouncer(() {
|
||||
_reloadPgn();
|
||||
});
|
||||
@@ -104,7 +115,7 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController
|
||||
|
||||
_appLifecycleListener = AppLifecycleListener(
|
||||
onResume: () {
|
||||
if (state.valueOrNull?.isNewOrOngoing == true) {
|
||||
if (state.value?.isNewOrOngoing == true) {
|
||||
_syncDebouncer(() {
|
||||
_reloadPgn();
|
||||
});
|
||||
@@ -112,7 +123,9 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController
|
||||
},
|
||||
);
|
||||
|
||||
final pgn = await ref.read(broadcastRepositoryProvider).getGamePgn(roundId, gameId);
|
||||
final pgn = await ref
|
||||
.read(broadcastRepositoryProvider)
|
||||
.getGamePgn(params.roundId, params.gameId);
|
||||
|
||||
final game = PgnGame.parsePgn(pgn);
|
||||
final pgnHeaders = IMap(game.headers);
|
||||
@@ -128,7 +141,7 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController
|
||||
final prefs = ref.read(broadcastPreferencesProvider);
|
||||
|
||||
final broadcastState = BroadcastAnalysisState(
|
||||
id: gameId,
|
||||
id: params.gameId,
|
||||
currentPath: currentPath,
|
||||
broadcastPath: currentPath,
|
||||
isOnMainline: _root.isOnMainline(currentPath),
|
||||
@@ -163,7 +176,9 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController
|
||||
if (!state.hasValue) return;
|
||||
final key = _key;
|
||||
|
||||
final pgn = await ref.read(broadcastRepositoryProvider).getGamePgn(roundId, gameId);
|
||||
final pgn = await ref
|
||||
.read(broadcastRepositoryProvider)
|
||||
.getGamePgn(params.roundId, params.gameId);
|
||||
|
||||
// check provider is still mounted
|
||||
if (key == _key) {
|
||||
@@ -235,7 +250,7 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController
|
||||
final broadcastGameId = pick(event.data, 'p', 'chapterId').asBroadcastGameIdOrThrow();
|
||||
|
||||
// We check if the event is for this game
|
||||
if (broadcastGameId != gameId) return;
|
||||
if (broadcastGameId != params.gameId) return;
|
||||
|
||||
// The path of the last and current move of the broadcasted game
|
||||
// Its value is "!" if the path is identical to one of the node that was received
|
||||
@@ -276,7 +291,7 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController
|
||||
final broadcastGameId = pick(event.data, 'chapterId').asBroadcastGameIdOrThrow();
|
||||
|
||||
// We check if the event is for this game
|
||||
if (broadcastGameId != gameId) return;
|
||||
if (broadcastGameId != params.gameId) return;
|
||||
|
||||
final pgnHeadersEntries = pick(
|
||||
event.data,
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/analysis/common_analysis_prefs.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'broadcast_preferences.freezed.dart';
|
||||
part 'broadcast_preferences.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class BroadcastPreferences extends _$BroadcastPreferences with PreferencesStorage<BroadcastPrefs> {
|
||||
final broadcastPreferencesProvider = NotifierProvider<BroadcastPreferences, BroadcastPrefs>(
|
||||
BroadcastPreferences.new,
|
||||
name: 'BroadcastPreferencesProvider',
|
||||
);
|
||||
|
||||
class BroadcastPreferences extends Notifier<BroadcastPrefs>
|
||||
with PreferencesStorage<BroadcastPrefs> {
|
||||
@override
|
||||
@protected
|
||||
PrefCategory get prefCategory => PrefCategory.broadcast;
|
||||
|
||||
@@ -3,13 +3,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/model/broadcast/broadcast.dart';
|
||||
import 'package:lichess_mobile/src/model/broadcast/broadcast_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'broadcast_providers.g.dart';
|
||||
|
||||
/// A provider that fetches a paginated list of broadcasts.
|
||||
@riverpod
|
||||
class BroadcastsPaginator extends _$BroadcastsPaginator {
|
||||
final broadcastsPaginatorProvider =
|
||||
AsyncNotifierProvider.autoDispose<BroadcastsPaginator, BroadcastList>(
|
||||
BroadcastsPaginator.new,
|
||||
name: 'BroadcastsPaginatorProvider',
|
||||
);
|
||||
|
||||
class BroadcastsPaginator extends AsyncNotifier<BroadcastList> {
|
||||
@override
|
||||
Future<BroadcastList> build() {
|
||||
return ref.read(broadcastRepositoryProvider).getBroadcasts();
|
||||
@@ -35,10 +37,19 @@ class BroadcastsPaginator extends _$BroadcastsPaginator {
|
||||
}
|
||||
|
||||
/// A provider that fetches a paginated list of broadcasts matching the [searchTerm].
|
||||
@riverpod
|
||||
class BroadcastsSearchPaginator extends _$BroadcastsSearchPaginator {
|
||||
final broadcastsSearchPaginatorProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<BroadcastsSearchPaginator, BroadcastSearchList, String>(
|
||||
BroadcastsSearchPaginator.new,
|
||||
name: 'BroadcastsSearchPaginatorProvider',
|
||||
);
|
||||
|
||||
class BroadcastsSearchPaginator extends AsyncNotifier<BroadcastSearchList> {
|
||||
BroadcastsSearchPaginator(this.searchTerm);
|
||||
|
||||
final String searchTerm;
|
||||
|
||||
@override
|
||||
Future<BroadcastSearchList> build(String searchTerm) {
|
||||
Future<BroadcastSearchList> build() {
|
||||
return ref.read(broadcastRepositoryProvider).searchBroadcasts(searchTerm: searchTerm);
|
||||
}
|
||||
|
||||
@@ -60,37 +71,36 @@ class BroadcastsSearchPaginator extends _$BroadcastsSearchPaginator {
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<BroadcastTournament> broadcastTournament(
|
||||
final broadcastTournamentProvider = FutureProvider.autoDispose
|
||||
.family<BroadcastTournament, BroadcastTournamentId>((
|
||||
Ref ref,
|
||||
BroadcastTournamentId broadcastTournamentId,
|
||||
) {
|
||||
return ref.read(broadcastRepositoryProvider).getTournament(broadcastTournamentId);
|
||||
}
|
||||
}, name: 'BroadcastTournamentProvider');
|
||||
|
||||
@riverpod
|
||||
Future<BroadcastRoundResponse> broadcastRound(Ref ref, BroadcastRoundId roundId) {
|
||||
final broadcastRoundProvider = FutureProvider.autoDispose
|
||||
.family<BroadcastRoundResponse, BroadcastRoundId>((Ref ref, BroadcastRoundId roundId) {
|
||||
return ref.read(broadcastRepositoryProvider).getRound(roundId);
|
||||
}
|
||||
}, name: 'BroadcastRoundProvider');
|
||||
|
||||
@riverpod
|
||||
Future<IList<BroadcastPlayerWithOverallResult>> broadcastPlayers(
|
||||
final broadcastPlayersProvider = FutureProvider.autoDispose
|
||||
.family<IList<BroadcastPlayerWithOverallResult>, BroadcastTournamentId>((
|
||||
Ref ref,
|
||||
BroadcastTournamentId tournamentId,
|
||||
) {
|
||||
return ref.read(broadcastRepositoryProvider).getPlayers(tournamentId);
|
||||
}
|
||||
}, name: 'BroadcastPlayersProvider');
|
||||
|
||||
@riverpod
|
||||
Future<BroadcastPlayerWithGameResults> broadcastPlayer(
|
||||
final broadcastPlayerProvider = FutureProvider.autoDispose
|
||||
.family<BroadcastPlayerWithGameResults, (BroadcastTournamentId, String)>((
|
||||
Ref ref,
|
||||
BroadcastTournamentId broadcastTournamentId,
|
||||
String playerId,
|
||||
(BroadcastTournamentId, String) params,
|
||||
) {
|
||||
return ref.read(broadcastRepositoryProvider).getPlayerResults(broadcastTournamentId, playerId);
|
||||
}
|
||||
return ref.read(broadcastRepositoryProvider).getPlayerResults(params.$1, params.$2);
|
||||
}, name: 'BroadcastPlayerProvider');
|
||||
|
||||
@riverpod
|
||||
Future<IList<BroadcastTeamMatch>> broadcastTeamMatches(Ref ref, BroadcastRoundId roundId) {
|
||||
final broadcastTeamMatchesProvider = FutureProvider.autoDispose
|
||||
.family<IList<BroadcastTeamMatch>, BroadcastRoundId>((Ref ref, BroadcastRoundId roundId) {
|
||||
return ref.read(broadcastRepositoryProvider).getTeamMatches(roundId);
|
||||
}
|
||||
}, name: 'BroadcastTeamMatchesProvider');
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:dartchess/dartchess.dart';
|
||||
import 'package:deep_pick/deep_pick.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/broadcast/broadcast.dart';
|
||||
import 'package:lichess_mobile/src/model/broadcast/broadcast_preferences.dart';
|
||||
@@ -15,13 +16,21 @@ import 'package:lichess_mobile/src/model/common/socket.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:lichess_mobile/src/utils/json.dart';
|
||||
import 'package:lichess_mobile/src/utils/rate_limit.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'broadcast_round_controller.freezed.dart';
|
||||
part 'broadcast_round_controller.g.dart';
|
||||
|
||||
@riverpod
|
||||
class BroadcastRoundController extends _$BroadcastRoundController {
|
||||
/// A provider for [BroadcastRoundController].
|
||||
final broadcastRoundControllerProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<BroadcastRoundController, BroadcastRoundState, BroadcastRoundId>(
|
||||
BroadcastRoundController.new,
|
||||
name: 'BroadcastRoundControllerProvider',
|
||||
);
|
||||
|
||||
class BroadcastRoundController extends AsyncNotifier<BroadcastRoundState> {
|
||||
BroadcastRoundController(this.broadcastRoundId);
|
||||
|
||||
final BroadcastRoundId broadcastRoundId;
|
||||
|
||||
static Uri broadcastSocketUri(BroadcastRoundId broadcastRoundId) =>
|
||||
Uri(path: 'study/$broadcastRoundId/socket/v6');
|
||||
|
||||
@@ -37,7 +46,7 @@ class BroadcastRoundController extends _$BroadcastRoundController {
|
||||
Object? _key = Object();
|
||||
|
||||
@override
|
||||
Future<BroadcastRoundState> build(BroadcastRoundId broadcastRoundId) async {
|
||||
Future<BroadcastRoundState> build() async {
|
||||
ref.onDispose(() {
|
||||
_key = null;
|
||||
_subscription?.cancel();
|
||||
@@ -56,7 +65,7 @@ class BroadcastRoundController extends _$BroadcastRoundController {
|
||||
await _socketClient.firstConnection;
|
||||
|
||||
_socketOpenSubscription = _socketClient.connectedStream.listen((_) {
|
||||
if (state.valueOrNull?.round.status == RoundStatus.live) {
|
||||
if (state.value?.round.status == RoundStatus.live) {
|
||||
_syncRoundDebouncer(() {
|
||||
_syncRound();
|
||||
});
|
||||
@@ -65,7 +74,7 @@ class BroadcastRoundController extends _$BroadcastRoundController {
|
||||
|
||||
_appLifecycleListener = AppLifecycleListener(
|
||||
onResume: () {
|
||||
if (state.valueOrNull?.round.status == RoundStatus.live) {
|
||||
if (state.value?.round.status == RoundStatus.live) {
|
||||
_syncRoundDebouncer(() {
|
||||
_syncRound();
|
||||
});
|
||||
@@ -254,7 +263,7 @@ class BroadcastRoundController extends _$BroadcastRoundController {
|
||||
}
|
||||
|
||||
void addObservedGame(BroadcastGameId gameId) {
|
||||
if (state.valueOrNull?.games.containsKey(gameId) != true) return;
|
||||
if (state.value?.games.containsKey(gameId) != true) return;
|
||||
|
||||
state = AsyncData(
|
||||
state.requireValue.copyWith(observedGames: state.requireValue.observedGames.add(gameId)),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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/chess.dart';
|
||||
@@ -6,13 +7,16 @@ import 'package:lichess_mobile/src/model/common/speed.dart';
|
||||
import 'package:lichess_mobile/src/model/common/time_increment.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'challenge_preferences.freezed.dart';
|
||||
part 'challenge_preferences.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class ChallengePreferences extends _$ChallengePreferences
|
||||
final challengePreferencesProvider = NotifierProvider<ChallengePreferences, ChallengePrefs>(
|
||||
ChallengePreferences.new,
|
||||
name: 'ChallengePreferencesProvider',
|
||||
);
|
||||
|
||||
class ChallengePreferences extends Notifier<ChallengePrefs>
|
||||
with SessionPreferencesStorage<ChallengePrefs> {
|
||||
@override
|
||||
@protected
|
||||
|
||||
@@ -7,14 +7,11 @@ import 'package:lichess_mobile/src/model/challenge/challenge.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/network/aggregator.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'challenge_repository.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
ChallengeRepository challengeRepository(Ref ref) {
|
||||
/// A provider for [ChallengeRepository].
|
||||
final challengeRepositoryProvider = Provider<ChallengeRepository>((Ref ref) {
|
||||
return ChallengeRepository(ref.read(lichessClientProvider), ref.read(aggregatorProvider));
|
||||
}
|
||||
}, name: 'ChallengeRepositoryProvider');
|
||||
|
||||
typedef ChallengesList = ({IList<Challenge> inward, IList<Challenge> outward});
|
||||
|
||||
|
||||
@@ -20,17 +20,14 @@ 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';
|
||||
|
||||
part 'challenge_service.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
ChallengeService challengeService(Ref ref) {
|
||||
/// A provider for [ChallengeService].
|
||||
final challengeServiceProvider = Provider<ChallengeService>((Ref ref) {
|
||||
final service = ChallengeService(ref);
|
||||
ref.onDispose(() => service.dispose());
|
||||
return service;
|
||||
}
|
||||
}, name: 'ChallengeServiceProvider');
|
||||
|
||||
/// A service that listens to challenge events and shows notifications.
|
||||
class ChallengeService {
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
import 'package:lichess_mobile/src/model/challenge/challenge.dart';
|
||||
import 'package:lichess_mobile/src/model/challenge/challenge_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/challenge/challenge_service.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'challenges.g.dart';
|
||||
final challengesProvider = AsyncNotifierProvider.autoDispose<Challenges, ChallengesList>(
|
||||
Challenges.new,
|
||||
name: 'ChallengesProvider',
|
||||
);
|
||||
|
||||
@riverpod
|
||||
class Challenges extends _$Challenges {
|
||||
class Challenges extends AsyncNotifier<ChallengesList> {
|
||||
StreamSubscription<ChallengesList>? _subscription;
|
||||
|
||||
@override
|
||||
|
||||
@@ -16,11 +16,9 @@ import 'package:lichess_mobile/src/model/tournament/tournament_controller.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
part 'chat_controller.freezed.dart';
|
||||
part 'chat_controller.g.dart';
|
||||
|
||||
const _tableName = 'chat_read_messages';
|
||||
String _storeKey(StringId id) => 'chat.$id';
|
||||
@@ -79,21 +77,33 @@ abstract class StudyChatOptions extends ChatOptions with _$StudyChatOptions {
|
||||
}
|
||||
|
||||
/// A provider that gets the chat unread messages
|
||||
@riverpod
|
||||
Future<int> chatUnread(Ref ref, ChatOptions options) async {
|
||||
return ref.watch(chatControllerProvider(options).selectAsync((s) => s.unreadMessages));
|
||||
}
|
||||
final chatUnreadProvider = FutureProvider.autoDispose.family<int, ChatOptions>((
|
||||
Ref ref,
|
||||
ChatOptions options,
|
||||
) async {
|
||||
return (await ref.watch(chatControllerProvider(options).future)).unreadMessages;
|
||||
}, name: 'ChatUnreadProvider');
|
||||
|
||||
const IList<ChatMessage> _kEmptyMessages = IListConst([]);
|
||||
|
||||
@riverpod
|
||||
class ChatController extends _$ChatController {
|
||||
/// A provider for [ChatController].
|
||||
final chatControllerProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<ChatController, ChatState, ChatOptions>(
|
||||
ChatController.new,
|
||||
name: 'ChatControllerProvider',
|
||||
);
|
||||
|
||||
class ChatController extends AsyncNotifier<ChatState> {
|
||||
ChatController(this.options);
|
||||
|
||||
final ChatOptions options;
|
||||
|
||||
StreamSubscription<SocketEvent>? _subscription;
|
||||
|
||||
LightUser? get _me => ref.read(authSessionProvider)?.user;
|
||||
|
||||
@override
|
||||
Future<ChatState> build(ChatOptions options) async {
|
||||
Future<ChatState> build() async {
|
||||
_subscription?.cancel();
|
||||
_subscription = socketGlobalStream.listen(_handleSocketEvent);
|
||||
|
||||
@@ -101,16 +111,16 @@ class ChatController extends _$ChatController {
|
||||
_subscription?.cancel();
|
||||
});
|
||||
|
||||
final initialMessages = await switch (options) {
|
||||
GameChatOptions(:final id) => ref.watch(
|
||||
gameControllerProvider(id).selectAsync((s) => s.game.chat?.lines),
|
||||
),
|
||||
TournamentChatOptions(:final id) => ref.watch(
|
||||
tournamentControllerProvider(id).selectAsync((s) => s.tournament.chat?.lines),
|
||||
),
|
||||
StudyChatOptions(:final id) => ref.watch(
|
||||
studyControllerProvider(id).selectAsync((s) => s.study.chat?.lines),
|
||||
),
|
||||
final initialMessages = switch (options) {
|
||||
GameChatOptions(:final id) => (await ref.watch(
|
||||
gameControllerProvider(id).future,
|
||||
)).game.chat?.lines,
|
||||
TournamentChatOptions(:final id) => (await ref.watch(
|
||||
tournamentControllerProvider(id).future,
|
||||
)).tournament.chat?.lines,
|
||||
StudyChatOptions(:final id) => (await ref.watch(
|
||||
studyControllerProvider(id).future,
|
||||
)).study.chat?.lines,
|
||||
};
|
||||
|
||||
final filteredMessages = _selectMessages(initialMessages ?? _kEmptyMessages);
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/clock/chess_clock.dart';
|
||||
import 'package:lichess_mobile/src/model/common/service/sound_service.dart';
|
||||
import 'package:lichess_mobile/src/model/common/time_increment.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'clock_tool_controller.freezed.dart';
|
||||
part 'clock_tool_controller.g.dart';
|
||||
|
||||
enum ClockSide {
|
||||
top,
|
||||
@@ -18,8 +17,13 @@ enum ClockSide {
|
||||
Side get chessClockSide => this == ClockSide.top ? Side.black : Side.white;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class ClockToolController extends _$ClockToolController {
|
||||
/// A provider for [ClockToolController].
|
||||
final clockToolControllerProvider = NotifierProvider.autoDispose<ClockToolController, ClockState>(
|
||||
ClockToolController.new,
|
||||
name: 'ClockToolControllerProvider',
|
||||
);
|
||||
|
||||
class ClockToolController extends Notifier<ClockState> {
|
||||
late ChessClock _clock;
|
||||
final Map<ClockSide, bool> _hasPlayedLowTimeSound = {
|
||||
ClockSide.top: false,
|
||||
|
||||
@@ -12,9 +12,6 @@ import 'package:lichess_mobile/src/utils/system.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path_provider/path_provider.dart'
|
||||
show getApplicationDocumentsDirectory, getApplicationSupportDirectory;
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'preloaded_data.g.dart';
|
||||
|
||||
typedef PreloadedData = ({
|
||||
PackageInfo packageInfo,
|
||||
@@ -26,8 +23,8 @@ typedef PreloadedData = ({
|
||||
Directory? appSupportDirectory,
|
||||
});
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<PreloadedData> preloadedData(Ref ref) async {
|
||||
/// A provider that preloads various data needed throughout the app.
|
||||
final preloadedDataProvider = FutureProvider<PreloadedData>((Ref ref) async {
|
||||
final sessionStorage = ref.watch(sessionStorageProvider);
|
||||
|
||||
final pInfo = await PackageInfo.fromPlatform();
|
||||
@@ -74,4 +71,4 @@ Future<PreloadedData> preloadedData(Ref ref) async {
|
||||
appDocumentsDirectory: appDocumentsDirectory,
|
||||
appSupportDirectory: appSupportDirectory,
|
||||
);
|
||||
}
|
||||
}, name: 'PreloadedDataProvider');
|
||||
|
||||
@@ -2,15 +2,12 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/model/common/service/sound_service.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/board_preferences.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'move_feedback.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
MoveFeedbackService moveFeedbackService(Ref ref) {
|
||||
/// A provider for [MoveFeedbackService].
|
||||
final moveFeedbackServiceProvider = Provider<MoveFeedbackService>((Ref ref) {
|
||||
final soundService = ref.watch(soundServiceProvider);
|
||||
return MoveFeedbackService(soundService, ref);
|
||||
}
|
||||
}, name: 'MoveFeedbackServiceProvider');
|
||||
|
||||
class MoveFeedbackService {
|
||||
MoveFeedbackService(this._soundService, this._ref);
|
||||
|
||||
@@ -7,11 +7,8 @@ import 'package:lichess_mobile/src/binding.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/general_preferences.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sound_effect/sound_effect.dart';
|
||||
|
||||
part 'sound_service.g.dart';
|
||||
|
||||
/// Maximum number of concurrent sounds that can be played.
|
||||
const _kMaxConcurrentStreams = 2;
|
||||
|
||||
@@ -22,12 +19,12 @@ final _logger = Logger('SoundService');
|
||||
// Must match name of files in assets/sounds/standard
|
||||
enum Sound { move, capture, lowTime, dong, error, confirmation, puzzleStormEnd, clock, berserk }
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
SoundService soundService(Ref ref) {
|
||||
/// A provider for [SoundService].
|
||||
final soundServiceProvider = Provider<SoundService>((Ref ref) {
|
||||
final service = SoundService(ref);
|
||||
ref.onDispose(() => service.release());
|
||||
return service;
|
||||
}
|
||||
}, name: 'SoundServiceProvider');
|
||||
|
||||
final _extension = defaultTargetPlatform == TargetPlatform.iOS ? 'aifc' : 'mp3';
|
||||
|
||||
|
||||
@@ -2,18 +2,22 @@ import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/common/game.dart';
|
||||
import 'package:lichess_mobile/src/model/coordinate_training/coordinate_training_preferences.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'coordinate_training_controller.freezed.dart';
|
||||
part 'coordinate_training_controller.g.dart';
|
||||
|
||||
enum Guess { correct, incorrect }
|
||||
|
||||
@riverpod
|
||||
class CoordinateTrainingController extends _$CoordinateTrainingController {
|
||||
final coordinateTrainingControllerProvider =
|
||||
NotifierProvider.autoDispose<CoordinateTrainingController, CoordinateTrainingState>(
|
||||
CoordinateTrainingController.new,
|
||||
name: 'CoordinateTrainingControllerProvider',
|
||||
);
|
||||
|
||||
class CoordinateTrainingController extends Notifier<CoordinateTrainingState> {
|
||||
final _random = Random(DateTime.now().millisecondsSinceEpoch);
|
||||
|
||||
final _stopwatch = Stopwatch();
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/l10n/l10n.dart';
|
||||
import 'package:lichess_mobile/src/model/common/game.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'coordinate_training_preferences.freezed.dart';
|
||||
part 'coordinate_training_preferences.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class CoordinateTrainingPreferences extends _$CoordinateTrainingPreferences
|
||||
final coordinateTrainingPreferencesProvider =
|
||||
NotifierProvider<CoordinateTrainingPreferences, CoordinateTrainingPrefs>(
|
||||
CoordinateTrainingPreferences.new,
|
||||
name: 'CoordinateTrainingPreferencesProvider',
|
||||
);
|
||||
|
||||
class CoordinateTrainingPreferences extends Notifier<CoordinateTrainingPrefs>
|
||||
with PreferencesStorage<CoordinateTrainingPrefs> {
|
||||
@override
|
||||
@protected
|
||||
|
||||
@@ -7,21 +7,19 @@ import 'package:lichess_mobile/src/db/database.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/correspondence/offline_correspondence_game.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
part 'correspondence_game_storage.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<CorrespondenceGameStorage> correspondenceGameStorage(Ref ref) async {
|
||||
final db = await ref.watch(databaseProvider.future);
|
||||
return CorrespondenceGameStorage(db, ref);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<IList<(DateTime, OfflineCorrespondenceGame)>> offlineOngoingCorrespondenceGames(
|
||||
/// A provider for [CorrespondenceGameStorage].
|
||||
final correspondenceGameStorageProvider = FutureProvider<CorrespondenceGameStorage>((
|
||||
Ref ref,
|
||||
) async {
|
||||
final db = await ref.watch(databaseProvider.future);
|
||||
return CorrespondenceGameStorage(db, ref);
|
||||
}, name: 'CorrespondenceGameStorageProvider');
|
||||
|
||||
/// Fetches all ongoing offline correspondence games, sorted by whose turn it is and last modified time.
|
||||
final offlineOngoingCorrespondenceGamesProvider =
|
||||
FutureProvider.autoDispose<IList<(DateTime, OfflineCorrespondenceGame)>>((Ref ref) async {
|
||||
final session = ref.watch(authSessionProvider);
|
||||
// cannot use ref.watch because it would create a circular dependency
|
||||
// as we invalidate this provider in the storage save and delete methods
|
||||
@@ -34,7 +32,7 @@ Future<IList<(DateTime, OfflineCorrespondenceGame)>> offlineOngoingCorrespondenc
|
||||
if (!aIsMyTurn && bIsMyTurn) return 1;
|
||||
return b.$1.compareTo(a.$1);
|
||||
});
|
||||
}
|
||||
}, name: 'OfflineOngoingCorrespondenceGamesProvider');
|
||||
|
||||
const kCorrespondenceStorageTable = 'correspondence_game';
|
||||
|
||||
|
||||
@@ -24,16 +24,13 @@ import 'package:lichess_mobile/src/tab_scaffold.dart' show currentNavigatorKeyPr
|
||||
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';
|
||||
|
||||
part 'correspondence_service.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
CorrespondenceService correspondenceService(Ref ref) {
|
||||
/// A provider for [CorrespondenceService].
|
||||
final correspondenceServiceProvider = Provider<CorrespondenceService>((Ref ref) {
|
||||
final service = CorrespondenceService(Logger('CorrespondenceService'), ref: ref);
|
||||
ref.onDispose(() => service.dispose());
|
||||
return service;
|
||||
}
|
||||
}, name: 'CorrespondenceServiceProvider');
|
||||
|
||||
/// Service that manages correspondence games.
|
||||
class CorrespondenceService {
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/engine/evaluation_service.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'evaluation_preferences.freezed.dart';
|
||||
part 'evaluation_preferences.g.dart';
|
||||
|
||||
final engineEvaluationPreferencesProvider =
|
||||
NotifierProvider<EngineEvaluationPreferences, EngineEvaluationPrefState>(
|
||||
EngineEvaluationPreferences.new,
|
||||
name: 'EngineEvaluationPreferencesProvider',
|
||||
);
|
||||
|
||||
/// Preferences for engine evaluation.
|
||||
///
|
||||
/// Same preferences are used in various screens where engine evaluation is used:
|
||||
/// - Analysis screen
|
||||
/// - Study screen
|
||||
/// - Broadcast game screen
|
||||
@Riverpod(keepAlive: true)
|
||||
class EngineEvaluationPreferences extends _$EngineEvaluationPreferences
|
||||
class EngineEvaluationPreferences extends Notifier<EngineEvaluationPrefState>
|
||||
with PreferencesStorage<EngineEvaluationPrefState> {
|
||||
@override
|
||||
@protected
|
||||
|
||||
@@ -25,11 +25,9 @@ import 'package:lichess_mobile/src/utils/l10n_context.dart';
|
||||
import 'package:lichess_mobile/src/widgets/platform_alert_dialog.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:multistockfish/multistockfish.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:stream_transform/stream_transform.dart';
|
||||
|
||||
part 'evaluation_service.freezed.dart';
|
||||
part 'evaluation_service.g.dart';
|
||||
|
||||
final _logger = Logger('EvaluationService');
|
||||
|
||||
@@ -47,8 +45,8 @@ typedef ShouldEmitEvalFilter = bool Function(Work work);
|
||||
|
||||
typedef NNUEFiles = ({File bigNet, File smallNet});
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
EvaluationService evaluationService(Ref ref) {
|
||||
/// A provider for [EvaluationService].
|
||||
final evaluationServiceProvider = Provider<EvaluationService>((Ref ref) {
|
||||
final maxMemory = ref.read(preloadedDataProvider).requireValue.engineMaxMemoryInMb;
|
||||
final service = EvaluationService(ref, maxMemory: maxMemory);
|
||||
|
||||
@@ -57,7 +55,7 @@ EvaluationService evaluationService(Ref ref) {
|
||||
});
|
||||
|
||||
return service;
|
||||
}
|
||||
}, name: 'EvaluationServiceProvider');
|
||||
|
||||
/// A service to evaluate chess positions using an engine.
|
||||
class EvaluationService {
|
||||
@@ -387,8 +385,13 @@ typedef EngineEvaluationState = ({
|
||||
});
|
||||
|
||||
/// A provider that holds the state of the engine and the current evaluation.
|
||||
@riverpod
|
||||
class EngineEvaluation extends _$EngineEvaluation {
|
||||
final engineEvaluationProvider =
|
||||
NotifierProvider.autoDispose<EngineEvaluation, EngineEvaluationState>(
|
||||
EngineEvaluation.new,
|
||||
name: 'EngineEvaluationProvider',
|
||||
);
|
||||
|
||||
class EngineEvaluation extends Notifier<EngineEvaluationState> {
|
||||
@override
|
||||
EngineEvaluationState build() {
|
||||
final listenable = ref.watch(evaluationServiceProvider).state;
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/common/speed.dart';
|
||||
import 'package:lichess_mobile/src/model/explorer/opening_explorer.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'opening_explorer_preferences.freezed.dart';
|
||||
part 'opening_explorer_preferences.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class OpeningExplorerPreferences extends _$OpeningExplorerPreferences
|
||||
final openingExplorerPreferencesProvider =
|
||||
NotifierProvider<OpeningExplorerPreferences, OpeningExplorerPrefs>(
|
||||
OpeningExplorerPreferences.new,
|
||||
name: 'OpeningExplorerPreferencesProvider',
|
||||
);
|
||||
|
||||
class OpeningExplorerPreferences extends Notifier<OpeningExplorerPrefs>
|
||||
with SessionPreferencesStorage<OpeningExplorerPrefs> {
|
||||
@override
|
||||
@protected
|
||||
|
||||
@@ -10,16 +10,21 @@ import 'package:lichess_mobile/src/model/explorer/opening_explorer.dart';
|
||||
import 'package:lichess_mobile/src/model/explorer/opening_explorer_preferences.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/utils/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'opening_explorer_repository.g.dart';
|
||||
final openingExplorerProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<OpeningExplorer, ({OpeningExplorerEntry entry, bool isIndexing})?, String>(
|
||||
OpeningExplorer.new,
|
||||
name: 'OpeningExplorerProvider',
|
||||
);
|
||||
|
||||
class OpeningExplorer extends AsyncNotifier<({OpeningExplorerEntry entry, bool isIndexing})?> {
|
||||
OpeningExplorer(this.fen);
|
||||
final String fen;
|
||||
|
||||
@riverpod
|
||||
class OpeningExplorer extends _$OpeningExplorer {
|
||||
StreamSubscription<OpeningExplorerEntry>? _openingExplorerSubscription;
|
||||
|
||||
@override
|
||||
Future<({OpeningExplorerEntry entry, bool isIndexing})?> build({required String fen}) async {
|
||||
Future<({OpeningExplorerEntry entry, bool isIndexing})?> build() async {
|
||||
ref.onDispose(() {
|
||||
_openingExplorerSubscription?.cancel();
|
||||
});
|
||||
@@ -67,10 +72,10 @@ class OpeningExplorer extends _$OpeningExplorer {
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
OpeningExplorerRepository openingExplorerRepository(Ref ref) {
|
||||
/// A provider for [OpeningExplorerRepository].
|
||||
final openingExplorerRepositoryProvider = Provider<OpeningExplorerRepository>((Ref ref) {
|
||||
return OpeningExplorerRepository(ref.read(defaultClientProvider));
|
||||
}
|
||||
}, name: 'OpeningExplorerRepositoryProvider');
|
||||
|
||||
class OpeningExplorerRepository {
|
||||
const OpeningExplorerRepository(this.client);
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:lichess_mobile/src/constants.dart';
|
||||
import 'package:lichess_mobile/src/model/explorer/tablebase.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/utils/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'tablebase_repository.g.dart';
|
||||
|
||||
@riverpod
|
||||
class Tablebase extends _$Tablebase {
|
||||
@override
|
||||
Future<TablebaseEntry?> build({required String fen}) async {
|
||||
/// A provider for fetching tablebase entries.
|
||||
final tablebaseProvider = FutureProvider.autoDispose.family<TablebaseEntry?, String>((
|
||||
ref,
|
||||
fen,
|
||||
) async {
|
||||
await ref.debounce(const Duration(milliseconds: 300));
|
||||
|
||||
final client = ref.read(defaultClientProvider);
|
||||
|
||||
final tablebaseEntry = await TablebaseRepository(client).getTablebaseEntry(fen);
|
||||
return tablebaseEntry;
|
||||
}
|
||||
}
|
||||
}, name: 'TablebaseProvider');
|
||||
|
||||
class TablebaseRepository {
|
||||
const TablebaseRepository(this.client);
|
||||
|
||||
@@ -2,21 +2,25 @@ import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/game/exported_game.dart';
|
||||
import 'package:lichess_mobile/src/model/game/game_repository.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'game_bookmarks.freezed.dart';
|
||||
part 'game_bookmarks.g.dart';
|
||||
|
||||
const _nbPerPage = 20;
|
||||
|
||||
/// A provider that paginates the game bookmarks for the current app user.
|
||||
@riverpod
|
||||
class GameBookmarksPaginator extends _$GameBookmarksPaginator {
|
||||
final gameBookmarksPaginatorProvider =
|
||||
AsyncNotifierProvider.autoDispose<GameBookmarksPaginator, GameBookmarksPaginatorState>(
|
||||
GameBookmarksPaginator.new,
|
||||
name: 'GameBookmarksPaginatorProvider',
|
||||
);
|
||||
|
||||
class GameBookmarksPaginator extends AsyncNotifier<GameBookmarksPaginatorState> {
|
||||
final _list = <LightExportedGameWithPov>[];
|
||||
|
||||
GameRepository get _gameRepository => ref.read(gameRepositoryProvider);
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:deep_pick/deep_pick.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/binding.dart';
|
||||
import 'package:lichess_mobile/src/model/account/account_preferences.dart';
|
||||
@@ -33,13 +34,21 @@ import 'package:lichess_mobile/src/model/settings/board_preferences.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:lichess_mobile/src/utils/rate_limit.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'game_controller.freezed.dart';
|
||||
part 'game_controller.g.dart';
|
||||
|
||||
@riverpod
|
||||
class GameController extends _$GameController {
|
||||
/// A provider for [GameController].
|
||||
final gameControllerProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<GameController, GameState, GameFullId>(
|
||||
GameController.new,
|
||||
name: 'GameControllerProvider',
|
||||
);
|
||||
|
||||
class GameController extends AsyncNotifier<GameState> {
|
||||
GameController(this.gameFullId);
|
||||
|
||||
final GameFullId gameFullId;
|
||||
|
||||
final _logger = Logger('GameController');
|
||||
|
||||
StreamSubscription<SocketEvent>? _socketSubscription;
|
||||
@@ -67,7 +76,7 @@ class GameController extends _$GameController {
|
||||
GameRepository get _gameRepository => ref.read(gameRepositoryProvider);
|
||||
|
||||
@override
|
||||
Future<GameState> build(GameFullId gameFullId) {
|
||||
Future<GameState> build() {
|
||||
_socketClient = _openSocket();
|
||||
|
||||
_onFullReload = () {
|
||||
@@ -374,7 +383,7 @@ class GameController extends _$GameController {
|
||||
|
||||
/// Play a sound when the clock is about to run out
|
||||
Future<void> onClockEmergency(Side activeSide) async {
|
||||
if (activeSide != state.valueOrNull?.game.youAre) return;
|
||||
if (activeSide != state.value?.game.youAre) return;
|
||||
final shouldPlay = await ref.read(clockSoundProvider.future);
|
||||
if (shouldPlay) {
|
||||
ref.read(soundServiceProvider).play(Sound.lowTime);
|
||||
@@ -394,7 +403,7 @@ class GameController extends _$GameController {
|
||||
}
|
||||
|
||||
void berserk() {
|
||||
if (state.valueOrNull?.canBerserk == true && state.valueOrNull?.hasBerserked == false) {
|
||||
if (state.value?.canBerserk == true && state.value?.hasBerserked == false) {
|
||||
_socketClient.send('berserk', null);
|
||||
}
|
||||
}
|
||||
@@ -702,8 +711,8 @@ class GameController extends _$GameController {
|
||||
playedSide == curState.game.youAre?.opposite &&
|
||||
curState.premove != null) {
|
||||
scheduleMicrotask(() {
|
||||
final postMovePremove = state.valueOrNull?.premove;
|
||||
final postMovePosition = state.valueOrNull?.game.lastPosition;
|
||||
final postMovePremove = state.value?.premove;
|
||||
final postMovePosition = state.value?.game.lastPosition;
|
||||
if (postMovePremove != null && postMovePosition?.isLegal(postMovePremove) == true) {
|
||||
userMove(postMovePremove, isPremove: true);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/l10n/l10n.dart';
|
||||
import 'package:lichess_mobile/src/model/common/perf.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'game_filter.freezed.dart';
|
||||
part 'game_filter.g.dart';
|
||||
|
||||
@riverpod
|
||||
class GameFilter extends _$GameFilter {
|
||||
/// A provider for [GameFilter].
|
||||
final gameFilterProvider = NotifierProvider.autoDispose
|
||||
.family<GameFilter, GameFilterState, GameFilterState?>(
|
||||
GameFilter.new,
|
||||
name: 'GameFilterProvider',
|
||||
);
|
||||
|
||||
class GameFilter extends Notifier<GameFilterState> {
|
||||
GameFilter(this.filter);
|
||||
|
||||
final GameFilterState? filter;
|
||||
|
||||
@override
|
||||
GameFilterState build({GameFilterState? filter}) {
|
||||
GameFilterState build() {
|
||||
return filter ?? const GameFilterState();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,8 @@ import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user_repository_providers.dart';
|
||||
import 'package:lichess_mobile/src/network/connectivity.dart';
|
||||
import 'package:lichess_mobile/src/utils/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'game_history.freezed.dart';
|
||||
part 'game_history.g.dart';
|
||||
|
||||
const kNumberOfRecentGames = 10;
|
||||
|
||||
@@ -32,9 +30,10 @@ const _nbPerPage = 20;
|
||||
/// If the user is logged in, the recent games are fetched from the server.
|
||||
/// If the user is not logged in, or there is no connectivity, the recent games
|
||||
/// stored locally are fetched instead.
|
||||
@riverpod
|
||||
Future<IList<LightExportedGameWithPov>> myRecentGames(Ref ref) async {
|
||||
final online = await ref.watch(connectivityChangesProvider.selectAsync((c) => c.isOnline));
|
||||
final myRecentGamesProvider = FutureProvider.autoDispose<IList<LightExportedGameWithPov>>((
|
||||
Ref ref,
|
||||
) async {
|
||||
final online = (await ref.watch(connectivityChangesProvider.future)).isOnline;
|
||||
final session = ref.watch(authSessionProvider);
|
||||
if (session != null && online) {
|
||||
return ref
|
||||
@@ -52,38 +51,51 @@ Future<IList<LightExportedGameWithPov>> myRecentGames(Ref ref) async {
|
||||
.toIList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}, name: 'MyRecentGamesProvider');
|
||||
|
||||
/// A provider that fetches the recent games from the server for a given user.
|
||||
@riverpod
|
||||
Future<IList<LightExportedGameWithPov>> userRecentGames(Ref ref, {required UserId userId}) {
|
||||
final userRecentGamesProvider = FutureProvider.autoDispose
|
||||
.family<IList<LightExportedGameWithPov>, UserId>((Ref ref, UserId userId) {
|
||||
return ref
|
||||
.read(gameRepositoryProvider)
|
||||
.getUserGames(userId, withBookmarked: true, max: kNumberOfRecentGames);
|
||||
}
|
||||
}, name: 'UserRecentGamesProvider');
|
||||
|
||||
/// A provider that fetches the total number of games played by given user, or the current app user if no user is provided.
|
||||
///
|
||||
/// If the user is logged in, the number of games is fetched from the server.
|
||||
/// If the user is not logged in, or there is no connectivity, the number of games
|
||||
/// stored locally are fetched instead.
|
||||
@riverpod
|
||||
Future<int> userNumberOfGames(Ref ref, LightUser? user) async {
|
||||
final userNumberOfGamesProvider = FutureProvider.autoDispose.family<int, LightUser?>((
|
||||
Ref ref,
|
||||
LightUser? user,
|
||||
) async {
|
||||
final session = ref.watch(authSessionProvider);
|
||||
final online = await ref.watch(connectivityChangesProvider.selectAsync((c) => c.isOnline));
|
||||
final online = (await ref.watch(connectivityChangesProvider.future)).isOnline;
|
||||
return user != null
|
||||
? ref.watch(userProvider(id: user.id).selectAsync((u) => u.count?.all ?? 0))
|
||||
? (await ref.watch(userProvider(user.id).future)).count?.all ?? 0
|
||||
: session != null && online
|
||||
? ref.watch(accountProvider.selectAsync((u) => u?.count?.all ?? 0))
|
||||
? (await ref.watch(accountProvider.future))?.count?.all ?? 0
|
||||
: (await ref.watch(gameStorageProvider.future)).count(userId: user?.id);
|
||||
}
|
||||
}, name: 'UserNumberOfGamesProvider');
|
||||
|
||||
typedef UserGameHistoryNotifierParams = ({UserId? userId, GameFilterState filter});
|
||||
|
||||
/// A provider that paginates the game history for a given user, or the current app user if no user is provided.
|
||||
///
|
||||
/// The game history is fetched from the server if the user is logged in and app is online.
|
||||
/// Otherwise, the game history is fetched from the local storage.
|
||||
@riverpod
|
||||
class UserGameHistory extends _$UserGameHistory {
|
||||
final userGameHistoryProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<UserGameHistoryNotifier, UserGameHistoryState, UserGameHistoryNotifierParams>(
|
||||
UserGameHistoryNotifier.new,
|
||||
name: 'UserGameHistoryProvider',
|
||||
);
|
||||
|
||||
class UserGameHistoryNotifier extends AsyncNotifier<UserGameHistoryState> {
|
||||
UserGameHistoryNotifier(this.params);
|
||||
|
||||
final UserGameHistoryNotifierParams params;
|
||||
|
||||
final _list = <LightExportedGameWithPov>[];
|
||||
|
||||
StreamSubscription<(GameId, bool)>? _bookmarkChangesSubscription;
|
||||
@@ -91,18 +103,7 @@ class UserGameHistory extends _$UserGameHistory {
|
||||
GameRepository get _gameRepository => ref.read(gameRepositoryProvider);
|
||||
|
||||
@override
|
||||
Future<UserGameHistoryState> build(
|
||||
UserId? userId, {
|
||||
|
||||
/// Whether the history is requested in an online context. Applicable only
|
||||
/// when [userId] is null.
|
||||
///
|
||||
/// If this is true, the provider will attempt to fetch the games from the
|
||||
/// server. If this is false, the provider will fetch the games from the
|
||||
/// local storage.
|
||||
required bool isOnline,
|
||||
GameFilterState filter = const GameFilterState(),
|
||||
}) async {
|
||||
Future<UserGameHistoryState> build() async {
|
||||
_bookmarkChangesSubscription?.cancel();
|
||||
_bookmarkChangesSubscription = ref.read(accountServiceProvider).bookmarkChanges.listen((data) {
|
||||
final (id, bookmarked) = data;
|
||||
@@ -119,19 +120,19 @@ class UserGameHistory extends _$UserGameHistory {
|
||||
|
||||
final session = ref.watch(authSessionProvider);
|
||||
final prefs = ref.watch(gameHistoryPreferencesProvider);
|
||||
final online = await ref.watch(connectivityChangesProvider.selectAsync((c) => c.isOnline));
|
||||
final online = (await ref.watch(connectivityChangesProvider.future)).isOnline;
|
||||
final storage = await ref.watch(gameStorageProvider.future);
|
||||
|
||||
final id = userId ?? session?.user.id;
|
||||
final id = params.userId ?? session?.user.id;
|
||||
final recentGames = id != null && online
|
||||
? _gameRepository.getUserGames(
|
||||
id,
|
||||
filter: filter,
|
||||
filter: params.filter,
|
||||
withBookmarked: true,
|
||||
withMoves: prefs.displayMode == GameHistoryDisplayMode.detail,
|
||||
)
|
||||
: storage
|
||||
.page(userId: id, filter: filter)
|
||||
.page(userId: id, filter: params.filter)
|
||||
.then(
|
||||
(value) => value
|
||||
// we can assume that `youAre` is not null either for logged
|
||||
@@ -147,8 +148,8 @@ class UserGameHistory extends _$UserGameHistory {
|
||||
isLoading: false,
|
||||
hasMore: true,
|
||||
hasError: false,
|
||||
online: isOnline,
|
||||
filter: filter,
|
||||
online: online,
|
||||
filter: params.filter,
|
||||
session: session,
|
||||
);
|
||||
}
|
||||
@@ -161,9 +162,9 @@ class UserGameHistory extends _$UserGameHistory {
|
||||
final currentVal = state.requireValue;
|
||||
state = AsyncData(currentVal.copyWith(isLoading: true));
|
||||
try {
|
||||
final value = await (userId != null
|
||||
final value = await (params.userId != null
|
||||
? _gameRepository.getUserGames(
|
||||
userId!,
|
||||
params.userId!,
|
||||
max: _nbPerPage,
|
||||
until: _list.last.game.createdAt,
|
||||
filter: currentVal.filter,
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'game_preferences.freezed.dart';
|
||||
part 'game_preferences.g.dart';
|
||||
|
||||
/// Provider for [GamePreferences].
|
||||
final gamePreferencesProvider = NotifierProvider<GamePreferences, GamePrefs>(
|
||||
GamePreferences.new,
|
||||
name: 'GamePreferencesProvider',
|
||||
);
|
||||
|
||||
/// Local game preferences, defined client-side only.
|
||||
@Riverpod(keepAlive: true)
|
||||
class GamePreferences extends _$GamePreferences with PreferencesStorage<GamePrefs> {
|
||||
class GamePreferences extends Notifier<GamePrefs> with PreferencesStorage<GamePrefs> {
|
||||
@override
|
||||
@protected
|
||||
final prefCategory = PrefCategory.game;
|
||||
|
||||
@@ -5,13 +5,12 @@ import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/game/exported_game.dart';
|
||||
import 'package:lichess_mobile/src/model/game/game_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/game/game_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'game_repository_providers.g.dart';
|
||||
|
||||
/// Fetches a game from the server or from the local storage if not available online.
|
||||
@riverpod
|
||||
Future<ExportedGame> archivedGame(Ref ref, {required GameId id}) async {
|
||||
final archivedGameProvider = FutureProvider.autoDispose.family<ExportedGame, GameId>((
|
||||
Ref ref,
|
||||
GameId id,
|
||||
) async {
|
||||
ExportedGame game;
|
||||
try {
|
||||
final isLoggedIn = ref.watch(isLoggedInProvider);
|
||||
@@ -26,9 +25,11 @@ Future<ExportedGame> archivedGame(Ref ref, {required GameId id}) async {
|
||||
}
|
||||
}
|
||||
return game;
|
||||
}
|
||||
}, name: 'ArchivedGameProvider');
|
||||
|
||||
@riverpod
|
||||
Future<IList<LightExportedGame>> gamesById(Ref ref, {required ISet<GameId> ids}) {
|
||||
final gamesByIdProvider = FutureProvider.autoDispose.family<IList<LightExportedGame>, ISet<GameId>>(
|
||||
(Ref ref, ISet<GameId> ids) {
|
||||
return ref.read(gameRepositoryProvider).getGamesByIds(ids);
|
||||
}
|
||||
},
|
||||
name: 'GamesByIdProvider',
|
||||
);
|
||||
|
||||
@@ -9,15 +9,12 @@ import 'package:lichess_mobile/src/model/game/exported_game.dart';
|
||||
import 'package:lichess_mobile/src/model/game/game_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/board_preferences.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
part 'game_share_service.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
GameShareService gameShareService(Ref ref) {
|
||||
/// A provider for [GameShareService].
|
||||
final gameShareServiceProvider = Provider<GameShareService>((Ref ref) {
|
||||
return GameShareService(ref);
|
||||
}
|
||||
}, name: 'GameShareServiceProvider');
|
||||
|
||||
class GameShareService {
|
||||
GameShareService(this._ref);
|
||||
|
||||
@@ -6,16 +6,13 @@ import 'package:lichess_mobile/src/db/database.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/game/exported_game.dart';
|
||||
import 'package:lichess_mobile/src/model/game/game_filter.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
part 'game_storage.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<GameStorage> gameStorage(Ref ref) async {
|
||||
/// A provider for [GameStorage].
|
||||
final gameStorageProvider = FutureProvider<GameStorage>((Ref ref) async {
|
||||
final db = await ref.watch(databaseProvider.future);
|
||||
return GameStorage(db);
|
||||
}
|
||||
}, name: 'GameStorageProvider');
|
||||
|
||||
const kGameStorageTable = 'game';
|
||||
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/http_log/http_log_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'http_log_paginator.freezed.dart';
|
||||
part 'http_log_paginator.g.dart';
|
||||
|
||||
/// The number of HTTP logs to fetch per page.
|
||||
const _pageSize = 20;
|
||||
|
||||
/// A provider for [HttpLogPaginator].
|
||||
final httpLogPaginatorProvider = AsyncNotifierProvider.autoDispose<HttpLogPaginator, HttpLogState>(
|
||||
HttpLogPaginator.new,
|
||||
name: 'HttpLogPaginatorProvider',
|
||||
);
|
||||
|
||||
/// A Riverpod controller for managing HTTP logs.
|
||||
///
|
||||
/// The `HttpLogController` class is responsible for fetching and managing
|
||||
/// paginated HTTP log entries from the storage. It uses a throttler to limit
|
||||
/// the rate of fetching new pages.
|
||||
@riverpod
|
||||
class HttpLogPaginator extends _$HttpLogPaginator {
|
||||
class HttpLogPaginator extends AsyncNotifier<HttpLogState> {
|
||||
@override
|
||||
Future<HttpLogState> build() async {
|
||||
final storage = await ref.read(httpLogStorageProvider.future);
|
||||
@@ -68,9 +72,8 @@ sealed class HttpLogState with _$HttpLogState {
|
||||
const factory HttpLogState({required IList<AsyncValue<HttpLog>> data}) = _HttpLogState;
|
||||
|
||||
bool get initialized => data.isNotEmpty;
|
||||
List<HttpLogEntry> get logs =>
|
||||
data.expand((e) => e.valueOrNull?.items ?? <HttpLogEntry>[]).toList();
|
||||
int? get nextPage => data.lastOrNull?.valueOrNull?.next;
|
||||
List<HttpLogEntry> get logs => data.expand((e) => e.value?.items ?? <HttpLogEntry>[]).toList();
|
||||
int? get nextPage => data.lastOrNull?.value?.next;
|
||||
bool get hasMore => initialized && nextPage != null;
|
||||
bool get isLoading => data.lastOrNull?.isLoading == true;
|
||||
bool get isDeleteButtonVisible => logs.isNotEmpty;
|
||||
|
||||
@@ -3,18 +3,16 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/db/database.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
part 'http_log_storage.g.dart';
|
||||
part 'http_log_storage.freezed.dart';
|
||||
|
||||
/// Provides an instance of [HttpLogStorage] using Riverpod.
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<HttpLogStorage> httpLogStorage(Ref ref) async {
|
||||
final httpLogStorageProvider = FutureProvider<HttpLogStorage>((Ref ref) async {
|
||||
final db = await ref.watch(databaseProvider.future);
|
||||
return HttpLogStorage(db);
|
||||
}
|
||||
}, name: 'HttpLogStorageProvider');
|
||||
|
||||
const kHttpLogStorageTable = 'http_log';
|
||||
|
||||
|
||||
@@ -14,9 +14,7 @@ import 'package:lichess_mobile/src/model/lobby/lobby_repository.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'create_game_service.g.dart';
|
||||
part 'create_game_service.freezed.dart';
|
||||
|
||||
/// The user response of a challenge request.
|
||||
@@ -47,14 +45,13 @@ sealed class ChallengeResponseDeclined
|
||||
}
|
||||
|
||||
/// A provider for the [CreateGameService].
|
||||
@riverpod
|
||||
CreateGameService createGameService(Ref ref) {
|
||||
final createGameServiceProvider = Provider.autoDispose<CreateGameService>((Ref ref) {
|
||||
final service = CreateGameService(Logger('CreateGameService'), ref: ref);
|
||||
ref.onDispose(() {
|
||||
service.dispose();
|
||||
});
|
||||
return service;
|
||||
}
|
||||
}, name: 'CreateGameServiceProvider');
|
||||
|
||||
/// A service to create a new game from the lobby or from a challenge.
|
||||
class CreateGameService {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/binding.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
@@ -13,7 +14,6 @@ import 'package:lichess_mobile/src/model/game/playable_game.dart';
|
||||
import 'package:lichess_mobile/src/model/lobby/game_setup_preferences.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'game_seek.freezed.dart';
|
||||
part 'game_seek.g.dart';
|
||||
@@ -139,8 +139,13 @@ sealed class RecentGameSeekPrefs with _$RecentGameSeekPrefs implements Serializa
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class RecentGameSeek extends _$RecentGameSeek with PreferencesStorage<RecentGameSeekPrefs> {
|
||||
final recentGameSeekProvider = NotifierProvider<RecentGameSeek, RecentGameSeekPrefs>(
|
||||
RecentGameSeek.new,
|
||||
name: 'RecentGameSeekProvider',
|
||||
);
|
||||
|
||||
class RecentGameSeek extends Notifier<RecentGameSeekPrefs>
|
||||
with PreferencesStorage<RecentGameSeekPrefs> {
|
||||
@override
|
||||
@protected
|
||||
final prefCategory = PrefCategory.gameSeeks;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/common/chess.dart';
|
||||
import 'package:lichess_mobile/src/model/common/perf.dart';
|
||||
@@ -6,13 +7,16 @@ import 'package:lichess_mobile/src/model/common/speed.dart';
|
||||
import 'package:lichess_mobile/src/model/common/time_increment.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'game_setup_preferences.freezed.dart';
|
||||
part 'game_setup_preferences.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class GameSetupPreferences extends _$GameSetupPreferences
|
||||
final gameSetupPreferencesProvider = NotifierProvider<GameSetupPreferences, GameSetupPrefs>(
|
||||
GameSetupPreferences.new,
|
||||
name: 'GameSetupPreferencesProvider',
|
||||
);
|
||||
|
||||
class GameSetupPreferences extends Notifier<GameSetupPrefs>
|
||||
with SessionPreferencesStorage<GameSetupPrefs> {
|
||||
@override
|
||||
@protected
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/model/common/socket.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'lobby_numbers.g.dart';
|
||||
|
||||
/// The [LobbyNumbers] provider is used to display the number of players and
|
||||
/// games on lichess in real time.
|
||||
@riverpod
|
||||
class LobbyNumbers extends _$LobbyNumbers {
|
||||
final lobbyNumbersProvider =
|
||||
NotifierProvider.autoDispose<LobbyNumbers, ({int nbPlayers, int nbGames})?>(
|
||||
LobbyNumbers.new,
|
||||
name: 'LobbyNumbersProvider',
|
||||
);
|
||||
|
||||
class LobbyNumbers extends Notifier<({int nbPlayers, int nbGames})?> {
|
||||
StreamSubscription<SocketEvent>? _socketSubscription;
|
||||
|
||||
@override
|
||||
|
||||
@@ -7,14 +7,13 @@ import 'package:lichess_mobile/src/model/common/perf.dart';
|
||||
import 'package:lichess_mobile/src/model/lobby/correspondence_challenge.dart';
|
||||
import 'package:lichess_mobile/src/model/lobby/game_seek.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'lobby_repository.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<IList<CorrespondenceChallenge>> correspondenceChallenges(Ref ref) {
|
||||
final correspondenceChallengesProvider = FutureProvider.autoDispose<IList<CorrespondenceChallenge>>(
|
||||
(Ref ref) {
|
||||
return ref.withClient((client) => LobbyRepository(client).getCorrespondenceChallenges());
|
||||
}
|
||||
},
|
||||
name: 'CorrespondenceChallengesProvider',
|
||||
);
|
||||
|
||||
class LobbyRepository {
|
||||
LobbyRepository(this.client);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
@@ -8,15 +9,21 @@ import 'package:lichess_mobile/src/model/message/message_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:lichess_mobile/src/utils/rate_limit.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'conversation_controller.g.dart';
|
||||
part 'conversation_controller.freezed.dart';
|
||||
|
||||
const kMessagesPerPage = 100;
|
||||
|
||||
@riverpod
|
||||
class ConversationController extends _$ConversationController {
|
||||
final conversationControllerProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<ConversationController, ConversationState, UserId>(
|
||||
ConversationController.new,
|
||||
name: 'ConversationControllerProvider',
|
||||
);
|
||||
|
||||
class ConversationController extends AsyncNotifier<ConversationState> {
|
||||
ConversationController(this.userId);
|
||||
final UserId userId;
|
||||
|
||||
late SocketClient _client;
|
||||
StreamSubscription<SocketEvent>? _socketSubscription;
|
||||
Timer? _setReadTimer;
|
||||
@@ -27,7 +34,7 @@ class ConversationController extends _$ConversationController {
|
||||
LightUser? get _me => ref.read(authSessionProvider)?.user;
|
||||
|
||||
@override
|
||||
Future<ConversationState> build(UserId userId) async {
|
||||
Future<ConversationState> build() async {
|
||||
_connectSocket();
|
||||
|
||||
ref.onDispose(() {
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/message/message.dart';
|
||||
import 'package:lichess_mobile/src/network/aggregator.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
/// A provider that gets the conversation data for the current user.
|
||||
final contactsProvider = FutureProvider.autoDispose<Contacts>((ref) {
|
||||
|
||||
@@ -9,16 +9,13 @@ import 'package:lichess_mobile/src/model/notifications/notifications.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user_repository.dart';
|
||||
import 'package:lichess_mobile/src/tab_scaffold.dart';
|
||||
import 'package:lichess_mobile/src/view/message/conversation_screen.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'message_service.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
MessageService messageService(Ref ref) {
|
||||
/// A provider for [MessageService].
|
||||
final messageServiceProvider = Provider<MessageService>((Ref ref) {
|
||||
final service = MessageService(ref);
|
||||
ref.onDispose(service.dispose);
|
||||
return service;
|
||||
}
|
||||
}, name: 'MessageServiceProvider');
|
||||
|
||||
class MessageService {
|
||||
MessageService(this.ref);
|
||||
|
||||
@@ -15,25 +15,22 @@ import 'package:lichess_mobile/src/network/connectivity.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/utils/badge_service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'notification_service.g.dart';
|
||||
|
||||
final _logger = Logger('NotificationService');
|
||||
|
||||
/// A provider instance of the [FlutterLocalNotificationsPlugin].
|
||||
@Riverpod(keepAlive: true)
|
||||
FlutterLocalNotificationsPlugin notificationDisplay(Ref _) => FlutterLocalNotificationsPlugin();
|
||||
final notificationDisplayProvider = Provider<FlutterLocalNotificationsPlugin>(
|
||||
(Ref _) => FlutterLocalNotificationsPlugin(),
|
||||
);
|
||||
|
||||
/// A provider instance of the [NotificationService].
|
||||
@Riverpod(keepAlive: true)
|
||||
NotificationService notificationService(Ref ref) {
|
||||
final notificationServiceProvider = Provider<NotificationService>((Ref ref) {
|
||||
final service = NotificationService(ref);
|
||||
|
||||
ref.onDispose(() => service._dispose());
|
||||
|
||||
return service;
|
||||
}
|
||||
});
|
||||
|
||||
/// Received FCM message and whether it was from the background.
|
||||
typedef ReceivedFcmMessage = ({FcmMessage message, bool fromBackground});
|
||||
|
||||
@@ -2,15 +2,19 @@ import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/common/time_increment.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'over_the_board_clock.freezed.dart';
|
||||
part 'over_the_board_clock.g.dart';
|
||||
|
||||
@riverpod
|
||||
class OverTheBoardClock extends _$OverTheBoardClock {
|
||||
final overTheBoardClockProvider =
|
||||
NotifierProvider.autoDispose<OverTheBoardClock, OverTheBoardClockState>(
|
||||
OverTheBoardClock.new,
|
||||
name: 'OverTheBoardClockProvider',
|
||||
);
|
||||
|
||||
class OverTheBoardClock extends Notifier<OverTheBoardClockState> {
|
||||
final Stopwatch _stopwatch = Stopwatch();
|
||||
|
||||
Timer? _updateTimer;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/common/chess.dart';
|
||||
import 'package:lichess_mobile/src/model/common/chess960.dart';
|
||||
@@ -11,13 +12,16 @@ 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/material_diff.dart';
|
||||
import 'package:lichess_mobile/src/model/game/over_the_board_game.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'over_the_board_game_controller.freezed.dart';
|
||||
part 'over_the_board_game_controller.g.dart';
|
||||
|
||||
@riverpod
|
||||
class OverTheBoardGameController extends _$OverTheBoardGameController {
|
||||
final overTheBoardGameControllerProvider =
|
||||
NotifierProvider.autoDispose<OverTheBoardGameController, OverTheBoardGameState>(
|
||||
OverTheBoardGameController.new,
|
||||
name: 'OverTheBoardGameControllerProvider',
|
||||
);
|
||||
|
||||
class OverTheBoardGameController extends Notifier<OverTheBoardGameState> {
|
||||
@override
|
||||
OverTheBoardGameState build() => OverTheBoardGameState.fromVariant(
|
||||
Variant.standard,
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_providers.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_repository.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/utils/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'puzzle_activity.freezed.dart';
|
||||
part 'puzzle_activity.g.dart';
|
||||
|
||||
const _nbPerPage = 50;
|
||||
const _maxPuzzles = 500;
|
||||
|
||||
@riverpod
|
||||
class PuzzleActivity extends _$PuzzleActivity {
|
||||
final puzzleActivityProvider =
|
||||
AsyncNotifierProvider.autoDispose<PuzzleActivity, PuzzleActivityState>(
|
||||
PuzzleActivity.new,
|
||||
name: 'PuzzleActivityProvider',
|
||||
);
|
||||
|
||||
class PuzzleActivity extends AsyncNotifier<PuzzleActivityState> {
|
||||
final _list = <PuzzleHistoryEntry>[];
|
||||
|
||||
@override
|
||||
|
||||
@@ -9,17 +9,16 @@ import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_angle.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_theme.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
part 'puzzle_batch_storage.freezed.dart';
|
||||
part 'puzzle_batch_storage.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<PuzzleBatchStorage> puzzleBatchStorage(Ref ref) async {
|
||||
/// A provider for [PuzzleBatchStorage].
|
||||
final puzzleBatchStorageProvider = FutureProvider<PuzzleBatchStorage>((Ref ref) async {
|
||||
final database = await ref.watch(databaseProvider.future);
|
||||
return PuzzleBatchStorage(database, ref);
|
||||
}
|
||||
}, name: 'PuzzleBatchStorageProvider');
|
||||
|
||||
const _anonUserKey = '**anon**';
|
||||
const _tableName = 'puzzle_batchs';
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:collection/collection.dart' show IterableExtension;
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/common/chess.dart';
|
||||
import 'package:lichess_mobile/src/model/common/node.dart';
|
||||
@@ -20,13 +21,20 @@ import 'package:lichess_mobile/src/model/puzzle/puzzle_service.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_session.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'puzzle_controller.freezed.dart';
|
||||
part 'puzzle_controller.g.dart';
|
||||
|
||||
@riverpod
|
||||
class PuzzleController extends _$PuzzleController with EngineEvaluationMixin {
|
||||
final puzzleControllerProvider = NotifierProvider.autoDispose
|
||||
.family<PuzzleController, PuzzleState, PuzzleContext>(
|
||||
PuzzleController.new,
|
||||
name: 'PuzzleControllerProvider',
|
||||
);
|
||||
|
||||
class PuzzleController extends Notifier<PuzzleState> with EngineEvaluationMixin {
|
||||
PuzzleController(this.initialContext);
|
||||
|
||||
final PuzzleContext initialContext;
|
||||
|
||||
static final Uri socketUri = Uri(path: '/analysis/socket/v5');
|
||||
|
||||
late Branch _gameTree;
|
||||
@@ -62,7 +70,7 @@ class PuzzleController extends _$PuzzleController with EngineEvaluationMixin {
|
||||
ref.read(puzzleServiceFactoryProvider)(queueLength: kPuzzleLocalQueueLength);
|
||||
|
||||
@override
|
||||
PuzzleState build(PuzzleContext initialContext, {bool isPuzzleStreak = false}) {
|
||||
PuzzleState build() {
|
||||
initEngineEvaluation();
|
||||
|
||||
ref.onDispose(() {
|
||||
@@ -165,7 +173,7 @@ class PuzzleController extends _$PuzzleController with EngineEvaluationMixin {
|
||||
} else {
|
||||
state = state.copyWith(feedback: PuzzleFeedback.bad);
|
||||
_onFailOrWin(PuzzleResult.lose);
|
||||
if (!isPuzzleStreak) {
|
||||
if (initialContext.isPuzzleStreak != true) {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 500));
|
||||
_setPath(state.currentPath.penultimate);
|
||||
}
|
||||
@@ -224,7 +232,7 @@ class PuzzleController extends _$PuzzleController with EngineEvaluationMixin {
|
||||
}
|
||||
|
||||
void skipMove() {
|
||||
if (isPuzzleStreak && state._nextSolutionMove != null) {
|
||||
if (initialContext.isPuzzleStreak == true && state._nextSolutionMove != null) {
|
||||
onUserMove(state._nextSolutionMove!);
|
||||
}
|
||||
}
|
||||
@@ -271,7 +279,7 @@ class PuzzleController extends _$PuzzleController with EngineEvaluationMixin {
|
||||
|
||||
final soundService = ref.read(soundServiceProvider);
|
||||
|
||||
if (isPuzzleStreak) {
|
||||
if (initialContext.isPuzzleStreak == true) {
|
||||
// one fail and streak is over
|
||||
if (result == PuzzleResult.lose) {
|
||||
soundService.play(Sound.error);
|
||||
@@ -307,13 +315,23 @@ class PuzzleController extends _$PuzzleController with EngineEvaluationMixin {
|
||||
state = state.copyWith(nextContext: next);
|
||||
|
||||
ref
|
||||
.read(puzzleSessionProvider(initialContext.userId, initialContext.angle).notifier)
|
||||
.read(
|
||||
puzzleSessionProvider((
|
||||
userId: initialContext.userId,
|
||||
angle: initialContext.angle,
|
||||
)).notifier,
|
||||
)
|
||||
.addAttempt(state.puzzle.puzzle.id, win: result == PuzzleResult.win);
|
||||
|
||||
final rounds = next?.rounds;
|
||||
if (rounds != null) {
|
||||
ref
|
||||
.read(puzzleSessionProvider(initialContext.userId, initialContext.angle).notifier)
|
||||
.read(
|
||||
puzzleSessionProvider((
|
||||
userId: initialContext.userId,
|
||||
angle: initialContext.angle,
|
||||
)).notifier,
|
||||
)
|
||||
.setRatingDiffs(rounds);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'puzzle_opening.g.dart';
|
||||
|
||||
typedef PuzzleOpeningFamily = ({
|
||||
String key,
|
||||
@@ -15,8 +12,9 @@ typedef PuzzleOpeningFamily = ({
|
||||
typedef PuzzleOpeningData = ({String key, String name, int count});
|
||||
|
||||
/// Returns a flattened list of openings with their respective counts.
|
||||
@riverpod
|
||||
Future<IList<PuzzleOpeningData>> flatOpeningsList(Ref ref) async {
|
||||
final flatOpeningsListProvider = FutureProvider.autoDispose<IList<PuzzleOpeningData>>((
|
||||
Ref ref,
|
||||
) async {
|
||||
final families = await ref.watch(puzzleOpeningsProvider.future);
|
||||
return families
|
||||
.map(
|
||||
@@ -27,10 +25,13 @@ Future<IList<PuzzleOpeningData>> flatOpeningsList(Ref ref) async {
|
||||
)
|
||||
.expand((e) => e)
|
||||
.toIList();
|
||||
}
|
||||
}, name: 'FlatOpeningsListProvider');
|
||||
|
||||
@riverpod
|
||||
Future<String> puzzleOpeningName(Ref ref, String key) async {
|
||||
/// Provides the name of a puzzle opening given its key.
|
||||
final puzzleOpeningNameProvider = FutureProvider.autoDispose.family<String, String>((
|
||||
Ref ref,
|
||||
String key,
|
||||
) async {
|
||||
final openings = await ref.watch(flatOpeningsListProvider.future);
|
||||
return openings.firstWhere((element) => element.key == key).name;
|
||||
}
|
||||
}, name: 'PuzzleOpeningNameProvider');
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_difficulty.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'puzzle_preferences.freezed.dart';
|
||||
part 'puzzle_preferences.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class PuzzlePreferences extends _$PuzzlePreferences with SessionPreferencesStorage<PuzzlePrefs> {
|
||||
final puzzlePreferencesProvider = NotifierProvider<PuzzlePreferences, PuzzlePrefs>(
|
||||
PuzzlePreferences.new,
|
||||
name: 'PuzzlePreferencesProvider',
|
||||
);
|
||||
|
||||
class PuzzlePreferences extends Notifier<PuzzlePrefs> with SessionPreferencesStorage<PuzzlePrefs> {
|
||||
@override
|
||||
@protected
|
||||
final prefCategory = PrefCategory.puzzle;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
@@ -15,12 +13,12 @@ import 'package:lichess_mobile/src/model/puzzle/puzzle_theme.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/storm.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/utils/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'puzzle_providers.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<PuzzleContext?> nextPuzzle(Ref ref, PuzzleAngle angle) async {
|
||||
/// Fetches the next puzzle for the given [PuzzleAngle].
|
||||
final nextPuzzleProvider = FutureProvider.autoDispose.family<PuzzleContext?, PuzzleAngle>((
|
||||
Ref ref,
|
||||
PuzzleAngle angle,
|
||||
) async {
|
||||
final session = ref.watch(authSessionProvider);
|
||||
final puzzleService = await ref.read(puzzleServiceFactoryProvider)(
|
||||
queueLength: kPuzzleLocalQueueLength,
|
||||
@@ -30,88 +28,102 @@ Future<PuzzleContext?> nextPuzzle(Ref ref, PuzzleAngle angle) async {
|
||||
ref.cacheFor(const Duration(minutes: 1));
|
||||
|
||||
return puzzleService.nextPuzzle(userId: session?.user.id, angle: angle);
|
||||
}
|
||||
}, name: 'NextPuzzleProvider');
|
||||
|
||||
@riverpod
|
||||
Future<PuzzleStormResponse> storm(Ref ref) {
|
||||
/// Fetches a storm of puzzles.
|
||||
final stormProvider = FutureProvider.autoDispose<PuzzleStormResponse>((Ref ref) {
|
||||
return ref.withClient((client) => PuzzleRepository(client).storm());
|
||||
}
|
||||
}, name: 'StormProvider');
|
||||
|
||||
/// Fetches a puzzle from the local storage if available, otherwise fetches it from the server.
|
||||
@riverpod
|
||||
Future<Puzzle> puzzle(Ref ref, PuzzleId id) async {
|
||||
final puzzleProvider = FutureProvider.autoDispose.family<Puzzle, PuzzleId>((
|
||||
Ref ref,
|
||||
PuzzleId id,
|
||||
) async {
|
||||
final puzzleStorage = await ref.watch(puzzleStorageProvider.future);
|
||||
final puzzle = await puzzleStorage.fetch(puzzleId: id);
|
||||
if (puzzle != null) return puzzle;
|
||||
return ref.withClient((client) => PuzzleRepository(client).fetch(id));
|
||||
}
|
||||
}, name: 'PuzzleProvider');
|
||||
|
||||
@riverpod
|
||||
Future<Puzzle> dailyPuzzle(Ref ref) {
|
||||
/// Fetches the daily puzzle.
|
||||
final dailyPuzzleProvider = FutureProvider.autoDispose<Puzzle>((Ref ref) {
|
||||
return ref.withClientCacheFor(
|
||||
(client) => PuzzleRepository(client).daily(),
|
||||
const Duration(hours: 6),
|
||||
);
|
||||
}
|
||||
}, name: 'DailyPuzzleProvider');
|
||||
|
||||
@riverpod
|
||||
Future<IList<(PuzzleAngle, int)>> savedBatches(Ref ref) async {
|
||||
/// Fetches all saved puzzle batches for the current user.
|
||||
final savedBatchesProvider = FutureProvider.autoDispose<IList<(PuzzleAngle, int)>>((Ref ref) async {
|
||||
final session = ref.watch(authSessionProvider);
|
||||
final storage = await ref.watch(puzzleBatchStorageProvider.future);
|
||||
return storage.fetchAll(userId: session?.user.id);
|
||||
}
|
||||
}, name: 'SavedBatchesProvider');
|
||||
|
||||
@riverpod
|
||||
Future<IMap<PuzzleThemeKey, int>> savedThemeBatches(Ref ref) async {
|
||||
/// Fetches saved puzzle theme batches for the current user.
|
||||
final savedThemeBatchesProvider = FutureProvider.autoDispose<IMap<PuzzleThemeKey, int>>((
|
||||
Ref ref,
|
||||
) async {
|
||||
final session = ref.watch(authSessionProvider);
|
||||
final storage = await ref.watch(puzzleBatchStorageProvider.future);
|
||||
return storage.fetchSavedThemes(userId: session?.user.id);
|
||||
}
|
||||
}, name: 'SavedThemeBatchesProvider');
|
||||
|
||||
@riverpod
|
||||
Future<IMap<String, int>> savedOpeningBatches(Ref ref) async {
|
||||
/// Fetches saved puzzle opening batches for the current user.
|
||||
final savedOpeningBatchesProvider = FutureProvider.autoDispose<IMap<String, int>>((Ref ref) async {
|
||||
final session = ref.watch(authSessionProvider);
|
||||
final storage = await ref.watch(puzzleBatchStorageProvider.future);
|
||||
return storage.fetchSavedOpenings(userId: session?.user.id);
|
||||
}
|
||||
}, name: 'SavedOpeningBatchesProvider');
|
||||
|
||||
@riverpod
|
||||
Future<PuzzleDashboard?> puzzleDashboard(Ref ref, int days) async {
|
||||
/// Fetches the puzzle dashboard for the current user for the given number of [days].
|
||||
final puzzleDashboardProvider = FutureProvider.autoDispose.family<PuzzleDashboard?, int>((
|
||||
Ref ref,
|
||||
int days,
|
||||
) {
|
||||
final session = ref.watch(authSessionProvider);
|
||||
if (session == null) return null;
|
||||
return ref.withClientCacheFor(
|
||||
(client) => PuzzleRepository(client).puzzleDashboard(days),
|
||||
const Duration(hours: 3),
|
||||
);
|
||||
}
|
||||
}, name: 'PuzzleDashboardProvider');
|
||||
|
||||
@riverpod
|
||||
Future<IList<PuzzleHistoryEntry>?> puzzleRecentActivity(Ref ref) async {
|
||||
/// Fetches recent puzzle activity for the current user.
|
||||
final puzzleRecentActivityProvider = FutureProvider.autoDispose<IList<PuzzleHistoryEntry>?>((
|
||||
Ref ref,
|
||||
) {
|
||||
final session = ref.watch(authSessionProvider);
|
||||
if (session == null) return null;
|
||||
return ref.withClientCacheFor(
|
||||
(client) => PuzzleRepository(client).puzzleActivity(20),
|
||||
const Duration(hours: 3),
|
||||
);
|
||||
}
|
||||
}, name: 'PuzzleRecentActivityProvider');
|
||||
|
||||
@riverpod
|
||||
Future<StormDashboard?> stormDashboard(Ref ref, UserId id) {
|
||||
/// Fetches the storm dashboard for a given user [UserId].
|
||||
final stormDashboardProvider = FutureProvider.autoDispose.family<StormDashboard?, UserId>((
|
||||
Ref ref,
|
||||
UserId id,
|
||||
) {
|
||||
return ref.withClient((client) => PuzzleRepository(client).stormDashboard(id));
|
||||
}
|
||||
}, name: 'StormDashboardProvider');
|
||||
|
||||
@riverpod
|
||||
Future<IMap<PuzzleThemeKey, PuzzleThemeData>> puzzleThemes(Ref ref) {
|
||||
/// Fetches available puzzle themes.
|
||||
final puzzleThemesProvider = FutureProvider.autoDispose<IMap<PuzzleThemeKey, PuzzleThemeData>>((
|
||||
Ref ref,
|
||||
) {
|
||||
return ref.withClientCacheFor(
|
||||
(client) => PuzzleRepository(client).puzzleThemes(),
|
||||
const Duration(days: 1),
|
||||
);
|
||||
}
|
||||
}, name: 'PuzzleThemesProvider');
|
||||
|
||||
@riverpod
|
||||
Future<IList<PuzzleOpeningFamily>> puzzleOpenings(Ref ref) {
|
||||
/// Fetches available puzzle openings.
|
||||
final puzzleOpeningsProvider = FutureProvider.autoDispose<IList<PuzzleOpeningFamily>>((Ref ref) {
|
||||
return ref.withClientCacheFor(
|
||||
(client) => PuzzleRepository(client).puzzleOpenings(),
|
||||
const Duration(days: 1),
|
||||
);
|
||||
}
|
||||
}, name: 'PuzzleOpeningsProvider');
|
||||
|
||||
@@ -15,23 +15,21 @@ import 'package:lichess_mobile/src/model/puzzle/puzzle_theme.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:result_extensions/result_extensions.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'puzzle_service.freezed.dart';
|
||||
part 'puzzle_service.g.dart';
|
||||
|
||||
/// Size of puzzle local cache
|
||||
const kPuzzleLocalQueueLength = 50;
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<PuzzleService> puzzleService(Ref ref) {
|
||||
/// A provider for [PuzzleService].
|
||||
final puzzleServiceProvider = FutureProvider<PuzzleService>((Ref ref) {
|
||||
return ref.read(puzzleServiceFactoryProvider)(queueLength: kPuzzleLocalQueueLength);
|
||||
}
|
||||
}, name: 'PuzzleServiceProvider');
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
PuzzleServiceFactory puzzleServiceFactory(Ref ref) {
|
||||
/// A provider for [PuzzleServiceFactory].
|
||||
final puzzleServiceFactoryProvider = Provider<PuzzleServiceFactory>((Ref ref) {
|
||||
return PuzzleServiceFactory(ref);
|
||||
}
|
||||
}, name: 'PuzzleServiceFactoryProvider');
|
||||
|
||||
class PuzzleServiceFactory {
|
||||
PuzzleServiceFactory(this._ref);
|
||||
@@ -63,6 +61,7 @@ sealed class PuzzleContext with _$PuzzleContext {
|
||||
|
||||
/// If true, the result won't be recorded on the server for this puzzle.
|
||||
bool? casual,
|
||||
bool? isPuzzleStreak,
|
||||
}) = _PuzzleContext;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,32 +2,43 @@ import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/binding.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_angle.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_theme.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
part 'puzzle_session.freezed.dart';
|
||||
part 'puzzle_session.g.dart';
|
||||
|
||||
@riverpod
|
||||
class PuzzleSession extends _$PuzzleSession {
|
||||
final puzzleSessionProvider =
|
||||
NotifierProvider.family<PuzzleSession, PuzzleSessionData, PuzzleSessionParams>(
|
||||
PuzzleSession.new,
|
||||
name: 'PuzzleSessionProvider',
|
||||
);
|
||||
|
||||
typedef PuzzleSessionParams = ({UserId? userId, PuzzleAngle angle});
|
||||
|
||||
class PuzzleSession extends Notifier<PuzzleSessionData> {
|
||||
PuzzleSession(this.params);
|
||||
|
||||
final PuzzleSessionParams params;
|
||||
|
||||
static const maxAge = Duration(hours: 1);
|
||||
static const maxSize = 150;
|
||||
|
||||
@override
|
||||
PuzzleSessionData build(UserId? userId, PuzzleAngle angle) {
|
||||
PuzzleSessionData build() {
|
||||
final data = _stored;
|
||||
if (data != null &&
|
||||
data.angle == angle &&
|
||||
data.angle == params.angle &&
|
||||
data.lastUpdatedAt.isAfter(DateTime.now().subtract(maxAge))) {
|
||||
return data;
|
||||
}
|
||||
return PuzzleSessionData.initial(angle: angle);
|
||||
return PuzzleSessionData.initial(angle: params.angle);
|
||||
}
|
||||
|
||||
Future<void> addAttempt(PuzzleId id, {required bool win}) async {
|
||||
@@ -66,14 +77,14 @@ class PuzzleSession extends _$PuzzleSession {
|
||||
PuzzleSessionData? get _stored {
|
||||
final stored = _store.getString(_storageKey);
|
||||
if (stored == null) {
|
||||
return PuzzleSessionData.initial(angle: angle);
|
||||
return PuzzleSessionData.initial(angle: params.angle);
|
||||
}
|
||||
return PuzzleSessionData.fromJson(jsonDecode(stored) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
SharedPreferencesWithCache get _store => LichessBinding.instance.sharedPreferences;
|
||||
|
||||
String get _storageKey => 'puzzle_session.${userId ?? '**anon**'}';
|
||||
String get _storageKey => 'puzzle_session.${params.userId ?? '**anon**'}';
|
||||
}
|
||||
|
||||
@Freezed(fromJson: true, toJson: true)
|
||||
|
||||
@@ -4,16 +4,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/db/database.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
part 'puzzle_storage.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<PuzzleStorage> puzzleStorage(Ref ref) async {
|
||||
/// A provider for [PuzzleStorage].
|
||||
final puzzleStorageProvider = FutureProvider<PuzzleStorage>((Ref ref) async {
|
||||
final db = await ref.watch(databaseProvider.future);
|
||||
return PuzzleStorage(db);
|
||||
}
|
||||
}, name: 'PuzzleStorageProvider');
|
||||
|
||||
const _tableName = 'puzzle';
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
@@ -10,7 +11,6 @@ import 'package:lichess_mobile/src/model/puzzle/streak_storage.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/tab_scaffold.dart' show currentNavigatorKeyProvider;
|
||||
import 'package:lichess_mobile/src/widgets/feedback.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'puzzle_streak.freezed.dart';
|
||||
part 'puzzle_streak.g.dart';
|
||||
@@ -37,8 +37,13 @@ sealed class PuzzleStreak with _$PuzzleStreak {
|
||||
/// [PuzzleStreak] with its current [Puzzle].
|
||||
typedef StreakState = ({PuzzleStreak streak, Puzzle puzzle, Puzzle? nextPuzzle});
|
||||
|
||||
@riverpod
|
||||
class PuzzleStreakController extends _$PuzzleStreakController {
|
||||
final puzzleStreakControllerProvider =
|
||||
AsyncNotifierProvider.autoDispose<PuzzleStreakController, StreakState>(
|
||||
PuzzleStreakController.new,
|
||||
name: 'PuzzleStreakControllerProvider',
|
||||
);
|
||||
|
||||
class PuzzleStreakController extends AsyncNotifier<StreakState> {
|
||||
@override
|
||||
Future<StreakState> build() async {
|
||||
final session = ref.watch(authSessionProvider);
|
||||
|
||||
@@ -5,10 +5,8 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/l10n/l10n.dart';
|
||||
import 'package:lichess_mobile/src/localizations.dart';
|
||||
import 'package:lichess_mobile/src/styles/puzzle_icons.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'puzzle_theme.freezed.dart';
|
||||
part 'puzzle_theme.g.dart';
|
||||
|
||||
@freezed
|
||||
sealed class PuzzleThemeData with _$PuzzleThemeData {
|
||||
@@ -412,8 +410,7 @@ final IMap<String, PuzzleThemeKey> puzzleThemeNameMap = IMap(PuzzleThemeKey.valu
|
||||
|
||||
typedef PuzzleThemeCategory = (String, List<PuzzleThemeKey>);
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
IList<PuzzleThemeCategory> puzzleThemeCategories(Ref ref) {
|
||||
final puzzleThemeCategoriesProvider = Provider<IList<PuzzleThemeCategory>>((Ref ref) {
|
||||
final l10n = ref.watch(localizationsProvider);
|
||||
|
||||
return IList([
|
||||
@@ -512,7 +509,7 @@ IList<PuzzleThemeCategory> puzzleThemeCategories(Ref ref) {
|
||||
[PuzzleThemeKey.master, PuzzleThemeKey.masterVsMaster, PuzzleThemeKey.superGM],
|
||||
),
|
||||
]);
|
||||
}
|
||||
}, name: 'PuzzleThemeCategoriesProvider');
|
||||
|
||||
class PuzzleThemeL10n {
|
||||
const PuzzleThemeL10n({required this.name, required this.description});
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:async/async.dart';
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
import 'package:lichess_mobile/src/model/common/chess.dart';
|
||||
@@ -16,22 +17,33 @@ import 'package:lichess_mobile/src/model/puzzle/puzzle_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/storm.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:result_extensions/result_extensions.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'storm_controller.freezed.dart';
|
||||
part 'storm_controller.g.dart';
|
||||
|
||||
const malus = Duration(seconds: 10);
|
||||
const moveDelay = Duration(milliseconds: 200);
|
||||
const startTime = Duration(minutes: 3);
|
||||
|
||||
@riverpod
|
||||
class StormController extends _$StormController {
|
||||
typedef StormControllerParams = (IList<LitePuzzle> puzzles, DateTime timestamp);
|
||||
|
||||
final stormControllerProvider = NotifierProvider.autoDispose
|
||||
.family<StormController, StormState, StormControllerParams>(
|
||||
StormController.new,
|
||||
name: 'StormControllerProvider',
|
||||
);
|
||||
|
||||
class StormController extends Notifier<StormState> {
|
||||
StormController(this.params);
|
||||
|
||||
final StormControllerParams params;
|
||||
|
||||
Timer? _firstMoveTimer;
|
||||
|
||||
IList<LitePuzzle> get _puzzles => params.$1;
|
||||
|
||||
@override
|
||||
StormState build(IList<LitePuzzle> puzzles, DateTime timestamp) {
|
||||
final pov = Chess.fromSetup(Setup.parseFen(puzzles.first.fen));
|
||||
StormState build() {
|
||||
final pov = Chess.fromSetup(Setup.parseFen(_puzzles.first.fen));
|
||||
final clock = StormClock();
|
||||
|
||||
ref.onDispose(() {
|
||||
@@ -43,7 +55,7 @@ class StormController extends _$StormController {
|
||||
firstMovePlayed: false,
|
||||
mode: StormMode.initial,
|
||||
puzzleIndex: 0,
|
||||
puzzle: puzzles.first,
|
||||
puzzle: _puzzles.first,
|
||||
moves: 0,
|
||||
errors: 0,
|
||||
history: const IList.empty(),
|
||||
@@ -181,8 +193,8 @@ class StormController extends _$StormController {
|
||||
|
||||
state = state.copyWith(
|
||||
puzzleIndex: newPuzzleIndex,
|
||||
puzzle: puzzles[newPuzzleIndex],
|
||||
position: Chess.fromSetup(Setup.parseFen(puzzles[newPuzzleIndex].fen)),
|
||||
puzzle: _puzzles[newPuzzleIndex],
|
||||
position: Chess.fromSetup(Setup.parseFen(_puzzles[newPuzzleIndex].fen)),
|
||||
moveIndex: -1,
|
||||
numSolved: result ? state.numSolved + 1 : state.numSolved,
|
||||
lastSolvedTime: DateTime.now(),
|
||||
@@ -269,7 +281,7 @@ class StormController extends _$StormController {
|
||||
}
|
||||
|
||||
bool _isNextPuzzleAvailable() {
|
||||
return state.puzzleIndex + 1 < puzzles.length;
|
||||
return state.puzzleIndex + 1 < _puzzles.length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,29 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/binding.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_streak.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
part 'streak_storage.g.dart';
|
||||
|
||||
@riverpod
|
||||
StreakStorage streakStorage(Ref ref, UserId? userId) {
|
||||
/// Provider for the streak storage for a given user.
|
||||
final streakStorageProvider = Provider.autoDispose.family<StreakStorage, UserId?>((
|
||||
Ref ref,
|
||||
UserId? userId,
|
||||
) {
|
||||
return StreakStorage(ref, userId);
|
||||
}
|
||||
});
|
||||
|
||||
/// Fetches the current streak score from the local storage if available, returns null otherwise.
|
||||
@riverpod
|
||||
Future<int?> savedStreakScore(Ref ref) async {
|
||||
final savedStreakScoreProvider = FutureProvider.autoDispose<int?>((Ref ref) async {
|
||||
final session = ref.watch(authSessionProvider);
|
||||
// cannot use ref.watch because it would create a circular dependency
|
||||
// as we invalidate this provider in the storage saveActiveStreak and clearActiveStreak methods
|
||||
final streakStorage = ref.read(streakStorageProvider(session?.user.id));
|
||||
final streak = await streakStorage.loadActiveStreak();
|
||||
return streak?.index;
|
||||
}
|
||||
});
|
||||
|
||||
/// Local storage for the current puzzle streak.
|
||||
class StreakStorage {
|
||||
|
||||
@@ -2,18 +2,20 @@ import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/common/socket.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'online_friends.g.dart';
|
||||
|
||||
typedef OnlineFriend = ({LightUser user, bool playing});
|
||||
|
||||
@riverpod
|
||||
class OnlineFriends extends _$OnlineFriends {
|
||||
final onlineFriendsProvider = AsyncNotifierProvider.autoDispose<OnlineFriends, IList<OnlineFriend>>(
|
||||
OnlineFriends.new,
|
||||
name: 'OnlineFriendsProvider',
|
||||
);
|
||||
|
||||
class OnlineFriends extends AsyncNotifier<IList<OnlineFriend>> {
|
||||
StreamSubscription<SocketEvent>? _socketSubscription;
|
||||
StreamSubscription<void>? _socketOpenSubscription;
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ import 'package:chessground/chessground.dart';
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/l10n/l10n.dart';
|
||||
import 'package:lichess_mobile/src/model/common/chess.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:lichess_mobile/src/styles/styles.dart';
|
||||
import 'package:lichess_mobile/src/utils/color_palette.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'board_preferences.freezed.dart';
|
||||
part 'board_preferences.g.dart';
|
||||
@@ -16,8 +16,12 @@ part 'board_preferences.g.dart';
|
||||
const kBoardDefaultBrightnessFilter = 1.0;
|
||||
const kBoardDefaultHueFilter = 0.0;
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class BoardPreferences extends _$BoardPreferences with PreferencesStorage<BoardPrefs> {
|
||||
final boardPreferencesProvider = NotifierProvider<BoardPreferences, BoardPrefs>(
|
||||
BoardPreferences.new,
|
||||
name: 'BoardPreferencesProvider',
|
||||
);
|
||||
|
||||
class BoardPreferences extends Notifier<BoardPrefs> with PreferencesStorage<BoardPrefs> {
|
||||
@override
|
||||
@protected
|
||||
PrefCategory get prefCategory => PrefCategory.board;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import 'package:flutter/foundation.dart' show Brightness;
|
||||
import 'package:flutter/widgets.dart' show WidgetsBinding;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/general_preferences.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'brightness.g.dart';
|
||||
final currentBrightnessProvider = NotifierProvider<CurrentBrightness, Brightness>(
|
||||
CurrentBrightness.new,
|
||||
name: 'CurrentBrightnessProvider',
|
||||
);
|
||||
|
||||
@riverpod
|
||||
class CurrentBrightness extends _$CurrentBrightness {
|
||||
class CurrentBrightness extends Notifier<Brightness> {
|
||||
@override
|
||||
Brightness build() {
|
||||
final themeMode = ref.watch(generalPreferencesProvider.select((state) => state.themeMode));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/l10n/l10n.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/board_preferences.dart'
|
||||
@@ -7,13 +8,17 @@ import 'package:lichess_mobile/src/model/settings/board_preferences.dart'
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:lichess_mobile/src/theme.dart';
|
||||
import 'package:lichess_mobile/src/utils/json.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'general_preferences.freezed.dart';
|
||||
part 'general_preferences.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class GeneralPreferences extends _$GeneralPreferences with PreferencesStorage<GeneralPrefs> {
|
||||
final generalPreferencesProvider = NotifierProvider<GeneralPreferencesNotifier, GeneralPrefs>(
|
||||
GeneralPreferencesNotifier.new,
|
||||
name: 'GeneralPreferencesProvider',
|
||||
);
|
||||
|
||||
class GeneralPreferencesNotifier extends Notifier<GeneralPrefs>
|
||||
with PreferencesStorage<GeneralPrefs> {
|
||||
@override
|
||||
@protected
|
||||
final prefCategory = PrefCategory.general;
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'over_the_board_preferences.freezed.dart';
|
||||
part 'over_the_board_preferences.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class OverTheBoardPreferences extends _$OverTheBoardPreferences
|
||||
final overTheBoardPreferencesProvider =
|
||||
NotifierProvider<OverTheBoardPreferencesNotifier, OverTheBoardPrefs>(
|
||||
OverTheBoardPreferencesNotifier.new,
|
||||
name: 'OverTheBoardPreferencesProvider',
|
||||
);
|
||||
|
||||
class OverTheBoardPreferencesNotifier extends Notifier<OverTheBoardPrefs>
|
||||
with PreferencesStorage<OverTheBoardPrefs> {
|
||||
@override
|
||||
@protected
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:chessground/chessground.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/analysis/common_analysis_state.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
@@ -25,15 +26,22 @@ import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:lichess_mobile/src/utils/rate_limit.dart';
|
||||
import 'package:lichess_mobile/src/view/engine/engine_gauge.dart';
|
||||
import 'package:lichess_mobile/src/widgets/pgn.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'study_controller.freezed.dart';
|
||||
part 'study_controller.g.dart';
|
||||
|
||||
@riverpod
|
||||
class StudyController extends _$StudyController
|
||||
final studyControllerProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<StudyController, StudyState, StudyId>(
|
||||
StudyController.new,
|
||||
name: 'StudyControllerProvider',
|
||||
);
|
||||
|
||||
class StudyController extends AsyncNotifier<StudyState>
|
||||
with EngineEvaluationMixin
|
||||
implements PgnTreeNotifier {
|
||||
StudyController(this.id);
|
||||
|
||||
final StudyId id;
|
||||
|
||||
late Root _root;
|
||||
|
||||
Timer? _opponentFirstMoveTimer;
|
||||
@@ -69,7 +77,7 @@ class StudyController extends _$StudyController
|
||||
StudyState get evaluationState => state.requireValue;
|
||||
|
||||
@override
|
||||
Future<StudyState> build(StudyId id) async {
|
||||
Future<StudyState> build() async {
|
||||
ref.onDispose(() {
|
||||
_opponentFirstMoveTimer?.cancel();
|
||||
_sendMoveToSocketTimer?.cancel();
|
||||
@@ -275,7 +283,7 @@ class StudyController extends _$StudyController
|
||||
}
|
||||
|
||||
void onPromotionSelection(Role? role) {
|
||||
final state = this.state.valueOrNull;
|
||||
final state = this.state.value;
|
||||
if (state == null) return;
|
||||
|
||||
if (role == null) {
|
||||
@@ -300,7 +308,7 @@ class StudyController extends _$StudyController
|
||||
}
|
||||
|
||||
void userNext() {
|
||||
final state = this.state.valueOrNull;
|
||||
final state = this.state.value;
|
||||
if (state!.currentNode.children.isEmpty) return;
|
||||
_setPath(
|
||||
state.currentPath + _root.nodeAt(state.currentPath).children.first.id,
|
||||
@@ -332,7 +340,7 @@ class StudyController extends _$StudyController
|
||||
}
|
||||
|
||||
void toggleBoard() {
|
||||
final state = this.state.valueOrNull;
|
||||
final state = this.state.value;
|
||||
if (state != null) {
|
||||
this.state = AsyncValue.data(state.copyWith(pov: state.pov.opposite));
|
||||
}
|
||||
@@ -382,7 +390,7 @@ class StudyController extends _$StudyController
|
||||
|
||||
@override
|
||||
void promoteVariation(UciPath path, bool toMainline) {
|
||||
final state = this.state.valueOrNull;
|
||||
final state = this.state.value;
|
||||
if (state == null) return;
|
||||
_root.promoteAt(path, toMainline: toMainline);
|
||||
this.state = AsyncValue.data(
|
||||
@@ -440,7 +448,7 @@ class StudyController extends _$StudyController
|
||||
/// Whether the user is navigating through the moves (as opposed to playing a move).
|
||||
bool isNavigating = false,
|
||||
}) {
|
||||
final state = this.state.valueOrNull;
|
||||
final state = this.state.value;
|
||||
if (state == null) return;
|
||||
|
||||
final pathChange = state.currentPath != path;
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/model/study/study.dart';
|
||||
import 'package:lichess_mobile/src/model/study/study_filter.dart';
|
||||
import 'package:lichess_mobile/src/model/study/study_repository.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'study_list_paginator.g.dart';
|
||||
|
||||
typedef StudyList = ({IList<StudyPageItem> studies, int? nextPage});
|
||||
typedef StudyListNotifierParams = ({StudyCategory category, StudyListOrder order, String? search});
|
||||
|
||||
/// A provider that gets a list of studies from the paginated API.
|
||||
final studyListPaginatorProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<StudyListPaginatorNotifier, StudyList, StudyListNotifierParams>(
|
||||
StudyListPaginatorNotifier.new,
|
||||
name: 'StudyListPaginatorProvider',
|
||||
);
|
||||
|
||||
class StudyListPaginatorNotifier extends AsyncNotifier<StudyList> {
|
||||
StudyListPaginatorNotifier(this.params);
|
||||
|
||||
final StudyListNotifierParams params;
|
||||
|
||||
/// Gets a list of studies from the paginated API.
|
||||
@riverpod
|
||||
class StudyListPaginator extends _$StudyListPaginator {
|
||||
@override
|
||||
Future<StudyList> build({
|
||||
required StudyCategory category,
|
||||
required StudyListOrder order,
|
||||
String? search,
|
||||
}) {
|
||||
Future<StudyList> build() {
|
||||
return _nextPage();
|
||||
}
|
||||
|
||||
@@ -36,8 +40,8 @@ class StudyListPaginator extends _$StudyListPaginator {
|
||||
final nextPage = state.value?.nextPage ?? 1;
|
||||
|
||||
final repo = ref.read(studyRepositoryProvider);
|
||||
return search == null
|
||||
? repo.getStudies(category: category, order: order, page: nextPage)
|
||||
: repo.searchStudies(query: search!, page: nextPage);
|
||||
return params.search == null
|
||||
? repo.getStudies(category: params.category, order: params.order, page: nextPage)
|
||||
: repo.searchStudies(query: params.search!, page: nextPage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/analysis/common_analysis_prefs.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'study_preferences.freezed.dart';
|
||||
part 'study_preferences.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class StudyPreferences extends _$StudyPreferences with PreferencesStorage<StudyPrefs> {
|
||||
final studyPreferencesProvider = NotifierProvider<StudyPreferencesNotifier, StudyPrefs>(
|
||||
StudyPreferencesNotifier.new,
|
||||
name: 'StudyPreferencesProvider',
|
||||
);
|
||||
|
||||
class StudyPreferencesNotifier extends Notifier<StudyPrefs> with PreferencesStorage<StudyPrefs> {
|
||||
@override
|
||||
@protected
|
||||
final prefCategory = PrefCategory.study;
|
||||
|
||||
@@ -9,14 +9,11 @@ import 'package:lichess_mobile/src/model/study/study.dart';
|
||||
import 'package:lichess_mobile/src/model/study/study_filter.dart';
|
||||
import 'package:lichess_mobile/src/model/study/study_list_paginator.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'study_repository.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
StudyRepository studyRepository(Ref ref) {
|
||||
/// A provider for [StudyRepository].
|
||||
final studyRepositoryProvider = Provider<StudyRepository>((Ref ref) {
|
||||
return StudyRepository(ref, ref.read(lichessClientProvider));
|
||||
}
|
||||
}, name: 'StudyRepositoryProvider');
|
||||
|
||||
class StudyRepository {
|
||||
StudyRepository(this.ref, this.client);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/chat/chat_controller.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
@@ -9,15 +10,22 @@ import 'package:lichess_mobile/src/model/tournament/tournament_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/tv/tv_socket_events.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'tournament_controller.freezed.dart';
|
||||
part 'tournament_controller.g.dart';
|
||||
|
||||
final _logger = Logger('TournamentController');
|
||||
|
||||
@riverpod
|
||||
class TournamentController extends _$TournamentController {
|
||||
final tournamentControllerProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<TournamentController, TournamentState, TournamentId>(
|
||||
TournamentController.new,
|
||||
name: 'TournamentControllerProvider',
|
||||
);
|
||||
|
||||
class TournamentController extends AsyncNotifier<TournamentState> {
|
||||
TournamentController(this.id);
|
||||
|
||||
final TournamentId id;
|
||||
|
||||
StreamSubscription<SocketEvent>? _socketSubscription;
|
||||
|
||||
SocketClient? _socketClient;
|
||||
@@ -34,7 +42,7 @@ class TournamentController extends _$TournamentController {
|
||||
SocketPool get _socketPool => ref.read(socketPoolProvider);
|
||||
|
||||
@override
|
||||
Future<TournamentState> build(TournamentId id) async {
|
||||
Future<TournamentState> build() async {
|
||||
ref.onDispose(() {
|
||||
_socketSubscription?.cancel();
|
||||
_pauseDelayTimer?.cancel();
|
||||
@@ -105,7 +113,7 @@ class TournamentController extends _$TournamentController {
|
||||
}
|
||||
|
||||
void jumpToMyPage() {
|
||||
if (state.valueOrNull?.tournament.me != null) {
|
||||
if (state.value?.tournament.me != null) {
|
||||
_loadPage(_pageOf(state.requireValue.tournament.me!.rank));
|
||||
}
|
||||
}
|
||||
@@ -200,7 +208,7 @@ class TournamentController extends _$TournamentController {
|
||||
}
|
||||
|
||||
void joinOrPause() {
|
||||
final state = this.state.valueOrNull;
|
||||
final state = this.state.value;
|
||||
if (state == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
@@ -7,26 +5,22 @@ import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/tournament/tournament.dart';
|
||||
import 'package:lichess_mobile/src/model/tournament/tournament_repository.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'tournament_providers.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<IList<LightTournament>> featuredTournaments(Ref ref) {
|
||||
final featuredTournamentsProvider = FutureProvider.autoDispose<IList<LightTournament>>((Ref ref) {
|
||||
// logged in users get personalized featured tournaments
|
||||
ref.watch(authSessionProvider);
|
||||
return ref.read(tournamentRepositoryProvider).featured();
|
||||
}
|
||||
}, name: 'FeaturedTournamentsProvider');
|
||||
|
||||
@riverpod
|
||||
Future<TournamentLists> tournaments(Ref ref) {
|
||||
final tournamentsProvider = FutureProvider.autoDispose<TournamentLists>((Ref ref) {
|
||||
return ref.read(tournamentRepositoryProvider).getTournaments();
|
||||
}
|
||||
}, name: 'TournamentsProvider');
|
||||
|
||||
@riverpod
|
||||
Future<TournamentPlayer> tournamentPlayer(Ref ref, TournamentId tournamentId, UserId userId) {
|
||||
final tournamentPlayerProvider = FutureProvider.autoDispose
|
||||
.family<TournamentPlayer, (TournamentId, UserId)>((Ref ref, (TournamentId, UserId) params) {
|
||||
return ref.withClientCacheFor(
|
||||
(client) => ref.read(tournamentRepositoryProvider).getTournamentPlayer(tournamentId, userId),
|
||||
(client) =>
|
||||
ref.read(tournamentRepositoryProvider).getTournamentPlayer(params.$1, params.$2),
|
||||
const Duration(seconds: 10),
|
||||
);
|
||||
}
|
||||
}, name: 'TournamentPlayerProvider');
|
||||
|
||||
@@ -8,14 +8,11 @@ import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
import 'package:lichess_mobile/src/model/tournament/tournament.dart';
|
||||
import 'package:lichess_mobile/src/network/aggregator.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'tournament_repository.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
TournamentRepository tournamentRepository(Ref ref) {
|
||||
/// A provider for [TournamentRepository].
|
||||
final tournamentRepositoryProvider = Provider<TournamentRepository>((Ref ref) {
|
||||
return TournamentRepository(ref.read(lichessClientProvider), ref.read(aggregatorProvider), ref);
|
||||
}
|
||||
}, name: 'TournamentRepositoryProvider');
|
||||
|
||||
class TournamentRepository {
|
||||
TournamentRepository(this.client, this.aggregator, Ref ref) : _ref = ref;
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:deep_pick/deep_pick.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/model/common/socket.dart';
|
||||
import 'package:lichess_mobile/src/model/tv/featured_player.dart';
|
||||
import 'package:lichess_mobile/src/model/tv/tv_channel.dart';
|
||||
@@ -10,14 +11,16 @@ import 'package:lichess_mobile/src/model/tv/tv_game.dart';
|
||||
import 'package:lichess_mobile/src/model/tv/tv_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/tv/tv_socket_events.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'live_tv_channels.g.dart';
|
||||
|
||||
typedef LiveTvChannelsState = IMap<TvChannel, TvGameSnapshot>;
|
||||
|
||||
@riverpod
|
||||
class LiveTvChannels extends _$LiveTvChannels {
|
||||
final liveTvChannelsProvider =
|
||||
AsyncNotifierProvider.autoDispose<LiveTvChannels, LiveTvChannelsState>(
|
||||
LiveTvChannels.new,
|
||||
name: 'LiveTvChannelsProvider',
|
||||
);
|
||||
|
||||
class LiveTvChannels extends AsyncNotifier<LiveTvChannelsState> {
|
||||
StreamSubscription<SocketEvent>? _socketSubscription;
|
||||
StreamSubscription<void>? _socketReadySubscription;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:dartchess/dartchess.dart';
|
||||
import 'package:deep_pick/deep_pick.dart';
|
||||
import 'package:flutter/foundation.dart' show VoidCallback;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/common/chess.dart';
|
||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||
@@ -18,24 +19,36 @@ import 'package:lichess_mobile/src/model/tv/tv_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/tv/tv_socket_events.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user_repository.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'tv_controller.freezed.dart';
|
||||
part 'tv_controller.g.dart';
|
||||
|
||||
@riverpod
|
||||
class TvController extends _$TvController {
|
||||
typedef TvControllerParams = ({
|
||||
TvChannel? channel,
|
||||
(GameId id, Side orientation)? initialGame,
|
||||
UserId? userId,
|
||||
});
|
||||
|
||||
final tvControllerProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<TvController, TvState, TvControllerParams>(
|
||||
TvController.new,
|
||||
name: 'TvControllerProvider',
|
||||
);
|
||||
|
||||
class TvController extends AsyncNotifier<TvState> {
|
||||
TvController(this.params);
|
||||
|
||||
final TvControllerParams params;
|
||||
|
||||
StreamSubscription<SocketEvent>? _socketSubscription;
|
||||
|
||||
VoidCallback? _onReload;
|
||||
|
||||
@override
|
||||
Future<TvState> build(
|
||||
TvChannel? channel, {
|
||||
(GameId id, Side orientation)? initialGame,
|
||||
UserId? userId,
|
||||
}) {
|
||||
assert(channel != null || userId != null, 'Either a channel or a userId must be provided');
|
||||
Future<TvState> build() {
|
||||
assert(
|
||||
params.channel != null || params.userId != null,
|
||||
'Either a channel or a userId must be provided',
|
||||
);
|
||||
|
||||
_onReload = ref.invalidateSelf;
|
||||
|
||||
@@ -44,7 +57,7 @@ class TvController extends _$TvController {
|
||||
_onReload = null;
|
||||
});
|
||||
|
||||
return _connectWebsocket(initialGame);
|
||||
return _connectWebsocket(params.initialGame);
|
||||
}
|
||||
|
||||
SoundService get _soundService => ref.read(soundServiceProvider);
|
||||
@@ -65,18 +78,18 @@ class TvController extends _$TvController {
|
||||
if (game != null) {
|
||||
id = game.$1;
|
||||
orientation = game.$2;
|
||||
} else if (channel != null) {
|
||||
} else if (params.channel != null) {
|
||||
final channels = await ref.read(tvRepositoryProvider).channels();
|
||||
final channelGame = channels[channel!]!;
|
||||
final channelGame = channels[params.channel!]!;
|
||||
id = channelGame.id;
|
||||
orientation = channelGame.side ?? Side.white;
|
||||
} else if (userId != null) {
|
||||
final game = await ref.read(userRepositoryProvider).getCurrentGame(userId!);
|
||||
} else if (params.userId != null) {
|
||||
final game = await ref.read(userRepositoryProvider).getCurrentGame(params.userId!);
|
||||
id = game.id;
|
||||
orientation = game.playerSideOf(userId!) ?? Side.white;
|
||||
orientation = game.playerSideOf(params.userId!) ?? Side.white;
|
||||
} else {
|
||||
id = state.valueOrNull?.game.id ?? initialGame!.$1;
|
||||
orientation = state.valueOrNull?.orientation ?? initialGame!.$2;
|
||||
id = state.value?.game.id ?? params.initialGame!.$1;
|
||||
orientation = state.value?.orientation ?? params.initialGame!.$2;
|
||||
}
|
||||
|
||||
final socketClient = ref
|
||||
@@ -84,7 +97,7 @@ class TvController extends _$TvController {
|
||||
.open(
|
||||
Uri(
|
||||
path: '/watch/$id/${orientation.name}/v6',
|
||||
queryParameters: userId != null ? {'userTv': userId.toString()} : null,
|
||||
queryParameters: params.userId != null ? {'userTv': params.userId.toString()} : null,
|
||||
),
|
||||
forceReconnect: true,
|
||||
onEventGapFailure: () {
|
||||
@@ -245,7 +258,7 @@ class TvController extends _$TvController {
|
||||
case 'tvSelect':
|
||||
final json = event.data as Map<String, dynamic>;
|
||||
final eventChannel = pick(json, 'channel').asTvChannelOrNull();
|
||||
if (eventChannel != null && eventChannel == channel) {
|
||||
if (eventChannel != null && eventChannel == params.channel) {
|
||||
final data = TvSelectEvent.fromJson(json);
|
||||
_moveToNextGame((data.id, data.orientation));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'game_history_preferences.freezed.dart';
|
||||
part 'game_history_preferences.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class GameHistoryPreferences extends _$GameHistoryPreferences
|
||||
final gameHistoryPreferencesProvider =
|
||||
NotifierProvider<GameHistoryPreferencesNotifier, GameHistoryPrefs>(
|
||||
GameHistoryPreferencesNotifier.new,
|
||||
name: 'GameHistoryPreferencesProvider',
|
||||
);
|
||||
|
||||
class GameHistoryPreferencesNotifier extends Notifier<GameHistoryPrefs>
|
||||
with PreferencesStorage<GameHistoryPrefs> {
|
||||
@override
|
||||
@protected
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lichess_mobile/src/binding.dart';
|
||||
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
part 'search_history.g.dart';
|
||||
part 'search_history.freezed.dart';
|
||||
|
||||
@riverpod
|
||||
class SearchHistory extends _$SearchHistory {
|
||||
final searchHistoryProvider = NotifierProvider<SearchHistory, SearchHistoryState>(
|
||||
SearchHistory.new,
|
||||
name: 'SearchHistoryProvider',
|
||||
);
|
||||
|
||||
class SearchHistory extends Notifier<SearchHistoryState> {
|
||||
static const maxHistory = 10;
|
||||
|
||||
String _storageKey(AuthSessionState? session) =>
|
||||
|
||||
@@ -7,57 +7,53 @@ import 'package:lichess_mobile/src/model/user/streamer.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:lichess_mobile/src/model/user/user_repository.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'user_repository_providers.g.dart';
|
||||
|
||||
const _kAutoCompleteDebounceTimer = Duration(milliseconds: 300);
|
||||
|
||||
@riverpod
|
||||
Future<User> user(Ref ref, {required UserId id}) {
|
||||
final userProvider = FutureProvider.autoDispose.family<User, UserId>((Ref ref, UserId id) {
|
||||
return ref.read(userRepositoryProvider).getUser(id, withCanChallenge: true);
|
||||
}
|
||||
}, name: 'UserProvider');
|
||||
|
||||
@riverpod
|
||||
Future<UserPerfStats> userPerfStats(Ref ref, {required UserId id, required Perf perf}) {
|
||||
return ref.read(userRepositoryProvider).getPerfStats(id, perf);
|
||||
}
|
||||
final userPerfStatsProvider = FutureProvider.autoDispose.family<UserPerfStats, (UserId, Perf)>((
|
||||
Ref ref,
|
||||
(UserId, Perf) params,
|
||||
) {
|
||||
return ref.read(userRepositoryProvider).getPerfStats(params.$1, params.$2);
|
||||
}, name: 'UserPerfStatsProvider');
|
||||
|
||||
@riverpod
|
||||
Future<IList<Streamer>> liveStreamers(Ref ref) {
|
||||
final liveStreamersProvider = FutureProvider.autoDispose<IList<Streamer>>((Ref ref) {
|
||||
return ref.withAggregatorCacheFor(
|
||||
(client, aggregator) => UserRepository(client, aggregator).getLiveStreamers(),
|
||||
const Duration(minutes: 1),
|
||||
);
|
||||
}
|
||||
}, name: 'LiveStreamersProvider');
|
||||
|
||||
@riverpod
|
||||
Future<Top1Leaderboard> top1(Ref ref) {
|
||||
final top1Provider = FutureProvider.autoDispose<Top1Leaderboard>((Ref ref) {
|
||||
return ref.withAggregatorCacheFor(
|
||||
(client, aggregator) => UserRepository(client, aggregator).getTop1(),
|
||||
const Duration(hours: 12),
|
||||
);
|
||||
}
|
||||
}, name: 'Top1Provider');
|
||||
|
||||
@riverpod
|
||||
Future<Leaderboard> leaderboard(Ref ref) {
|
||||
final leaderboardProvider = FutureProvider.autoDispose<Leaderboard>((Ref ref) {
|
||||
return ref.withAggregatorCacheFor(
|
||||
(client, aggregator) => UserRepository(client, aggregator).getLeaderboard(),
|
||||
const Duration(hours: 2),
|
||||
);
|
||||
}
|
||||
}, name: 'LeaderboardProvider');
|
||||
|
||||
@riverpod
|
||||
Future<IList<User>> onlineBots(Ref ref) {
|
||||
final onlineBotsProvider = FutureProvider.autoDispose<IList<User>>((Ref ref) {
|
||||
return ref.withAggregatorCacheFor(
|
||||
(client, aggregator) =>
|
||||
UserRepository(client, aggregator).getOnlineBots().then((bots) => bots.toIList()),
|
||||
const Duration(hours: 5),
|
||||
);
|
||||
}
|
||||
}, name: 'OnlineBotsProvider');
|
||||
|
||||
@riverpod
|
||||
Future<IList<LightUser>> autoCompleteUser(Ref ref, String term) async {
|
||||
final autoCompleteUserProvider = FutureProvider.autoDispose.family<IList<LightUser>, String>((
|
||||
Ref ref,
|
||||
String term,
|
||||
) async {
|
||||
// debounce calls as user might be typing
|
||||
var didDispose = false;
|
||||
ref.onDispose(() => didDispose = true);
|
||||
@@ -67,17 +63,23 @@ Future<IList<LightUser>> autoCompleteUser(Ref ref, String term) async {
|
||||
}
|
||||
|
||||
return ref.read(userRepositoryProvider).autocompleteUser(term);
|
||||
}
|
||||
}, name: 'AutoCompleteUserProvider');
|
||||
|
||||
@riverpod
|
||||
Future<IList<UserRatingHistoryPerf>> userRatingHistory(Ref ref, {required UserId id}) {
|
||||
final userRatingHistoryProvider = FutureProvider.autoDispose
|
||||
.family<IList<UserRatingHistoryPerf>, UserId>((Ref ref, UserId id) {
|
||||
return ref.withAggregatorCacheFor(
|
||||
(client, aggregator) => UserRepository(client, aggregator).getRatingHistory(id),
|
||||
const Duration(minutes: 1),
|
||||
);
|
||||
}
|
||||
}, name: 'UserRatingHistoryProvider');
|
||||
|
||||
@riverpod
|
||||
Future<Crosstable> crosstable(Ref ref, UserId userId1, UserId userId2, {bool matchup = true}) {
|
||||
return ref.read(userRepositoryProvider).getCrosstable(userId1, userId2, matchup: matchup);
|
||||
}
|
||||
typedef CrosstableProviderParams = ({UserId userId1, UserId userId2, bool matchup});
|
||||
|
||||
final crosstableProvider = FutureProvider.autoDispose.family<Crosstable, CrosstableProviderParams>((
|
||||
Ref ref,
|
||||
CrosstableProviderParams params,
|
||||
) {
|
||||
return ref
|
||||
.read(userRepositoryProvider)
|
||||
.getCrosstable(params.userId1, params.userId2, matchup: params.matchup);
|
||||
}, name: 'CrosstableProvider');
|
||||
|
||||
@@ -8,23 +8,24 @@ import 'package:lichess_mobile/src/constants.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/utils/rate_limit.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'connectivity.g.dart';
|
||||
|
||||
final _logger = Logger('Connectivity');
|
||||
|
||||
/// A provider that exposes a [Connectivity] instance.
|
||||
@Riverpod(keepAlive: true)
|
||||
Connectivity connectivityPlugin(Ref _) => Connectivity();
|
||||
final connectivityPluginProvider = Provider<Connectivity>((Ref _) => Connectivity());
|
||||
|
||||
/// This provider is used to check the device's connectivity status, reacting to
|
||||
/// changes in connectivity and app lifecycle events.
|
||||
///
|
||||
/// - Uses the [Connectivity] plugin to listen to connectivity changes
|
||||
/// - Uses [AppLifecycleListener] to check connectivity on app resume
|
||||
@Riverpod(keepAlive: true)
|
||||
class ConnectivityChanges extends _$ConnectivityChanges {
|
||||
final connectivityChangesProvider =
|
||||
AsyncNotifierProvider<ConnectivityChangesNotifier, ConnectivityStatus>(
|
||||
ConnectivityChangesNotifier.new,
|
||||
name: 'ConnectivityChangesProvider',
|
||||
);
|
||||
|
||||
class ConnectivityChangesNotifier extends AsyncNotifier<ConnectivityStatus> {
|
||||
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
|
||||
AppLifecycleListener? _appLifecycleListener;
|
||||
|
||||
@@ -83,7 +84,7 @@ class ConnectivityChanges extends _$ConnectivityChanges {
|
||||
|
||||
if (newIsOnline != wasOnline) {
|
||||
_logger.info('Connectivity status: $result, isOnline: $isOnline');
|
||||
state = AsyncValue.data((isOnline: newIsOnline, appState: state.valueOrNull?.appState));
|
||||
state = AsyncValue.data((isOnline: newIsOnline, appState: state.value?.appState));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,6 @@ import 'package:lichess_mobile/src/model/user/user.dart';
|
||||
import 'package:lichess_mobile/src/network/aggregator.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'http.g.dart';
|
||||
|
||||
final _logger = Logger('HttpClient');
|
||||
|
||||
@@ -82,8 +79,7 @@ class HttpClientFactory {
|
||||
/// The global [HttpClientFactory] provider.
|
||||
///
|
||||
/// Http clients created by this factory log all requests and responses to the database.
|
||||
@Riverpod(keepAlive: true)
|
||||
HttpClientFactory httpClientFactory(Ref ref) {
|
||||
final httpClientFactoryProvider = Provider<HttpClientFactory>((Ref ref) {
|
||||
return HttpClientFactory(
|
||||
wrapper: (client) => _RegisterCallbackClient(
|
||||
client,
|
||||
@@ -123,28 +119,26 @@ HttpClientFactory httpClientFactory(Ref ref) {
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/// The default http client.
|
||||
///
|
||||
/// This client is used for all requests that don't go to the lichess server, for
|
||||
/// example, requests to lichess CDN, or other APIs.
|
||||
/// Only one instance of this client is created and kept alive for the whole app.
|
||||
@Riverpod(keepAlive: true)
|
||||
Client defaultClient(Ref ref) {
|
||||
final defaultClientProvider = Provider<Client>((Ref ref) {
|
||||
final client = _RegisterCallbackClient(
|
||||
ref.read(httpClientFactoryProvider)(),
|
||||
onRequest: (request) => _logger.info('${request.method} ${request.url}'),
|
||||
);
|
||||
ref.onDispose(() => client.close());
|
||||
return client;
|
||||
}
|
||||
});
|
||||
|
||||
/// The http client configured to make requests to the lichess API.
|
||||
///
|
||||
/// Only one instance of this client is created and kept alive for the whole app.
|
||||
@Riverpod(keepAlive: true)
|
||||
LichessClient lichessClient(Ref ref) {
|
||||
final lichessClientProvider = Provider<LichessClient>((Ref ref) {
|
||||
final client = LichessClient(
|
||||
// Retry just once, after 500ms, on 429 Too Many Requests.
|
||||
RetryClient(
|
||||
@@ -157,13 +151,12 @@ LichessClient lichessClient(Ref ref) {
|
||||
);
|
||||
ref.onDispose(() => client.close());
|
||||
return client;
|
||||
}
|
||||
});
|
||||
|
||||
Duration _defaultDelay(int retryCount) =>
|
||||
const Duration(milliseconds: 900) * math.pow(1.5, retryCount);
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
String userAgent(Ref ref) {
|
||||
final userAgentProvider = Provider<String>((Ref ref) {
|
||||
final session = ref.watch(authSessionProvider);
|
||||
|
||||
return makeUserAgent(
|
||||
@@ -172,7 +165,7 @@ String userAgent(Ref ref) {
|
||||
ref.read(preloadedDataProvider).requireValue.sri,
|
||||
session?.user,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/// Creates a user-agent string with the app version, build number, and device info and possibly the user ID if a user is logged in.
|
||||
String makeUserAgent(PackageInfo info, BaseDeviceInfo deviceInfo, String sri, LightUser? user) {
|
||||
|
||||
+17
-11
@@ -17,12 +17,9 @@ import 'package:lichess_mobile/src/model/common/socket.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:web_socket_channel/io.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
part 'socket.g.dart';
|
||||
|
||||
const kDefaultSocketRoute = '/socket/v5';
|
||||
|
||||
const _kDefaultConnectTimeout = Duration(seconds: 10);
|
||||
@@ -624,8 +621,8 @@ class SocketPool {
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
SocketPool socketPool(Ref ref) {
|
||||
/// The global socket pool provider.
|
||||
final socketPoolProvider = Provider<SocketPool>((Ref ref) {
|
||||
final pool = SocketPool(ref);
|
||||
Timer? closeInBackgroundTimer;
|
||||
|
||||
@@ -654,17 +651,26 @@ SocketPool socketPool(Ref ref) {
|
||||
});
|
||||
|
||||
return pool;
|
||||
}
|
||||
}, name: 'SocketPoolProvider');
|
||||
|
||||
typedef SocketPingState = ({Duration averageLag, int rating});
|
||||
|
||||
/// A provider that exposes the average lag and ping rating for a given socket route.
|
||||
final socketPingProvider = NotifierProvider.autoDispose
|
||||
.family<SocketPingNotifier, SocketPingState, Uri?>(
|
||||
SocketPingNotifier.new,
|
||||
name: 'SocketPingProvider',
|
||||
);
|
||||
|
||||
/// Average lag and ping rating computed from WebSocket ping/pong protocol.
|
||||
///
|
||||
/// If [route] is provided, it will return the average lag for that route only, and if any other route
|
||||
/// is active, it will return [Duration.zero], meaning the socket is not connected.
|
||||
/// If no route is provided, it will return the average lag for the current active route.
|
||||
@riverpod
|
||||
class SocketPing extends _$SocketPing {
|
||||
class SocketPingNotifier extends Notifier<SocketPingState> {
|
||||
SocketPingNotifier(this.route);
|
||||
final Uri? route;
|
||||
|
||||
@override
|
||||
SocketPingState build({Uri? route}) {
|
||||
final pool = ref.watch(socketPoolProvider);
|
||||
@@ -708,10 +714,10 @@ class SocketPing extends _$SocketPing {
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
WebSocketChannelFactory webSocketChannelFactory(Ref ref) {
|
||||
/// A provider for the [WebSocketChannelFactory].
|
||||
final webSocketChannelFactoryProvider = Provider<WebSocketChannelFactory>((Ref ref) {
|
||||
return const WebSocketChannelFactory();
|
||||
}
|
||||
});
|
||||
|
||||
/// A factory to create a [WebSocketChannel].
|
||||
///
|
||||
|
||||
@@ -15,19 +15,16 @@ 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';
|
||||
|
||||
part 'quick_actions.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
QuickActionService quickActionService(Ref ref) {
|
||||
/// Provider for the [QuickActionService].
|
||||
final quickActionServiceProvider = Provider<QuickActionService>((Ref ref) {
|
||||
final service = QuickActionService(ref);
|
||||
ref.listen<RecentGameSeekPrefs>(recentGameSeekProvider, (previous, next) {
|
||||
if (previous?.seeks == next.seeks) return;
|
||||
service.setQuickActions(next.seeks);
|
||||
});
|
||||
return service;
|
||||
}
|
||||
});
|
||||
|
||||
class QuickActionService {
|
||||
QuickActionService(this.ref);
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_riverpod/legacy.dart';
|
||||
import 'package:lichess_mobile/l10n/l10n.dart';
|
||||
import 'package:lichess_mobile/src/constants.dart';
|
||||
import 'package:lichess_mobile/src/utils/l10n_context.dart';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user