diff --git a/analysis_options.yaml b/analysis_options.yaml index b6dfa1feec..3934e2b4b4 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -26,8 +26,12 @@ analyzer: plugins: - custom_lint +formatter: + page_width: 100 + linter: rules: + require_trailing_commas: false prefer_single_quotes: true always_use_package_imports: false avoid_redundant_argument_values: false diff --git a/lib/src/app.dart b/lib/src/app.dart index 0cb59123d7..158855a519 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -26,23 +26,20 @@ class AppInitializationScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - ref.listen>( - preloadedDataProvider, - (_, state) { - if (state.hasValue || state.hasError) { - FlutterNativeSplash.remove(); - } - }, - ); + ref.listen>(preloadedDataProvider, (_, state) { + if (state.hasValue || state.hasError) { + FlutterNativeSplash.remove(); + } + }); - return ref.watch(preloadedDataProvider).when( + return ref + .watch(preloadedDataProvider) + .when( data: (_) => const Application(), // loading screen is handled by the native splash screen loading: () => const SizedBox.shrink(), error: (err, st) { - debugPrint( - 'SEVERE: [App] could not initialize app; $err\n$st', - ); + debugPrint('SEVERE: [App] could not initialize app; $err\n$st'); return const SizedBox.shrink(); }, ); @@ -88,8 +85,7 @@ class _AppState extends ConsumerState { // Play registered moves whenever the app comes back online. if (prevWasOffline && currentIsOnline) { - final nbMovesPlayed = - await ref.read(correspondenceServiceProvider).playRegisteredMoves(); + final nbMovesPlayed = await ref.read(correspondenceServiceProvider).playRegisteredMoves(); if (nbMovesPlayed > 0) { ref.invalidate(ongoingGamesProvider); } @@ -125,9 +121,7 @@ class _AppState extends ConsumerState { final generalPrefs = ref.watch(generalPreferencesProvider); final brightness = ref.watch(currentBrightnessProvider); - final boardTheme = ref.watch( - boardPreferencesProvider.select((state) => state.boardTheme), - ); + final boardTheme = ref.watch(boardPreferencesProvider.select((state) => state.boardTheme)); final remainingHeight = estimateRemainingHeightLeftBoard(context); @@ -135,12 +129,10 @@ class _AppState extends ConsumerState { builder: (lightColorScheme, darkColorScheme) { // TODO remove this workaround when the dynamic_color colorScheme bug is fixed // See: https://github.com/material-foundation/flutter-packages/issues/574 - final ( - fixedLightScheme, - fixedDarkScheme - ) = lightColorScheme != null && darkColorScheme != null - ? _generateDynamicColourSchemes(lightColorScheme, darkColorScheme) - : (null, null); + final (fixedLightScheme, fixedDarkScheme) = + lightColorScheme != null && darkColorScheme != null + ? _generateDynamicColourSchemes(lightColorScheme, darkColorScheme) + : (null, null); final isTablet = isTabletOrLarger(context); @@ -151,33 +143,29 @@ class _AppState extends ConsumerState { generalPrefs.systemColors && dynamicColorScheme != null ? dynamicColorScheme : ColorScheme.fromSeed( - seedColor: boardTheme.colors.darkSquare, - brightness: brightness, - ); + seedColor: boardTheme.colors.darkSquare, + brightness: brightness, + ); final cupertinoThemeData = CupertinoThemeData( primaryColor: colorScheme.primary, primaryContrastingColor: colorScheme.onPrimary, brightness: brightness, textTheme: CupertinoTheme.of(context).textTheme.copyWith( - primaryColor: colorScheme.primary, - textStyle: CupertinoTheme.of(context) - .textTheme - .textStyle - .copyWith(color: Styles.cupertinoLabelColor), - navTitleTextStyle: CupertinoTheme.of(context) - .textTheme - .navTitleTextStyle - .copyWith(color: Styles.cupertinoTitleColor), - navLargeTitleTextStyle: CupertinoTheme.of(context) - .textTheme - .navLargeTitleTextStyle - .copyWith(color: Styles.cupertinoTitleColor), - ), + primaryColor: colorScheme.primary, + textStyle: CupertinoTheme.of( + context, + ).textTheme.textStyle.copyWith(color: Styles.cupertinoLabelColor), + navTitleTextStyle: CupertinoTheme.of( + context, + ).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + navLargeTitleTextStyle: CupertinoTheme.of( + context, + ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + ), scaffoldBackgroundColor: Styles.cupertinoScaffoldColor, - barBackgroundColor: isTablet - ? Styles.cupertinoTabletAppBarColor - : Styles.cupertinoAppBarColor, + barBackgroundColor: + isTablet ? Styles.cupertinoTabletAppBarColor : Styles.cupertinoAppBarColor, ); return MaterialApp( @@ -187,47 +175,40 @@ class _AppState extends ConsumerState { locale: generalPrefs.locale, theme: ThemeData.from( colorScheme: colorScheme, - textTheme: Theme.of(context).platform == TargetPlatform.iOS - ? brightness == Brightness.light - ? Typography.blackCupertino - : Styles.whiteCupertinoTextTheme - : null, + textTheme: + Theme.of(context).platform == TargetPlatform.iOS + ? brightness == Brightness.light + ? Typography.blackCupertino + : Styles.whiteCupertinoTextTheme + : null, ).copyWith( cupertinoOverrideTheme: cupertinoThemeData, navigationBarTheme: NavigationBarTheme.of(context).copyWith( - height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold - ? 60 - : null, + height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null, ), - extensions: [ - lichessCustomColors.harmonized(colorScheme), - ], + extensions: [lichessCustomColors.harmonized(colorScheme)], ), themeMode: switch (generalPrefs.themeMode) { BackgroundThemeMode.light => ThemeMode.light, BackgroundThemeMode.dark => ThemeMode.dark, BackgroundThemeMode.system => ThemeMode.system, }, - builder: Theme.of(context).platform == TargetPlatform.iOS - ? (context, child) { - return CupertinoTheme( - data: cupertinoThemeData, - child: IconTheme.merge( - data: IconThemeData( - color: CupertinoTheme.of(context) - .textTheme - .textStyle - .color, + builder: + Theme.of(context).platform == TargetPlatform.iOS + ? (context, child) { + return CupertinoTheme( + data: cupertinoThemeData, + child: IconTheme.merge( + data: IconThemeData( + color: CupertinoTheme.of(context).textTheme.textStyle.color, + ), + child: Material(child: child), ), - child: Material(child: child), - ), - ); - } - : null, + ); + } + : null, home: const BottomNavScaffold(), - navigatorObservers: [ - rootNavPageRouteObserver, - ], + navigatorObservers: [rootNavPageRouteObserver], ); }, ); @@ -249,28 +230,24 @@ class _AppState extends ConsumerState { final lightAdditionalColours = _extractAdditionalColours(lightBase); final darkAdditionalColours = _extractAdditionalColours(darkBase); - final lightScheme = - _insertAdditionalColours(lightBase, lightAdditionalColours); + final lightScheme = _insertAdditionalColours(lightBase, lightAdditionalColours); final darkScheme = _insertAdditionalColours(darkBase, darkAdditionalColours); return (lightScheme.harmonized(), darkScheme.harmonized()); } List _extractAdditionalColours(ColorScheme scheme) => [ - scheme.surface, - scheme.surfaceDim, - scheme.surfaceBright, - scheme.surfaceContainerLowest, - scheme.surfaceContainerLow, - scheme.surfaceContainer, - scheme.surfaceContainerHigh, - scheme.surfaceContainerHighest, - ]; + scheme.surface, + scheme.surfaceDim, + scheme.surfaceBright, + scheme.surfaceContainerLowest, + scheme.surfaceContainerLow, + scheme.surfaceContainer, + scheme.surfaceContainerHigh, + scheme.surfaceContainerHighest, +]; -ColorScheme _insertAdditionalColours( - ColorScheme scheme, - List additionalColours, -) => +ColorScheme _insertAdditionalColours(ColorScheme scheme, List additionalColours) => scheme.copyWith( surface: additionalColours[0], surfaceDim: additionalColours[1], diff --git a/lib/src/binding.dart b/lib/src/binding.dart index 6b42a43f8f..4b0baa6fb1 100644 --- a/lib/src/binding.dart +++ b/lib/src/binding.dart @@ -131,13 +131,10 @@ class AppLichessBinding extends LichessBinding { @override Future initializeFirebase() async { - await Firebase.initializeApp( - options: DefaultFirebaseOptions.currentPlatform, - ); + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); if (kReleaseMode) { - FlutterError.onError = - FirebaseCrashlytics.instance.recordFlutterFatalError; + FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; PlatformDispatcher.instance.onError = (error, stack) { FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); return true; @@ -154,8 +151,7 @@ class AppLichessBinding extends LichessBinding { } @override - Stream get firebaseMessagingOnMessage => - FirebaseMessaging.onMessage; + Stream get firebaseMessagingOnMessage => FirebaseMessaging.onMessage; @override Stream get firebaseMessagingOnMessageOpenedApp => diff --git a/lib/src/constants.dart b/lib/src/constants.dart index 7d3bbf0b19..240db4322b 100644 --- a/lib/src/constants.dart +++ b/lib/src/constants.dart @@ -1,10 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -const kLichessHost = String.fromEnvironment( - 'LICHESS_HOST', - defaultValue: 'lichess.dev', -); +const kLichessHost = String.fromEnvironment('LICHESS_HOST', defaultValue: 'lichess.dev'); const kLichessWSHost = String.fromEnvironment( 'LICHESS_WS_HOST', @@ -26,8 +23,7 @@ const kLichessOpeningExplorerHost = String.fromEnvironment( defaultValue: 'explorer.lichess.ovh', ); -const kLichessDevUser = - String.fromEnvironment('LICHESS_DEV_USER', defaultValue: 'lichess'); +const kLichessDevUser = String.fromEnvironment('LICHESS_DEV_USER', defaultValue: 'lichess'); const kLichessDevPassword = String.fromEnvironment('LICHESS_DEV_PASSWORD'); const kLichessClientId = 'lichess_mobile'; @@ -50,9 +46,8 @@ const kFlexGoldenRatioBase = 100000000000; const kFlexGoldenRatio = 161803398875; /// Use same box shadows as material widgets with elevation 1. -final List boardShadows = defaultTargetPlatform == TargetPlatform.iOS - ? [] - : kElevationToShadow[1]!; +final List boardShadows = + defaultTargetPlatform == TargetPlatform.iOS ? [] : kElevationToShadow[1]!; const kCardTextScaleFactor = 1.64; const kMaxClockTextScaleFactor = 1.94; diff --git a/lib/src/db/database.dart b/lib/src/db/database.dart index a5e17f389a..bde1c7f7ba 100644 --- a/lib/src/db/database.dart +++ b/lib/src/db/database.dart @@ -35,13 +35,8 @@ Future sqliteVersion(Ref ref) async { Future _getDatabaseVersion(Database db) async { try { - final versionStr = (await db.rawQuery('SELECT sqlite_version()')) - .first - .values - .first - .toString(); - final versionCells = - versionStr.split('.').map((i) => int.parse(i)).toList(); + final versionStr = (await db.rawQuery('SELECT sqlite_version()')).first.values.first.toString(); + final versionCells = versionStr.split('.').map((i) => int.parse(i)).toList(); return versionCells[0] * 100000 + versionCells[1] * 1000 + versionCells[2]; } catch (_) { return null; @@ -71,11 +66,7 @@ Future openAppDatabase(DatabaseFactory dbFactory, String path) async { _deleteOldEntries(db, 'puzzle', puzzleTTL), _deleteOldEntries(db, 'correspondence_game', corresGameTTL), _deleteOldEntries(db, 'game', gameTTL), - _deleteOldEntries( - db, - 'chat_read_messages', - chatReadMessagesTTL, - ), + _deleteOldEntries(db, 'chat_read_messages', chatReadMessagesTTL), ]); }, onCreate: (db, version) async { @@ -190,11 +181,7 @@ Future _deleteOldEntries(Database db, String table, Duration ttl) async { return; } - await db.delete( - table, - where: 'lastModified < ?', - whereArgs: [date.toIso8601String()], - ); + await db.delete(table, where: 'lastModified < ?', whereArgs: [date.toIso8601String()]); } Future _doesTableExist(Database db, String table) async { diff --git a/lib/src/db/openings_database.dart b/lib/src/db/openings_database.dart index 5b0e72aa3c..c9056620b4 100644 --- a/lib/src/db/openings_database.dart +++ b/lib/src/db/openings_database.dart @@ -45,17 +45,12 @@ Future _openDb(String path) async { }); // Copy from asset - final ByteData data = - await rootBundle.load(p.url.join('assets', 'chess_openings.db')); - final List bytes = - data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); + final ByteData data = await rootBundle.load(p.url.join('assets', 'chess_openings.db')); + final List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); // Write and flush the bytes written await File(path).writeAsBytes(bytes, flush: true); } - return databaseFactory.openDatabase( - path, - options: OpenDatabaseOptions(readOnly: true), - ); + return databaseFactory.openDatabase(path, options: OpenDatabaseOptions(readOnly: true)); } diff --git a/lib/src/db/secure_storage.dart b/lib/src/db/secure_storage.dart index 67bd33b4d4..f0bb643a63 100644 --- a/lib/src/db/secure_storage.dart +++ b/lib/src/db/secure_storage.dart @@ -1,9 +1,9 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; AndroidOptions _getAndroidOptions() => const AndroidOptions( - encryptedSharedPreferences: true, - sharedPreferencesName: 'org.lichess.mobile.secure', - ); + encryptedSharedPreferences: true, + sharedPreferencesName: 'org.lichess.mobile.secure', +); class SecureStorage extends FlutterSecureStorage { const SecureStorage._({super.aOptions}); diff --git a/lib/src/init.dart b/lib/src/init.dart index 788d8040b4..145117567f 100644 --- a/lib/src/init.dart +++ b/lib/src/init.dart @@ -31,8 +31,7 @@ Future setupFirstLaunch() async { final appVersion = Version.parse(pInfo.version); final installedVersion = prefs.getString('installed_version'); - if (installedVersion == null || - Version.parse(installedVersion) != appVersion) { + if (installedVersion == null || Version.parse(installedVersion) != appVersion) { prefs.setString('installed_version', appVersion.canonicalizedVersion); } @@ -62,8 +61,7 @@ Future initializeLocalNotifications(Locale locale) async { ], ), ), - onDidReceiveNotificationResponse: - NotificationService.onDidReceiveNotificationResponse, + onDidReceiveNotificationResponse: NotificationService.onDidReceiveNotificationResponse, // onDidReceiveBackgroundNotificationResponse: notificationTapBackground, ); } @@ -74,8 +72,7 @@ Future preloadPieceImages() async { BoardPrefs boardPrefs = BoardPrefs.defaults; if (storedPrefs != null) { try { - boardPrefs = - BoardPrefs.fromJson(jsonDecode(storedPrefs) as Map); + boardPrefs = BoardPrefs.fromJson(jsonDecode(storedPrefs) as Map); } catch (e) { _logger.warning('Failed to decode board preferences: $e'); } @@ -95,13 +92,10 @@ Future androidDisplayInitialization(WidgetsBinding widgetsBinding) async { await DynamicColorPlugin.getCorePalette().then((value) { setCorePalette(value); - if (getCorePalette() != null && - prefs.getString(PrefCategory.board.storageKey) == null) { + if (getCorePalette() != null && prefs.getString(PrefCategory.board.storageKey) == null) { prefs.setString( PrefCategory.board.storageKey, - jsonEncode( - BoardPrefs.defaults.copyWith(boardTheme: BoardTheme.system), - ), + jsonEncode(BoardPrefs.defaults.copyWith(boardTheme: BoardTheme.system)), ); } }); @@ -130,17 +124,13 @@ Future androidDisplayInitialization(WidgetsBinding widgetsBinding) async { final List supported = await FlutterDisplayMode.supported; final DisplayMode active = await FlutterDisplayMode.active; - final List sameResolution = supported - .where( - (DisplayMode m) => m.width == active.width && m.height == active.height, - ) - .toList() - ..sort( - (DisplayMode a, DisplayMode b) => b.refreshRate.compareTo(a.refreshRate), - ); - - final DisplayMode mostOptimalMode = - sameResolution.isNotEmpty ? sameResolution.first : active; + final List sameResolution = + supported + .where((DisplayMode m) => m.width == active.width && m.height == active.height) + .toList() + ..sort((DisplayMode a, DisplayMode b) => b.refreshRate.compareTo(a.refreshRate)); + + final DisplayMode mostOptimalMode = sameResolution.isNotEmpty ? sameResolution.first : active; // This setting is per session. await FlutterDisplayMode.setPreferredMode(mostOptimalMode); diff --git a/lib/src/intl.dart b/lib/src/intl.dart index bda5051faf..b93d216850 100644 --- a/lib/src/intl.dart +++ b/lib/src/intl.dart @@ -12,11 +12,11 @@ Future setupIntl(WidgetsBinding widgetsBinding) async { final systemLocale = widgetsBinding.platformDispatcher.locale; // Get locale from shared preferences, if any - final json = LichessBinding.instance.sharedPreferences - .getString(PrefCategory.general.storageKey); - final generalPref = json != null - ? GeneralPrefs.fromJson(jsonDecode(json) as Map) - : GeneralPrefs.defaults; + final json = LichessBinding.instance.sharedPreferences.getString(PrefCategory.general.storageKey); + final generalPref = + json != null + ? GeneralPrefs.fromJson(jsonDecode(json) as Map) + : GeneralPrefs.defaults; final prefsLocale = generalPref.locale; final locale = prefsLocale ?? systemLocale; diff --git a/lib/src/localizations.dart b/lib/src/localizations.dart index d89448ccb3..62767000ad 100644 --- a/lib/src/localizations.dart +++ b/lib/src/localizations.dart @@ -5,10 +5,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'localizations.g.dart'; -typedef ActiveLocalizations = ({ - Locale locale, - AppLocalizations strings, -}); +typedef ActiveLocalizations = ({Locale locale, AppLocalizations strings}); @Riverpod(keepAlive: true) class Localizations extends _$Localizations { @@ -32,16 +29,10 @@ class Localizations extends _$Localizations { ActiveLocalizations _getLocale(GeneralPrefs prefs) { if (prefs.locale != null) { - return ( - locale: prefs.locale!, - strings: lookupAppLocalizations(prefs.locale!), - ); + return (locale: prefs.locale!, strings: lookupAppLocalizations(prefs.locale!)); } final locale = WidgetsBinding.instance.platformDispatcher.locale; - return ( - locale: locale, - strings: lookupAppLocalizations(locale), - ); + return (locale: locale, strings: lookupAppLocalizations(locale)); } } diff --git a/lib/src/log.dart b/lib/src/log.dart index 77eb73912c..5526448da9 100644 --- a/lib/src/log.dart +++ b/lib/src/log.dart @@ -5,10 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; // to see http requests and websocket connections in terminal -const _loggersToShowInTerminal = { - 'HttpClient', - 'Socket', -}; +const _loggersToShowInTerminal = {'HttpClient', 'Socket'}; /// Setup logging void setupLogging() { @@ -24,13 +21,11 @@ void setupLogging() { stackTrace: record.stackTrace, ); - if (_loggersToShowInTerminal.contains(record.loggerName) && - record.level >= Level.INFO) { + if (_loggersToShowInTerminal.contains(record.loggerName) && record.level >= Level.INFO) { debugPrint('[${record.loggerName}] ${record.message}'); } - if (!_loggersToShowInTerminal.contains(record.loggerName) && - record.level >= Level.WARNING) { + if (!_loggersToShowInTerminal.contains(record.loggerName) && record.level >= Level.WARNING) { debugPrint('[${record.loggerName}] ${record.message}'); } }); @@ -41,22 +36,12 @@ class ProviderLogger extends ProviderObserver { final _logger = Logger('Provider'); @override - void didAddProvider( - ProviderBase provider, - Object? value, - ProviderContainer container, - ) { - _logger.info( - '${provider.name ?? provider.runtimeType} initialized', - value, - ); + void didAddProvider(ProviderBase provider, Object? value, ProviderContainer container) { + _logger.info('${provider.name ?? provider.runtimeType} initialized', value); } @override - void didDisposeProvider( - ProviderBase provider, - ProviderContainer container, - ) { + void didDisposeProvider(ProviderBase provider, ProviderContainer container) { _logger.info('${provider.name ?? provider.runtimeType} disposed'); } @@ -67,10 +52,6 @@ class ProviderLogger extends ProviderObserver { StackTrace stackTrace, ProviderContainer container, ) { - _logger.severe( - '${provider.name ?? provider.runtimeType} error', - error, - stackTrace, - ); + _logger.severe('${provider.name ?? provider.runtimeType} error', error, stackTrace); } } diff --git a/lib/src/model/account/account_preferences.dart b/lib/src/model/account/account_preferences.dart index 27d1488df1..9984025ac0 100644 --- a/lib/src/model/account/account_preferences.dart +++ b/lib/src/model/account/account_preferences.dart @@ -10,40 +10,39 @@ import 'account_repository.dart'; part 'account_preferences.g.dart'; -typedef AccountPrefState = ({ - // game display - Zen zenMode, - PieceNotation pieceNotation, - BooleanPref showRatings, - // game behavior - BooleanPref premove, - AutoQueen autoQueen, - AutoThreefold autoThreefold, - Takeback takeback, - BooleanPref confirmResign, - SubmitMove submitMove, - // clock - Moretime moretime, - BooleanPref clockSound, - // privacy - BooleanPref follow, - Challenge challenge, -}); +typedef AccountPrefState = + ({ + // game display + Zen zenMode, + PieceNotation pieceNotation, + BooleanPref showRatings, + // game behavior + BooleanPref premove, + AutoQueen autoQueen, + AutoThreefold autoThreefold, + Takeback takeback, + BooleanPref confirmResign, + SubmitMove submitMove, + // clock + Moretime moretime, + BooleanPref clockSound, + // privacy + BooleanPref follow, + Challenge challenge, + }); /// A provider that tells if the user wants to see ratings in the app. @Riverpod(keepAlive: true) Future showRatingsPref(Ref ref) async { return ref.watch( - accountPreferencesProvider - .selectAsync((state) => state?.showRatings.value ?? true), + accountPreferencesProvider.selectAsync((state) => state?.showRatings.value ?? true), ); } @Riverpod(keepAlive: true) Future clockSound(Ref ref) async { return ref.watch( - accountPreferencesProvider - .selectAsync((state) => state?.clockSound.value ?? true), + accountPreferencesProvider.selectAsync((state) => state?.clockSound.value ?? true), ); } @@ -51,8 +50,7 @@ Future clockSound(Ref ref) async { Future pieceNotation(Ref ref) async { return ref.watch( accountPreferencesProvider.selectAsync( - (state) => - state?.pieceNotation ?? defaultAccountPreferences.pieceNotation, + (state) => state?.pieceNotation ?? defaultAccountPreferences.pieceNotation, ), ); } @@ -68,9 +66,7 @@ final defaultAccountPreferences = ( moretime: Moretime.always, clockSound: const BooleanPref(true), confirmResign: const BooleanPref(true), - submitMove: SubmitMove({ - SubmitMoveChoice.correspondence, - }), + submitMove: SubmitMove({SubmitMoveChoice.correspondence}), follow: const BooleanPref(true), challenge: Challenge.registered, ); @@ -90,41 +86,31 @@ class AccountPreferences extends _$AccountPreferences { } try { - return ref.withClient( - (client) => AccountRepository(client).getPreferences(), - ); + return ref.withClient((client) => AccountRepository(client).getPreferences()); } catch (e) { - debugPrint( - '[AccountPreferences] Error getting account preferences: $e', - ); + debugPrint('[AccountPreferences] Error getting account preferences: $e'); return defaultAccountPreferences; } } Future setZen(Zen value) => _setPref('zen', value); - Future setPieceNotation(PieceNotation value) => - _setPref('pieceNotation', value); + Future setPieceNotation(PieceNotation value) => _setPref('pieceNotation', value); Future setShowRatings(BooleanPref value) => _setPref('ratings', value); Future setPremove(BooleanPref value) => _setPref('premove', value); Future setTakeback(Takeback value) => _setPref('takeback', value); Future setAutoQueen(AutoQueen value) => _setPref('autoQueen', value); - Future setAutoThreefold(AutoThreefold value) => - _setPref('autoThreefold', value); + Future setAutoThreefold(AutoThreefold value) => _setPref('autoThreefold', value); Future setMoretime(Moretime value) => _setPref('moretime', value); - Future setClockSound(BooleanPref value) => - _setPref('clockSound', value); - Future setConfirmResign(BooleanPref value) => - _setPref('confirmResign', value); + Future setClockSound(BooleanPref value) => _setPref('clockSound', value); + Future setConfirmResign(BooleanPref value) => _setPref('confirmResign', value); Future setSubmitMove(SubmitMove value) => _setPref('submitMove', value); Future setFollow(BooleanPref value) => _setPref('follow', value); Future setChallenge(Challenge value) => _setPref('challenge', value); Future _setPref(String key, AccountPref value) async { await Future.delayed(const Duration(milliseconds: 200)); - await ref.withClient( - (client) => AccountRepository(client).setPreference(key, value), - ); + await ref.withClient((client) => AccountRepository(client).setPreference(key, value)); ref.invalidateSelf(); } } @@ -427,8 +413,7 @@ enum Challenge implements AccountPref { } class SubmitMove implements AccountPref { - SubmitMove(Iterable choices) - : choices = ISet(choices.toSet()); + SubmitMove(Iterable choices) : choices = ISet(choices.toSet()); final ISet choices; @@ -446,10 +431,8 @@ class SubmitMove implements AccountPref { return choices.map((choice) => choice.label(context)).join(', '); } - factory SubmitMove.fromInt(int value) => SubmitMove( - SubmitMoveChoice.values - .where((choice) => _bitPresent(value, choice.value)), - ); + factory SubmitMove.fromInt(int value) => + SubmitMove(SubmitMoveChoice.values.where((choice) => _bitPresent(value, choice.value))); } enum SubmitMoveChoice { diff --git a/lib/src/model/account/account_repository.dart b/lib/src/model/account/account_repository.dart index 2ab4a6370a..443199d070 100644 --- a/lib/src/model/account/account_repository.dart +++ b/lib/src/model/account/account_repository.dart @@ -62,44 +62,25 @@ class AccountRepository { final Logger _log = Logger('AccountRepository'); Future getProfile() { - return client.readJson( - Uri(path: '/api/account'), - mapper: User.fromServerJson, - ); + return client.readJson(Uri(path: '/api/account'), mapper: User.fromServerJson); } Future saveProfile(Map profile) async { final uri = Uri(path: '/account/profile'); - final response = await client.post( - uri, - headers: {'Accept': 'application/json'}, - body: profile, - ); + final response = await client.post(uri, headers: {'Accept': 'application/json'}, body: profile); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to post save profile: ${response.statusCode}', - uri, - ); + throw http.ClientException('Failed to post save profile: ${response.statusCode}', uri); } } Future> getOngoingGames({int? nb}) { return client.readJson( - Uri( - path: '/api/account/playing', - queryParameters: nb != null - ? { - 'nb': nb.toString(), - } - : null, - ), + Uri(path: '/api/account/playing', queryParameters: nb != null ? {'nb': nb.toString()} : null), mapper: (Map json) { final list = json['nowPlaying']; if (list is! List) { - _log.severe( - 'Could not read json object as {nowPlaying: []}: expected a list.', - ); + _log.severe('Could not read json object as {nowPlaying: []}: expected a list.'); throw Exception('Could not read json object as {nowPlaying: []}'); } return list @@ -114,9 +95,7 @@ class AccountRepository { return client.readJson( Uri(path: '/api/account/preferences'), mapper: (Map json) { - return _accountPreferencesFromPick( - pick(json, 'prefs').required(), - ); + return _accountPreferencesFromPick(pick(json, 'prefs').required()); }, ); } @@ -127,43 +106,24 @@ class AccountRepository { final response = await client.post(uri, body: {prefKey: pref.toFormData}); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to set preference: ${response.statusCode}', - uri, - ); + throw http.ClientException('Failed to set preference: ${response.statusCode}', uri); } } } AccountPrefState _accountPreferencesFromPick(RequiredPick pick) { return ( - zenMode: Zen.fromInt( - pick('zen').asIntOrThrow(), - ), - pieceNotation: PieceNotation.fromInt( - pick('pieceNotation').asIntOrThrow(), - ), + zenMode: Zen.fromInt(pick('zen').asIntOrThrow()), + pieceNotation: PieceNotation.fromInt(pick('pieceNotation').asIntOrThrow()), showRatings: BooleanPref.fromInt(pick('ratings').asIntOrThrow()), premove: BooleanPref(pick('premove').asBoolOrThrow()), - autoQueen: AutoQueen.fromInt( - pick('autoQueen').asIntOrThrow(), - ), - autoThreefold: AutoThreefold.fromInt( - pick('autoThreefold').asIntOrThrow(), - ), - takeback: Takeback.fromInt( - pick('takeback').asIntOrThrow(), - ), - moretime: Moretime.fromInt( - pick('moretime').asIntOrThrow(), - ), + autoQueen: AutoQueen.fromInt(pick('autoQueen').asIntOrThrow()), + autoThreefold: AutoThreefold.fromInt(pick('autoThreefold').asIntOrThrow()), + takeback: Takeback.fromInt(pick('takeback').asIntOrThrow()), + moretime: Moretime.fromInt(pick('moretime').asIntOrThrow()), clockSound: BooleanPref(pick('clockSound').asBoolOrThrow()), - confirmResign: BooleanPref.fromInt( - pick('confirmResign').asIntOrThrow(), - ), - submitMove: SubmitMove.fromInt( - pick('submitMove').asIntOrThrow(), - ), + confirmResign: BooleanPref.fromInt(pick('confirmResign').asIntOrThrow()), + submitMove: SubmitMove.fromInt(pick('submitMove').asIntOrThrow()), follow: BooleanPref(pick('follow').asBoolOrThrow()), challenge: Challenge.fromInt(pick('challenge').asIntOrThrow()), ); diff --git a/lib/src/model/analysis/analysis_controller.dart b/lib/src/model/analysis/analysis_controller.dart index 4ea9640325..6e6bdb08be 100644 --- a/lib/src/model/analysis/analysis_controller.dart +++ b/lib/src/model/analysis/analysis_controller.dart @@ -29,11 +29,7 @@ part 'analysis_controller.g.dart'; final _dateFormat = DateFormat('yyyy.MM.dd'); -typedef StandaloneAnalysis = ({ - String pgn, - Variant variant, - bool isComputerAnalysisAllowed, -}); +typedef StandaloneAnalysis = ({String pgn, Variant variant, bool isComputerAnalysisAllowed}); @freezed class AnalysisOptions with _$AnalysisOptions { @@ -51,8 +47,7 @@ class AnalysisOptions with _$AnalysisOptions { } @riverpod -class AnalysisController extends _$AnalysisController - implements PgnTreeNotifier { +class AnalysisController extends _$AnalysisController implements PgnTreeNotifier { late Root _root; late Variant _variant; @@ -71,8 +66,7 @@ class AnalysisController extends _$AnalysisController late final Division? division; if (options.gameId != null) { - final game = - await ref.watch(archivedGameProvider(id: options.gameId!).future); + final game = await ref.watch(archivedGameProvider(id: options.gameId!).future); _variant = game.meta.variant; pgn = game.makePgn(); opening = game.data.opening; @@ -91,27 +85,30 @@ class AnalysisController extends _$AnalysisController final game = PgnGame.parsePgn( pgn, - initHeaders: () => options.isLichessGameAnalysis - ? {} - : { - 'Event': '?', - 'Site': '?', - 'Date': _dateFormat.format(DateTime.now()), - 'Round': '?', - 'White': '?', - 'Black': '?', - 'Result': '*', - 'WhiteElo': '?', - 'BlackElo': '?', - }, + initHeaders: + () => + options.isLichessGameAnalysis + ? {} + : { + 'Event': '?', + 'Site': '?', + 'Date': _dateFormat.format(DateTime.now()), + 'Round': '?', + 'White': '?', + 'Black': '?', + 'Result': '*', + 'WhiteElo': '?', + 'BlackElo': '?', + }, ); final pgnHeaders = IMap(game.headers); final rootComments = IList(game.comments.map((c) => PgnComment.fromPgn(c))); - final isComputerAnalysisAllowed = options.isLichessGameAnalysis - ? pgnHeaders['Result'] != '*' - : options.standalone!.isComputerAnalysisAllowed; + final isComputerAnalysisAllowed = + options.isLichessGameAnalysis + ? pgnHeaders['Result'] != '*' + : options.standalone!.isComputerAnalysisAllowed; final List> openingFutures = []; @@ -122,8 +119,7 @@ class AnalysisController extends _$AnalysisController onVisitNode: (root, branch, isMainline) { if (isMainline && options.initialMoveCursor != null && - branch.position.ply <= - root.position.ply + options.initialMoveCursor!) { + branch.position.ply <= root.position.ply + options.initialMoveCursor!) { path = path + branch.id; lastMove = branch.sanMove.move; } @@ -134,26 +130,27 @@ class AnalysisController extends _$AnalysisController ); // wait for the opening to be fetched to recompute the branch opening - Future.wait(openingFutures).then((list) { - bool hasOpening = false; - for (final updated in list) { - if (updated != null) { - hasOpening = true; - final (path, opening) = updated; - _root.updateAt(path, (node) => node.opening = opening); - } - } - return hasOpening; - }).then((hasOpening) { - if (hasOpening) { - scheduleMicrotask(() { - _setPath(state.requireValue.currentPath); + Future.wait(openingFutures) + .then((list) { + bool hasOpening = false; + for (final updated in list) { + if (updated != null) { + hasOpening = true; + final (path, opening) = updated; + _root.updateAt(path, (node) => node.opening = opening); + } + } + return hasOpening; + }) + .then((hasOpening) { + if (hasOpening) { + scheduleMicrotask(() { + _setPath(state.requireValue.currentPath); + }); + } }); - } - }); - final currentPath = - options.initialMoveCursor == null ? _root.mainlinePath : path; + final currentPath = options.initialMoveCursor == null ? _root.mainlinePath : path; final currentNode = _root.nodeAt(currentPath); // don't use ref.watch here: we don't want to invalidate state when the @@ -168,12 +165,10 @@ class AnalysisController extends _$AnalysisController if (isEngineAllowed) { evaluationService.disposeEngine(); } - serverAnalysisService.lastAnalysisEvent - .removeListener(_listenToServerAnalysisEvents); + serverAnalysisService.lastAnalysisEvent.removeListener(_listenToServerAnalysisEvents); }); - serverAnalysisService.lastAnalysisEvent - .addListener(_listenToServerAnalysisEvents); + serverAnalysisService.lastAnalysisEvent.addListener(_listenToServerAnalysisEvents); final analysisState = AnalysisState( variant: _variant, @@ -198,35 +193,31 @@ class AnalysisController extends _$AnalysisController if (analysisState.isEngineAvailable) { evaluationService .initEngine( - _evaluationContext, - options: EvaluationOptions( - multiPv: prefs.numEvalLines, - cores: prefs.numEngineCores, - searchTime: prefs.engineSearchTime, - ), - ) + _evaluationContext, + options: EvaluationOptions( + multiPv: prefs.numEvalLines, + cores: prefs.numEngineCores, + searchTime: prefs.engineSearchTime, + ), + ) .then((_) { - _startEngineEvalTimer = Timer(const Duration(milliseconds: 250), () { - _startEngineEval(); - }); - }); + _startEngineEvalTimer = Timer(const Duration(milliseconds: 250), () { + _startEngineEval(); + }); + }); } return analysisState; } - EvaluationContext get _evaluationContext => EvaluationContext( - variant: _variant, - initialPosition: _root.position, - ); + EvaluationContext get _evaluationContext => + EvaluationContext(variant: _variant, initialPosition: _root.position); void onUserMove(NormalMove move, {bool shouldReplace = false}) { if (!state.requireValue.position.isLegal(move)) return; if (isPromotionPawnMove(state.requireValue.position, move)) { - state = AsyncValue.data( - state.requireValue.copyWith(promotionMove: move), - ); + state = AsyncValue.data(state.requireValue.copyWith(promotionMove: move)); return; } @@ -236,11 +227,7 @@ class AnalysisController extends _$AnalysisController replace: shouldReplace, ); if (newPath != null) { - _setPath( - newPath, - shouldRecomputeRootView: isNewNode, - shouldForceShowVariation: true, - ); + _setPath(newPath, shouldRecomputeRootView: isNewNode, shouldForceShowVariation: true); } } @@ -260,8 +247,7 @@ class AnalysisController extends _$AnalysisController final curState = state.requireValue; if (!curState.currentNode.hasChild) return; _setPath( - curState.currentPath + - _root.nodeAt(curState.currentPath).children.first.id, + curState.currentPath + _root.nodeAt(curState.currentPath).children.first.id, replaying: true, ); } @@ -307,8 +293,7 @@ class AnalysisController extends _$AnalysisController void expandVariations(UciPath path) { final node = _root.nodeAt(path); - final childrenToShow = - _root.isOnMainline(path) ? node.children.skip(1) : node.children; + final childrenToShow = _root.isOnMainline(path) ? node.children.skip(1) : node.children; for (final child in childrenToShow) { child.isCollapsed = false; @@ -335,10 +320,7 @@ class AnalysisController extends _$AnalysisController _root.promoteAt(path, toMainline: toMainline); final curState = state.requireValue; state = AsyncData( - curState.copyWith( - isOnMainline: _root.isOnMainline(curState.currentPath), - root: _root.view, - ), + curState.copyWith(isOnMainline: _root.isOnMainline(curState.currentPath), root: _root.view), ); } @@ -352,17 +334,13 @@ class AnalysisController extends _$AnalysisController /// /// Acts both on local evaluation and server analysis. Future toggleComputerAnalysis() async { - await ref - .read(analysisPreferencesProvider.notifier) - .toggleEnableComputerAnalysis(); + await ref.read(analysisPreferencesProvider.notifier).toggleEnableComputerAnalysis(); final curState = state.requireValue; final engineWasAvailable = curState.isEngineAvailable; state = AsyncData( - curState.copyWith( - isComputerAnalysisEnabled: !curState.isComputerAnalysisEnabled, - ), + curState.copyWith(isComputerAnalysisEnabled: !curState.isComputerAnalysisEnabled), ); final computerAllowed = state.requireValue.isComputerAnalysisEnabled; @@ -373,9 +351,7 @@ class AnalysisController extends _$AnalysisController /// Toggles the local evaluation on/off. Future toggleLocalEvaluation() async { - await ref - .read(analysisPreferencesProvider.notifier) - .toggleEnableLocalEvaluation(); + await ref.read(analysisPreferencesProvider.notifier).toggleEnableLocalEvaluation(); state = AsyncData( state.requireValue.copyWith( @@ -385,7 +361,9 @@ class AnalysisController extends _$AnalysisController if (state.requireValue.isEngineAvailable) { final prefs = ref.read(analysisPreferencesProvider); - await ref.read(evaluationServiceProvider).initEngine( + await ref + .read(evaluationServiceProvider) + .initEngine( _evaluationContext, options: EvaluationOptions( multiPv: prefs.numEvalLines, @@ -401,11 +379,11 @@ class AnalysisController extends _$AnalysisController } void setNumEvalLines(int numEvalLines) { - ref - .read(analysisPreferencesProvider.notifier) - .setNumEvalLines(numEvalLines); + ref.read(analysisPreferencesProvider.notifier).setNumEvalLines(numEvalLines); - ref.read(evaluationServiceProvider).setOptions( + ref + .read(evaluationServiceProvider) + .setOptions( EvaluationOptions( multiPv: numEvalLines, cores: ref.read(analysisPreferencesProvider).numEngineCores, @@ -418,8 +396,7 @@ class AnalysisController extends _$AnalysisController final curState = state.requireValue; state = AsyncData( curState.copyWith( - currentNode: - AnalysisCurrentNode.fromNode(_root.nodeAt(curState.currentPath)), + currentNode: AnalysisCurrentNode.fromNode(_root.nodeAt(curState.currentPath)), ), ); @@ -427,11 +404,11 @@ class AnalysisController extends _$AnalysisController } void setEngineCores(int numEngineCores) { - ref - .read(analysisPreferencesProvider.notifier) - .setEngineCores(numEngineCores); + ref.read(analysisPreferencesProvider.notifier).setEngineCores(numEngineCores); - ref.read(evaluationServiceProvider).setOptions( + ref + .read(evaluationServiceProvider) + .setOptions( EvaluationOptions( multiPv: ref.read(analysisPreferencesProvider).numEvalLines, cores: numEngineCores, @@ -443,11 +420,11 @@ class AnalysisController extends _$AnalysisController } void setEngineSearchTime(Duration searchTime) { - ref - .read(analysisPreferencesProvider.notifier) - .setEngineSearchTime(searchTime); + ref.read(analysisPreferencesProvider.notifier).setEngineSearchTime(searchTime); - ref.read(evaluationServiceProvider).setOptions( + ref + .read(evaluationServiceProvider) + .setOptions( EvaluationOptions( multiPv: ref.read(analysisPreferencesProvider).numEvalLines, cores: ref.read(analysisPreferencesProvider).numEngineCores, @@ -505,9 +482,7 @@ class AnalysisController extends _$AnalysisController final (currentNode, opening) = _nodeOpeningAt(_root, path); // always show variation if the user plays a move - if (shouldForceShowVariation && - currentNode is Branch && - currentNode.isCollapsed) { + if (shouldForceShowVariation && currentNode is Branch && currentNode.isCollapsed) { _root.updateAt(path, (node) { if (node is Branch) node.isCollapsed = false; }); @@ -516,9 +491,8 @@ class AnalysisController extends _$AnalysisController // root view is only used to display move list, so we need to // recompute the root view only when the nodelist length changes // or a variation is hidden/shown - final rootView = shouldForceShowVariation || shouldRecomputeRootView - ? _root.view - : curState.root; + final rootView = + shouldForceShowVariation || shouldRecomputeRootView ? _root.view : curState.root; final isForward = path.size > curState.currentPath.size; if (currentNode is Branch) { @@ -526,9 +500,7 @@ class AnalysisController extends _$AnalysisController if (isForward) { final isCheck = currentNode.sanMove.isCheck; if (currentNode.sanMove.isCapture) { - ref - .read(moveFeedbackServiceProvider) - .captureFeedback(check: isCheck); + ref.read(moveFeedbackServiceProvider).captureFeedback(check: isCheck); } else { ref.read(moveFeedbackServiceProvider).moveFeedback(check: isCheck); } @@ -581,18 +553,14 @@ class AnalysisController extends _$AnalysisController } } - Future<(UciPath, FullOpening)?> _fetchOpening( - Node fromNode, - UciPath path, - ) async { + Future<(UciPath, FullOpening)?> _fetchOpening(Node fromNode, UciPath path) async { if (!kOpeningAllowedVariants.contains(_variant)) return null; final moves = fromNode.branchesOn(path).map((node) => node.sanMove.move); if (moves.isEmpty) return null; if (moves.length > 40) return null; - final opening = - await ref.read(openingServiceProvider).fetchFromMoves(moves); + final opening = await ref.read(openingServiceProvider).fetchFromMoves(moves); if (opening != null) { return (path, opening); } @@ -605,9 +573,7 @@ class AnalysisController extends _$AnalysisController final curState = state.requireValue; if (curState.currentPath == path) { state = AsyncData( - curState.copyWith( - currentNode: AnalysisCurrentNode.fromNode(_root.nodeAt(path)), - ), + curState.copyWith(currentNode: AnalysisCurrentNode.fromNode(_root.nodeAt(path))), ); } } @@ -623,9 +589,7 @@ class AnalysisController extends _$AnalysisController initialPositionEval: _root.eval, shouldEmit: (work) => work.path == state.valueOrNull?.currentPath, ) - ?.forEach( - (t) => _root.updateAt(t.$1.path, (node) => node.eval = t.$2), - ); + ?.forEach((t) => _root.updateAt(t.$1.path, (node) => node.eval = t.$2)); } void _debouncedStartEngineEval() { @@ -640,26 +604,22 @@ class AnalysisController extends _$AnalysisController final curState = state.requireValue; state = AsyncData( curState.copyWith( - currentNode: - AnalysisCurrentNode.fromNode(_root.nodeAt(curState.currentPath)), + currentNode: AnalysisCurrentNode.fromNode(_root.nodeAt(curState.currentPath)), ), ); } void _listenToServerAnalysisEvents() { - final event = - ref.read(serverAnalysisServiceProvider).lastAnalysisEvent.value; + final event = ref.read(serverAnalysisServiceProvider).lastAnalysisEvent.value; if (event != null && event.$1 == state.requireValue.gameId) { _mergeOngoingAnalysis(_root, event.$2.tree); state = AsyncData( state.requireValue.copyWith( acplChartData: _makeAcplChartData(), - playersAnalysis: event.$2.analysis != null - ? ( - white: event.$2.analysis!.white, - black: event.$2.analysis!.black - ) - : null, + playersAnalysis: + event.$2.analysis != null + ? (white: event.$2.analysis!.white, black: event.$2.analysis!.black) + : null, root: _root.view, ), ); @@ -670,19 +630,18 @@ class AnalysisController extends _$AnalysisController final eval = n2['eval'] as Map?; final cp = eval?['cp'] as int?; final mate = eval?['mate'] as int?; - final pgnEval = cp != null - ? PgnEvaluation.pawns(pawns: cpToPawns(cp)) - : mate != null + final pgnEval = + cp != null + ? PgnEvaluation.pawns(pawns: cpToPawns(cp)) + : mate != null ? PgnEvaluation.mate(mate: mate) : null; final glyphs = n2['glyphs'] as List?; final glyph = glyphs?.first as Map?; final comments = n2['comments'] as List?; - final comment = - (comments?.first as Map?)?['text'] as String?; + final comment = (comments?.first as Map?)?['text'] as String?; final children = n2['children'] as List? ?? []; - final pgnComment = - pgnEval != null ? PgnComment(eval: pgnEval, text: comment) : null; + final pgnComment = pgnEval != null ? PgnComment(eval: pgnEval, text: comment) : null; if (n1 is Branch) { if (pgnComment != null) { if (n1.lichessAnalysisComments == null) { @@ -723,34 +682,32 @@ class AnalysisController extends _$AnalysisController } final list = _root.mainline .map( - (node) => ( - node.position.isCheckmate, - node.position.turn, - node.lichessAnalysisComments - ?.firstWhereOrNull((c) => c.eval != null) - ?.eval - ), - ) - .map( - (el) { - final (isCheckmate, side, eval) = el; - return eval != null - ? ExternalEval( + (node) => ( + node.position.isCheckmate, + node.position.turn, + node.lichessAnalysisComments?.firstWhereOrNull((c) => c.eval != null)?.eval, + ), + ) + .map((el) { + final (isCheckmate, side, eval) = el; + return eval != null + ? ExternalEval( cp: eval.pawns != null ? cpFromPawns(eval.pawns!) : null, mate: eval.mate, depth: eval.depth, ) - : ExternalEval( + : ExternalEval( cp: null, // hack to display checkmate as the max eval - mate: isCheckmate - ? side == Side.white - ? -1 - : 1 - : null, + mate: + isCheckmate + ? side == Side.white + ? -1 + : 1 + : null, ); - }, - ).toList(growable: false); + }) + .toList(growable: false); return list.isEmpty ? null : IList(list); } } @@ -833,10 +790,8 @@ class AnalysisState with _$AnalysisState { /// Whether the analysis is for a lichess game. bool get isLichessGameAnalysis => gameId != null; - IMap> get validMoves => makeLegalMoves( - currentNode.position, - isChess960: variant == Variant.chess960, - ); + IMap> get validMoves => + makeLegalMoves(currentNode.position, isChess960: variant == Variant.chess960); /// Whether the user can request server analysis. /// @@ -852,17 +807,14 @@ class AnalysisState with _$AnalysisState { /// Whether an evaluation can be available bool get hasAvailableEval => isEngineAvailable || - (isComputerAnalysisAllowedAndEnabled && - acplChartData != null && - acplChartData!.isNotEmpty); + (isComputerAnalysisAllowedAndEnabled && acplChartData != null && acplChartData!.isNotEmpty); bool get isComputerAnalysisAllowedAndEnabled => isComputerAnalysisAllowed && isComputerAnalysisEnabled; /// Whether the engine is allowed for this analysis and variant. bool get isEngineAllowed => - isComputerAnalysisAllowedAndEnabled && - engineSupportedVariants.contains(variant); + isComputerAnalysisAllowedAndEnabled && engineSupportedVariants.contains(variant); /// Whether the engine is available for evaluation bool get isEngineAvailable => isEngineAllowed && isLocalEvaluationEnabled; @@ -872,11 +824,11 @@ class AnalysisState with _$AnalysisState { bool get canGoBack => currentPath.size > UciPath.empty.size; EngineGaugeParams get engineGaugeParams => ( - orientation: pov, - isLocalEngineAvailable: isEngineAvailable, - position: position, - savedEval: currentNode.eval ?? currentNode.serverEval, - ); + orientation: pov, + isLocalEngineAvailable: isEngineAvailable, + position: position, + savedEval: currentNode.eval ?? currentNode.serverEval, + ); } @freezed @@ -925,14 +877,13 @@ class AnalysisCurrentNode with _$AnalysisCurrentNode { /// /// For now we only trust the eval coming from lichess analysis. ExternalEval? get serverEval { - final pgnEval = - lichessAnalysisComments?.firstWhereOrNull((c) => c.eval != null)?.eval; + final pgnEval = lichessAnalysisComments?.firstWhereOrNull((c) => c.eval != null)?.eval; return pgnEval != null ? ExternalEval( - cp: pgnEval.pawns != null ? cpFromPawns(pgnEval.pawns!) : null, - mate: pgnEval.mate, - depth: pgnEval.depth, - ) + cp: pgnEval.pawns != null ? cpFromPawns(pgnEval.pawns!) : null, + mate: pgnEval.mate, + depth: pgnEval.depth, + ) : null; } } diff --git a/lib/src/model/analysis/analysis_preferences.dart b/lib/src/model/analysis/analysis_preferences.dart index c3b682536b..0916da00a0 100644 --- a/lib/src/model/analysis/analysis_preferences.dart +++ b/lib/src/model/analysis/analysis_preferences.dart @@ -7,8 +7,7 @@ part 'analysis_preferences.freezed.dart'; part 'analysis_preferences.g.dart'; @riverpod -class AnalysisPreferences extends _$AnalysisPreferences - with PreferencesStorage { +class AnalysisPreferences extends _$AnalysisPreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties @override final prefCategory = PrefCategory.analysis; @@ -18,8 +17,7 @@ class AnalysisPreferences extends _$AnalysisPreferences AnalysisPrefs get defaults => AnalysisPrefs.defaults; @override - AnalysisPrefs fromJson(Map json) => - AnalysisPrefs.fromJson(json); + AnalysisPrefs fromJson(Map json) => AnalysisPrefs.fromJson(json); @override AnalysisPrefs build() { @@ -27,77 +25,41 @@ class AnalysisPreferences extends _$AnalysisPreferences } Future toggleEnableComputerAnalysis() { - return save( - state.copyWith( - enableComputerAnalysis: !state.enableComputerAnalysis, - ), - ); + return save(state.copyWith(enableComputerAnalysis: !state.enableComputerAnalysis)); } Future toggleEnableLocalEvaluation() { - return save( - state.copyWith( - enableLocalEvaluation: !state.enableLocalEvaluation, - ), - ); + return save(state.copyWith(enableLocalEvaluation: !state.enableLocalEvaluation)); } Future toggleShowEvaluationGauge() { - return save( - state.copyWith( - showEvaluationGauge: !state.showEvaluationGauge, - ), - ); + return save(state.copyWith(showEvaluationGauge: !state.showEvaluationGauge)); } Future toggleAnnotations() { - return save( - state.copyWith( - showAnnotations: !state.showAnnotations, - ), - ); + return save(state.copyWith(showAnnotations: !state.showAnnotations)); } Future togglePgnComments() { - return save( - state.copyWith( - showPgnComments: !state.showPgnComments, - ), - ); + return save(state.copyWith(showPgnComments: !state.showPgnComments)); } Future toggleShowBestMoveArrow() { - return save( - state.copyWith( - showBestMoveArrow: !state.showBestMoveArrow, - ), - ); + return save(state.copyWith(showBestMoveArrow: !state.showBestMoveArrow)); } Future setNumEvalLines(int numEvalLines) { assert(numEvalLines >= 0 && numEvalLines <= 3); - return save( - state.copyWith( - numEvalLines: numEvalLines, - ), - ); + return save(state.copyWith(numEvalLines: numEvalLines)); } Future setEngineCores(int numEngineCores) { assert(numEngineCores >= 1 && numEngineCores <= maxEngineCores); - return save( - state.copyWith( - numEngineCores: numEngineCores, - ), - ); + return save(state.copyWith(numEngineCores: numEngineCores)); } Future setEngineSearchTime(Duration engineSearchTime) { - return save( - state.copyWith( - engineSearchTime: engineSearchTime, - ), - ); + return save(state.copyWith(engineSearchTime: engineSearchTime)); } } @@ -113,8 +75,7 @@ class AnalysisPrefs with _$AnalysisPrefs implements Serializable { required bool showAnnotations, required bool showPgnComments, @Assert('numEvalLines >= 0 && numEvalLines <= 3') required int numEvalLines, - @Assert('numEngineCores >= 1 && numEngineCores <= maxEngineCores') - required int numEngineCores, + @Assert('numEngineCores >= 1 && numEngineCores <= maxEngineCores') required int numEngineCores, @JsonKey( defaultValue: _searchTimeDefault, fromJson: _searchTimeFromJson, diff --git a/lib/src/model/analysis/opening_service.dart b/lib/src/model/analysis/opening_service.dart index 3d1f67a739..74c8a8bb9c 100644 --- a/lib/src/model/analysis/opening_service.dart +++ b/lib/src/model/analysis/opening_service.dart @@ -30,17 +30,9 @@ class OpeningService { Future fetchFromMoves(Iterable moves) async { final db = await _db; final movesString = moves - .map( - (move) => altCastles.containsKey(move.uci) - ? altCastles[move.uci] - : move.uci, - ) + .map((move) => altCastles.containsKey(move.uci) ? altCastles[move.uci] : move.uci) .join(' '); - final list = await db.query( - 'openings', - where: 'uci = ?', - whereArgs: [movesString], - ); + final list = await db.query('openings', where: 'uci = ?', whereArgs: [movesString]); final first = list.firstOrNull; if (first != null) { diff --git a/lib/src/model/analysis/server_analysis_service.dart b/lib/src/model/analysis/server_analysis_service.dart index ed73593547..c7926e294b 100644 --- a/lib/src/model/analysis/server_analysis_service.dart +++ b/lib/src/model/analysis/server_analysis_service.dart @@ -33,8 +33,7 @@ class ServerAnalysisService { ValueListenable get currentAnalysis => _currentAnalysis; /// The last analysis progress event received from the server. - ValueListenable<(GameAnyId, ServerEvalEvent)?> get lastAnalysisEvent => - _analysisProgress; + ValueListenable<(GameAnyId, ServerEvalEvent)?> get lastAnalysisEvent => _analysisProgress; /// Request server analysis for a game. /// @@ -51,8 +50,7 @@ class ServerAnalysisService { socketClient.stream.listen( (event) { if (event.topic == 'analysisProgress') { - final data = - ServerEvalEvent.fromJson(event.data as Map); + final data = ServerEvalEvent.fromJson(event.data as Map); _analysisProgress.value = (id, data); @@ -69,13 +67,11 @@ class ServerAnalysisService { _socketSubscription = null; }, cancelOnError: true, - ) + ), ); try { - await ref.withClient( - (client) => GameRepository(client).requestServerAnalysis(id.gameId), - ); + await ref.withClient((client) => GameRepository(client).requestServerAnalysis(id.gameId)); _currentAnalysis.value = id.gameId; } catch (e) { _socketSubscription?.$2.cancel(); @@ -100,8 +96,7 @@ class CurrentAnalysis extends _$CurrentAnalysis { } void _listener() { - final gameId = - ref.read(serverAnalysisServiceProvider).currentAnalysis.value; + final gameId = ref.read(serverAnalysisServiceProvider).currentAnalysis.value; if (state != gameId) { state = gameId; } diff --git a/lib/src/model/auth/auth_controller.dart b/lib/src/model/auth/auth_controller.dart index b4ced58fa7..52cedd91b1 100644 --- a/lib/src/model/auth/auth_controller.dart +++ b/lib/src/model/auth/auth_controller.dart @@ -21,8 +21,7 @@ class AuthController extends _$AuthController { final appAuth = ref.read(appAuthProvider); try { - final session = await ref - .withClient((client) => AuthRepository(client, appAuth).signIn()); + final session = await ref.withClient((client) => AuthRepository(client, appAuth).signIn()); await ref.read(authSessionProvider.notifier).update(session); @@ -47,9 +46,7 @@ class AuthController extends _$AuthController { try { await ref.read(notificationServiceProvider).unregister(); - await ref.withClient( - (client) => AuthRepository(client, appAuth).signOut(), - ); + await ref.withClient((client) => AuthRepository(client, appAuth).signOut()); await ref.read(authSessionProvider.notifier).delete(); // force reconnect to the current socket await ref.read(socketPoolProvider).currentClient.connect(); diff --git a/lib/src/model/auth/auth_repository.dart b/lib/src/model/auth/auth_repository.dart index 9d364ad31c..242d68ed45 100644 --- a/lib/src/model/auth/auth_repository.dart +++ b/lib/src/model/auth/auth_repository.dart @@ -21,11 +21,9 @@ FlutterAppAuth appAuth(Ref ref) { } class AuthRepository { - AuthRepository( - LichessClient client, - FlutterAppAuth appAuth, - ) : _client = client, - _appAuth = appAuth; + AuthRepository(LichessClient client, FlutterAppAuth appAuth) + : _client = client, + _appAuth = appAuth; final LichessClient _client; final Logger _log = Logger('AuthRepository'); @@ -61,25 +59,17 @@ class AuthRepository { final user = await _client.readJson( Uri(path: '/api/account'), - headers: { - 'Authorization': 'Bearer ${signBearerToken(token)}', - }, + headers: {'Authorization': 'Bearer ${signBearerToken(token)}'}, mapper: User.fromServerJson, ); - return AuthSessionState( - token: token, - user: user.lightUser, - ); + return AuthSessionState(token: token, user: user.lightUser); } Future signOut() async { final url = Uri(path: '/api/token'); final response = await _client.delete(Uri(path: '/api/token')); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to delete token: ${response.statusCode}', - url, - ); + throw http.ClientException('Failed to delete token: ${response.statusCode}', url); } } } diff --git a/lib/src/model/auth/auth_session.dart b/lib/src/model/auth/auth_session.dart index d104df6edc..a5ae274926 100644 --- a/lib/src/model/auth/auth_session.dart +++ b/lib/src/model/auth/auth_session.dart @@ -30,11 +30,8 @@ class AuthSession extends _$AuthSession { @Freezed(fromJson: true, toJson: true) class AuthSessionState with _$AuthSessionState { - const factory AuthSessionState({ - required LightUser user, - required String token, - }) = _AuthSessionState; + const factory AuthSessionState({required LightUser user, required String token}) = + _AuthSessionState; - factory AuthSessionState.fromJson(Map json) => - _$AuthSessionStateFromJson(json); + factory AuthSessionState.fromJson(Map json) => _$AuthSessionStateFromJson(json); } diff --git a/lib/src/model/auth/session_storage.dart b/lib/src/model/auth/session_storage.dart index 0509aa3e64..2b33ba16b7 100644 --- a/lib/src/model/auth/session_storage.dart +++ b/lib/src/model/auth/session_storage.dart @@ -21,9 +21,7 @@ class SessionStorage { Future read() async { final string = await SecureStorage.instance.read(key: kSessionStorageKey); if (string != null) { - return AuthSessionState.fromJson( - jsonDecode(string) as Map, - ); + return AuthSessionState.fromJson(jsonDecode(string) as Map); } return null; } diff --git a/lib/src/model/board_editor/board_editor_controller.dart b/lib/src/model/board_editor/board_editor_controller.dart index 0b45be6ffb..3a6061a4ed 100644 --- a/lib/src/model/board_editor/board_editor_controller.dart +++ b/lib/src/model/board_editor/board_editor_controller.dart @@ -32,10 +32,7 @@ class BoardEditorController extends _$BoardEditorController { } void updateMode(EditorPointerMode mode, [Piece? pieceToAddOnEdit]) { - state = state.copyWith( - editorPointerMode: mode, - pieceToAddOnEdit: pieceToAddOnEdit, - ); + state = state.copyWith(editorPointerMode: mode, pieceToAddOnEdit: pieceToAddOnEdit); } void discardPiece(Square square) { @@ -44,9 +41,7 @@ class BoardEditorController extends _$BoardEditorController { void movePiece(Square? origin, Square destination, Piece piece) { if (origin != destination) { - _updatePosition( - state.pieces.remove(origin ?? destination).add(destination, piece), - ); + _updatePosition(state.pieces.remove(origin ?? destination).add(destination, piece)); } } @@ -60,9 +55,7 @@ class BoardEditorController extends _$BoardEditorController { } void flipBoard() { - state = state.copyWith( - orientation: state.orientation.opposite, - ); + state = state.copyWith(orientation: state.orientation.opposite); } void setSideToPlay(Side side) { @@ -85,8 +78,7 @@ class BoardEditorController extends _$BoardEditorController { /// For en passant to be possible, there needs to be an adjacent pawn which has moved two squares forward. /// So the two squares behind must be empty void checkEnPassant(Square square, int fileOffset) { - final adjacentSquare = - Square.fromCoords(square.file.offset(fileOffset)!, square.rank); + final adjacentSquare = Square.fromCoords(square.file.offset(fileOffset)!, square.rank); final targetSquare = Square.fromCoords( square.file.offset(fileOffset)!, square.rank.offset(side == Side.white ? 1 : -1)!, @@ -100,8 +92,7 @@ class BoardEditorController extends _$BoardEditorController { board.roleAt(adjacentSquare) == Role.pawn && board.sideAt(targetSquare) == null && board.sideAt(originSquare) == null) { - enPassantOptions = - enPassantOptions.union(SquareSet.fromSquare(targetSquare)); + enPassantOptions = enPassantOptions.union(SquareSet.fromSquare(targetSquare)); } } @@ -119,9 +110,7 @@ class BoardEditorController extends _$BoardEditorController { } void toggleEnPassantSquare(Square square) { - state = state.copyWith( - enPassantSquare: state.enPassantSquare == square ? null : square, - ); + state = state.copyWith(enPassantSquare: state.enPassantSquare == square ? null : square); } void _updatePosition(IMap pieces) { @@ -137,38 +126,29 @@ class BoardEditorController extends _$BoardEditorController { switch (castlingSide) { case CastlingSide.king: state = state.copyWith( - castlingRights: - state.castlingRights.add(CastlingRight.whiteKing, allowed), + castlingRights: state.castlingRights.add(CastlingRight.whiteKing, allowed), ); case CastlingSide.queen: state = state.copyWith( - castlingRights: - state.castlingRights.add(CastlingRight.whiteQueen, allowed), + castlingRights: state.castlingRights.add(CastlingRight.whiteQueen, allowed), ); } case Side.black: switch (castlingSide) { case CastlingSide.king: state = state.copyWith( - castlingRights: - state.castlingRights.add(CastlingRight.blackKing, allowed), + castlingRights: state.castlingRights.add(CastlingRight.blackKing, allowed), ); case CastlingSide.queen: state = state.copyWith( - castlingRights: - state.castlingRights.add(CastlingRight.blackQueen, allowed), + castlingRights: state.castlingRights.add(CastlingRight.blackQueen, allowed), ); } } } } -enum CastlingRight { - whiteKing, - whiteQueen, - blackKing, - blackQueen, -} +enum CastlingRight { whiteKing, whiteQueen, blackKing, blackQueen } @freezed class BoardEditorState with _$BoardEditorState { @@ -189,17 +169,16 @@ class BoardEditorState with _$BoardEditorState { required Piece? pieceToAddOnEdit, }) = _BoardEditorState; - bool isCastlingAllowed(Side side, CastlingSide castlingSide) => - switch (side) { - Side.white => switch (castlingSide) { - CastlingSide.king => castlingRights[CastlingRight.whiteKing]!, - CastlingSide.queen => castlingRights[CastlingRight.whiteQueen]!, - }, - Side.black => switch (castlingSide) { - CastlingSide.king => castlingRights[CastlingRight.blackKing]!, - CastlingSide.queen => castlingRights[CastlingRight.blackQueen]!, - }, - }; + bool isCastlingAllowed(Side side, CastlingSide castlingSide) => switch (side) { + Side.white => switch (castlingSide) { + CastlingSide.king => castlingRights[CastlingRight.whiteKing]!, + CastlingSide.queen => castlingRights[CastlingRight.whiteQueen]!, + }, + Side.black => switch (castlingSide) { + CastlingSide.king => castlingRights[CastlingRight.blackKing]!, + CastlingSide.queen => castlingRights[CastlingRight.blackQueen]!, + }, + }; /// Returns the castling rights part of the FEN string. /// @@ -211,15 +190,15 @@ class BoardEditorState with _$BoardEditorState { final Board board = Board.parseFen(writeFen(pieces.unlock)); for (final side in Side.values) { final backrankKing = SquareSet.backrankOf(side) & board.kings; - final rooksAndKings = (board.bySide(side) & SquareSet.backrankOf(side)) & - (board.rooks | board.kings); + final rooksAndKings = + (board.bySide(side) & SquareSet.backrankOf(side)) & (board.rooks | board.kings); for (final castlingSide in CastlingSide.values) { - final candidate = castlingSide == CastlingSide.king - ? rooksAndKings.squares.lastOrNull - : rooksAndKings.squares.firstOrNull; - final isCastlingPossible = candidate != null && - board.rooks.has(candidate) && - backrankKing.singleSquare != null; + final candidate = + castlingSide == CastlingSide.king + ? rooksAndKings.squares.lastOrNull + : rooksAndKings.squares.firstOrNull; + final isCastlingPossible = + candidate != null && board.rooks.has(candidate) && backrankKing.singleSquare != null; switch ((side, castlingSide)) { case (Side.white, CastlingSide.king): hasRook[CastlingRight.whiteKing] = isCastlingPossible; diff --git a/lib/src/model/broadcast/broadcast.dart b/lib/src/model/broadcast/broadcast.dart index 24c5759a53..9188498756 100644 --- a/lib/src/model/broadcast/broadcast.dart +++ b/lib/src/model/broadcast/broadcast.dart @@ -5,11 +5,7 @@ import 'package:lichess_mobile/src/model/common/id.dart'; part 'broadcast.freezed.dart'; -typedef BroadcastList = ({ - IList active, - IList past, - int? nextPage, -}); +typedef BroadcastList = ({IList active, IList past, int? nextPage}); enum BroadcastResult { whiteWins, blackWins, draw, ongoing, noResultPgnTag } @@ -57,24 +53,19 @@ class BroadcastTournamentData with _$BroadcastTournamentData { }) = _BroadcastTournamentData; } -typedef BroadcastTournamentInformation = ({ - String? format, - String? timeControl, - String? players, - String? location, - BroadcastTournamentDates? dates, - Uri? website, -}); - -typedef BroadcastTournamentDates = ({ - DateTime startsAt, - DateTime? endsAt, -}); - -typedef BroadcastTournamentGroup = ({ - BroadcastTournamentId id, - String name, -}); +typedef BroadcastTournamentInformation = + ({ + String? format, + String? timeControl, + String? players, + String? location, + BroadcastTournamentDates? dates, + Uri? website, + }); + +typedef BroadcastTournamentDates = ({DateTime startsAt, DateTime? endsAt}); + +typedef BroadcastTournamentGroup = ({BroadcastTournamentId id, String name}); @freezed class BroadcastRound with _$BroadcastRound { @@ -89,10 +80,7 @@ class BroadcastRound with _$BroadcastRound { }) = _BroadcastRound; } -typedef BroadcastRoundWithGames = ({ - BroadcastRound round, - BroadcastRoundGames games, -}); +typedef BroadcastRoundWithGames = ({BroadcastRound round, BroadcastRoundGames games}); typedef BroadcastRoundGames = IMap; @@ -144,20 +132,14 @@ class BroadcastPlayerExtended with _$BroadcastPlayerExtended { }) = _BroadcastPlayerExtended; } -typedef BroadcastFideData = ({ - ({ - int? standard, - int? rapid, - int? blitz, - }) ratings, - int? birthYear, -}); - -typedef BroadcastPlayerResults = ({ - BroadcastPlayerExtended player, - BroadcastFideData fideData, - IList games, -}); +typedef BroadcastFideData = ({({int? standard, int? rapid, int? blitz}) ratings, int? birthYear}); + +typedef BroadcastPlayerResults = + ({ + BroadcastPlayerExtended player, + BroadcastFideData fideData, + IList games, + }); enum BroadcastPoints { one, half, zero } @@ -173,8 +155,4 @@ class BroadcastPlayerResultData with _$BroadcastPlayerResultData { }) = _BroadcastPlayerResult; } -enum RoundStatus { - live, - finished, - upcoming, -} +enum RoundStatus { live, finished, upcoming } diff --git a/lib/src/model/broadcast/broadcast_game_controller.dart b/lib/src/model/broadcast/broadcast_game_controller.dart index 79ffab5cb1..ee14654f14 100644 --- a/lib/src/model/broadcast/broadcast_game_controller.dart +++ b/lib/src/model/broadcast/broadcast_game_controller.dart @@ -29,8 +29,7 @@ part 'broadcast_game_controller.freezed.dart'; part 'broadcast_game_controller.g.dart'; @riverpod -class BroadcastGameController extends _$BroadcastGameController - implements PgnTreeNotifier { +class BroadcastGameController extends _$BroadcastGameController implements PgnTreeNotifier { static Uri broadcastSocketUri(BroadcastRoundId broadcastRoundId) => Uri(path: 'study/$broadcastRoundId/socket/v6'); @@ -49,10 +48,7 @@ class BroadcastGameController extends _$BroadcastGameController Object? _key = Object(); @override - Future build( - BroadcastRoundId roundId, - BroadcastGameId gameId, - ) async { + Future build(BroadcastRoundId roundId, BroadcastGameId gameId) async { _socketClient = ref .watch(socketPoolProvider) .open(BroadcastGameController.broadcastSocketUri(roundId)); @@ -126,18 +122,18 @@ class BroadcastGameController extends _$BroadcastGameController if (broadcastState.isLocalEvaluationEnabled) { evaluationService .initEngine( - _evaluationContext, - options: EvaluationOptions( - multiPv: prefs.numEvalLines, - cores: prefs.numEngineCores, - searchTime: ref.read(analysisPreferencesProvider).engineSearchTime, - ), - ) + _evaluationContext, + options: EvaluationOptions( + multiPv: prefs.numEvalLines, + cores: prefs.numEngineCores, + searchTime: ref.read(analysisPreferencesProvider).engineSearchTime, + ), + ) .then((_) { - _startEngineEvalTimer = Timer(const Duration(milliseconds: 250), () { - _startEngineEval(); - }); - }); + _startEngineEvalTimer = Timer(const Duration(milliseconds: 250), () { + _startEngineEval(); + }); + }); } return broadcastState; @@ -157,8 +153,7 @@ class BroadcastGameController extends _$BroadcastGameController final wasOnLivePath = curState.broadcastLivePath == curState.currentPath; final game = PgnGame.parsePgn(pgn); final pgnHeaders = IMap(game.headers); - final rootComments = - IList(game.comments.map((c) => PgnComment.fromPgn(c))); + final rootComments = IList(game.comments.map((c) => PgnComment.fromPgn(c))); final newRoot = Root.fromPgnGame(game); @@ -169,8 +164,7 @@ class BroadcastGameController extends _$BroadcastGameController _root = newRoot; - final newCurrentPath = - wasOnLivePath ? broadcastPath : curState.currentPath; + final newCurrentPath = wasOnLivePath ? broadcastPath : curState.currentPath; state = AsyncData( state.requireValue.copyWith( currentPath: newCurrentPath, @@ -199,8 +193,7 @@ class BroadcastGameController extends _$BroadcastGameController } void _handleAddNodeEvent(SocketEvent event) { - final broadcastGameId = - pick(event.data, 'p', 'chapterId').asBroadcastGameIdOrThrow(); + final broadcastGameId = pick(event.data, 'p', 'chapterId').asBroadcastGameIdOrThrow(); // We check if the event is for this game if (broadcastGameId != gameId) return; @@ -215,18 +208,14 @@ class BroadcastGameController extends _$BroadcastGameController // The path for the node that was received final path = pick(event.data, 'p', 'path').asUciPathOrThrow(); final uciMove = pick(event.data, 'n', 'uci').asUciMoveOrThrow(); - final clock = - pick(event.data, 'n', 'clock').asDurationFromCentiSecondsOrNull(); + final clock = pick(event.data, 'n', 'clock').asDurationFromCentiSecondsOrNull(); final (newPath, isNewNode) = _root.addMoveAt(path, uciMove, clock: clock); if (newPath != null && isNewNode == false) { _root.updateAt(newPath, (node) { if (node is Branch) { - node.comments = [ - ...node.comments ?? [], - PgnComment(clock: clock), - ]; + node.comments = [...node.comments ?? [], PgnComment(clock: clock)]; } }); } @@ -245,30 +234,22 @@ class BroadcastGameController extends _$BroadcastGameController } void _handleSetTagsEvent(SocketEvent event) { - final broadcastGameId = - pick(event.data, 'chapterId').asBroadcastGameIdOrThrow(); + final broadcastGameId = pick(event.data, 'chapterId').asBroadcastGameIdOrThrow(); // We check if the event is for this game if (broadcastGameId != gameId) return; - final pgnHeadersEntries = pick(event.data, 'tags').asListOrThrow( - (header) => MapEntry( - header(0).asStringOrThrow(), - header(1).asStringOrThrow(), - ), - ); + final pgnHeadersEntries = pick( + event.data, + 'tags', + ).asListOrThrow((header) => MapEntry(header(0).asStringOrThrow(), header(1).asStringOrThrow())); - final pgnHeaders = - state.requireValue.pgnHeaders.addEntries(pgnHeadersEntries); - state = AsyncData( - state.requireValue.copyWith(pgnHeaders: pgnHeaders), - ); + final pgnHeaders = state.requireValue.pgnHeaders.addEntries(pgnHeadersEntries); + state = AsyncData(state.requireValue.copyWith(pgnHeaders: pgnHeaders)); } - EvaluationContext get _evaluationContext => EvaluationContext( - variant: Variant.standard, - initialPosition: _root.position, - ); + EvaluationContext get _evaluationContext => + EvaluationContext(variant: Variant.standard, initialPosition: _root.position); void onUserMove(NormalMove move) { if (!state.hasValue) return; @@ -280,16 +261,9 @@ class BroadcastGameController extends _$BroadcastGameController return; } - final (newPath, isNewNode) = _root.addMoveAt( - state.requireValue.currentPath, - move, - ); + final (newPath, isNewNode) = _root.addMoveAt(state.requireValue.currentPath, move); if (newPath != null) { - _setPath( - newPath, - shouldRecomputeRootView: isNewNode, - shouldForceShowVariation: true, - ); + _setPath(newPath, shouldRecomputeRootView: isNewNode, shouldForceShowVariation: true); } } @@ -344,9 +318,7 @@ class BroadcastGameController extends _$BroadcastGameController void toggleBoard() { if (!state.hasValue) return; - state = AsyncData( - state.requireValue.copyWith(pov: state.requireValue.pov.opposite), - ); + state = AsyncData(state.requireValue.copyWith(pov: state.requireValue.pov.opposite)); } void userPrevious() { @@ -407,9 +379,7 @@ class BroadcastGameController extends _$BroadcastGameController Future toggleLocalEvaluation() async { if (!state.hasValue) return; - ref - .read(analysisPreferencesProvider.notifier) - .toggleEnableLocalEvaluation(); + ref.read(analysisPreferencesProvider.notifier).toggleEnableLocalEvaluation(); state = AsyncData( state.requireValue.copyWith( @@ -419,13 +389,14 @@ class BroadcastGameController extends _$BroadcastGameController if (state.requireValue.isLocalEvaluationEnabled) { final prefs = ref.read(analysisPreferencesProvider); - await ref.read(evaluationServiceProvider).initEngine( + await ref + .read(evaluationServiceProvider) + .initEngine( _evaluationContext, options: EvaluationOptions( multiPv: prefs.numEvalLines, cores: prefs.numEngineCores, - searchTime: - ref.read(analysisPreferencesProvider).engineSearchTime, + searchTime: ref.read(analysisPreferencesProvider).engineSearchTime, ), ); _startEngineEval(); @@ -438,11 +409,11 @@ class BroadcastGameController extends _$BroadcastGameController void setNumEvalLines(int numEvalLines) { if (!state.hasValue) return; - ref - .read(analysisPreferencesProvider.notifier) - .setNumEvalLines(numEvalLines); + ref.read(analysisPreferencesProvider.notifier).setNumEvalLines(numEvalLines); - ref.read(evaluationServiceProvider).setOptions( + ref + .read(evaluationServiceProvider) + .setOptions( EvaluationOptions( multiPv: numEvalLines, cores: ref.read(analysisPreferencesProvider).numEngineCores, @@ -454,9 +425,7 @@ class BroadcastGameController extends _$BroadcastGameController state = AsyncData( state.requireValue.copyWith( - currentNode: AnalysisCurrentNode.fromNode( - _root.nodeAt(state.requireValue.currentPath), - ), + currentNode: AnalysisCurrentNode.fromNode(_root.nodeAt(state.requireValue.currentPath)), ), ); @@ -464,11 +433,11 @@ class BroadcastGameController extends _$BroadcastGameController } void setEngineCores(int numEngineCores) { - ref - .read(analysisPreferencesProvider.notifier) - .setEngineCores(numEngineCores); + ref.read(analysisPreferencesProvider.notifier).setEngineCores(numEngineCores); - ref.read(evaluationServiceProvider).setOptions( + ref + .read(evaluationServiceProvider) + .setOptions( EvaluationOptions( multiPv: ref.read(analysisPreferencesProvider).numEvalLines, cores: numEngineCores, @@ -480,11 +449,11 @@ class BroadcastGameController extends _$BroadcastGameController } void setEngineSearchTime(Duration searchTime) { - ref - .read(analysisPreferencesProvider.notifier) - .setEngineSearchTime(searchTime); + ref.read(analysisPreferencesProvider.notifier).setEngineSearchTime(searchTime); - ref.read(evaluationServiceProvider).setOptions( + ref + .read(evaluationServiceProvider) + .setOptions( EvaluationOptions( multiPv: ref.read(analysisPreferencesProvider).numEvalLines, cores: ref.read(analysisPreferencesProvider).numEngineCores, @@ -508,9 +477,7 @@ class BroadcastGameController extends _$BroadcastGameController final currentNode = _root.nodeAt(path); // always show variation if the user plays a move - if (shouldForceShowVariation && - currentNode is Branch && - currentNode.isCollapsed) { + if (shouldForceShowVariation && currentNode is Branch && currentNode.isCollapsed) { _root.updateAt(path, (node) { if (node is Branch) node.isCollapsed = false; }); @@ -519,9 +486,8 @@ class BroadcastGameController extends _$BroadcastGameController // root view is only used to display move list, so we need to // recompute the root view only when the nodelist length changes // or a variation is hidden/shown - final rootView = shouldForceShowVariation || shouldRecomputeRootView - ? _root.view - : state.requireValue.root; + final rootView = + shouldForceShowVariation || shouldRecomputeRootView ? _root.view : state.requireValue.root; final isForward = path.size > state.requireValue.currentPath.size; if (currentNode is Branch) { @@ -529,9 +495,7 @@ class BroadcastGameController extends _$BroadcastGameController if (isForward) { final isCheck = currentNode.sanMove.isCheck; if (currentNode.sanMove.isCapture) { - ref - .read(moveFeedbackServiceProvider) - .captureFeedback(check: isCheck); + ref.read(moveFeedbackServiceProvider).captureFeedback(check: isCheck); } else { ref.read(moveFeedbackServiceProvider).moveFeedback(check: isCheck); } @@ -588,9 +552,7 @@ class BroadcastGameController extends _$BroadcastGameController initialPositionEval: _root.eval, shouldEmit: (work) => work.path == state.valueOrNull?.currentPath, ) - ?.forEach( - (t) => _root.updateAt(t.$1.path, (node) => node.eval = t.$2), - ); + ?.forEach((t) => _root.updateAt(t.$1.path, (node) => node.eval = t.$2)); } void _debouncedStartEngineEval() { @@ -606,9 +568,7 @@ class BroadcastGameController extends _$BroadcastGameController // update the current node with last cached eval state = AsyncData( state.requireValue.copyWith( - currentNode: AnalysisCurrentNode.fromNode( - _root.nodeAt(state.requireValue.currentPath), - ), + currentNode: AnalysisCurrentNode.fromNode(_root.nodeAt(state.requireValue.currentPath)), ), ); } @@ -675,8 +635,7 @@ class BroadcastGameState with _$BroadcastGameState { IList? pgnRootComments, }) = _BroadcastGameState; - IMap> get validMoves => - makeLegalMoves(currentNode.position); + IMap> get validMoves => makeLegalMoves(currentNode.position); Position get position => currentNode.position; bool get canGoNext => currentNode.hasChild; @@ -689,9 +648,9 @@ class BroadcastGameState with _$BroadcastGameState { UciPath? get broadcastLivePath => isOngoing ? broadcastPath : null; EngineGaugeParams get engineGaugeParams => ( - orientation: pov, - isLocalEngineAvailable: isLocalEvaluationEnabled, - position: position, - savedEval: currentNode.eval ?? currentNode.serverEval, - ); + orientation: pov, + isLocalEngineAvailable: isLocalEvaluationEnabled, + position: position, + savedEval: currentNode.eval ?? currentNode.serverEval, + ); } diff --git a/lib/src/model/broadcast/broadcast_providers.dart b/lib/src/model/broadcast/broadcast_providers.dart index f06c422cc8..c8696d35eb 100644 --- a/lib/src/model/broadcast/broadcast_providers.dart +++ b/lib/src/model/broadcast/broadcast_providers.dart @@ -34,13 +34,11 @@ class BroadcastsPaginator extends _$BroadcastsPaginator { (client) => BroadcastRepository(client).getBroadcasts(page: nextPage), ); - state = AsyncData( - ( - active: broadcastList.active, - past: broadcastList.past.addAll(broadcastListNewPage.past), - nextPage: broadcastListNewPage.nextPage, - ), - ); + state = AsyncData(( + active: broadcastList.active, + past: broadcastList.past.addAll(broadcastListNewPage.past), + nextPage: broadcastListNewPage.nextPage, + )); } } @@ -50,8 +48,7 @@ Future broadcastTournament( BroadcastTournamentId broadcastTournamentId, ) { return ref.withClient( - (client) => - BroadcastRepository(client).getTournament(broadcastTournamentId), + (client) => BroadcastRepository(client).getTournament(broadcastTournamentId), ); } @@ -60,9 +57,7 @@ Future> broadcastPlayers( Ref ref, BroadcastTournamentId tournamentId, ) { - return ref.withClient( - (client) => BroadcastRepository(client).getPlayers(tournamentId), - ); + return ref.withClient((client) => BroadcastRepository(client).getPlayers(tournamentId)); } @riverpod @@ -72,8 +67,7 @@ Future broadcastPlayerResult( String playerId, ) { return ref.withClient( - (client) => BroadcastRepository(client) - .getPlayerResults(broadcastTournamentId, playerId), + (client) => BroadcastRepository(client).getPlayerResults(broadcastTournamentId, playerId), ); } diff --git a/lib/src/model/broadcast/broadcast_repository.dart b/lib/src/model/broadcast/broadcast_repository.dart index 821b249285..832e0e3c8a 100644 --- a/lib/src/model/broadcast/broadcast_repository.dart +++ b/lib/src/model/broadcast/broadcast_repository.dart @@ -14,26 +14,19 @@ class BroadcastRepository { Future getBroadcasts({int page = 1}) { return client.readJson( - Uri( - path: '/api/broadcast/top', - queryParameters: {'page': page.toString()}, - ), + Uri(path: '/api/broadcast/top', queryParameters: {'page': page.toString()}), mapper: _makeBroadcastResponseFromJson, ); } - Future getTournament( - BroadcastTournamentId broadcastTournamentId, - ) { + Future getTournament(BroadcastTournamentId broadcastTournamentId) { return client.readJson( Uri(path: 'api/broadcast/$broadcastTournamentId'), mapper: _makeTournamentFromJson, ); } - Future getRound( - BroadcastRoundId broadcastRoundId, - ) { + Future getRound(BroadcastRoundId broadcastRoundId) { return client.readJson( Uri(path: 'api/broadcast/-/-/$broadcastRoundId'), // The path parameters with - are the broadcast tournament and round slugs @@ -42,16 +35,11 @@ class BroadcastRepository { ); } - Future getGamePgn( - BroadcastRoundId roundId, - BroadcastGameId gameId, - ) { + Future getGamePgn(BroadcastRoundId roundId, BroadcastGameId gameId) { return client.read(Uri(path: 'api/study/$roundId/$gameId.pgn')); } - Future> getPlayers( - BroadcastTournamentId tournamentId, - ) { + Future> getPlayers(BroadcastTournamentId tournamentId) { return client.readJsonList( Uri(path: '/broadcast/$tournamentId/players'), mapper: _makePlayerFromJson, @@ -69,14 +57,10 @@ class BroadcastRepository { } } -BroadcastList _makeBroadcastResponseFromJson( - Map json, -) { +BroadcastList _makeBroadcastResponseFromJson(Map json) { return ( active: pick(json, 'active').asListOrThrow(_broadcastFromPick).toIList(), - past: pick(json, 'past', 'currentPageResults') - .asListOrThrow(_broadcastFromPick) - .toIList(), + past: pick(json, 'past', 'currentPageResults').asListOrThrow(_broadcastFromPick).toIList(), nextPage: pick(json, 'past', 'nextPage').asIntOrNull(), ); } @@ -88,47 +72,38 @@ Broadcast _broadcastFromPick(RequiredPick pick) { tour: _tournamentDataFromPick(pick('tour').required()), round: _roundFromPick(pick('round').required()), group: pick('group').asStringOrNull(), - roundToLinkId: - pick('roundToLink', 'id').asBroadcastRoundIdOrNull() ?? roundId, + roundToLinkId: pick('roundToLink', 'id').asBroadcastRoundIdOrNull() ?? roundId, ); } -BroadcastTournamentData _tournamentDataFromPick( - RequiredPick pick, -) => - BroadcastTournamentData( - id: pick('id').asBroadcastTournamentIdOrThrow(), - name: pick('name').asStringOrThrow(), - slug: pick('slug').asStringOrThrow(), - tier: pick('tier').asIntOrNull(), - imageUrl: pick('image').asStringOrNull(), - description: pick('description').asStringOrNull(), - information: ( - format: pick('info', 'format').asStringOrNull(), - timeControl: pick('info', 'tc').asStringOrNull(), - players: pick('info', 'players').asStringOrNull(), - location: pick('info', 'location').asStringOrNull(), - dates: pick('dates').letOrNull( - (pick) => ( - startsAt: pick(0).asDateTimeFromMillisecondsOrThrow(), - endsAt: pick(1).asDateTimeFromMillisecondsOrNull(), - ), - ), - website: pick('info', 'website') - .letOrNull((p) => Uri.tryParse(p.asStringOrThrow())), +BroadcastTournamentData _tournamentDataFromPick(RequiredPick pick) => BroadcastTournamentData( + id: pick('id').asBroadcastTournamentIdOrThrow(), + name: pick('name').asStringOrThrow(), + slug: pick('slug').asStringOrThrow(), + tier: pick('tier').asIntOrNull(), + imageUrl: pick('image').asStringOrNull(), + description: pick('description').asStringOrNull(), + information: ( + format: pick('info', 'format').asStringOrNull(), + timeControl: pick('info', 'tc').asStringOrNull(), + players: pick('info', 'players').asStringOrNull(), + location: pick('info', 'location').asStringOrNull(), + dates: pick('dates').letOrNull( + (pick) => ( + startsAt: pick(0).asDateTimeFromMillisecondsOrThrow(), + endsAt: pick(1).asDateTimeFromMillisecondsOrNull(), ), - ); + ), + website: pick('info', 'website').letOrNull((p) => Uri.tryParse(p.asStringOrThrow())), + ), +); -BroadcastTournament _makeTournamentFromJson( - Map json, -) { +BroadcastTournament _makeTournamentFromJson(Map json) { return BroadcastTournament( data: _tournamentDataFromPick(pick(json, 'tour').required()), rounds: pick(json, 'rounds').asListOrThrow(_roundFromPick).toIList(), defaultRoundId: pick(json, 'defaultRoundId').asBroadcastRoundIdOrThrow(), - group: pick(json, 'group', 'tours') - .asListOrNull(_tournamentGroupFromPick) - ?.toIList(), + group: pick(json, 'group', 'tours').asListOrNull(_tournamentGroupFromPick)?.toIList(), ); } @@ -142,9 +117,10 @@ BroadcastTournamentGroup _tournamentGroupFromPick(RequiredPick pick) { BroadcastRound _roundFromPick(RequiredPick pick) { final live = pick('ongoing').asBoolOrFalse(); final finished = pick('finished').asBoolOrFalse(); - final status = live - ? RoundStatus.live - : finished + final status = + live + ? RoundStatus.live + : finished ? RoundStatus.finished : RoundStatus.upcoming; @@ -165,33 +141,29 @@ BroadcastRoundWithGames _makeRoundWithGamesFromJson(Map json) { return (round: _roundFromPick(round), games: _gamesFromPick(games)); } -BroadcastRoundGames _gamesFromPick( - RequiredPick pick, -) => +BroadcastRoundGames _gamesFromPick(RequiredPick pick) => IMap.fromEntries(pick.asListOrThrow(gameFromPick)); -MapEntry gameFromPick( - RequiredPick pick, -) { +MapEntry gameFromPick(RequiredPick pick) { final stringStatus = pick('status').asStringOrNull(); - final status = (stringStatus == null) - ? BroadcastResult.noResultPgnTag - : switch (stringStatus) { - '½-½' => BroadcastResult.draw, - '1-0' => BroadcastResult.whiteWins, - '0-1' => BroadcastResult.blackWins, - '*' => BroadcastResult.ongoing, - _ => throw FormatException( - "value $stringStatus can't be interpreted as a broadcast result", - ) - }; + final status = + (stringStatus == null) + ? BroadcastResult.noResultPgnTag + : switch (stringStatus) { + '½-½' => BroadcastResult.draw, + '1-0' => BroadcastResult.whiteWins, + '0-1' => BroadcastResult.blackWins, + '*' => BroadcastResult.ongoing, + _ => + throw FormatException( + "value $stringStatus can't be interpreted as a broadcast result", + ), + }; /// The amount of time that the player whose turn it is has been thinking since his last move - final thinkTime = - pick('thinkTime').asDurationFromSecondsOrNull() ?? Duration.zero; - final fen = - pick('fen').asStringOrNull() ?? Variant.standard.initialPosition.fen; + final thinkTime = pick('thinkTime').asDurationFromSecondsOrNull() ?? Duration.zero; + final fen = pick('fen').asStringOrNull() ?? Variant.standard.initialPosition.fen; final playingSide = Setup.parseFen(fen).turn; return MapEntry( @@ -224,8 +196,7 @@ BroadcastPlayer _playerFromPick( required Duration thinkingTime, }) { final clock = pick('clock').asDurationFromCentiSecondsOrNull(); - final updatedClock = - clock != null && isPlaying ? clock - thinkingTime : clock; + final updatedClock = clock != null && isPlaying ? clock - thinkingTime : clock; return BroadcastPlayer( name: pick('name').asStringOrThrow(), title: pick('title').asStringOrNull(), @@ -254,14 +225,11 @@ BroadcastPlayerExtended _playerExtendedFromPick(RequiredPick pick) { ); } -BroadcastPlayerResults _makePlayerResultsFromJson( - Map json, -) { +BroadcastPlayerResults _makePlayerResultsFromJson(Map json) { return ( player: _playerExtendedFromPick(pick(json).required()), fideData: _fideDataFromPick(pick(json, 'fide')), - games: - pick(json, 'games').asListOrThrow(_makePlayerResultFromPick).toIList() + games: pick(json, 'games').asListOrThrow(_makePlayerResultFromPick).toIList(), ); } @@ -270,7 +238,7 @@ BroadcastFideData _fideDataFromPick(Pick pick) { ratings: ( standard: pick('ratings', 'standard').asIntOrNull(), rapid: pick('ratings', 'rapid').asIntOrNull(), - blitz: pick('ratings', 'blitz').asIntOrNull() + blitz: pick('ratings', 'blitz').asIntOrNull(), ), birthYear: pick('year').asIntOrNull(), ); diff --git a/lib/src/model/broadcast/broadcast_round_controller.dart b/lib/src/model/broadcast/broadcast_round_controller.dart index fd9569cf77..4596c417bc 100644 --- a/lib/src/model/broadcast/broadcast_round_controller.dart +++ b/lib/src/model/broadcast/broadcast_round_controller.dart @@ -33,9 +33,7 @@ class BroadcastRoundController extends _$BroadcastRoundController { Object? _key = Object(); @override - Future build( - BroadcastRoundId broadcastRoundId, - ) async { + Future build(BroadcastRoundId broadcastRoundId) async { _socketClient = ref .watch(socketPoolProvider) .open(BroadcastRoundController.broadcastSocketUri(broadcastRoundId)); @@ -70,9 +68,7 @@ class BroadcastRoundController extends _$BroadcastRoundController { _debouncer.dispose(); }); - return ref.withClient( - (client) => BroadcastRepository(client).getRound(broadcastRoundId), - ); + return ref.withClient((client) => BroadcastRepository(client).getRound(broadcastRoundId)); } Future _syncRound() async { @@ -114,36 +110,29 @@ class BroadcastRoundController extends _$BroadcastRoundController { // We check that the event we received is for the last move of the game if (currentPath.value != '!') return; - final broadcastGameId = - pick(event.data, 'p', 'chapterId').asBroadcastGameIdOrThrow(); + final broadcastGameId = pick(event.data, 'p', 'chapterId').asBroadcastGameIdOrThrow(); final fen = pick(event.data, 'n', 'fen').asStringOrThrow(); final playingSide = Setup.parseFen(fen).turn; - state = AsyncData( - ( - round: state.requireValue.round, - games: state.requireValue.games.update( - broadcastGameId, - (broadcastGame) => broadcastGame.copyWith( - players: IMap( - { - playingSide: broadcastGame.players[playingSide]!, - playingSide.opposite: - broadcastGame.players[playingSide.opposite]!.copyWith( - clock: pick(event.data, 'n', 'clock') - .asDurationFromCentiSecondsOrNull(), - ), - }, + state = AsyncData(( + round: state.requireValue.round, + games: state.requireValue.games.update( + broadcastGameId, + (broadcastGame) => broadcastGame.copyWith( + players: IMap({ + playingSide: broadcastGame.players[playingSide]!, + playingSide.opposite: broadcastGame.players[playingSide.opposite]!.copyWith( + clock: pick(event.data, 'n', 'clock').asDurationFromCentiSecondsOrNull(), ), - fen: fen, - lastMove: pick(event.data, 'n', 'uci').asUciMoveOrThrow(), - updatedClockAt: DateTime.now(), - ), + }), + fen: fen, + lastMove: pick(event.data, 'n', 'uci').asUciMoveOrThrow(), + updatedClockAt: DateTime.now(), ), ), - ); + )); } void _handleAddChapterEvent(SocketEvent event) { @@ -152,39 +141,32 @@ class BroadcastRoundController extends _$BroadcastRoundController { void _handleChaptersEvent(SocketEvent event) { final games = pick(event.data).asListOrThrow(gameFromPick); - state = AsyncData( - (round: state.requireValue.round, games: IMap.fromEntries(games)), - ); + state = AsyncData((round: state.requireValue.round, games: IMap.fromEntries(games))); } void _handleClockEvent(SocketEvent event) { - final broadcastGameId = - pick(event.data, 'p', 'chapterId').asBroadcastGameIdOrThrow(); + final broadcastGameId = pick(event.data, 'p', 'chapterId').asBroadcastGameIdOrThrow(); final relayClocks = pick(event.data, 'p', 'relayClocks'); // We check that the clocks for the broadcast game preview have been updated else we do nothing if (relayClocks.value == null) return; - state = AsyncData( - ( - round: state.requireValue.round, - games: state.requireValue.games.update( - broadcastGameId, - (broadcastsGame) => broadcastsGame.copyWith( - players: IMap( - { - Side.white: broadcastsGame.players[Side.white]!.copyWith( - clock: relayClocks(0).asDurationFromCentiSecondsOrNull(), - ), - Side.black: broadcastsGame.players[Side.black]!.copyWith( - clock: relayClocks(1).asDurationFromCentiSecondsOrNull(), - ), - }, + state = AsyncData(( + round: state.requireValue.round, + games: state.requireValue.games.update( + broadcastGameId, + (broadcastsGame) => broadcastsGame.copyWith( + players: IMap({ + Side.white: broadcastsGame.players[Side.white]!.copyWith( + clock: relayClocks(0).asDurationFromCentiSecondsOrNull(), + ), + Side.black: broadcastsGame.players[Side.black]!.copyWith( + clock: relayClocks(1).asDurationFromCentiSecondsOrNull(), ), - updatedClockAt: DateTime.now(), - ), + }), + updatedClockAt: DateTime.now(), ), ), - ); + )); } } diff --git a/lib/src/model/challenge/challenge.dart b/lib/src/model/challenge/challenge.dart index 7fc0ac749a..78dea6630f 100644 --- a/lib/src/model/challenge/challenge.dart +++ b/lib/src/model/challenge/challenge.dart @@ -23,16 +23,13 @@ abstract mixin class BaseChallenge { SideChoice get sideChoice; String? get initialFen; - TimeIncrement? get timeIncrement => clock != null - ? TimeIncrement(clock!.time.inSeconds, clock!.increment.inSeconds) - : null; + TimeIncrement? get timeIncrement => + clock != null ? TimeIncrement(clock!.time.inSeconds, clock!.increment.inSeconds) : null; Perf get perf => Perf.fromVariantAndSpeed( - variant, - timeIncrement != null - ? Speed.fromTimeIncrement(timeIncrement!) - : Speed.correspondence, - ); + variant, + timeIncrement != null ? Speed.fromTimeIncrement(timeIncrement!) : Speed.correspondence, + ); } /// A challenge already created server-side. @@ -59,8 +56,7 @@ class Challenge with _$Challenge, BaseChallenge implements BaseChallenge { ChallengeDirection? direction, }) = _Challenge; - factory Challenge.fromJson(Map json) => - _$ChallengeFromJson(json); + factory Challenge.fromJson(Map json) => _$ChallengeFromJson(json); factory Challenge.fromServerJson(Map json) { return _challengeFromPick(pick(json).required()); @@ -77,30 +73,32 @@ class Challenge with _$Challenge, BaseChallenge implements BaseChallenge { final time = switch (timeControl) { ChallengeTimeControlType.clock => () { - final minutes = switch (clock!.time.inSeconds) { - 15 => '¼', - 30 => '½', - 45 => '¾', - 90 => '1.5', - _ => clock!.time.inMinutes, - }; - return '$minutes+${clock!.increment.inSeconds}'; - }(), + final minutes = switch (clock!.time.inSeconds) { + 15 => '¼', + 30 => '½', + 45 => '¾', + 90 => '1.5', + _ => clock!.time.inMinutes, + }; + return '$minutes+${clock!.increment.inSeconds}'; + }(), ChallengeTimeControlType.correspondence => '${l10n.daysPerTurn}: $days', ChallengeTimeControlType.unlimited => '∞', }; final variantStr = variant == Variant.standard ? '' : ' • ${variant.label}'; - final sidePiece = sideChoice == SideChoice.black - ? '♔ ' - : sideChoice == SideChoice.white + final sidePiece = + sideChoice == SideChoice.black + ? '♔ ' + : sideChoice == SideChoice.white ? '♚ ' : ''; - final side = sideChoice == SideChoice.black - ? l10n.white - : sideChoice == SideChoice.white + final side = + sideChoice == SideChoice.black + ? l10n.white + : sideChoice == SideChoice.white ? l10n.black : l10n.randomColor; @@ -112,15 +110,10 @@ class Challenge with _$Challenge, BaseChallenge implements BaseChallenge { /// A challenge request to play a game with another user. @freezed -class ChallengeRequest - with _$ChallengeRequest, BaseChallenge - implements BaseChallenge { +class ChallengeRequest with _$ChallengeRequest, BaseChallenge implements BaseChallenge { const ChallengeRequest._(); - @Assert( - 'clock != null || days != null', - 'Either clock or days must be set but not both.', - ) + @Assert('clock != null || days != null', 'Either clock or days must be set but not both.') const factory ChallengeRequest({ required LightUser destUser, required Variant variant, @@ -134,17 +127,16 @@ class ChallengeRequest @override Speed get speed => Speed.fromTimeIncrement( - TimeIncrement( - clock != null ? clock!.time.inSeconds : 0, - clock != null ? clock!.increment.inSeconds : 0, - ), - ); + TimeIncrement( + clock != null ? clock!.time.inSeconds : 0, + clock != null ? clock!.increment.inSeconds : 0, + ), + ); Map get toRequestBody { return { if (clock != null) 'clock.limit': clock!.time.inSeconds.toString(), - if (clock != null) - 'clock.increment': clock!.increment.inSeconds.toString(), + if (clock != null) 'clock.increment': clock!.increment.inSeconds.toString(), if (days != null) 'days': days.toString(), 'rated': variant == Variant.fromPosition ? 'false' : rated.toString(), 'variant': variant.name, @@ -154,24 +146,11 @@ class ChallengeRequest } } -enum ChallengeDirection { - outward, - inward, -} +enum ChallengeDirection { outward, inward } -enum ChallengeStatus { - created, - offline, - canceled, - declined, - accepted, -} +enum ChallengeStatus { created, offline, canceled, declined, accepted } -enum ChallengeTimeControlType { - unlimited, - clock, - correspondence, -} +enum ChallengeTimeControlType { unlimited, clock, correspondence } enum ChallengeDeclineReason { generic, @@ -187,26 +166,21 @@ enum ChallengeDeclineReason { onlyBot; String label(AppLocalizations l10n) => switch (this) { - ChallengeDeclineReason.generic => l10n.challengeDeclineGeneric, - ChallengeDeclineReason.later => l10n.challengeDeclineLater, - ChallengeDeclineReason.tooFast => l10n.challengeDeclineTooFast, - ChallengeDeclineReason.tooSlow => l10n.challengeDeclineTooSlow, - ChallengeDeclineReason.timeControl => l10n.challengeDeclineTimeControl, - ChallengeDeclineReason.rated => l10n.challengeDeclineRated, - ChallengeDeclineReason.casual => l10n.challengeDeclineCasual, - ChallengeDeclineReason.standard => l10n.challengeDeclineStandard, - ChallengeDeclineReason.variant => l10n.challengeDeclineVariant, - ChallengeDeclineReason.noBot => l10n.challengeDeclineNoBot, - ChallengeDeclineReason.onlyBot => l10n.challengeDeclineOnlyBot, - }; + ChallengeDeclineReason.generic => l10n.challengeDeclineGeneric, + ChallengeDeclineReason.later => l10n.challengeDeclineLater, + ChallengeDeclineReason.tooFast => l10n.challengeDeclineTooFast, + ChallengeDeclineReason.tooSlow => l10n.challengeDeclineTooSlow, + ChallengeDeclineReason.timeControl => l10n.challengeDeclineTimeControl, + ChallengeDeclineReason.rated => l10n.challengeDeclineRated, + ChallengeDeclineReason.casual => l10n.challengeDeclineCasual, + ChallengeDeclineReason.standard => l10n.challengeDeclineStandard, + ChallengeDeclineReason.variant => l10n.challengeDeclineVariant, + ChallengeDeclineReason.noBot => l10n.challengeDeclineNoBot, + ChallengeDeclineReason.onlyBot => l10n.challengeDeclineOnlyBot, + }; } -typedef ChallengeUser = ({ - LightUser user, - int? rating, - bool? provisionalRating, - int? lagRating, -}); +typedef ChallengeUser = ({LightUser user, int? rating, bool? provisionalRating, int? lagRating}); extension ChallengeExtension on Pick { ChallengeDirection asChallengeDirectionOrThrow() { @@ -226,9 +200,7 @@ extension ChallengeExtension on Pick { ); } } - throw PickException( - "value $value at $debugParsingExit can't be casted to ChallengeDirection", - ); + throw PickException("value $value at $debugParsingExit can't be casted to ChallengeDirection"); } ChallengeDirection? asChallengeDirectionOrNull() { @@ -263,9 +235,7 @@ extension ChallengeExtension on Pick { ); } } - throw PickException( - "value $value at $debugParsingExit can't be casted to ChallengeStatus", - ); + throw PickException("value $value at $debugParsingExit can't be casted to ChallengeStatus"); } ChallengeStatus? asChallengeStatusOrNull() { @@ -329,9 +299,7 @@ extension ChallengeExtension on Pick { ); } } - throw PickException( - "value $value at $debugParsingExit can't be casted to SideChoice", - ); + throw PickException("value $value at $debugParsingExit can't be casted to SideChoice"); } SideChoice? asSideChoiceOrNull() { @@ -351,9 +319,11 @@ extension ChallengeExtension on Pick { if (value is String) { return ChallengeDeclineReason.values.firstWhere( (element) => element.name.toLowerCase() == value, - orElse: () => throw PickException( - "value $value at $debugParsingExit can't be casted to ChallengeDeclineReason: invalid string.", - ), + orElse: + () => + throw PickException( + "value $value at $debugParsingExit can't be casted to ChallengeDeclineReason: invalid string.", + ), ); } throw PickException( @@ -379,42 +349,33 @@ Challenge _challengeFromPick(RequiredPick pick) { status: pick('status').asChallengeStatusOrThrow(), variant: pick('variant').asVariantOrThrow(), speed: pick('speed').asSpeedOrThrow(), - timeControl: - pick('timeControl', 'type').asChallengeTimeControlTypeOrThrow(), - clock: pick('timeControl').letOrThrow( - (clockPick) { - final time = clockPick('limit').asDurationFromSecondsOrNull(); - final increment = clockPick('increment').asDurationFromSecondsOrNull(); - return time != null && increment != null - ? (time: time, increment: increment) - : null; - }, - ), + timeControl: pick('timeControl', 'type').asChallengeTimeControlTypeOrThrow(), + clock: pick('timeControl').letOrThrow((clockPick) { + final time = clockPick('limit').asDurationFromSecondsOrNull(); + final increment = clockPick('increment').asDurationFromSecondsOrNull(); + return time != null && increment != null ? (time: time, increment: increment) : null; + }), days: pick('timeControl', 'daysPerTurn').asIntOrNull(), rated: pick('rated').asBoolOrThrow(), sideChoice: pick('color').asSideChoiceOrThrow(), - challenger: pick('challenger').letOrNull( - (challengerPick) { - final challengerUser = pick('challenger').asLightUserOrThrow(); - return ( - user: challengerUser, - rating: challengerPick('rating').asIntOrNull(), - provisionalRating: challengerPick('provisional').asBoolOrNull(), - lagRating: challengerPick('lag').asIntOrNull(), - ); - }, - ), - destUser: pick('destUser').letOrNull( - (destPick) { - final destUser = pick('destUser').asLightUserOrThrow(); - return ( - user: destUser, - rating: destPick('rating').asIntOrNull(), - provisionalRating: destPick('provisional').asBoolOrNull(), - lagRating: destPick('lag').asIntOrNull(), - ); - }, - ), + challenger: pick('challenger').letOrNull((challengerPick) { + final challengerUser = pick('challenger').asLightUserOrThrow(); + return ( + user: challengerUser, + rating: challengerPick('rating').asIntOrNull(), + provisionalRating: challengerPick('provisional').asBoolOrNull(), + lagRating: challengerPick('lag').asIntOrNull(), + ); + }), + destUser: pick('destUser').letOrNull((destPick) { + final destUser = pick('destUser').asLightUserOrThrow(); + return ( + user: destUser, + rating: destPick('rating').asIntOrNull(), + provisionalRating: destPick('provisional').asBoolOrNull(), + lagRating: destPick('lag').asIntOrNull(), + ); + }), initialFen: pick('initialFen').asStringOrNull(), direction: pick('direction').asChallengeDirectionOrNull(), declineReason: pick('declineReasonKey').asDeclineReasonOrNull(), diff --git a/lib/src/model/challenge/challenge_preferences.dart b/lib/src/model/challenge/challenge_preferences.dart index 76453530e9..dbbe66db28 100644 --- a/lib/src/model/challenge/challenge_preferences.dart +++ b/lib/src/model/challenge/challenge_preferences.dart @@ -22,8 +22,7 @@ class ChallengePreferences extends _$ChallengePreferences ChallengePrefs defaults({LightUser? user}) => ChallengePrefs.defaults; @override - ChallengePrefs fromJson(Map json) => - ChallengePrefs.fromJson(json); + ChallengePrefs fromJson(Map json) => ChallengePrefs.fromJson(json); @override ChallengePrefs build() { @@ -77,14 +76,10 @@ class ChallengePrefs with _$ChallengePrefs implements Serializable { sideChoice: SideChoice.random, ); - Speed get speed => timeControl == ChallengeTimeControlType.clock - ? Speed.fromTimeIncrement( - TimeIncrement( - clock.time.inSeconds, - clock.increment.inSeconds, - ), - ) - : Speed.correspondence; + Speed get speed => + timeControl == ChallengeTimeControlType.clock + ? Speed.fromTimeIncrement(TimeIncrement(clock.time.inSeconds, clock.increment.inSeconds)) + : Speed.correspondence; ChallengeRequest makeRequest(LightUser destUser, [String? initialFen]) { return ChallengeRequest( @@ -92,8 +87,7 @@ class ChallengePrefs with _$ChallengePrefs implements Serializable { variant: variant, timeControl: timeControl, clock: timeControl == ChallengeTimeControlType.clock ? clock : null, - days: - timeControl == ChallengeTimeControlType.correspondence ? days : null, + days: timeControl == ChallengeTimeControlType.correspondence ? days : null, rated: rated, sideChoice: sideChoice, initialFen: initialFen, diff --git a/lib/src/model/challenge/challenge_repository.dart b/lib/src/model/challenge/challenge_repository.dart index 17593a0cb5..7510bae576 100644 --- a/lib/src/model/challenge/challenge_repository.dart +++ b/lib/src/model/challenge/challenge_repository.dart @@ -16,10 +16,7 @@ ChallengeRepository challengeRepository(Ref ref) { return ChallengeRepository(ref.read(lichessClientProvider)); } -typedef ChallengesList = ({ - IList inward, - IList outward, -}); +typedef ChallengesList = ({IList inward, IList outward}); class ChallengeRepository { const ChallengeRepository(this.client); @@ -42,10 +39,7 @@ class ChallengeRepository { Future show(ChallengeId id) async { final uri = Uri(path: '/api/challenge/$id/show'); - return client.readJson( - uri, - mapper: Challenge.fromServerJson, - ); + return client.readJson(uri, mapper: Challenge.fromServerJson); } Future create(ChallengeRequest challenge) async { @@ -62,25 +56,16 @@ class ChallengeRepository { final response = await client.post(uri); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to accept challenge: ${response.statusCode}', - uri, - ); + throw http.ClientException('Failed to accept challenge: ${response.statusCode}', uri); } } Future decline(ChallengeId id, {ChallengeDeclineReason? reason}) async { final uri = Uri(path: '/api/challenge/$id/decline'); - final response = await client.post( - uri, - body: reason != null ? {'reason': reason.name} : null, - ); + final response = await client.post(uri, body: reason != null ? {'reason': reason.name} : null); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to decline challenge: ${response.statusCode}', - uri, - ); + throw http.ClientException('Failed to decline challenge: ${response.statusCode}', uri); } } @@ -89,10 +74,7 @@ class ChallengeRepository { final response = await client.post(uri); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to cancel challenge: ${response.statusCode}', - uri, - ); + throw http.ClientException('Failed to cancel challenge: ${response.statusCode}', uri); } } } diff --git a/lib/src/model/challenge/challenge_service.dart b/lib/src/model/challenge/challenge_service.dart index a670d924ea..29d7b1c68a 100644 --- a/lib/src/model/challenge/challenge_service.dart +++ b/lib/src/model/challenge/challenge_service.dart @@ -41,15 +41,14 @@ class ChallengeService { StreamSubscription? _socketSubscription; /// The stream of challenge events that are received from the server. - static Stream get stream => socketGlobalStream.map( - (event) { - if (event.topic != 'challenges') return null; - final listPick = pick(event.data).required(); - final inward = listPick('in').asListOrEmpty(Challenge.fromPick); - final outward = listPick('out').asListOrEmpty(Challenge.fromPick); - return (inward: inward.lock, outward: outward.lock); - }, - ).whereNotNull(); + static Stream get stream => + socketGlobalStream.map((event) { + if (event.topic != 'challenges') return null; + final listPick = pick(event.data).required(); + final inward = listPick('in').asListOrEmpty(Challenge.fromPick); + final outward = listPick('out').asListOrEmpty(Challenge.fromPick); + return (inward: inward.lock, outward: outward.lock); + }).whereNotNull(); /// Start listening to challenge events from the server. void start() { @@ -75,21 +74,16 @@ class ChallengeService { await Future.wait( prevInwardIds .whereNot((challengeId) => currentInwardIds.contains(challengeId)) - .map( - (id) async => await notificationService.cancel(id.value.hashCode), - ), + .map((id) async => await notificationService.cancel(id.value.hashCode)), ); // new incoming challenges await Future.wait( - _current?.inward - .whereNot((challenge) => prevInwardIds.contains(challenge.id)) - .map( - (challenge) async { - return await notificationService - .show(ChallengeNotification(challenge)); - }, - ) ?? + _current?.inward.whereNot((challenge) => prevInwardIds.contains(challenge.id)).map(( + challenge, + ) async { + return await notificationService.show(ChallengeNotification(challenge)); + }) ?? >[], ); } @@ -100,10 +94,7 @@ class ChallengeService { } /// Handle a local notification response when the app is in the foreground. - Future onNotificationResponse( - String? actionid, - Challenge challenge, - ) async { + Future onNotificationResponse(String? actionid, Challenge challenge) async { final challengeId = challenge.id; switch (actionid) { @@ -111,9 +102,7 @@ class ChallengeService { final repo = ref.read(challengeRepositoryProvider); await repo.accept(challengeId); - final fullId = await repo - .show(challengeId) - .then((challenge) => challenge.gameFullId); + final fullId = await repo.show(challengeId).then((challenge) => challenge.gameFullId); final context = ref.read(currentNavigatorKeyProvider).currentContext; if (context == null || !context.mounted) break; @@ -134,17 +123,18 @@ class ChallengeService { if (context == null || !context.mounted) break; showAdaptiveActionSheet( context: context, - actions: ChallengeDeclineReason.values - .map( - (reason) => BottomSheetAction( - makeLabel: (context) => Text(reason.label(context.l10n)), - onPressed: (_) { - final repo = ref.read(challengeRepositoryProvider); - repo.decline(challengeId, reason: reason); - }, - ), - ) - .toList(), + actions: + ChallengeDeclineReason.values + .map( + (reason) => BottomSheetAction( + makeLabel: (context) => Text(reason.label(context.l10n)), + onPressed: (_) { + final repo = ref.read(challengeRepositoryProvider); + repo.decline(challengeId, reason: reason); + }, + ), + ) + .toList(), ); case null: diff --git a/lib/src/model/challenge/challenges.dart b/lib/src/model/challenge/challenges.dart index ff788bffc8..8fa8c495c7 100644 --- a/lib/src/model/challenge/challenges.dart +++ b/lib/src/model/challenge/challenges.dart @@ -15,8 +15,7 @@ class Challenges extends _$Challenges { @override Future build() async { - _subscription = - ChallengeService.stream.listen((list) => state = AsyncValue.data(list)); + _subscription = ChallengeService.stream.listen((list) => state = AsyncValue.data(list)); ref.onDispose(() { _subscription?.cancel(); @@ -24,12 +23,10 @@ class Challenges extends _$Challenges { final session = ref.watch(authSessionProvider); if (session == null) { - return Future.value( - ( - inward: const IList.empty(), - outward: const IList.empty(), - ), - ); + return Future.value(( + inward: const IList.empty(), + outward: const IList.empty(), + )); } return ref.read(challengeRepositoryProvider).list(); diff --git a/lib/src/model/clock/chess_clock.dart b/lib/src/model/clock/chess_clock.dart index b4560932e2..02e75bfcf0 100644 --- a/lib/src/model/clock/chess_clock.dart +++ b/lib/src/model/clock/chess_clock.dart @@ -15,9 +15,9 @@ class ChessClock { this.emergencyThreshold, this.onFlag, this.onEmergency, - }) : _whiteTime = ValueNotifier(whiteTime), - _blackTime = ValueNotifier(blackTime), - _activeSide = Side.white; + }) : _whiteTime = ValueNotifier(whiteTime), + _blackTime = ValueNotifier(blackTime), + _activeSide = Side.white; /// The threshold at which the clock will call [onEmergency] if provided. final Duration? emergencyThreshold; @@ -176,8 +176,7 @@ class ChessClock { _shouldPlayEmergencyFeedback = false; _nextEmergency = clock.now().add(_emergencyDelay); onEmergency?.call(_activeSide); - } else if (emergencyThreshold != null && - timeLeft > emergencyThreshold! * 1.5) { + } else if (emergencyThreshold != null && timeLeft > emergencyThreshold! * 1.5) { _shouldPlayEmergencyFeedback = true; } } diff --git a/lib/src/model/clock/clock_tool_controller.dart b/lib/src/model/clock/clock_tool_controller.dart index 524b543a9d..4f559511df 100644 --- a/lib/src/model/clock/clock_tool_controller.dart +++ b/lib/src/model/clock/clock_tool_controller.dart @@ -23,11 +23,7 @@ class ClockToolController extends _$ClockToolController { whiteIncrement: increment, blackIncrement: increment, ); - _clock = ChessClock( - whiteTime: time, - blackTime: time, - onFlag: _onFlagged, - ); + _clock = ChessClock(whiteTime: time, blackTime: time, onFlag: _onFlagged); ref.onDispose(() { _clock.dispose(); @@ -65,9 +61,7 @@ class ClockToolController extends _$ClockToolController { _clock.startSide(playerType.opposite); _clock.incTime( playerType, - playerType == Side.white - ? state.options.whiteIncrement - : state.options.blackIncrement, + playerType == Side.white ? state.options.whiteIncrement : state.options.blackIncrement, ); } @@ -77,21 +71,14 @@ class ClockToolController extends _$ClockToolController { } _clock.setTimes( - whiteTime: playerType == Side.white - ? duration + state.options.whiteIncrement - : null, - blackTime: playerType == Side.black - ? duration + state.options.blackIncrement - : null, + whiteTime: playerType == Side.white ? duration + state.options.whiteIncrement : null, + blackTime: playerType == Side.black ? duration + state.options.blackIncrement : null, ); } void updateOptions(TimeIncrement timeIncrement) { final options = ClockOptions.fromTimeIncrement(timeIncrement); - _clock.setTimes( - whiteTime: options.whiteTime, - blackTime: options.blackTime, - ); + _clock.setTimes(whiteTime: options.whiteTime, blackTime: options.blackTime); state = state.copyWith( options: options, whiteTime: _clock.whiteTime, @@ -99,28 +86,16 @@ class ClockToolController extends _$ClockToolController { ); } - void updateOptionsCustom( - TimeIncrement clock, - Side player, - ) { + void updateOptionsCustom(TimeIncrement clock, Side player) { final options = ClockOptions( - whiteTime: player == Side.white - ? Duration(seconds: clock.time) - : state.options.whiteTime, - blackTime: player == Side.black - ? Duration(seconds: clock.time) - : state.options.blackTime, - whiteIncrement: player == Side.white - ? Duration(seconds: clock.increment) - : state.options.whiteIncrement, - blackIncrement: player == Side.black - ? Duration(seconds: clock.increment) - : state.options.blackIncrement, - ); - _clock.setTimes( - whiteTime: options.whiteTime, - blackTime: options.blackTime, + whiteTime: player == Side.white ? Duration(seconds: clock.time) : state.options.whiteTime, + blackTime: player == Side.black ? Duration(seconds: clock.time) : state.options.blackTime, + whiteIncrement: + player == Side.white ? Duration(seconds: clock.increment) : state.options.whiteIncrement, + blackIncrement: + player == Side.black ? Duration(seconds: clock.increment) : state.options.blackIncrement, ); + _clock.setTimes(whiteTime: options.whiteTime, blackTime: options.blackTime); state = ClockState( options: options, whiteTime: _clock.whiteTime, @@ -129,14 +104,10 @@ class ClockToolController extends _$ClockToolController { ); } - void setBottomPlayer(Side playerType) => - state = state.copyWith(bottomPlayer: playerType); + void setBottomPlayer(Side playerType) => state = state.copyWith(bottomPlayer: playerType); void reset() { - _clock.setTimes( - whiteTime: state.options.whiteTime, - blackTime: state.options.whiteTime, - ); + _clock.setTimes(whiteTime: state.options.whiteTime, blackTime: state.options.whiteTime); state = state.copyWith( whiteTime: _clock.whiteTime, blackTime: _clock.blackTime, @@ -176,24 +147,22 @@ class ClockOptions with _$ClockOptions { required Duration blackIncrement, }) = _ClockOptions; - factory ClockOptions.fromTimeIncrement(TimeIncrement timeIncrement) => - ClockOptions( - whiteTime: Duration(seconds: timeIncrement.time), - blackTime: Duration(seconds: timeIncrement.time), - whiteIncrement: Duration(seconds: timeIncrement.increment), - blackIncrement: Duration(seconds: timeIncrement.increment), - ); + factory ClockOptions.fromTimeIncrement(TimeIncrement timeIncrement) => ClockOptions( + whiteTime: Duration(seconds: timeIncrement.time), + blackTime: Duration(seconds: timeIncrement.time), + whiteIncrement: Duration(seconds: timeIncrement.increment), + blackIncrement: Duration(seconds: timeIncrement.increment), + ); factory ClockOptions.fromSeparateTimeIncrements( TimeIncrement playerTop, TimeIncrement playerBottom, - ) => - ClockOptions( - whiteTime: Duration(seconds: playerTop.time), - blackTime: Duration(seconds: playerBottom.time), - whiteIncrement: Duration(seconds: playerTop.increment), - blackIncrement: Duration(seconds: playerBottom.increment), - ); + ) => ClockOptions( + whiteTime: Duration(seconds: playerTop.time), + blackTime: Duration(seconds: playerBottom.time), + whiteIncrement: Duration(seconds: playerTop.increment), + blackIncrement: Duration(seconds: playerBottom.increment), + ); } @freezed @@ -216,14 +185,11 @@ class ClockState with _$ClockState { ValueListenable getDuration(Side playerType) => playerType == Side.white ? whiteTime : blackTime; - int getMovesCount(Side playerType) => - playerType == Side.white ? whiteMoves : blackMoves; + int getMovesCount(Side playerType) => playerType == Side.white ? whiteMoves : blackMoves; - bool isPlayersTurn(Side playerType) => - started && activeSide == playerType && flagged == null; + bool isPlayersTurn(Side playerType) => started && activeSide == playerType && flagged == null; - bool isPlayersMoveAllowed(Side playerType) => - isPlayersTurn(playerType) && !paused; + bool isPlayersMoveAllowed(Side playerType) => isPlayersTurn(playerType) && !paused; bool isActivePlayer(Side playerType) => isPlayersTurn(playerType) && !paused; diff --git a/lib/src/model/common/chess.dart b/lib/src/model/common/chess.dart index 74e521ef97..a26b8b3789 100644 --- a/lib/src/model/common/chess.dart +++ b/lib/src/model/common/chess.dart @@ -15,13 +15,9 @@ typedef UCIMove = String; @Freezed(fromJson: true, toJson: true) class SanMove with _$SanMove { const SanMove._(); - const factory SanMove( - String san, - @MoveConverter() Move move, - ) = _SanMove; + const factory SanMove(String san, @MoveConverter() Move move) = _SanMove; - factory SanMove.fromJson(Map json) => - _$SanMoveFromJson(json); + factory SanMove.fromJson(Map json) => _$SanMoveFromJson(json); bool get isCheck => san.contains('+'); bool get isCapture => san.contains('x'); @@ -39,12 +35,7 @@ class MoveConverter implements JsonConverter { } /// Alternative castling uci notations. -const altCastles = { - 'e1a1': 'e1c1', - 'e1h1': 'e1g1', - 'e8a8': 'e8c8', - 'e8h8': 'e8g8', -}; +const altCastles = {'e1a1': 'e1c1', 'e1h1': 'e1g1', 'e8a8': 'e8c8', 'e8h8': 'e8g8'}; /// Returns `true` if the move is a pawn promotion move that is not yet promoted. bool isPromotionPawnMove(Position position, NormalMove move) { @@ -180,24 +171,16 @@ sealed class Opening { @Freezed(fromJson: true, toJson: true) class LightOpening with _$LightOpening implements Opening { const LightOpening._(); - const factory LightOpening({ - required String eco, - required String name, - }) = _LightOpening; + const factory LightOpening({required String eco, required String name}) = _LightOpening; - factory LightOpening.fromJson(Map json) => - _$LightOpeningFromJson(json); + factory LightOpening.fromJson(Map json) => _$LightOpeningFromJson(json); } @Freezed(fromJson: true, toJson: true) class Division with _$Division { - const factory Division({ - double? middlegame, - double? endgame, - }) = _Division; + const factory Division({double? middlegame, double? endgame}) = _Division; - factory Division.fromJson(Map json) => - _$DivisionFromJson(json); + factory Division.fromJson(Map json) => _$DivisionFromJson(json); } @freezed @@ -228,9 +211,7 @@ extension ChessExtension on Pick { ); } } - throw PickException( - "value $value at $debugParsingExit can't be casted to Move", - ); + throw PickException("value $value at $debugParsingExit can't be casted to Move"); } Move? asUciMoveOrNull() { @@ -251,14 +232,12 @@ extension ChessExtension on Pick { return value == 'white' ? Side.white : value == 'black' - ? Side.black - : throw PickException( - "value $value at $debugParsingExit can't be casted to Side: invalid string.", - ); + ? Side.black + : throw PickException( + "value $value at $debugParsingExit can't be casted to Side: invalid string.", + ); } - throw PickException( - "value $value at $debugParsingExit can't be casted to Side", - ); + throw PickException("value $value at $debugParsingExit can't be casted to Side"); } Side? asSideOrNull() { @@ -299,9 +278,7 @@ extension ChessExtension on Pick { return variant; } } - throw PickException( - "value $value at $debugParsingExit can't be casted to Variant", - ); + throw PickException("value $value at $debugParsingExit can't be casted to Variant"); } Variant? asVariantOrNull() { diff --git a/lib/src/model/common/chess960.dart b/lib/src/model/common/chess960.dart index c1390ec352..6daf1bd857 100644 --- a/lib/src/model/common/chess960.dart +++ b/lib/src/model/common/chess960.dart @@ -8,9 +8,7 @@ Position randomChess960Position() { final rank8 = _positions[_random.nextInt(_positions.length)]; return Chess.fromSetup( - Setup.parseFen( - '$rank8/pppppppp/8/8/8/8/PPPPPPPP/${rank8.toUpperCase()} w KQkq - 0 1', - ), + Setup.parseFen('$rank8/pppppppp/8/8/8/8/PPPPPPPP/${rank8.toUpperCase()} w KQkq - 0 1'), ); } diff --git a/lib/src/model/common/eval.dart b/lib/src/model/common/eval.dart index 46976bc307..a1ef6181d0 100644 --- a/lib/src/model/common/eval.dart +++ b/lib/src/model/common/eval.dart @@ -39,8 +39,7 @@ class ExternalEval with _$ExternalEval implements Eval { ); } - factory ExternalEval.fromJson(Map json) => - _$ExternalEvalFromJson(json); + factory ExternalEval.fromJson(Map json) => _$ExternalEvalFromJson(json); @override String get evalString => _evalString(cp, mate); @@ -120,11 +119,7 @@ class ClientEval with _$ClientEval implements Eval { @freezed class PvData with _$PvData { const PvData._(); - const factory PvData({ - required IList moves, - int? mate, - int? cp, - }) = _PvData; + const factory PvData({required IList moves, int? mate, int? cp}) = _PvData; String get evalString => _evalString(cp, mate); @@ -158,11 +153,7 @@ class PvData with _$PvData { MoveWithWinningChances? _firstMoveWithWinningChances(Side sideToMove) { final uciMove = (moves.isNotEmpty) ? Move.parse(moves.first) : null; return (uciMove != null) - ? ( - move: uciMove, - winningChances: - _toPov(sideToMove, _toWhiteWinningChances(cp, mate)), - ) + ? (move: uciMove, winningChances: _toPov(sideToMove, _toWhiteWinningChances(cp, mate))) : null; } } @@ -182,59 +173,55 @@ ISet computeBestMoveShapes( const winningDiffScaleFactor = 2.5; final bestMove = moves[0]; - final winningDiffComparedToBestMove = - bestMove.winningChances - moves[index].winningChances; + final winningDiffComparedToBestMove = bestMove.winningChances - moves[index].winningChances; // Force minimum scale if the best move is significantly better than this move if (winningDiffComparedToBestMove > 0.3) { return minScale; } return clampDouble( - math.max( - minScale, - maxScale - winningDiffScaleFactor * winningDiffComparedToBestMove, - ), + math.max(minScale, maxScale - winningDiffScaleFactor * winningDiffComparedToBestMove), 0, 1, ); } return ISet( - moves.mapIndexed( - (i, m) { - final move = m.move; - // Same colors as in the Web UI with a slightly different opacity - // The best move has a different color than the other moves - final color = Color((i == 0) ? 0x66003088 : 0x664A4A4A); - switch (move) { - case NormalMove(from: _, to: _, promotion: final promRole): - return [ - Arrow( - color: color, - orig: move.from, - dest: move.to, - scale: scaleArrowAgainstBestMove(i), - ), - if (promRole != null) + moves + .mapIndexed((i, m) { + final move = m.move; + // Same colors as in the Web UI with a slightly different opacity + // The best move has a different color than the other moves + final color = Color((i == 0) ? 0x66003088 : 0x664A4A4A); + switch (move) { + case NormalMove(from: _, to: _, promotion: final promRole): + return [ + Arrow( + color: color, + orig: move.from, + dest: move.to, + scale: scaleArrowAgainstBestMove(i), + ), + if (promRole != null) + PieceShape( + color: color, + orig: move.to, + pieceAssets: pieceAssets, + piece: Piece(color: sideToMove, role: promRole), + ), + ]; + case DropMove(role: final role, to: _): + return [ PieceShape( color: color, orig: move.to, pieceAssets: pieceAssets, - piece: Piece(color: sideToMove, role: promRole), + opacity: 0.5, + piece: Piece(color: sideToMove, role: role), ), - ]; - case DropMove(role: final role, to: _): - return [ - PieceShape( - color: color, - orig: move.to, - pieceAssets: pieceAssets, - opacity: 0.5, - piece: Piece(color: sideToMove, role: role), - ), - ]; - } - }, - ).expand((e) => e), + ]; + } + }) + .expand((e) => e), ); } @@ -242,8 +229,7 @@ double cpToPawns(int cp) => cp / 100; int cpFromPawns(double pawns) => (pawns * 100).round(); -double cpWinningChances(int cp) => - _rawWinningChances(math.min(math.max(-1000, cp), 1000)); +double cpWinningChances(int cp) => _rawWinningChances(math.min(math.max(-1000, cp), 1000)); double mateWinningChances(int mate) { final cp = (21 - math.min(10, mate.abs())) * 100; diff --git a/lib/src/model/common/game.dart b/lib/src/model/common/game.dart index 024ced5571..f9dfefeb92 100644 --- a/lib/src/model/common/game.dart +++ b/lib/src/model/common/game.dart @@ -7,8 +7,8 @@ enum SideChoice { black; String label(AppLocalizations l10n) => switch (this) { - SideChoice.white => l10n.white, - SideChoice.random => l10n.randomColor, - SideChoice.black => l10n.black, - }; + SideChoice.white => l10n.white, + SideChoice.random => l10n.randomColor, + SideChoice.black => l10n.black, + }; } diff --git a/lib/src/model/common/id.dart b/lib/src/model/common/id.dart index 6f0a3729d6..f7cbe93049 100644 --- a/lib/src/model/common/id.dart +++ b/lib/src/model/common/id.dart @@ -75,9 +75,7 @@ extension IDPick on Pick { if (value is String) { return UserId(value); } - throw PickException( - "value $value at $debugParsingExit can't be casted to UserId", - ); + throw PickException("value $value at $debugParsingExit can't be casted to UserId"); } UserId? asUserIdOrNull() { @@ -94,9 +92,7 @@ extension IDPick on Pick { if (value is String) { return GameId(value); } - throw PickException( - "value $value at $debugParsingExit can't be casted to GameId", - ); + throw PickException("value $value at $debugParsingExit can't be casted to GameId"); } GameId? asGameIdOrNull() { @@ -113,9 +109,7 @@ extension IDPick on Pick { if (value is String) { return GameFullId(value); } - throw PickException( - "value $value at $debugParsingExit can't be casted to GameId", - ); + throw PickException("value $value at $debugParsingExit can't be casted to GameId"); } GameFullId? asGameFullIdOrNull() { @@ -132,9 +126,7 @@ extension IDPick on Pick { if (value is String) { return PuzzleId(value); } - throw PickException( - "value $value at $debugParsingExit can't be casted to PuzzleId", - ); + throw PickException("value $value at $debugParsingExit can't be casted to PuzzleId"); } PuzzleId? asPuzzleIdOrNull() { @@ -151,9 +143,7 @@ extension IDPick on Pick { if (value is String) { return ChallengeId(value); } - throw PickException( - "value $value at $debugParsingExit can't be casted to ChallengeId", - ); + throw PickException("value $value at $debugParsingExit can't be casted to ChallengeId"); } ChallengeId? asChallengeIdOrNull() { @@ -189,9 +179,7 @@ extension IDPick on Pick { if (value is String) { return BroadcastRoundId(value); } - throw PickException( - "value $value at $debugParsingExit can't be casted to BroadcastRoundId", - ); + throw PickException("value $value at $debugParsingExit can't be casted to BroadcastRoundId"); } BroadcastRoundId? asBroadcastRoundIdOrNull() { @@ -208,9 +196,7 @@ extension IDPick on Pick { if (value is String) { return BroadcastGameId(value); } - throw PickException( - "value $value at $debugParsingExit can't be casted to BroadcastGameId", - ); + throw PickException("value $value at $debugParsingExit can't be casted to BroadcastGameId"); } BroadcastGameId? asBroadcastGameIdOrNull() { @@ -227,9 +213,7 @@ extension IDPick on Pick { if (value is String) { return StudyId(value); } - throw PickException( - "value $value at $debugParsingExit can't be casted to StudyId", - ); + throw PickException("value $value at $debugParsingExit can't be casted to StudyId"); } FideId asFideIdOrThrow() { @@ -237,9 +221,7 @@ extension IDPick on Pick { if (value is int && value != 0) { return FideId(value); } - throw PickException( - "value $value at $debugParsingExit can't be casted to FideId", - ); + throw PickException("value $value at $debugParsingExit can't be casted to FideId"); } FideId? asFideIdOrNull() { diff --git a/lib/src/model/common/node.dart b/lib/src/model/common/node.dart index fd971cc615..5aed5f5540 100644 --- a/lib/src/model/common/node.dart +++ b/lib/src/model/common/node.dart @@ -19,11 +19,7 @@ final _logger = Logger('Node'); /// It should not be directly used in a riverpod state, because it is mutable. /// It can be converted into an immutable [ViewNode], using the [view] getter. abstract class Node { - Node({ - required this.position, - this.eval, - this.opening, - }); + Node({required this.position, this.eval, this.opening}); final Position position; @@ -194,17 +190,11 @@ abstract class Node { } /// Adds a list of nodes at the given path and returns the new path. - UciPath? addNodesAt( - UciPath path, - Iterable newNodes, { - bool prepend = false, - }) { + UciPath? addNodesAt(UciPath path, Iterable newNodes, {bool prepend = false}) { final node = newNodes.elementAtOrNull(0); if (node == null) return path; final (newPath, _) = addNodeAt(path, node, prepend: prepend); - return newPath != null - ? addNodesAt(newPath, newNodes.skip(1), prepend: prepend) - : null; + return newPath != null ? addNodesAt(newPath, newNodes.skip(1), prepend: prepend) : null; } /// Adds a new node with that [Move] at the given path. @@ -225,12 +215,12 @@ abstract class Node { }) { final pos = nodeAt(path).position; - final potentialAltCastlingMove = move is NormalMove && + final potentialAltCastlingMove = + move is NormalMove && pos.board.roleAt(move.from) == Role.king && pos.board.roleAt(move.to) != Role.rook; - final convertedMove = - potentialAltCastlingMove ? convertAltCastlingMove(move) ?? move : move; + final convertedMove = potentialAltCastlingMove ? convertAltCastlingMove(move) ?? move : move; final (newPos, newSan) = pos.makeSan(convertedMove); final newNode = Branch( @@ -245,9 +235,7 @@ abstract class Node { /// castling move and converts it to the corresponding standard castling move if so. Move? convertAltCastlingMove(Move move) { return altCastles.containsValue(move.uci) - ? Move.parse( - altCastles.entries.firstWhere((e) => e.value == move.uci).key, - ) + ? Move.parse(altCastles.entries.firstWhere((e) => e.value == move.uci).key) : move; } @@ -310,51 +298,35 @@ abstract class Node { /// Export the tree to a PGN string. /// /// Optionally, headers and initial game comments can be provided. - String makePgn([ - IMap? headers, - IList? rootComments, - ]) { + String makePgn([IMap? headers, IList? rootComments]) { final pgnNode = PgnNode(); - final List<({Node from, PgnNode to})> stack = [ - (from: this, to: pgnNode), - ]; + final List<({Node from, PgnNode to})> stack = [(from: this, to: pgnNode)]; while (stack.isNotEmpty) { final frame = stack.removeLast(); - for (int childIdx = 0; - childIdx < frame.from.children.length; - childIdx++) { + for (int childIdx = 0; childIdx < frame.from.children.length; childIdx++) { final childFrom = frame.from.children[childIdx]; final childTo = PgnChildNode( PgnNodeData( san: childFrom.sanMove.san, - startingComments: childFrom.startingComments - ?.map((c) => c.makeComment()) - .toList(), + startingComments: childFrom.startingComments?.map((c) => c.makeComment()).toList(), comments: - (childFrom.lichessAnalysisComments ?? childFrom.comments)?.map( - (c) { - final eval = childFrom.eval; - final pgnEval = eval?.cp != null - ? PgnEvaluation.pawns( - pawns: cpToPawns(eval!.cp!), - depth: eval.depth, - ) - : eval?.mate != null - ? PgnEvaluation.mate( - mate: eval!.mate, - depth: eval.depth, - ) - : c.eval; - return PgnComment( - text: c.text, - shapes: c.shapes, - clock: c.clock, - emt: c.emt, - eval: pgnEval, - ).makeComment(); - }, - ).toList(), + (childFrom.lichessAnalysisComments ?? childFrom.comments)?.map((c) { + final eval = childFrom.eval; + final pgnEval = + eval?.cp != null + ? PgnEvaluation.pawns(pawns: cpToPawns(eval!.cp!), depth: eval.depth) + : eval?.mate != null + ? PgnEvaluation.mate(mate: eval!.mate, depth: eval.depth) + : c.eval; + return PgnComment( + text: c.text, + shapes: c.shapes, + clock: c.clock, + emt: c.emt, + eval: pgnEval, + ).makeComment(); + }).toList(), nags: childFrom.nags, ), ); @@ -419,18 +391,18 @@ class Branch extends Node { @override ViewBranch get view => ViewBranch( - position: position, - sanMove: sanMove, - eval: eval, - opening: opening, - children: IList(children.map((child) => child.view)), - isComputerVariation: isComputerVariation, - isCollapsed: isCollapsed, - lichessAnalysisComments: lichessAnalysisComments?.lock, - startingComments: startingComments?.lock, - comments: comments?.lock, - nags: nags?.lock, - ); + position: position, + sanMove: sanMove, + eval: eval, + opening: opening, + children: IList(children.map((child) => child.view)), + isComputerVariation: isComputerVariation, + isCollapsed: isCollapsed, + lichessAnalysisComments: lichessAnalysisComments?.lock, + startingComments: startingComments?.lock, + comments: comments?.lock, + nags: nags?.lock, + ); /// Gets the branch at the given path @override @@ -443,9 +415,7 @@ class Branch extends Node { if (lichessAnalysisComments == null) { lichessAnalysisComments = [c]; } else { - final existing = lichessAnalysisComments?.firstWhereOrNull( - (e) => e.text == c.text, - ); + final existing = lichessAnalysisComments?.firstWhereOrNull((e) => e.text == c.text); if (existing == null) { lichessAnalysisComments?.add(c); } @@ -455,9 +425,7 @@ class Branch extends Node { if (startingComments == null) { startingComments = [c]; } else { - final existing = startingComments?.firstWhereOrNull( - (e) => e.text == c.text, - ); + final existing = startingComments?.firstWhereOrNull((e) => e.text == c.text); if (existing == null) { startingComments?.add(c); } @@ -467,9 +435,7 @@ class Branch extends Node { if (comments == null) { comments = [c]; } else { - final existing = comments?.firstWhereOrNull( - (e) => e.text == c.text, - ); + final existing = comments?.firstWhereOrNull((e) => e.text == c.text); if (existing == null) { comments?.add(c); } @@ -484,19 +450,16 @@ class Branch extends Node { /// Gets the first available clock from the comments. Duration? get clock { - final clockComment = (lichessAnalysisComments ?? comments) - ?.firstWhereOrNull((c) => c.clock != null); + final clockComment = (lichessAnalysisComments ?? comments)?.firstWhereOrNull( + (c) => c.clock != null, + ); return clockComment?.clock; } /// Gets the first available external eval from the comments. ExternalEval? get externalEval { - final comment = (lichessAnalysisComments ?? comments)?.firstWhereOrNull( - (c) => c.eval != null, - ); - return comment?.eval != null - ? ExternalEval.fromPgnEval(comment!.eval!) - : null; + final comment = (lichessAnalysisComments ?? comments)?.firstWhereOrNull((c) => c.eval != null); + return comment?.eval != null ? ExternalEval.fromPgnEval(comment!.eval!) : null; } @override @@ -509,35 +472,27 @@ class Branch extends Node { /// /// Represents the initial position, where no move has been played yet. class Root extends Node { - Root({ - required super.position, - super.eval, - }); + Root({required super.position, super.eval}); @override ViewRoot get view => ViewRoot( - position: position, - eval: eval, - children: IList(children.map((child) => child.view)), - ); + position: position, + eval: eval, + children: IList(children.map((child) => child.view)), + ); /// Creates a flat game tree from a PGN string. /// /// Assumes that the PGN string is valid and that the moves are legal. factory Root.fromPgnMoves(String pgn) { Position position = Chess.initial; - final root = Root( - position: position, - ); + final root = Root(position: position); Node current = root; final moves = pgn.split(' '); for (final san in moves) { final move = position.parseSan(san); position = position.playUnchecked(move!); - final nextNode = Branch( - sanMove: SanMove(san, move), - position: position, - ); + final nextNode = Branch(sanMove: SanMove(san, move), position: position); current.addChild(nextNode); current = nextNode; } @@ -554,9 +509,7 @@ class Root extends Node { bool hideVariations = false, void Function(Root root, Branch branch, bool isMainline)? onVisitNode, }) { - final root = Root( - position: PgnGame.startingPosition(game.headers), - ); + final root = Root(position: PgnGame.startingPosition(game.headers)); final List<({PgnNode from, Node to, int nesting})> stack = [ (from: game.moves, to: root, nesting: 1), @@ -564,9 +517,7 @@ class Root extends Node { while (stack.isNotEmpty) { final frame = stack.removeLast(); - for (int childIdx = 0; - childIdx < frame.from.children.length; - childIdx++) { + for (int childIdx = 0; childIdx < frame.from.children.length; childIdx++) { final childFrom = frame.from.children[childIdx]; final move = frame.to.position.parseSan(childFrom.data.san); if (move != null) { @@ -579,35 +530,30 @@ class Root extends Node { position: newPos, isCollapsed: frame.nesting > 2 || hideVariations && childIdx > 0, isComputerVariation: isLichessAnalysis && childIdx > 0, - lichessAnalysisComments: - isLichessAnalysis ? comments?.toList() : null, - startingComments: isLichessAnalysis - ? null - : childFrom.data.startingComments - ?.map(PgnComment.fromPgn) - .toList(), + lichessAnalysisComments: isLichessAnalysis ? comments?.toList() : null, + startingComments: + isLichessAnalysis + ? null + : childFrom.data.startingComments?.map(PgnComment.fromPgn).toList(), comments: isLichessAnalysis ? null : comments?.toList(), nags: childFrom.data.nags, ); frame.to.addChild(branch); - stack.add( - ( - from: childFrom, - to: branch, - nesting: frame.from.children.length == 1 || - // mainline continuation - (childIdx == 0 && frame.nesting == 1) - ? frame.nesting - : frame.nesting + 1, - ), - ); + stack.add(( + from: childFrom, + to: branch, + nesting: + frame.from.children.length == 1 || + // mainline continuation + (childIdx == 0 && frame.nesting == 1) + ? frame.nesting + : frame.nesting + 1, + )); onVisitNode?.call(root, branch, isMainline); } else { - _logger.warning( - 'Invalid move: ${childFrom.data.san}, on position: ${frame.to.position}', - ); + _logger.warning('Invalid move: ${childFrom.data.san}, on position: ${frame.to.position}'); } } } @@ -707,14 +653,16 @@ class ViewBranch extends ViewNode with _$ViewBranch { bool get hasTextComment => textComments.isNotEmpty; Duration? get clock { - final clockComment = (lichessAnalysisComments ?? comments) - ?.firstWhereOrNull((c) => c.clock != null); + final clockComment = (lichessAnalysisComments ?? comments)?.firstWhereOrNull( + (c) => c.clock != null, + ); return clockComment?.clock; } Duration? get elapsedMoveTime { - final clockComment = (lichessAnalysisComments ?? comments) - ?.firstWhereOrNull((c) => c.emt != null); + final clockComment = (lichessAnalysisComments ?? comments)?.firstWhereOrNull( + (c) => c.emt != null, + ); return clockComment?.emt; } diff --git a/lib/src/model/common/perf.dart b/lib/src/model/common/perf.dart index 5df2cbe0e5..8b297cad48 100644 --- a/lib/src/model/common/perf.dart +++ b/lib/src/model/common/perf.dart @@ -73,11 +73,11 @@ enum Perf { static final IMap nameMap = IMap(Perf.values.asNameMap()); } -String _titleKey(String title) => - title.toLowerCase().replaceAll(RegExp('[ -_]'), ''); +String _titleKey(String title) => title.toLowerCase().replaceAll(RegExp('[ -_]'), ''); -final IMap _lowerCaseTitleMap = - Perf.nameMap.map((key, value) => MapEntry(_titleKey(value.title), value)); +final IMap _lowerCaseTitleMap = Perf.nameMap.map( + (key, value) => MapEntry(_titleKey(value.title), value), +); extension PerfExtension on Pick { Perf asPerfOrThrow() { @@ -104,9 +104,7 @@ extension PerfExtension on Pick { return perf; } } - throw PickException( - "value $value at $debugParsingExit can't be casted to Perf", - ); + throw PickException("value $value at $debugParsingExit can't be casted to Perf"); } Perf? asPerfOrNull() { diff --git a/lib/src/model/common/preloaded_data.dart b/lib/src/model/common/preloaded_data.dart index 95b3cbfc05..da0531202a 100644 --- a/lib/src/model/common/preloaded_data.dart +++ b/lib/src/model/common/preloaded_data.dart @@ -12,13 +12,14 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'preloaded_data.g.dart'; -typedef PreloadedData = ({ - PackageInfo packageInfo, - BaseDeviceInfo deviceInfo, - AuthSessionState? userSession, - String sri, - int engineMaxMemoryInMb, -}); +typedef PreloadedData = + ({ + PackageInfo packageInfo, + BaseDeviceInfo deviceInfo, + AuthSessionState? userSession, + String sri, + int engineMaxMemoryInMb, + }); @Riverpod(keepAlive: true) Future preloadedData(Ref ref) async { diff --git a/lib/src/model/common/service/sound_service.dart b/lib/src/model/common/service/sound_service.dart index ad398c86ea..c7da648e31 100644 --- a/lib/src/model/common/service/sound_service.dart +++ b/lib/src/model/common/service/sound_service.dart @@ -17,16 +17,7 @@ final _soundEffectPlugin = SoundEffect(); final _logger = Logger('SoundService'); // Must match name of files in assets/sounds/standard -enum Sound { - move, - capture, - lowTime, - dong, - error, - confirmation, - puzzleStormEnd, - clock, -} +enum Sound { move, capture, lowTime, dong, error, confirmation, puzzleStormEnd, clock } @Riverpod(keepAlive: true) SoundService soundService(Ref ref) { @@ -40,14 +31,9 @@ final _extension = defaultTargetPlatform == TargetPlatform.iOS ? 'aifc' : 'mp3'; const Set _emtpySet = {}; /// Loads all sounds of the given [SoundTheme]. -Future _loadAllSounds( - SoundTheme soundTheme, { - Set excluded = _emtpySet, -}) async { +Future _loadAllSounds(SoundTheme soundTheme, {Set excluded = _emtpySet}) async { await Future.wait( - Sound.values - .where((s) => !excluded.contains(s)) - .map((sound) => _loadSound(soundTheme, sound)), + Sound.values.where((s) => !excluded.contains(s)).map((sound) => _loadSound(soundTheme, sound)), ); } @@ -79,14 +65,14 @@ class SoundService { /// This should be called once when the app starts. static Future initialize() async { try { - final stored = LichessBinding.instance.sharedPreferences - .getString(PrefCategory.general.storageKey); - final theme = (stored != null - ? GeneralPrefs.fromJson( - jsonDecode(stored) as Map, - ) - : GeneralPrefs.defaults) - .soundTheme; + final stored = LichessBinding.instance.sharedPreferences.getString( + PrefCategory.general.storageKey, + ); + final theme = + (stored != null + ? GeneralPrefs.fromJson(jsonDecode(stored) as Map) + : GeneralPrefs.defaults) + .soundTheme; await _soundEffectPlugin.initialize(); await _loadAllSounds(theme); } catch (e) { @@ -109,10 +95,7 @@ class SoundService { /// This will release the previous sounds and load the new ones. /// /// If [playSound] is true, a move sound will be played. - Future changeTheme( - SoundTheme theme, { - bool playSound = false, - }) async { + Future changeTheme(SoundTheme theme, {bool playSound = false}) async { await _soundEffectPlugin.release(); await _soundEffectPlugin.initialize(); await _loadSound(theme, Sound.move); diff --git a/lib/src/model/common/socket.dart b/lib/src/model/common/socket.dart index 696d140d46..cb258fee08 100644 --- a/lib/src/model/common/socket.dart +++ b/lib/src/model/common/socket.dart @@ -21,10 +21,7 @@ class SocketEvent with _$SocketEvent { factory SocketEvent.fromJson(Map json) { if (json['t'] == null) { if (json['v'] != null) { - return SocketEvent( - topic: '_version', - version: json['v'] as int, - ); + return SocketEvent(topic: '_version', version: json['v'] as int); } else { assert(false, 'Unsupported socket event json: $json'); return pong; @@ -34,16 +31,9 @@ class SocketEvent with _$SocketEvent { if (topic == 'n') { return SocketEvent( topic: topic, - data: { - 'nbPlayers': json['d'] as int, - 'nbGames': json['r'] as int, - }, + data: {'nbPlayers': json['d'] as int, 'nbGames': json['r'] as int}, ); } - return SocketEvent( - topic: topic, - data: json['d'], - version: json['v'] as int?, - ); + return SocketEvent(topic: topic, data: json['d'], version: json['v'] as int?); } } diff --git a/lib/src/model/common/speed.dart b/lib/src/model/common/speed.dart index b608fdf9aa..b3656c12f2 100644 --- a/lib/src/model/common/speed.dart +++ b/lib/src/model/common/speed.dart @@ -47,9 +47,7 @@ extension SpeedExtension on Pick { final speed = Speed.nameMap[value]; if (speed != null) return speed; } - throw PickException( - "value $value at $debugParsingExit can't be casted to Speed", - ); + throw PickException("value $value at $debugParsingExit can't be casted to Speed"); } Speed? asSpeedOrNull() { diff --git a/lib/src/model/common/time_increment.dart b/lib/src/model/common/time_increment.dart index 8359b98b8c..adc4558ae7 100644 --- a/lib/src/model/common/time_increment.dart +++ b/lib/src/model/common/time_increment.dart @@ -5,13 +5,12 @@ import 'package:lichess_mobile/src/model/common/speed.dart'; /// A pair of time and increment in seconds used as game clock @immutable class TimeIncrement { - const TimeIncrement(this.time, this.increment) - : assert(time >= 0 && increment >= 0); + const TimeIncrement(this.time, this.increment) : assert(time >= 0 && increment >= 0); TimeIncrement.fromDurations(Duration time, Duration increment) - : time = time.inSeconds, - increment = increment.inSeconds, - assert(time >= Duration.zero && increment >= Duration.zero); + : time = time.inSeconds, + increment = increment.inSeconds, + assert(time >= Duration.zero && increment >= Duration.zero); /// Clock initial time in seconds final int time; @@ -20,13 +19,10 @@ class TimeIncrement { final int increment; TimeIncrement.fromJson(Map json) - : time = json['time'] as int, - increment = json['increment'] as int; + : time = json['time'] as int, + increment = json['increment'] as int; - Map toJson() => { - 'time': time, - 'increment': increment, - }; + Map toJson() => {'time': time, 'increment': increment}; /// Returns the estimated duration of the game, with increment * 40 added to /// the initial time. diff --git a/lib/src/model/common/uci.dart b/lib/src/model/common/uci.dart index cc8c8b6716..85185f268c 100644 --- a/lib/src/model/common/uci.dart +++ b/lib/src/model/common/uci.dart @@ -32,43 +32,25 @@ class UciCharPair with _$UciCharPair { } factory UciCharPair.fromMove(Move move) => switch (move) { - NormalMove(from: final f, to: final t, promotion: final p) => - UciCharPair( - String.fromCharCode(35 + f), - String.fromCharCode( - p != null - ? 35 + 64 + 8 * _promotionRoles.indexOf(p) + t.file - : 35 + t, - ), - ), - DropMove(to: final t, role: final r) => UciCharPair( - String.fromCharCode(35 + t), - String.fromCharCode(35 + 64 + 8 * 5 + _dropRoles.indexOf(r)), - ), - }; - - factory UciCharPair.fromJson(Map json) => - _$UciCharPairFromJson(json); + NormalMove(from: final f, to: final t, promotion: final p) => UciCharPair( + String.fromCharCode(35 + f), + String.fromCharCode(p != null ? 35 + 64 + 8 * _promotionRoles.indexOf(p) + t.file : 35 + t), + ), + DropMove(to: final t, role: final r) => UciCharPair( + String.fromCharCode(35 + t), + String.fromCharCode(35 + 64 + 8 * 5 + _dropRoles.indexOf(r)), + ), + }; + + factory UciCharPair.fromJson(Map json) => _$UciCharPairFromJson(json); @override String toString() => '$a$b'; } -const _promotionRoles = [ - Role.queen, - Role.rook, - Role.bishop, - Role.knight, - Role.king, -]; - -const _dropRoles = [ - Role.queen, - Role.rook, - Role.bishop, - Role.knight, - Role.pawn, -]; +const _promotionRoles = [Role.queen, Role.rook, Role.bishop, Role.knight, Role.king]; + +const _dropRoles = [Role.queen, Role.rook, Role.bishop, Role.knight, Role.pawn]; /// Compact representation of a path to a game node made from concatenated /// UciCharPair strings. @@ -111,22 +93,17 @@ class UciPath with _$UciPath { int get size => value.length ~/ 2; - UciCharPair? get head => - value.isEmpty ? null : UciCharPair(value[0], value[1]); + UciCharPair? get head => value.isEmpty ? null : UciCharPair(value[0], value[1]); - UciCharPair? get last => value.isEmpty - ? null - : UciCharPair(value[value.length - 2], value[value.length - 1]); + UciCharPair? get last => + value.isEmpty ? null : UciCharPair(value[value.length - 2], value[value.length - 1]); - UciPath get tail => - value.isEmpty ? UciPath.empty : UciPath(value.substring(2)); + UciPath get tail => value.isEmpty ? UciPath.empty : UciPath(value.substring(2)); - UciPath get penultimate => value.isEmpty - ? UciPath.empty - : UciPath(value.substring(0, value.length - 2)); + UciPath get penultimate => + value.isEmpty ? UciPath.empty : UciPath(value.substring(0, value.length - 2)); bool get isEmpty => value.isEmpty; - factory UciPath.fromJson(Map json) => - _$UciPathFromJson(json); + factory UciPath.fromJson(Map json) => _$UciPathFromJson(json); } diff --git a/lib/src/model/coordinate_training/coordinate_training_controller.dart b/lib/src/model/coordinate_training/coordinate_training_controller.dart index f0e7bf5d93..94dbbbc228 100644 --- a/lib/src/model/coordinate_training/coordinate_training_controller.dart +++ b/lib/src/model/coordinate_training/coordinate_training_controller.dart @@ -28,9 +28,7 @@ class CoordinateTrainingController extends _$CoordinateTrainingController { final sideChoice = ref.watch( coordinateTrainingPreferencesProvider.select((value) => value.sideChoice), ); - return CoordinateTrainingState( - orientation: _getOrientation(sideChoice), - ); + return CoordinateTrainingState(orientation: _getOrientation(sideChoice)); } void startTraining(Duration? timeLimit) { @@ -51,18 +49,14 @@ class CoordinateTrainingController extends _$CoordinateTrainingController { if (state.timeLimit != null && _stopwatch.elapsed > state.timeLimit!) { _finishTraining(); } else { - state = state.copyWith( - elapsed: _stopwatch.elapsed, - ); + state = state.copyWith(elapsed: _stopwatch.elapsed); } }); } void _finishTraining() { // TODO save score in local storage here (and display high score and/or average score in UI) - final orientation = _getOrientation( - ref.read(coordinateTrainingPreferencesProvider).sideChoice, - ); + final orientation = _getOrientation(ref.read(coordinateTrainingPreferencesProvider).sideChoice); _updateTimer?.cancel(); state = CoordinateTrainingState( lastGuess: state.lastGuess, @@ -72,18 +66,16 @@ class CoordinateTrainingController extends _$CoordinateTrainingController { } void abortTraining() { - final orientation = _getOrientation( - ref.read(coordinateTrainingPreferencesProvider).sideChoice, - ); + final orientation = _getOrientation(ref.read(coordinateTrainingPreferencesProvider).sideChoice); _updateTimer?.cancel(); state = CoordinateTrainingState(orientation: orientation); } Side _getOrientation(SideChoice choice) => switch (choice) { - SideChoice.white => Side.white, - SideChoice.black => Side.black, - SideChoice.random => _randomSide(), - }; + SideChoice.white => Side.white, + SideChoice.black => Side.black, + SideChoice.random => _randomSide(), + }; /// Generate a random side Side _randomSide() => Side.values[Random().nextInt(Side.values.length)]; @@ -109,9 +101,7 @@ class CoordinateTrainingController extends _$CoordinateTrainingController { ); } - state = state.copyWith( - lastGuess: correctGuess ? Guess.correct : Guess.incorrect, - ); + state = state.copyWith(lastGuess: correctGuess ? Guess.correct : Guess.incorrect); } } @@ -132,7 +122,8 @@ class CoordinateTrainingState with _$CoordinateTrainingState { bool get trainingActive => elapsed != null; - double? get timeFractionElapsed => (elapsed != null && timeLimit != null) - ? elapsed!.inMilliseconds / timeLimit!.inMilliseconds - : null; + double? get timeFractionElapsed => + (elapsed != null && timeLimit != null) + ? elapsed!.inMilliseconds / timeLimit!.inMilliseconds + : null; } diff --git a/lib/src/model/coordinate_training/coordinate_training_preferences.dart b/lib/src/model/coordinate_training/coordinate_training_preferences.dart index 435d97d672..09273ce082 100644 --- a/lib/src/model/coordinate_training/coordinate_training_preferences.dart +++ b/lib/src/model/coordinate_training/coordinate_training_preferences.dart @@ -85,9 +85,7 @@ enum TrainingMode { } @Freezed(fromJson: true, toJson: true) -class CoordinateTrainingPrefs - with _$CoordinateTrainingPrefs - implements Serializable { +class CoordinateTrainingPrefs with _$CoordinateTrainingPrefs implements Serializable { const CoordinateTrainingPrefs._(); const factory CoordinateTrainingPrefs({ diff --git a/lib/src/model/correspondence/correspondence_game_storage.dart b/lib/src/model/correspondence/correspondence_game_storage.dart index bfa4881f9c..fe1a259177 100644 --- a/lib/src/model/correspondence/correspondence_game_storage.dart +++ b/lib/src/model/correspondence/correspondence_game_storage.dart @@ -14,30 +14,27 @@ import 'offline_correspondence_game.dart'; part 'correspondence_game_storage.g.dart'; @Riverpod(keepAlive: true) -Future correspondenceGameStorage( - Ref ref, -) async { +Future correspondenceGameStorage(Ref ref) async { final db = await ref.watch(databaseProvider.future); return CorrespondenceGameStorage(db, ref); } @riverpod -Future> - offlineOngoingCorrespondenceGames(Ref ref) async { +Future> offlineOngoingCorrespondenceGames( + 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 final storage = await ref.read(correspondenceGameStorageProvider.future); final data = await storage.fetchOngoingGames(session?.user.id); - return data.sort( - (a, b) { - final aIsMyTurn = a.$2.isMyTurn; - final bIsMyTurn = b.$2.isMyTurn; - if (aIsMyTurn && !bIsMyTurn) return -1; - if (!aIsMyTurn && bIsMyTurn) return 1; - return b.$1.compareTo(a.$1); - }, - ); + return data.sort((a, b) { + final aIsMyTurn = a.$2.isMyTurn; + final bIsMyTurn = b.$2.isMyTurn; + if (aIsMyTurn && !bIsMyTurn) return -1; + if (!aIsMyTurn && bIsMyTurn) return 1; + return b.$1.compareTo(a.$1); + }); } const kCorrespondenceStorageTable = 'correspondence_game'; @@ -50,16 +47,11 @@ class CorrespondenceGameStorage { final Ref ref; /// Fetches all ongoing correspondence games, sorted by time left. - Future> fetchOngoingGames( - UserId? userId, - ) async { + Future> fetchOngoingGames(UserId? userId) async { final list = await _db.query( kCorrespondenceStorageTable, where: 'userId = ? AND data LIKE ?', - whereArgs: [ - '${userId ?? kCorrespondenceStorageAnonId}', - '%"status":"started"%', - ], + whereArgs: ['${userId ?? kCorrespondenceStorageAnonId}', '%"status":"started"%'], ); return _decodeGames(list).sort((a, b) { @@ -76,8 +68,9 @@ class CorrespondenceGameStorage { } /// Fetches all correspondence games with a registered move. - Future> - fetchGamesWithRegisteredMove(UserId? userId) async { + Future> fetchGamesWithRegisteredMove( + UserId? userId, + ) async { try { final list = await _db.query( kCorrespondenceStorageTable, @@ -88,10 +81,7 @@ class CorrespondenceGameStorage { final list = await _db.query( kCorrespondenceStorageTable, where: 'userId = ? AND data LIKE ?', - whereArgs: [ - '${userId ?? kCorrespondenceStorageAnonId}', - '%status":"started"%', - ], + whereArgs: ['${userId ?? kCorrespondenceStorageAnonId}', '%status":"started"%'], ); return _decodeGames(list).where((e) { @@ -101,9 +91,7 @@ class CorrespondenceGameStorage { } } - Future fetch({ - required GameId gameId, - }) async { + Future fetch({required GameId gameId}) async { final list = await _db.query( kCorrespondenceStorageTable, where: 'gameId = ?', @@ -126,16 +114,12 @@ class CorrespondenceGameStorage { Future save(OfflineCorrespondenceGame game) async { try { - await _db.insert( - kCorrespondenceStorageTable, - { - 'userId': game.me.user?.id.toString() ?? kCorrespondenceStorageAnonId, - 'gameId': game.id.toString(), - 'lastModified': DateTime.now().toIso8601String(), - 'data': jsonEncode(game.toJson()), - }, - conflictAlgorithm: ConflictAlgorithm.replace, - ); + await _db.insert(kCorrespondenceStorageTable, { + 'userId': game.me.user?.id.toString() ?? kCorrespondenceStorageAnonId, + 'gameId': game.id.toString(), + 'lastModified': DateTime.now().toIso8601String(), + 'data': jsonEncode(game.toJson()), + }, conflictAlgorithm: ConflictAlgorithm.replace); ref.invalidate(offlineOngoingCorrespondenceGamesProvider); } catch (e) { debugPrint('[CorrespondenceGameStorage] failed to save game: $e'); @@ -151,9 +135,7 @@ class CorrespondenceGameStorage { ref.invalidate(offlineOngoingCorrespondenceGamesProvider); } - IList<(DateTime, OfflineCorrespondenceGame)> _decodeGames( - List> list, - ) { + IList<(DateTime, OfflineCorrespondenceGame)> _decodeGames(List> list) { return list.map((e) { final lmString = e['lastModified'] as String?; final raw = e['data'] as String?; diff --git a/lib/src/model/correspondence/correspondence_service.dart b/lib/src/model/correspondence/correspondence_service.dart index 05ad5f800a..f8fa995758 100644 --- a/lib/src/model/correspondence/correspondence_service.dart +++ b/lib/src/model/correspondence/correspondence_service.dart @@ -28,10 +28,7 @@ part 'correspondence_service.g.dart'; @Riverpod(keepAlive: true) CorrespondenceService correspondenceService(Ref ref) { - return CorrespondenceService( - Logger('CorrespondenceService'), - ref: ref, - ); + return CorrespondenceService(Logger('CorrespondenceService'), ref: ref); } /// Services that manages correspondence games. @@ -68,8 +65,7 @@ class CorrespondenceService { await playRegisteredMoves(); - final storedOngoingGames = - await (await _storage).fetchOngoingGames(_session?.user.id); + final storedOngoingGames = await (await _storage).fetchOngoingGames(_session?.user.id); ref.withClient((client) async { try { @@ -113,16 +109,11 @@ class CorrespondenceService { final games = await (await _storage) .fetchGamesWithRegisteredMove(_session?.user.id) - .then( - (games) => games.map((e) => e.$2).toList(), - ); + .then((games) => games.map((e) => e.$2).toList()); WebSocket.userAgent = ref.read(userAgentProvider); - final Map wsHeaders = _session != null - ? { - 'Authorization': 'Bearer ${signBearerToken(_session!.token)}', - } - : {}; + final Map wsHeaders = + _session != null ? {'Authorization': 'Bearer ${signBearerToken(_session!.token)}'} : {}; int movesPlayed = 0; @@ -134,14 +125,14 @@ class CorrespondenceService { WebSocket? socket; StreamSubscription? streamSubscription; try { - socket = await WebSocket.connect(uri.toString(), headers: wsHeaders) - .timeout(const Duration(seconds: 5)); + socket = await WebSocket.connect( + uri.toString(), + headers: wsHeaders, + ).timeout(const Duration(seconds: 5)); - final eventStream = socket.where((e) => e != '0').map( - (e) => SocketEvent.fromJson( - jsonDecode(e as String) as Map, - ), - ); + final eventStream = socket + .where((e) => e != '0') + .map((e) => SocketEvent.fromJson(jsonDecode(e as String) as Map)); final Completer gameCompleter = Completer(); final Completer movePlayedCompleter = Completer(); @@ -149,14 +140,11 @@ class CorrespondenceService { streamSubscription = eventStream.listen((event) { switch (event.topic) { case 'full': - final playableGame = GameFullEvent.fromJson( - event.data as Map, - ).game; + final playableGame = GameFullEvent.fromJson(event.data as Map).game; gameCompleter.complete(playableGame); case 'move': - final moveEvent = - MoveEvent.fromJson(event.data as Map); + final moveEvent = MoveEvent.fromJson(event.data as Map); // move acknowledged if (moveEvent.uci == gameToSync.registeredMoveAtPgn!.$2.uci) { movesPlayed++; @@ -174,31 +162,21 @@ class CorrespondenceService { socket.add( jsonEncode({ 't': 'move', - 'd': { - 'u': gameToSync.registeredMoveAtPgn!.$2.uci, - }, + 'd': {'u': gameToSync.registeredMoveAtPgn!.$2.uci}, }), ); await movePlayedCompleter.future.timeout(const Duration(seconds: 3)); - (await ref.read(correspondenceGameStorageProvider.future)).save( - gameToSync.copyWith( - registeredMoveAtPgn: null, - ), - ); + (await ref.read( + correspondenceGameStorageProvider.future, + )).save(gameToSync.copyWith(registeredMoveAtPgn: null)); } else { - _log.info( - 'Cannot play game ${gameToSync.id} move because its state has changed', - ); + _log.info('Cannot play game ${gameToSync.id} move because its state has changed'); updateGame(gameToSync.fullId, playableGame); } } catch (e, s) { - _log.severe( - 'Failed to sync correspondence game ${gameToSync.id}', - e, - s, - ); + _log.severe('Failed to sync correspondence game ${gameToSync.id}', e, s); } finally { streamSubscription?.cancel(); socket?.close(); diff --git a/lib/src/model/correspondence/offline_correspondence_game.dart b/lib/src/model/correspondence/offline_correspondence_game.dart index d7bcb1599f..83a1d6a354 100644 --- a/lib/src/model/correspondence/offline_correspondence_game.dart +++ b/lib/src/model/correspondence/offline_correspondence_game.dart @@ -25,8 +25,7 @@ class OfflineCorrespondenceGame required GameId id, required GameFullId fullId, required GameMeta meta, - @JsonKey(fromJson: stepsFromJson, toJson: stepsToJson) - required IList steps, + @JsonKey(fromJson: stepsFromJson, toJson: stepsToJson) required IList steps, CorrespondenceClockData? clock, String? initialFen, required bool rated, @@ -61,8 +60,7 @@ class OfflineCorrespondenceGame bool get isMyTurn => sideToMove == youAre; - Duration? myTimeLeft(DateTime lastModifiedTime) => - estimatedTimeLeft(youAre, lastModifiedTime); + Duration? myTimeLeft(DateTime lastModifiedTime) => estimatedTimeLeft(youAre, lastModifiedTime); Duration? estimatedTimeLeft(Side side, DateTime lastModifiedTime) { final timeLeft = side == Side.white ? clock?.white : clock?.black; diff --git a/lib/src/model/engine/engine.dart b/lib/src/model/engine/engine.dart index ef8163ba28..9833f06658 100644 --- a/lib/src/model/engine/engine.dart +++ b/lib/src/model/engine/engine.dart @@ -7,14 +7,7 @@ import 'package:stockfish/stockfish.dart'; import 'uci_protocol.dart'; import 'work.dart'; -enum EngineState { - initial, - loading, - idle, - computing, - error, - disposed, -} +enum EngineState { initial, loading, idle, computing, error, disposed } abstract class Engine { ValueListenable get state; @@ -44,36 +37,36 @@ class StockfishEngine implements Engine { @override Stream start(Work work) { - _log.info( - 'engine start at ply ${work.ply} and path ${work.path}', - ); + _log.info('engine start at ply ${work.ply} and path ${work.path}'); _protocol.compute(work); if (_stockfish == null) { - stockfishAsync().then((stockfish) { - _state.value = EngineState.loading; - _stockfish = stockfish; - _stdoutSubscription = stockfish.stdout.listen((line) { - _protocol.received(line); - }); - stockfish.state.addListener(_stockfishStateListener); - _protocol.isComputing.addListener(() { - if (_protocol.isComputing.value) { - _state.value = EngineState.computing; - } else { - _state.value = EngineState.idle; - } - }); - _protocol.connected((String cmd) { - stockfish.stdin = cmd; - }); - _protocol.engineName.then((name) { - _name = name; - }); - }).catchError((Object e, StackTrace s) { - _log.severe('error loading stockfish', e, s); - _state.value = EngineState.error; - }); + stockfishAsync() + .then((stockfish) { + _state.value = EngineState.loading; + _stockfish = stockfish; + _stdoutSubscription = stockfish.stdout.listen((line) { + _protocol.received(line); + }); + stockfish.state.addListener(_stockfishStateListener); + _protocol.isComputing.addListener(() { + if (_protocol.isComputing.value) { + _state.value = EngineState.computing; + } else { + _state.value = EngineState.idle; + } + }); + _protocol.connected((String cmd) { + stockfish.stdin = cmd; + }); + _protocol.engineName.then((name) { + _name = name; + }); + }) + .catchError((Object e, StackTrace s) { + _log.severe('error loading stockfish', e, s); + _state.value = EngineState.error; + }); } return _protocol.evalStream.where((e) => e.$1 == work); @@ -100,8 +93,7 @@ class StockfishEngine implements Engine { @override Future dispose() { _log.fine('disposing engine'); - if (_stockfish == null || - _stockfish?.state.value == StockfishState.disposed) { + if (_stockfish == null || _stockfish?.state.value == StockfishState.disposed) { return Future.value(); } _stdoutSubscription?.cancel(); diff --git a/lib/src/model/engine/evaluation_service.dart b/lib/src/model/engine/evaluation_service.dart index 0fc2cf0680..e2006aac28 100644 --- a/lib/src/model/engine/evaluation_service.dart +++ b/lib/src/model/engine/evaluation_service.dart @@ -21,14 +21,9 @@ part 'evaluation_service.g.dart'; part 'evaluation_service.freezed.dart'; final maxEngineCores = max(Platform.numberOfProcessors - 1, 1); -final defaultEngineCores = - min((Platform.numberOfProcessors / 2).ceil(), maxEngineCores); +final defaultEngineCores = min((Platform.numberOfProcessors / 2).ceil(), maxEngineCores); -const engineSupportedVariants = { - Variant.standard, - Variant.chess960, - Variant.fromPosition, -}; +const engineSupportedVariants = {Variant.standard, Variant.chess960, Variant.fromPosition}; /// A service to evaluate chess positions using an engine. class EvaluationService { @@ -47,11 +42,9 @@ class EvaluationService { searchTime: const Duration(seconds: 10), ); - static const _defaultState = - (engineName: 'Stockfish', state: EngineState.initial, eval: null); + static const _defaultState = (engineName: 'Stockfish', state: EngineState.initial, eval: null); - final ValueNotifier _state = - ValueNotifier(_defaultState); + final ValueNotifier _state = ValueNotifier(_defaultState); ValueListenable get state => _state; /// Initialize the engine with the given context and options. @@ -82,7 +75,7 @@ class EvaluationService { _state.value = ( engineName: _engine!.name, state: _engine!.state.value, - eval: _state.value.eval + eval: _state.value.eval, ); } }); @@ -141,8 +134,7 @@ class EvaluationService { ); // cancel evaluation if we already have a cached eval at max search time - final cachedEval = - work.steps.isEmpty ? initialPositionEval : work.evalCache; + final cachedEval = work.steps.isEmpty ? initialPositionEval : work.evalCache; if (cachedEval != null && cachedEval.searchTime >= _options.searchTime) { _state.value = ( engineName: _state.value.engineName, @@ -152,19 +144,14 @@ class EvaluationService { return null; } - final evalStream = engine.start(work).throttle( - const Duration(milliseconds: 200), - trailing: true, - ); + final evalStream = engine + .start(work) + .throttle(const Duration(milliseconds: 200), trailing: true); evalStream.forEach((t) { final (work, eval) = t; if (shouldEmit(work)) { - _state.value = ( - engineName: _state.value.engineName, - state: _state.value.state, - eval: eval, - ); + _state.value = (engineName: _state.value.engineName, state: _state.value.state, eval: eval); } }); @@ -178,8 +165,7 @@ class EvaluationService { @Riverpod(keepAlive: true) EvaluationService evaluationService(Ref ref) { - final maxMemory = - ref.read(preloadedDataProvider).requireValue.engineMaxMemoryInMb; + final maxMemory = ref.read(preloadedDataProvider).requireValue.engineMaxMemoryInMb; final service = EvaluationService(ref, maxMemory: maxMemory); ref.onDispose(() { @@ -188,11 +174,7 @@ EvaluationService evaluationService(Ref ref) { return service; } -typedef EngineEvaluationState = ({ - String engineName, - EngineState state, - ClientEval? eval -}); +typedef EngineEvaluationState = ({String engineName, EngineState state, ClientEval? eval}); /// A provider that holds the state of the engine and the current evaluation. @riverpod @@ -220,10 +202,8 @@ class EngineEvaluation extends _$EngineEvaluation { @freezed class EvaluationContext with _$EvaluationContext { - const factory EvaluationContext({ - required Variant variant, - required Position initialPosition, - }) = _EvaluationContext; + const factory EvaluationContext({required Variant variant, required Position initialPosition}) = + _EvaluationContext; } @freezed diff --git a/lib/src/model/engine/uci_protocol.dart b/lib/src/model/engine/uci_protocol.dart index f34a0f270e..5692f8f40f 100644 --- a/lib/src/model/engine/uci_protocol.dart +++ b/lib/src/model/engine/uci_protocol.dart @@ -13,12 +13,7 @@ const minDepth = 6; const maxPlies = 245; class UCIProtocol { - UCIProtocol() - : _options = { - 'Threads': '1', - 'Hash': '16', - 'MultiPV': '1', - }; + UCIProtocol() : _options = {'Threads': '1', 'Hash': '16', 'MultiPV': '1'}; final _log = Logger('UCIProtocol'); final Map _options; @@ -95,9 +90,7 @@ class UCIProtocol { _work = null; _swapWork(); return; - } else if (_work != null && - _stopRequested != true && - parts.first == 'info') { + } else if (_work != null && _stopRequested != true && parts.first == 'info') { int depth = 0; int nodes = 0; int multiPv = 1; @@ -120,8 +113,7 @@ class UCIProtocol { isMate = parts[++i] == 'mate'; povEv = int.parse(parts[++i]); if (i + 1 < parts.length && - (parts[i + 1] == 'lowerbound' || - parts[i + 1] == 'upperbound')) { + (parts[i + 1] == 'lowerbound' || parts[i + 1] == 'upperbound')) { evalType = parts[++i]; } case 'pv': @@ -142,11 +134,7 @@ class UCIProtocol { // However non-primary pvs may only have an upperbound. if (evalType != null && multiPv == 1) return; - final pvData = PvData( - moves: IList(moves), - cp: isMate ? null : ev, - mate: isMate ? ev : null, - ); + final pvData = PvData(moves: IList(moves), cp: isMate ? null : ev, mate: isMate ? ev : null); if (multiPv == 1) { _currentEval = ClientEval( @@ -206,9 +194,7 @@ class UCIProtocol { _work!.initialPosition.fen, 'moves', ..._work!.steps.map( - (s) => _work!.variant == Variant.chess960 - ? s.sanMove.move.uci - : s.castleSafeUCI, + (s) => _work!.variant == Variant.chess960 ? s.sanMove.move.uci : s.castleSafeUCI, ), ].join(' '), ); diff --git a/lib/src/model/engine/work.dart b/lib/src/model/engine/work.dart index 7bb491ab09..8cab1baa06 100644 --- a/lib/src/model/engine/work.dart +++ b/lib/src/model/engine/work.dart @@ -40,18 +40,11 @@ class Work with _$Work { class Step with _$Step { const Step._(); - const factory Step({ - required Position position, - required SanMove sanMove, - ClientEval? eval, - }) = _Step; + const factory Step({required Position position, required SanMove sanMove, ClientEval? eval}) = + _Step; factory Step.fromNode(Branch node) { - return Step( - position: node.position, - sanMove: node.sanMove, - eval: node.eval, - ); + return Step(position: node.position, sanMove: node.sanMove, eval: node.eval); } /// Stockfish in chess960 mode always needs a "king takes rook" UCI notation. @@ -69,9 +62,4 @@ class Step with _$Step { } } -const _castleMoves = { - 'e1c1': 'e1a1', - 'e1g1': 'e1h1', - 'e8c8': 'e8a8', - 'e8g8': 'e8h8', -}; +const _castleMoves = {'e1c1': 'e1a1', 'e1g1': 'e1h1', 'e8c8': 'e8a8', 'e8g8': 'e8h8'}; diff --git a/lib/src/model/game/archived_game.dart b/lib/src/model/game/archived_game.dart index 2be53c5065..d62aca0bae 100644 --- a/lib/src/model/game/archived_game.dart +++ b/lib/src/model/game/archived_game.dart @@ -19,10 +19,7 @@ import 'player.dart'; part 'archived_game.freezed.dart'; part 'archived_game.g.dart'; -typedef ClockData = ({ - Duration initial, - Duration increment, -}); +typedef ClockData = ({Duration initial, Duration increment}); /// A lichess game exported from the API. /// @@ -32,9 +29,7 @@ typedef ClockData = ({ /// See also [PlayableGame] for a game owned by the current user and that can be /// played unless finished. @Freezed(fromJson: true, toJson: true) -class ArchivedGame - with _$ArchivedGame, BaseGame, IndexableSteps - implements BaseGame { +class ArchivedGame with _$ArchivedGame, BaseGame, IndexableSteps implements BaseGame { const ArchivedGame._(); @Assert('steps.isNotEmpty') @@ -43,14 +38,10 @@ class ArchivedGame required GameMeta meta, // TODO refactor to not include this field required LightArchivedGame data, - @JsonKey(fromJson: stepsFromJson, toJson: stepsToJson) - required IList steps, + @JsonKey(fromJson: stepsFromJson, toJson: stepsToJson) required IList steps, String? initialFen, required GameStatus status, - @JsonKey( - defaultValue: GameSource.unknown, - unknownEnumValue: GameSource.unknown, - ) + @JsonKey(defaultValue: GameSource.unknown, unknownEnumValue: GameSource.unknown) required GameSource source, Side? winner, @@ -72,20 +63,21 @@ class ArchivedGame } /// Create an archived game from a local storage JSON. - factory ArchivedGame.fromJson(Map json) => - _$ArchivedGameFromJson(json); + factory ArchivedGame.fromJson(Map json) => _$ArchivedGameFromJson(json); /// Player point of view. Null if spectating. - Player? get me => youAre == null - ? null - : youAre == Side.white + Player? get me => + youAre == null + ? null + : youAre == Side.white ? white : black; /// Opponent point of view. Null if spectating. - Player? get opponent => youAre == null - ? null - : youAre == Side.white + Player? get opponent => + youAre == null + ? null + : youAre == Side.white ? black : white; } @@ -134,10 +126,7 @@ class LightArchivedGame with _$LightArchivedGame { _$LightArchivedGameFromJson(json); String get clockDisplay { - return TimeIncrement( - clock?.initial.inSeconds ?? 0, - clock?.increment.inSeconds ?? 0, - ).display; + return TimeIncrement(clock?.initial.inSeconds ?? 0, clock?.increment.inSeconds ?? 0).display; } } @@ -150,10 +139,7 @@ IList? gameEvalsFromPick(RequiredPick pick) { bestMove: p0('best').asStringOrNull(), variation: p0('variation').asStringOrNull(), judgment: p0('judgment').letOrNull( - (j) => ( - name: j('name').asStringOrThrow(), - comment: j('comment').asStringOrThrow(), - ), + (j) => (name: j('name').asStringOrThrow(), comment: j('comment').asStringOrThrow()), ), ), ) @@ -162,9 +148,9 @@ IList? gameEvalsFromPick(RequiredPick pick) { ArchivedGame _archivedGameFromPick(RequiredPick pick) { final data = _lightArchivedGameFromPick(pick); - final clocks = pick('clocks').asListOrNull( - (p0) => Duration(milliseconds: p0.asIntOrThrow() * 10), - ); + final clocks = pick( + 'clocks', + ).asListOrNull((p0) => Duration(milliseconds: p0.asIntOrThrow() * 10)); final division = pick('division').letOrNull(_divisionFromPick); final initialFen = pick('initialFen').asStringOrNull(); @@ -177,21 +163,21 @@ ArchivedGame _archivedGameFromPick(RequiredPick pick) { speed: data.speed, perf: data.perf, rated: data.rated, - clock: data.clock != null - ? ( - initial: data.clock!.initial, - increment: data.clock!.increment, - emergency: null, - moreTime: null - ) - : null, + clock: + data.clock != null + ? ( + initial: data.clock!.initial, + increment: data.clock!.increment, + emergency: null, + moreTime: null, + ) + : null, opening: data.opening, division: division, ), - source: pick('source').letOrThrow( - (pick) => - GameSource.nameMap[pick.asStringOrThrow()] ?? GameSource.unknown, - ), + source: pick( + 'source', + ).letOrThrow((pick) => GameSource.nameMap[pick.asStringOrThrow()] ?? GameSource.unknown), data: data, status: data.status, winner: data.winner, @@ -202,10 +188,10 @@ ArchivedGame _archivedGameFromPick(RequiredPick pick) { steps: pick('moves').letOrThrow((it) { final moves = it.asStringOrThrow().split(' '); // assume lichess always send initialFen with fromPosition and chess960 - Position position = (data.variant == Variant.fromPosition || - data.variant == Variant.chess960) - ? Chess.fromSetup(Setup.parseFen(initialFen!)) - : data.variant.initialPosition; + Position position = + (data.variant == Variant.fromPosition || data.variant == Variant.chess960) + ? Chess.fromSetup(Setup.parseFen(initialFen!)) + : data.variant.initialPosition; int index = 0; final List steps = [GameStep(position: position)]; Duration? clock = data.clock?.initial; @@ -237,10 +223,9 @@ LightArchivedGame _lightArchivedGameFromPick(RequiredPick pick) { return LightArchivedGame( id: pick('id').asGameIdOrThrow(), fullId: pick('fullId').asGameFullIdOrNull(), - source: pick('source').letOrNull( - (pick) => - GameSource.nameMap[pick.asStringOrThrow()] ?? GameSource.unknown, - ), + source: pick( + 'source', + ).letOrNull((pick) => GameSource.nameMap[pick.asStringOrThrow()] ?? GameSource.unknown), rated: pick('rated').asBoolOrThrow(), speed: pick('speed').asSpeedOrThrow(), perf: pick('perf').asPerfOrThrow(), @@ -259,10 +244,7 @@ LightArchivedGame _lightArchivedGameFromPick(RequiredPick pick) { } LightOpening _openingFromPick(RequiredPick pick) { - return LightOpening( - eco: pick('eco').asStringOrThrow(), - name: pick('name').asStringOrThrow(), - ); + return LightOpening(eco: pick('eco').asStringOrThrow(), name: pick('name').asStringOrThrow()); } ClockData _clockDataFromPick(RequiredPick pick) { @@ -277,8 +259,7 @@ Player _playerFromUserGamePick(RequiredPick pick) { return Player( user: pick('user').asLightUserOrNull(), name: _removeRatingFromName(originalName), - rating: - pick('rating').asIntOrNull() ?? _extractRatingFromName(originalName), + rating: pick('rating').asIntOrNull() ?? _extractRatingFromName(originalName), ratingDiff: pick('ratingDiff').asIntOrNull(), aiLevel: pick('aiLevel').asIntOrNull(), analysis: pick('analysis').letOrNull(_playerAnalysisFromPick), diff --git a/lib/src/model/game/chat_controller.dart b/lib/src/model/game/chat_controller.dart index 8c0f294945..108a73fd36 100644 --- a/lib/src/model/game/chat_controller.dart +++ b/lib/src/model/game/chat_controller.dart @@ -25,8 +25,7 @@ class ChatController extends _$ChatController { @override Future build(GameFullId id) async { - _socketClient = - ref.read(socketPoolProvider).open(GameController.gameSocketUri(id)); + _socketClient = ref.read(socketPoolProvider).open(GameController.gameSocketUri(id)); _subscription?.cancel(); _subscription = _socketClient.stream.listen(_handleSocketEvent); @@ -38,9 +37,7 @@ class ChatController extends _$ChatController { final messages = await _socketClient.stream .firstWhere((event) => event.topic == 'full') .then( - (event) => pick(event.data, 'chat', 'lines') - .asListOrNull(_messageFromPick) - ?.toIList(), + (event) => pick(event.data, 'chat', 'lines').asListOrNull(_messageFromPick)?.toIList(), ); final readMessagesCount = await _getReadMessagesCount(); @@ -53,10 +50,7 @@ class ChatController extends _$ChatController { /// Sends a message to the chat. void sendMessage(String message) { - _socketClient.send( - 'talk', - message, - ); + _socketClient.send('talk', message); } /// Resets the unread messages count to 0 and saves the number of read messages. @@ -64,9 +58,7 @@ class ChatController extends _$ChatController { if (state.hasValue) { await _setReadMessagesCount(state.requireValue.messages.length); } - state = state.whenData( - (s) => s.copyWith(unreadMessages: 0), - ); + state = state.whenData((s) => s.copyWith(unreadMessages: 0)); } Future _getReadMessagesCount() async { @@ -82,34 +74,24 @@ class ChatController extends _$ChatController { Future _setReadMessagesCount(int count) async { final db = await ref.read(databaseProvider.future); - await db.insert( - _tableName, - { - 'id': _storeKey(id), - 'lastModified': DateTime.now().toIso8601String(), - 'nbRead': count, - }, - conflictAlgorithm: ConflictAlgorithm.replace, - ); + await db.insert(_tableName, { + 'id': _storeKey(id), + 'lastModified': DateTime.now().toIso8601String(), + 'nbRead': count, + }, conflictAlgorithm: ConflictAlgorithm.replace); } Future _setMessages(IList messages) async { final readMessagesCount = await _getReadMessagesCount(); state = state.whenData( - (s) => s.copyWith( - messages: messages, - unreadMessages: messages.length - readMessagesCount, - ), + (s) => s.copyWith(messages: messages, unreadMessages: messages.length - readMessagesCount), ); } void _addMessage(Message message) { state = state.whenData( - (s) => s.copyWith( - messages: s.messages.add(message), - unreadMessages: s.unreadMessages + 1, - ), + (s) => s.copyWith(messages: s.messages.add(message), unreadMessages: s.unreadMessages + 1), ); } @@ -117,9 +99,7 @@ class ChatController extends _$ChatController { if (!state.hasValue) return; if (event.topic == 'full') { - final messages = pick(event.data, 'chat', 'lines') - .asListOrNull(_messageFromPick) - ?.toIList(); + final messages = pick(event.data, 'chat', 'lines').asListOrNull(_messageFromPick)?.toIList(); if (messages != null) { _setMessages(messages); } @@ -135,18 +115,11 @@ class ChatController extends _$ChatController { class ChatState with _$ChatState { const ChatState._(); - const factory ChatState({ - required IList messages, - required int unreadMessages, - }) = _ChatState; + const factory ChatState({required IList messages, required int unreadMessages}) = + _ChatState; } -typedef Message = ({ - String? username, - String message, - bool troll, - bool deleted, -}); +typedef Message = ({String? username, String message, bool troll, bool deleted}); Message _messageFromPick(RequiredPick pick) { return ( @@ -158,8 +131,7 @@ Message _messageFromPick(RequiredPick pick) { } bool isSpam(Message message) { - return spamRegex.hasMatch(message.message) || - followMeRegex.hasMatch(message.message); + return spamRegex.hasMatch(message.message) || followMeRegex.hasMatch(message.message); } final RegExp spamRegex = RegExp( diff --git a/lib/src/model/game/game.dart b/lib/src/model/game/game.dart index 5e0a3750fe..08866bf79d 100644 --- a/lib/src/model/game/game.dart +++ b/lib/src/model/game/game.dart @@ -78,16 +78,11 @@ abstract mixin class BaseGame { for (var i = 1; i < steps.length; i++) { final step = steps[i]; final eval = evals?.elementAtOrNull(i - 1); - final pgnEval = eval?.cp != null - ? PgnEvaluation.pawns( - pawns: cpToPawns(eval!.cp!), - depth: eval.depth, - ) - : eval?.mate != null - ? PgnEvaluation.mate( - mate: eval!.mate, - depth: eval.depth, - ) + final pgnEval = + eval?.cp != null + ? PgnEvaluation.pawns(pawns: cpToPawns(eval!.cp!), depth: eval.depth) + : eval?.mate != null + ? PgnEvaluation.mate(mate: eval!.mate, depth: eval.depth) : null; final clock = clocks?.elementAtOrNull(i - 1); Duration? emt; @@ -106,17 +101,11 @@ abstract mixin class BaseGame { } } - final comment = eval != null || clock != null - ? PgnComment( - text: eval?.judgment?.comment, - clock: clock, - emt: emt, - eval: pgnEval, - ) - : null; - final nag = eval?.judgment != null - ? _judgmentNameToNag(eval!.judgment!.name) - : null; + final comment = + eval != null || clock != null + ? PgnComment(text: eval?.judgment?.comment, clock: clock, emt: emt, eval: pgnEval) + : null; + final nag = eval?.judgment != null ? _judgmentNameToNag(eval!.judgment!.name) : null; final nextNode = Branch( sanMove: step.sanMove!, position: step.position, @@ -134,10 +123,7 @@ abstract mixin class BaseGame { for (final san in moves) { final move = position.parseSan(san); position = position.playUnchecked(move!); - final child = Branch( - sanMove: SanMove(san, move), - position: position, - ); + final child = Branch(sanMove: SanMove(san, move), position: position); variationNode.addChild(child); variationNode = child; } @@ -149,11 +135,11 @@ abstract mixin class BaseGame { } int? _judgmentNameToNag(String name) => switch (name) { - 'Inaccuracy' => 6, - 'Mistake' => 2, - 'Blunder' => 4, - String() => null, - }; + 'Inaccuracy' => 6, + 'Mistake' => 2, + 'Blunder' => 4, + String() => null, + }; String makePgn() { final node = makeTree(); @@ -162,35 +148,31 @@ abstract mixin class BaseGame { 'Event': '${meta.rated ? 'Rated' : ''} ${meta.perf.title} game', 'Site': lichessUri('/$id').toString(), 'Date': _dateFormat.format(meta.createdAt), - 'White': white.user?.name ?? + 'White': + white.user?.name ?? white.name ?? - (white.aiLevel != null - ? 'Stockfish level ${white.aiLevel}' - : 'Anonymous'), - 'Black': black.user?.name ?? + (white.aiLevel != null ? 'Stockfish level ${white.aiLevel}' : 'Anonymous'), + 'Black': + black.user?.name ?? black.name ?? - (black.aiLevel != null - ? 'Stockfish level ${black.aiLevel}' - : 'Anonymous'), - 'Result': status.value >= GameStatus.mate.value - ? winner == null - ? '½-½' - : winner == Side.white + (black.aiLevel != null ? 'Stockfish level ${black.aiLevel}' : 'Anonymous'), + 'Result': + status.value >= GameStatus.mate.value + ? winner == null + ? '½-½' + : winner == Side.white ? '1-0' : '0-1' - : '*', + : '*', if (white.rating != null) 'WhiteElo': white.rating!.toString(), if (black.rating != null) 'BlackElo': black.rating!.toString(), if (white.ratingDiff != null) - 'WhiteRatingDiff': - '${white.ratingDiff! > 0 ? '+' : ''}${white.ratingDiff!}', + 'WhiteRatingDiff': '${white.ratingDiff! > 0 ? '+' : ''}${white.ratingDiff!}', if (black.ratingDiff != null) - 'BlackRatingDiff': - '${black.ratingDiff! > 0 ? '+' : ''}${black.ratingDiff!}', + 'BlackRatingDiff': '${black.ratingDiff! > 0 ? '+' : ''}${black.ratingDiff!}', 'Variant': meta.variant.label, if (meta.clock != null) - 'TimeControl': - '${meta.clock!.initial.inSeconds}+${meta.clock!.increment.inSeconds}', + 'TimeControl': '${meta.clock!.initial.inSeconds}+${meta.clock!.increment.inSeconds}', if (initialFen != null) 'FEN': initialFen!, if (meta.opening != null) 'ECO': meta.opening!.eco, if (meta.opening != null) 'Opening': meta.opening!.name, @@ -202,13 +184,9 @@ abstract mixin class BaseGame { /// A mixin that provides methods to access game data at a specific step. mixin IndexableSteps on BaseGame { - String get sanMoves => steps - .where((e) => e.sanMove != null) - .map((e) => e.sanMove!.san) - .join(' '); + String get sanMoves => steps.where((e) => e.sanMove != null).map((e) => e.sanMove!.san).join(' '); - MaterialDiffSide? materialDiffAt(int cursor, Side side) => - steps[cursor].diff?.bySide(side); + MaterialDiffSide? materialDiffAt(int cursor, Side side) => steps[cursor].diff?.bySide(side); GameStep stepAt(int cursor) => steps[cursor]; @@ -220,11 +198,9 @@ mixin IndexableSteps on BaseGame { Position positionAt(int cursor) => steps[cursor].position; - Duration? archivedWhiteClockAt(int cursor) => - steps[cursor].archivedWhiteClock; + Duration? archivedWhiteClockAt(int cursor) => steps[cursor].archivedWhiteClock; - Duration? archivedBlackClockAt(int cursor) => - steps[cursor].archivedBlackClock; + Duration? archivedBlackClockAt(int cursor) => steps[cursor].archivedBlackClock; Move? get lastMove { return steps.last.sanMove?.move; @@ -238,8 +214,7 @@ mixin IndexableSteps on BaseGame { int get lastPly => steps.last.position.ply; - MaterialDiffSide? lastMaterialDiffAt(Side side) => - steps.last.diff?.bySide(side); + MaterialDiffSide? lastMaterialDiffAt(Side side) => steps.last.diff?.bySide(side); } enum GameSource { @@ -303,7 +278,8 @@ class GameMeta with _$GameMeta { /// Time added to the clock by the "add more time" button. Duration? moreTime, - })? clock, + })? + clock, int? daysPerTurn, int? startedAtTurn, ISet? rules, @@ -315,16 +291,13 @@ class GameMeta with _$GameMeta { Division? division, }) = _GameMeta; - factory GameMeta.fromJson(Map json) => - _$GameMetaFromJson(json); + factory GameMeta.fromJson(Map json) => _$GameMetaFromJson(json); } @Freezed(fromJson: true, toJson: true) class CorrespondenceClockData with _$CorrespondenceClockData { - const factory CorrespondenceClockData({ - required Duration white, - required Duration black, - }) = _CorrespondenceClockData; + const factory CorrespondenceClockData({required Duration white, required Duration black}) = + _CorrespondenceClockData; factory CorrespondenceClockData.fromJson(Map json) => _$CorrespondenceClockDataFromJson(json); diff --git a/lib/src/model/game/game_controller.dart b/lib/src/model/game/game_controller.dart index 88e637a761..dc29a0444c 100644 --- a/lib/src/model/game/game_controller.dart +++ b/lib/src/model/game/game_controller.dart @@ -63,8 +63,7 @@ class GameController extends _$GameController { /// Last socket version received int? _socketEventVersion; - static Uri gameSocketUri(GameFullId gameFullId) => - Uri(path: '/play/$gameFullId/v6'); + static Uri gameSocketUri(GameFullId gameFullId) => Uri(path: '/play/$gameFullId/v6'); ChessClock? _clock; late final SocketClient _socketClient; @@ -73,8 +72,7 @@ class GameController extends _$GameController { Future build(GameFullId gameFullId) { final socketPool = ref.watch(socketPoolProvider); - _socketClient = - socketPool.open(gameSocketUri(gameFullId), forceReconnect: true); + _socketClient = socketPool.open(gameSocketUri(gameFullId), forceReconnect: true); _socketEventVersion = null; _socketSubscription?.cancel(); _socketSubscription = _socketClient.stream.listen(_handleSocketEvent); @@ -87,85 +85,76 @@ class GameController extends _$GameController { _clock?.dispose(); }); - return _socketClient.stream.firstWhere((e) => e.topic == 'full').then( - (event) async { - final fullEvent = - GameFullEvent.fromJson(event.data as Map); + return _socketClient.stream.firstWhere((e) => e.topic == 'full').then((event) async { + final fullEvent = GameFullEvent.fromJson(event.data as Map); - PlayableGame game = fullEvent.game; + PlayableGame game = fullEvent.game; - if (fullEvent.game.finished) { - if (fullEvent.game.meta.speed == Speed.correspondence) { - ref.invalidate(ongoingGamesProvider); - ref - .read(correspondenceServiceProvider) - .updateGame(gameFullId, fullEvent.game); - } - - final result = await _getPostGameData(); - game = result.fold( - (data) => _mergePostGameData(game, data, rewriteSteps: true), - (e, s) { - _logger.warning('Could not get post game data: $e', e, s); - return game; - }); - await _storeGame(game); + if (fullEvent.game.finished) { + if (fullEvent.game.meta.speed == Speed.correspondence) { + ref.invalidate(ongoingGamesProvider); + ref.read(correspondenceServiceProvider).updateGame(gameFullId, fullEvent.game); } - _socketEventVersion = fullEvent.socketEventVersion; + final result = await _getPostGameData(); + game = result.fold((data) => _mergePostGameData(game, data, rewriteSteps: true), (e, s) { + _logger.warning('Could not get post game data: $e', e, s); + return game; + }); + await _storeGame(game); + } - // Play "dong" sound when this is a new game and we're playing it (not spectating) - final isMyGame = game.youAre != null; - final noMovePlayed = game.steps.length == 1; - if (isMyGame && noMovePlayed && game.status == GameStatus.started) { - ref.read(soundServiceProvider).play(Sound.dong); - } + _socketEventVersion = fullEvent.socketEventVersion; - if (game.playable) { - _appLifecycleListener = AppLifecycleListener( - onResume: () { - // socket client should never be disposed here, but in case it is - // we can safely skip the resync - if (!_socketClient.isDisposed && _socketClient.isConnected) { - _resyncGameData(); - } - }, - ); + // Play "dong" sound when this is a new game and we're playing it (not spectating) + final isMyGame = game.youAre != null; + final noMovePlayed = game.steps.length == 1; + if (isMyGame && noMovePlayed && game.status == GameStatus.started) { + ref.read(soundServiceProvider).play(Sound.dong); + } - if (game.clock != null) { - _clock = ChessClock( - whiteTime: game.clock!.white, - blackTime: game.clock!.black, - emergencyThreshold: game.meta.clock?.emergency, - onEmergency: onClockEmergency, - onFlag: onFlag, - ); - if (game.clock!.running) { - final pos = game.lastPosition; - if (pos.fullmoves > 1) { - _clock!.startSide(pos.turn); - } + if (game.playable) { + _appLifecycleListener = AppLifecycleListener( + onResume: () { + // socket client should never be disposed here, but in case it is + // we can safely skip the resync + if (!_socketClient.isDisposed && _socketClient.isConnected) { + _resyncGameData(); + } + }, + ); + + if (game.clock != null) { + _clock = ChessClock( + whiteTime: game.clock!.white, + blackTime: game.clock!.black, + emergencyThreshold: game.meta.clock?.emergency, + onEmergency: onClockEmergency, + onFlag: onFlag, + ); + if (game.clock!.running) { + final pos = game.lastPosition; + if (pos.fullmoves > 1) { + _clock!.startSide(pos.turn); } } } + } - return GameState( - gameFullId: gameFullId, - game: game, - stepCursor: game.steps.length - 1, - liveClock: _liveClock, - ); - }, - ); + return GameState( + gameFullId: gameFullId, + game: game, + stepCursor: game.steps.length - 1, + liveClock: _liveClock, + ); + }); } void userMove(NormalMove move, {bool? isDrop, bool? isPremove}) { final curState = state.requireValue; if (isPromotionPawnMove(curState.game.lastPosition, move)) { - state = AsyncValue.data( - curState.copyWith(promotionMove: move), - ); + state = AsyncValue.data(curState.copyWith(promotionMove: move)); return; } @@ -181,9 +170,7 @@ class GameController extends _$GameController { state = AsyncValue.data( curState.copyWith( - game: curState.game.copyWith( - steps: curState.game.steps.add(newStep), - ), + game: curState.game.copyWith(steps: curState.game.steps.add(newStep)), stepCursor: curState.stepCursor + 1, moveToConfirm: shouldConfirmMove ? move : null, promotionMove: null, @@ -199,8 +186,7 @@ class GameController extends _$GameController { isPremove: isPremove ?? false, // same logic as web client // we want to send client lag only at the beginning of the game when the clock is not running yet - withLag: - curState.game.clock != null && curState.activeClockSide == null, + withLag: curState.game.clock != null && curState.activeClockSide == null, ); } } @@ -208,9 +194,7 @@ class GameController extends _$GameController { void onPromotionSelection(Role? role) { final curState = state.requireValue; if (role == null) { - state = AsyncValue.data( - curState.copyWith(promotionMove: null), - ); + state = AsyncValue.data(curState.copyWith(promotionMove: null)); return; } if (curState.promotionMove == null) { @@ -231,9 +215,7 @@ class GameController extends _$GameController { } state = AsyncValue.data( curState.copyWith( - game: curState.game.copyWith( - steps: curState.game.steps.removeLast(), - ), + game: curState.game.copyWith(steps: curState.game.steps.removeLast()), stepCursor: curState.stepCursor - 1, moveToConfirm: null, ), @@ -249,11 +231,7 @@ class GameController extends _$GameController { return; } - state = AsyncValue.data( - curState.copyWith( - moveToConfirm: null, - ), - ); + state = AsyncValue.data(curState.copyWith(moveToConfirm: null)); _sendMoveToSocket( moveToConfirm, isPremove: false, @@ -266,21 +244,12 @@ class GameController extends _$GameController { /// Set or unset a premove. void setPremove(NormalMove? move) { final curState = state.requireValue; - state = AsyncValue.data( - curState.copyWith( - premove: move, - ), - ); + state = AsyncValue.data(curState.copyWith(premove: move)); } void cursorAt(int cursor) { if (state.hasValue) { - state = AsyncValue.data( - state.requireValue.copyWith( - stepCursor: cursor, - premove: null, - ), - ); + state = AsyncValue.data(state.requireValue.copyWith(stepCursor: cursor, premove: null)); final san = state.requireValue.game.stepAt(cursor).sanMove?.san; if (san != null) { _playReplayMoveSound(san); @@ -322,28 +291,21 @@ class GameController extends _$GameController { void toggleMoveConfirmation() { final curState = state.requireValue; state = AsyncValue.data( - curState.copyWith( - moveConfirmSettingOverride: - !(curState.moveConfirmSettingOverride ?? true), - ), + curState.copyWith(moveConfirmSettingOverride: !(curState.moveConfirmSettingOverride ?? true)), ); } void toggleZenMode() { final curState = state.requireValue; state = AsyncValue.data( - curState.copyWith( - zenModeGameSetting: !(curState.zenModeGameSetting ?? false), - ), + curState.copyWith(zenModeGameSetting: !(curState.zenModeGameSetting ?? false)), ); } void toggleAutoQueen() { final curState = state.requireValue; state = AsyncValue.data( - curState.copyWith( - autoQueenSettingOverride: !(curState.autoQueenSettingOverride ?? true), - ), + curState.copyWith(autoQueenSettingOverride: !(curState.autoQueenSettingOverride ?? true)), ); } @@ -425,12 +387,8 @@ class GameController extends _$GameController { } /// Gets the live game clock if available. - LiveGameClock? get _liveClock => _clock != null - ? ( - white: _clock!.whiteTime, - black: _clock!.blackTime, - ) - : null; + LiveGameClock? get _liveClock => + _clock != null ? (white: _clock!.whiteTime, black: _clock!.blackTime) : null; /// Update the internal clock on clock server event void _updateClock({ @@ -447,23 +405,19 @@ class GameController extends _$GameController { } } - void _sendMoveToSocket( - Move move, { - required bool isPremove, - required bool withLag, - }) { + void _sendMoveToSocket(Move move, {required bool isPremove, required bool withLag}) { final thinkTime = _clock?.stop(); - final moveTime = _clock != null - ? isPremove == true - ? Duration.zero - : thinkTime - : null; + final moveTime = + _clock != null + ? isPremove == true + ? Duration.zero + : thinkTime + : null; _socketClient.send( 'move', { 'u': move.uci, - if (moveTime != null) - 's': (moveTime.inMilliseconds * 0.1).round().toRadixString(36), + if (moveTime != null) 's': (moveTime.inMilliseconds * 0.1).round().toRadixString(36), }, ackable: true, withLag: _clock != null && (moveTime == null || withLag), @@ -474,8 +428,7 @@ class GameController extends _$GameController { /// Move feedback while playing void _playMoveFeedback(SanMove sanMove, {bool skipAnimationDelay = false}) { - final animationDuration = - ref.read(boardPreferencesProvider).pieceAnimationDuration; + final animationDuration = ref.read(boardPreferencesProvider).pieceAnimationDuration; final delay = animationDuration ~/ 2; @@ -527,9 +480,7 @@ class GameController extends _$GameController { return; } if (event.version! > currentEventVersion + 1) { - _logger.warning( - 'Event gap detected from $currentEventVersion to ${event.version}', - ); + _logger.warning('Event gap detected from $currentEventVersion to ${event.version}'); _resyncGameData(); } _socketEventVersion = event.version; @@ -558,10 +509,7 @@ class GameController extends _$GameController { _resyncGameData(); return; } - final reloadEvent = SocketEvent( - topic: data['t'] as String, - data: data['d'], - ); + final reloadEvent = SocketEvent(topic: data['t'] as String, data: data['d']); _handleSocketTopic(reloadEvent); } else { _resyncGameData(); @@ -569,11 +517,9 @@ class GameController extends _$GameController { // Full game data, received after a (re)connection to game socket case 'full': - final fullEvent = - GameFullEvent.fromJson(event.data as Map); + final fullEvent = GameFullEvent.fromJson(event.data as Map); - if (_socketEventVersion != null && - fullEvent.socketEventVersion < _socketEventVersion!) { + if (_socketEventVersion != null && fullEvent.socketEventVersion < _socketEventVersion!) { return; } _socketEventVersion = fullEvent.socketEventVersion; @@ -630,27 +576,24 @@ class GameController extends _$GameController { ); newState = newState.copyWith( - game: newState.game.copyWith( - steps: newState.game.steps.add(newStep), - ), + game: newState.game.copyWith(steps: newState.game.steps.add(newStep)), ); if (!curState.isReplaying) { - newState = newState.copyWith( - stepCursor: newState.stepCursor + 1, - ); + newState = newState.copyWith(stepCursor: newState.stepCursor + 1); _playMoveFeedback(sanMove); } } if (data.clock != null) { - final lag = newState.game.playable && newState.game.isMyTurn - // my own clock doesn't need to be compensated for - ? Duration.zero - // server will send the lag only if it's more than 10ms - // default lag of 10ms is also used by web client - : data.clock?.lag ?? const Duration(milliseconds: 10); + final lag = + newState.game.playable && newState.game.isMyTurn + // my own clock doesn't need to be compensated for + ? Duration.zero + // server will send the lag only if it's more than 10ms + // default lag of 10ms is also used by web client + : data.clock?.lag ?? const Duration(milliseconds: 10); _updateClock( white: data.clock!.white, @@ -676,9 +619,7 @@ class GameController extends _$GameController { if (newState.game.expiration != null) { if (newState.game.steps.length > 2) { - newState = newState.copyWith.game( - expiration: null, - ); + newState = newState.copyWith.game(expiration: null); } else { newState = newState.copyWith.game( expiration: ( @@ -692,9 +633,7 @@ class GameController extends _$GameController { if (curState.game.meta.speed == Speed.correspondence) { ref.invalidate(ongoingGamesProvider); - ref - .read(correspondenceServiceProvider) - .updateGame(gameFullId, newState.game); + ref.read(correspondenceServiceProvider).updateGame(gameFullId, newState.game); } if (!curState.isReplaying && @@ -703,8 +642,7 @@ class GameController extends _$GameController { scheduleMicrotask(() { final postMovePremove = state.valueOrNull?.premove; final postMovePosition = state.valueOrNull?.game.lastPosition; - if (postMovePremove != null && - postMovePosition?.isLegal(postMovePremove) == true) { + if (postMovePremove != null && postMovePosition?.isLegal(postMovePremove) == true) { userMove(postMovePremove, isPremove: true); } }); @@ -714,20 +652,15 @@ class GameController extends _$GameController { // End game event case 'endData': - final endData = - GameEndEvent.fromJson(event.data as Map); + final endData = GameEndEvent.fromJson(event.data as Map); final curState = state.requireValue; GameState newState = curState.copyWith( game: curState.game.copyWith( status: endData.status, winner: endData.winner, boosted: endData.boosted, - white: curState.game.white.copyWith( - ratingDiff: endData.ratingDiff?.white, - ), - black: curState.game.black.copyWith( - ratingDiff: endData.ratingDiff?.black, - ), + white: curState.game.white.copyWith(ratingDiff: endData.ratingDiff?.white), + black: curState.game.black.copyWith(ratingDiff: endData.ratingDiff?.black), ), premove: null, ); @@ -752,45 +685,42 @@ class GameController extends _$GameController { if (curState.game.meta.speed == Speed.correspondence) { ref.invalidate(ongoingGamesProvider); - ref - .read(correspondenceServiceProvider) - .updateGame(gameFullId, newState.game); + ref.read(correspondenceServiceProvider).updateGame(gameFullId, newState.game); } state = AsyncValue.data(newState); if (!newState.game.aborted) { _getPostGameData().then((result) { - result.fold((data) { - final game = _mergePostGameData(state.requireValue.game, data); - state = AsyncValue.data( - state.requireValue.copyWith(game: game), - ); - _storeGame(game); - }, (e, s) { - _logger.warning('Could not get post game data', e, s); - }); + result.fold( + (data) { + final game = _mergePostGameData(state.requireValue.game, data); + state = AsyncValue.data(state.requireValue.copyWith(game: game)); + _storeGame(game); + }, + (e, s) { + _logger.warning('Could not get post game data', e, s); + }, + ); }); } case 'clockInc': final data = event.data as Map; final side = pick(data['color']).asSideOrNull(); - final newClock = pick(data['total']) - .letOrNull((it) => Duration(milliseconds: it.asIntOrThrow() * 10)); + final newClock = pick( + data['total'], + ).letOrNull((it) => Duration(milliseconds: it.asIntOrThrow() * 10)); final curState = state.requireValue; if (side != null && newClock != null) { _clock?.setTime(side, newClock); // sync game clock object even if it's not used to display the clock - final newState = side == Side.white - ? curState.copyWith.game.clock!( - white: newClock, - ) - : curState.copyWith.game.clock!( - black: newClock, - ); + final newState = + side == Side.white + ? curState.copyWith.game.clock!(white: newClock) + : curState.copyWith.game.clock!(black: newClock); state = AsyncValue.data(newState); } @@ -803,25 +733,17 @@ class GameController extends _$GameController { final opponent = curState.game.youAre?.opposite; GameState newState = curState; if (whiteOnGame != null) { - newState = newState.copyWith.game( - white: newState.game.white.setOnGame(whiteOnGame), - ); + newState = newState.copyWith.game(white: newState.game.white.setOnGame(whiteOnGame)); if (opponent == Side.white && whiteOnGame == true) { _opponentLeftCountdownTimer?.cancel(); - newState = newState.copyWith( - opponentLeftCountdown: null, - ); + newState = newState.copyWith(opponentLeftCountdown: null); } } if (blackOnGame != null) { - newState = newState.copyWith.game( - black: newState.game.black.setOnGame(blackOnGame), - ); + newState = newState.copyWith.game(black: newState.game.black.setOnGame(blackOnGame)); if (opponent == Side.black && blackOnGame == true) { _opponentLeftCountdownTimer?.cancel(); - newState = newState.copyWith( - opponentLeftCountdown: null, - ); + newState = newState.copyWith(opponentLeftCountdown: null); } } state = AsyncValue.data(newState); @@ -834,12 +756,8 @@ class GameController extends _$GameController { GameState newState = state.requireValue; final youAre = newState.game.youAre; newState = newState.copyWith.game( - white: youAre == Side.white - ? newState.game.white - : newState.game.white.setGone(isGone), - black: youAre == Side.black - ? newState.game.black - : newState.game.black.setGone(isGone), + white: youAre == Side.white ? newState.game.white : newState.game.white.setGone(isGone), + black: youAre == Side.black ? newState.game.black : newState.game.black.setGone(isGone), ); state = AsyncValue.data(newState); @@ -847,41 +765,25 @@ class GameController extends _$GameController { // before claiming victory is possible case 'goneIn': final timeLeft = Duration(seconds: event.data as int); - state = AsyncValue.data( - state.requireValue.copyWith( - opponentLeftCountdown: timeLeft, - ), - ); + state = AsyncValue.data(state.requireValue.copyWith(opponentLeftCountdown: timeLeft)); _opponentLeftCountdownTimer?.cancel(); - _opponentLeftCountdownTimer = Timer.periodic( - const Duration(seconds: 1), - (_) { - final curState = state.requireValue; - final opponentLeftCountdown = curState.opponentLeftCountdown; - if (opponentLeftCountdown == null) { - _opponentLeftCountdownTimer?.cancel(); - } else if (!curState.canShowClaimWinCountdown) { + _opponentLeftCountdownTimer = Timer.periodic(const Duration(seconds: 1), (_) { + final curState = state.requireValue; + final opponentLeftCountdown = curState.opponentLeftCountdown; + if (opponentLeftCountdown == null) { + _opponentLeftCountdownTimer?.cancel(); + } else if (!curState.canShowClaimWinCountdown) { + _opponentLeftCountdownTimer?.cancel(); + state = AsyncValue.data(curState.copyWith(opponentLeftCountdown: null)); + } else { + final newTime = opponentLeftCountdown - const Duration(seconds: 1); + if (newTime <= Duration.zero) { _opponentLeftCountdownTimer?.cancel(); - state = AsyncValue.data( - curState.copyWith( - opponentLeftCountdown: null, - ), - ); - } else { - final newTime = - opponentLeftCountdown - const Duration(seconds: 1); - if (newTime <= Duration.zero) { - _opponentLeftCountdownTimer?.cancel(); - state = AsyncValue.data( - curState.copyWith(opponentLeftCountdown: null), - ); - } - state = AsyncValue.data( - curState.copyWith(opponentLeftCountdown: newTime), - ); + state = AsyncValue.data(curState.copyWith(opponentLeftCountdown: null)); } - }, - ); + state = AsyncValue.data(curState.copyWith(opponentLeftCountdown: newTime)); + } + }); // Event sent when a player adds or cancels a draw offer case 'drawOffer': @@ -889,9 +791,8 @@ class GameController extends _$GameController { final curState = state.requireValue; state = AsyncValue.data( curState.copyWith( - lastDrawOfferAtPly: side != null && side == curState.game.youAre - ? curState.game.lastPly - : null, + lastDrawOfferAtPly: + side != null && side == curState.game.youAre ? curState.game.lastPly : null, game: curState.game.copyWith( white: curState.game.white.copyWith( offeringDraw: side == null ? null : side == Side.white, @@ -912,12 +813,8 @@ class GameController extends _$GameController { state = AsyncValue.data( curState.copyWith( game: curState.game.copyWith( - white: curState.game.white.copyWith( - proposingTakeback: white ?? false, - ), - black: curState.game.black.copyWith( - proposingTakeback: black ?? false, - ), + white: curState.game.white.copyWith(proposingTakeback: white ?? false), + black: curState.game.black.copyWith(proposingTakeback: black ?? false), ), ), ); @@ -943,34 +840,21 @@ class GameController extends _$GameController { // sending another rematch offer, which should not happen case 'rematchTaken': final nextId = pick(event.data).asGameIdOrThrow(); - state = AsyncValue.data( - state.requireValue.copyWith.game( - rematch: nextId, - ), - ); + state = AsyncValue.data(state.requireValue.copyWith.game(rematch: nextId)); // Event sent after a rematch is taken, to redirect to the new game case 'redirect': final data = event.data as Map; final fullId = pick(data['id']).asGameFullIdOrThrow(); - state = AsyncValue.data( - state.requireValue.copyWith( - redirectGameId: fullId, - ), - ); + state = AsyncValue.data(state.requireValue.copyWith(redirectGameId: fullId)); case 'analysisProgress': - final data = - ServerEvalEvent.fromJson(event.data as Map); + final data = ServerEvalEvent.fromJson(event.data as Map); final curState = state.requireValue; state = AsyncValue.data( curState.copyWith.game( - white: curState.game.white.copyWith( - analysis: data.analysis?.white, - ), - black: curState.game.black.copyWith( - analysis: data.analysis?.black, - ), + white: curState.game.white.copyWith(analysis: data.analysis?.white), + black: curState.game.black.copyWith(analysis: data.analysis?.black), evals: data.evals, ), ); @@ -988,15 +872,14 @@ class GameController extends _$GameController { FutureResult _getPostGameData() { return Result.capture( - ref.withClient( - (client) => GameRepository(client).getGame(gameFullId.gameId), - ), + ref.withClient((client) => GameRepository(client).getGame(gameFullId.gameId)), ); } PlayableGame _mergePostGameData( PlayableGame game, ArchivedGame data, { + /// Whether to rewrite the steps with the clock data from the archived game /// /// This should not be done when the game has just finished, because we @@ -1006,44 +889,35 @@ class GameController extends _$GameController { IList newSteps = game.steps; if (rewriteSteps && game.meta.clock != null && data.clocks != null) { final initialTime = game.meta.clock!.initial; - newSteps = game.steps.mapIndexed((index, element) { - if (index == 0) { - return element.copyWith( - archivedWhiteClock: initialTime, - archivedBlackClock: initialTime, - ); - } - final prevClock = index > 1 ? data.clocks![index - 2] : initialTime; - final stepClock = data.clocks![index - 1]; - return element.copyWith( - archivedWhiteClock: index.isOdd ? stepClock : prevClock, - archivedBlackClock: index.isEven ? stepClock : prevClock, - ); - }).toIList(); + newSteps = + game.steps.mapIndexed((index, element) { + if (index == 0) { + return element.copyWith( + archivedWhiteClock: initialTime, + archivedBlackClock: initialTime, + ); + } + final prevClock = index > 1 ? data.clocks![index - 2] : initialTime; + final stepClock = data.clocks![index - 1]; + return element.copyWith( + archivedWhiteClock: index.isOdd ? stepClock : prevClock, + archivedBlackClock: index.isEven ? stepClock : prevClock, + ); + }).toIList(); } return game.copyWith( steps: newSteps, clocks: data.clocks, - meta: game.meta.copyWith( - opening: data.meta.opening, - division: data.meta.division, - ), - white: game.white.copyWith( - analysis: data.white.analysis, - ), - black: game.black.copyWith( - analysis: data.black.analysis, - ), + meta: game.meta.copyWith(opening: data.meta.opening, division: data.meta.division), + white: game.white.copyWith(analysis: data.white.analysis), + black: game.black.copyWith(analysis: data.black.analysis), evals: data.evals, ); } } -typedef LiveGameClock = ({ - ValueListenable white, - ValueListenable black, -}); +typedef LiveGameClock = ({ValueListenable white, ValueListenable black}); @freezed class GameState with _$GameState { @@ -1082,34 +956,25 @@ class GameState with _$GameState { /// The [Position] and its legal moves at the current cursor. (Position, IMap>) get currentPosition { final position = game.positionAt(stepCursor); - final legalMoves = makeLegalMoves( - position, - isChess960: game.meta.variant == Variant.chess960, - ); + final legalMoves = makeLegalMoves(position, isChess960: game.meta.variant == Variant.chess960); return (position, legalMoves); } /// Whether the zen mode is active - bool get isZenModeActive => - game.playable ? isZenModeEnabled : game.prefs?.zenMode == Zen.yes; + bool get isZenModeActive => game.playable ? isZenModeEnabled : game.prefs?.zenMode == Zen.yes; /// Whether zen mode is enabled by account preference or local game setting bool get isZenModeEnabled => - zenModeGameSetting ?? - game.prefs?.zenMode == Zen.yes || game.prefs?.zenMode == Zen.gameAuto; + zenModeGameSetting ?? game.prefs?.zenMode == Zen.yes || game.prefs?.zenMode == Zen.gameAuto; bool get canPremove => - game.meta.speed != Speed.correspondence && - (game.prefs?.enablePremove ?? true); - bool get canAutoQueen => - autoQueenSettingOverride ?? (game.prefs?.autoQueen == AutoQueen.always); + game.meta.speed != Speed.correspondence && (game.prefs?.enablePremove ?? true); + bool get canAutoQueen => autoQueenSettingOverride ?? (game.prefs?.autoQueen == AutoQueen.always); bool get canAutoQueenOnPremove => autoQueenSettingOverride ?? - (game.prefs?.autoQueen == AutoQueen.always || - game.prefs?.autoQueen == AutoQueen.premove); + (game.prefs?.autoQueen == AutoQueen.always || game.prefs?.autoQueen == AutoQueen.premove); bool get shouldConfirmResignAndDrawOffer => game.prefs?.confirmResign ?? true; - bool get shouldConfirmMove => - moveConfirmSettingOverride ?? game.prefs?.submitMove ?? false; + bool get shouldConfirmMove => moveConfirmSettingOverride ?? game.prefs?.submitMove ?? false; bool get isReplaying => stepCursor < game.steps.length - 1; bool get canGoForward => stepCursor < game.steps.length - 1; @@ -1120,23 +985,19 @@ class GameState with _$GameState { game.meta.speed != Speed.correspondence && (game.source == GameSource.lobby || game.source == GameSource.pool); - bool get canOfferDraw => - game.drawable && (lastDrawOfferAtPly ?? -99) < game.lastPly - 20; + bool get canOfferDraw => game.drawable && (lastDrawOfferAtPly ?? -99) < game.lastPly - 20; bool get canShowClaimWinCountdown => !game.isMyTurn && game.resignable && - (game.meta.rules == null || - !game.meta.rules!.contains(GameRule.noClaimWin)); + (game.meta.rules == null || !game.meta.rules!.contains(GameRule.noClaimWin)); bool get canOfferRematch => game.rematch == null && game.rematchable && (game.finished || (game.aborted && - (!game.meta.rated || - !{GameSource.lobby, GameSource.pool} - .contains(game.source)))) && + (!game.meta.rated || !{GameSource.lobby, GameSource.pool}.contains(game.source)))) && game.boosted != true; /// Time left to move for the active player if an expiration is set @@ -1144,8 +1005,8 @@ class GameState with _$GameState { if (!game.playable || game.expiration == null) { return null; } - final timeLeft = game.expiration!.movedAt.difference(DateTime.now()) + - game.expiration!.timeToMove; + final timeLeft = + game.expiration!.movedAt.difference(DateTime.now()) + game.expiration!.timeToMove; if (timeLeft.isNegative) { return Duration.zero; @@ -1169,19 +1030,20 @@ class GameState with _$GameState { String get analysisPgn => game.makePgn(); - AnalysisOptions get analysisOptions => game.finished - ? AnalysisOptions( - orientation: game.youAre ?? Side.white, - initialMoveCursor: stepCursor, - gameId: gameFullId.gameId, - ) - : AnalysisOptions( - orientation: game.youAre ?? Side.white, - initialMoveCursor: stepCursor, - standalone: ( - pgn: game.makePgn(), - variant: game.meta.variant, - isComputerAnalysisAllowed: false, - ), - ); + AnalysisOptions get analysisOptions => + game.finished + ? AnalysisOptions( + orientation: game.youAre ?? Side.white, + initialMoveCursor: stepCursor, + gameId: gameFullId.gameId, + ) + : AnalysisOptions( + orientation: game.youAre ?? Side.white, + initialMoveCursor: stepCursor, + standalone: ( + pgn: game.makePgn(), + variant: game.meta.variant, + isComputerAnalysisAllowed: false, + ), + ); } diff --git a/lib/src/model/game/game_filter.dart b/lib/src/model/game/game_filter.dart index 218f5f6ea1..3b218a74a4 100644 --- a/lib/src/model/game/game_filter.dart +++ b/lib/src/model/game/game_filter.dart @@ -16,43 +16,39 @@ class GameFilter extends _$GameFilter { return filter ?? const GameFilterState(); } - void setFilter(GameFilterState filter) => state = state.copyWith( - perfs: filter.perfs, - side: filter.side, - ); + void setFilter(GameFilterState filter) => + state = state.copyWith(perfs: filter.perfs, side: filter.side); } @freezed class GameFilterState with _$GameFilterState { const GameFilterState._(); - const factory GameFilterState({ - @Default(ISet.empty()) ISet perfs, - Side? side, - }) = _GameFilterState; + const factory GameFilterState({@Default(ISet.empty()) ISet perfs, Side? side}) = + _GameFilterState; /// Returns a translated label of the selected filters. String selectionLabel(BuildContext context) { final fields = [side, perfs]; - final labels = fields - .map( - (field) => field is ISet - ? field.map((e) => e.shortTitle).join(', ') - : (field as Side?) != null - ? field == Side.white - ? context.l10n.white - : context.l10n.black - : null, - ) - .where((label) => label != null && label.isNotEmpty) - .toList(); + final labels = + fields + .map( + (field) => + field is ISet + ? field.map((e) => e.shortTitle).join(', ') + : (field as Side?) != null + ? field == Side.white + ? context.l10n.white + : context.l10n.black + : null, + ) + .where((label) => label != null && label.isNotEmpty) + .toList(); return labels.isEmpty ? 'All' : labels.join(', '); } int get count { final fields = [perfs, side]; - return fields - .where((field) => field is Iterable ? field.isNotEmpty : field != null) - .length; + return fields.where((field) => field is Iterable ? field.isNotEmpty : field != null).length; } } diff --git a/lib/src/model/game/game_history.dart b/lib/src/model/game/game_history.dart index 50c4122e6d..9191de56bb 100644 --- a/lib/src/model/game/game_history.dart +++ b/lib/src/model/game/game_history.dart @@ -34,13 +34,11 @@ const _nbPerPage = 20; /// stored locally are fetched instead. @riverpod Future> myRecentGames(Ref ref) async { - final online = await ref - .watch(connectivityChangesProvider.selectAsync((c) => c.isOnline)); + final online = await ref.watch(connectivityChangesProvider.selectAsync((c) => c.isOnline)); final session = ref.watch(authSessionProvider); if (session != null && online) { return ref.withClientCacheFor( - (client) => GameRepository(client) - .getUserGames(session.user.id, max: kNumberOfRecentGames), + (client) => GameRepository(client).getUserGames(session.user.id, max: kNumberOfRecentGames), const Duration(hours: 1), ); } else { @@ -49,21 +47,19 @@ Future> myRecentGames(Ref ref) async { return storage .page(userId: session?.user.id, max: kNumberOfRecentGames) .then( - (value) => value - // we can assume that `youAre` is not null either for logged - // in users or for anonymous users - .map((e) => (game: e.game.data, pov: e.game.youAre ?? Side.white)) - .toIList(), + (value) => + value + // we can assume that `youAre` is not null either for logged + // in users or for anonymous users + .map((e) => (game: e.game.data, pov: e.game.youAre ?? Side.white)) + .toIList(), ); } } /// A provider that fetches the recent games from the server for a given user. @riverpod -Future> userRecentGames( - Ref ref, { - required UserId userId, -}) { +Future> userRecentGames(Ref ref, {required UserId userId}) { return ref.withClientCacheFor( (client) => GameRepository(client).getUserGames(userId), // cache is important because the associated widget is in a [ListView] and @@ -81,20 +77,13 @@ Future> userRecentGames( /// If the user is not logged in, or there is no connectivity, the number of games /// stored locally are fetched instead. @riverpod -Future userNumberOfGames( - Ref ref, - LightUser? user, { - required bool isOnline, -}) async { +Future userNumberOfGames(Ref ref, LightUser? user, {required bool isOnline}) async { final session = ref.watch(authSessionProvider); return user != null - ? ref.watch( - userProvider(id: user.id).selectAsync((u) => u.count?.all ?? 0), - ) + ? ref.watch(userProvider(id: user.id).selectAsync((u) => u.count?.all ?? 0)) : session != null && isOnline - ? ref.watch(accountProvider.selectAsync((u) => u?.count?.all ?? 0)) - : (await ref.watch(gameStorageProvider.future)) - .count(userId: user?.id); + ? ref.watch(accountProvider.selectAsync((u) => u?.count?.all ?? 0)) + : (await ref.watch(gameStorageProvider.future)).count(userId: user?.id); } /// A provider that paginates the game history for a given user, or the current app user if no user is provided. @@ -108,6 +97,7 @@ class UserGameHistory extends _$UserGameHistory { @override Future build( UserId? userId, { + /// Whether the history is requested in an online context. Applicable only /// when [userId] is null. /// @@ -123,25 +113,23 @@ class UserGameHistory extends _$UserGameHistory { }); final session = ref.watch(authSessionProvider); - final online = await ref - .watch(connectivityChangesProvider.selectAsync((c) => c.isOnline)); + final online = await ref.watch(connectivityChangesProvider.selectAsync((c) => c.isOnline)); final storage = await ref.watch(gameStorageProvider.future); final id = userId ?? session?.user.id; - final recentGames = id != null && online - ? ref.withClient( - (client) => GameRepository(client).getUserGames(id, filter: filter), - ) - : storage.page(userId: id, filter: filter).then( - (value) => value - // we can assume that `youAre` is not null either for logged - // in users or for anonymous users - .map( - (e) => - (game: e.game.data, pov: e.game.youAre ?? Side.white), - ) - .toIList(), - ); + final recentGames = + id != null && online + ? ref.withClient((client) => GameRepository(client).getUserGames(id, filter: filter)) + : storage + .page(userId: id, filter: filter) + .then( + (value) => + value + // we can assume that `youAre` is not null either for logged + // in users or for anonymous users + .map((e) => (game: e.game.data, pov: e.game.youAre ?? Side.white)) + .toIList(), + ); _list.addAll(await recentGames); @@ -165,42 +153,36 @@ class UserGameHistory extends _$UserGameHistory { Result.capture( userId != null ? ref.withClient( - (client) => GameRepository(client).getUserGames( - userId!, - max: _nbPerPage, - until: _list.last.game.createdAt, - filter: currentVal.filter, - ), - ) + (client) => GameRepository(client).getUserGames( + userId!, + max: _nbPerPage, + until: _list.last.game.createdAt, + filter: currentVal.filter, + ), + ) : currentVal.online && currentVal.session != null - ? ref.withClient( - (client) => GameRepository(client).getUserGames( - currentVal.session!.user.id, - max: _nbPerPage, - until: _list.last.game.createdAt, - filter: currentVal.filter, - ), - ) - : (await ref.watch(gameStorageProvider.future)) - .page(max: _nbPerPage, until: _list.last.game.createdAt) - .then( - (value) => value + ? ref.withClient( + (client) => GameRepository(client).getUserGames( + currentVal.session!.user.id, + max: _nbPerPage, + until: _list.last.game.createdAt, + filter: currentVal.filter, + ), + ) + : (await ref.watch(gameStorageProvider.future)) + .page(max: _nbPerPage, until: _list.last.game.createdAt) + .then( + (value) => + value // we can assume that `youAre` is not null either for logged // in users or for anonymous users - .map( - (e) => ( - game: e.game.data, - pov: e.game.youAre ?? Side.white - ), - ) + .map((e) => (game: e.game.data, pov: e.game.youAre ?? Side.white)) .toIList(), - ), + ), ).fold( (value) { if (value.isEmpty) { - state = AsyncData( - currentVal.copyWith(hasMore: false, isLoading: false), - ); + state = AsyncData(currentVal.copyWith(hasMore: false, isLoading: false)); return; } @@ -215,8 +197,7 @@ class UserGameHistory extends _$UserGameHistory { ); }, (error, stackTrace) { - state = - AsyncData(currentVal.copyWith(isLoading: false, hasError: true)); + state = AsyncData(currentVal.copyWith(isLoading: false, hasError: true)); }, ); } diff --git a/lib/src/model/game/game_preferences.dart b/lib/src/model/game/game_preferences.dart index d95a1e4795..31311b132a 100644 --- a/lib/src/model/game/game_preferences.dart +++ b/lib/src/model/game/game_preferences.dart @@ -7,8 +7,7 @@ part 'game_preferences.g.dart'; /// Local game preferences, defined client-side only. @riverpod -class GamePreferences extends _$GamePreferences - with PreferencesStorage { +class GamePreferences extends _$GamePreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties @override final prefCategory = PrefCategory.game; @@ -31,23 +30,15 @@ class GamePreferences extends _$GamePreferences } Future toggleBlindfoldMode() { - return save( - state.copyWith(blindfoldMode: !(state.blindfoldMode ?? false)), - ); + return save(state.copyWith(blindfoldMode: !(state.blindfoldMode ?? false))); } } @Freezed(fromJson: true, toJson: true) class GamePrefs with _$GamePrefs implements Serializable { - const factory GamePrefs({ - bool? enableChat, - bool? blindfoldMode, - }) = _GamePrefs; + const factory GamePrefs({bool? enableChat, bool? blindfoldMode}) = _GamePrefs; - static const defaults = GamePrefs( - enableChat: true, - ); + static const defaults = GamePrefs(enableChat: true); - factory GamePrefs.fromJson(Map json) => - _$GamePrefsFromJson(json); + factory GamePrefs.fromJson(Map json) => _$GamePrefsFromJson(json); } diff --git a/lib/src/model/game/game_repository.dart b/lib/src/model/game/game_repository.dart index 84f56e4ad2..8d6fc24b59 100644 --- a/lib/src/model/game/game_repository.dart +++ b/lib/src/model/game/game_repository.dart @@ -15,10 +15,7 @@ class GameRepository { Future getGame(GameId id) { return client.readJson( - Uri( - path: '/game/export/$id', - queryParameters: {'clocks': '1', 'accuracy': '1'}, - ), + Uri(path: '/game/export/$id', queryParameters: {'clocks': '1', 'accuracy': '1'}), headers: {'Accept': 'application/json'}, mapper: ArchivedGame.fromServerJson, ); @@ -26,14 +23,9 @@ class GameRepository { Future requestServerAnalysis(GameId id) async { final uri = Uri(path: '/$id/request-analysis'); - final response = await client.post( - Uri(path: '/$id/request-analysis'), - ); + final response = await client.post(Uri(path: '/$id/request-analysis')); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to request analysis: ${response.statusCode}', - uri, - ); + throw http.ClientException('Failed to request analysis: ${response.statusCode}', uri); } } @@ -53,8 +45,7 @@ class GameRepository { path: '/api/games/user/$userId', queryParameters: { 'max': max.toString(), - if (until != null) - 'until': until.millisecondsSinceEpoch.toString(), + if (until != null) 'until': until.millisecondsSinceEpoch.toString(), 'moves': 'false', 'lastFen': 'true', 'accuracy': 'true', @@ -68,15 +59,16 @@ class GameRepository { mapper: LightArchivedGame.fromServerJson, ) .then( - (value) => value - .map( - (e) => ( - game: e, - // we know here user is not null for at least one of the players - pov: e.white.user?.id == userId ? Side.white : Side.black, - ), - ) - .toIList(), + (value) => + value + .map( + (e) => ( + game: e, + // we know here user is not null for at least one of the players + pov: e.white.user?.id == userId ? Side.white : Side.black, + ), + ) + .toIList(), ); } @@ -86,22 +78,14 @@ class GameRepository { return Future.value(IList()); } return client.readJsonList( - Uri( - path: '/api/mobile/my-games', - queryParameters: { - 'ids': ids.join(','), - }, - ), + Uri(path: '/api/mobile/my-games', queryParameters: {'ids': ids.join(',')}), mapper: PlayableGame.fromServerJson, ); } Future> getGamesByIds(ISet ids) { return client.postReadNdJsonList( - Uri( - path: '/api/games/export/_ids', - queryParameters: {'moves': 'false', 'lastFen': 'true'}, - ), + Uri(path: '/api/games/export/_ids', queryParameters: {'moves': 'false', 'lastFen': 'true'}), headers: {'Accept': 'application/x-ndjson'}, body: ids.join(','), mapper: LightArchivedGame.fromServerJson, diff --git a/lib/src/model/game/game_repository_providers.dart b/lib/src/model/game/game_repository_providers.dart index fdc69944aa..c9a95b6e43 100644 --- a/lib/src/model/game/game_repository_providers.dart +++ b/lib/src/model/game/game_repository_providers.dart @@ -23,11 +23,6 @@ Future archivedGame(Ref ref, {required GameId id}) async { } @riverpod -Future> gamesById( - Ref ref, { - required ISet ids, -}) { - return ref.withClient( - (client) => GameRepository(client).getGamesByIds(ids), - ); +Future> gamesById(Ref ref, {required ISet ids}) { + return ref.withClient((client) => GameRepository(client).getGamesByIds(ids)); } diff --git a/lib/src/model/game/game_share_service.dart b/lib/src/model/game/game_share_service.dart index b182564e67..b0996ffc8d 100644 --- a/lib/src/model/game/game_share_service.dart +++ b/lib/src/model/game/game_share_service.dart @@ -25,12 +25,7 @@ class GameShareService { Future rawPgn(GameId id) async { final resp = await _ref.withClient( (client) => client - .get( - Uri( - path: '/game/export/$id', - queryParameters: {'evals': '0', 'clocks': '0'}, - ), - ) + .get(Uri(path: '/game/export/$id', queryParameters: {'evals': '0', 'clocks': '0'})) .timeout(const Duration(seconds: 1)), ); if (resp.statusCode != 200) { @@ -43,12 +38,7 @@ class GameShareService { Future annotatedPgn(GameId id) async { final resp = await _ref.withClient( (client) => client - .get( - Uri( - path: '/game/export/$id', - queryParameters: {'literate': '1'}, - ), - ) + .get(Uri(path: '/game/export/$id', queryParameters: {'literate': '1'})) .timeout(const Duration(seconds: 1)), ); if (resp.statusCode != 200) { @@ -58,11 +48,7 @@ class GameShareService { } /// Fetches the GIF screenshot of a position and launches the share dialog. - Future screenshotPosition( - Side orientation, - String fen, - Move? lastMove, - ) async { + Future screenshotPosition(Side orientation, String fen, Move? lastMove) async { final boardTheme = _ref.read(boardPreferencesProvider).boardTheme; final pieceTheme = _ref.read(boardPreferencesProvider).pieceSet; final resp = await _ref @@ -82,9 +68,10 @@ class GameShareService { /// Fetches the GIF animation of a game. Future gameGif(GameId id, Side orientation) async { final boardPreferences = _ref.read(boardPreferencesProvider); - final boardTheme = boardPreferences.boardTheme == BoardTheme.system - ? BoardTheme.brown - : boardPreferences.boardTheme; + final boardTheme = + boardPreferences.boardTheme == BoardTheme.system + ? BoardTheme.brown + : boardPreferences.boardTheme; final pieceTheme = boardPreferences.pieceSet; final resp = await _ref .read(defaultClientProvider) @@ -101,26 +88,21 @@ class GameShareService { } /// Fetches the GIF animation of a study chapter. - Future chapterGif( - StringId id, - StringId chapterId, - ) async { + Future chapterGif(StringId id, StringId chapterId) async { final boardPreferences = _ref.read(boardPreferencesProvider); - final boardTheme = boardPreferences.boardTheme == BoardTheme.system - ? BoardTheme.brown - : boardPreferences.boardTheme; + final boardTheme = + boardPreferences.boardTheme == BoardTheme.system + ? BoardTheme.brown + : boardPreferences.boardTheme; final pieceTheme = boardPreferences.pieceSet; final resp = await _ref .read(lichessClientProvider) .get( - lichessUri( - '/study/$id/$chapterId.gif', - { - 'theme': boardTheme.gifApiName, - 'piece': pieceTheme.name, - }, - ), + lichessUri('/study/$id/$chapterId.gif', { + 'theme': boardTheme.gifApiName, + 'piece': pieceTheme.name, + }), ) .timeout(const Duration(seconds: 1)); if (resp.statusCode != 200) { diff --git a/lib/src/model/game/game_socket_events.dart b/lib/src/model/game/game_socket_events.dart index cfd8dd10e8..bd4463a9bf 100644 --- a/lib/src/model/game/game_socket_events.dart +++ b/lib/src/model/game/game_socket_events.dart @@ -15,10 +15,8 @@ part 'game_socket_events.freezed.dart'; @freezed class GameFullEvent with _$GameFullEvent { - const factory GameFullEvent({ - required PlayableGame game, - required int socketEventVersion, - }) = _GameFullEvent; + const factory GameFullEvent({required PlayableGame game, required int socketEventVersion}) = + _GameFullEvent; factory GameFullEvent.fromJson(Map json) { return GameFullEvent( @@ -41,12 +39,7 @@ class MoveEvent with _$MoveEvent { bool? blackOfferingDraw, GameStatus? status, Side? winner, - ({ - Duration white, - Duration black, - Duration? lag, - DateTime at, - })? clock, + ({Duration white, Duration black, Duration? lag, DateTime at})? clock, }) = _MoveEvent; factory MoveEvent.fromJson(Map json) => @@ -87,8 +80,7 @@ MoveEvent _socketMoveEventFromPick(RequiredPick pick) { at: clock.now(), white: it('white').asDurationFromSecondsOrThrow(), black: it('black').asDurationFromSecondsOrThrow(), - lag: it('lag') - .letOrNull((it) => Duration(milliseconds: it.asIntOrThrow() * 10)), + lag: it('lag').letOrNull((it) => Duration(milliseconds: it.asIntOrThrow() * 10)), ), ), ); @@ -114,12 +106,9 @@ GameEndEvent _gameEndEventFromPick(RequiredPick pick) { return GameEndEvent( status: pick('status').asGameStatusOrThrow(), winner: pick('winner').asSideOrNull(), - ratingDiff: pick('ratingDiff').letOrNull( - (it) => ( - white: it('white').asIntOrThrow(), - black: it('black').asIntOrThrow(), - ), - ), + ratingDiff: pick( + 'ratingDiff', + ).letOrNull((it) => (white: it('white').asIntOrThrow(), black: it('black').asIntOrThrow())), boosted: pick('boosted').asBoolOrNull(), clock: pick('clock').letOrNull( (it) => ( @@ -170,12 +159,10 @@ ServerEvalEvent _serverEvalEventFromPick(RequiredPick pick) { final glyph = glyphs?.first as Map?; final comments = node['comments'] as List?; final comment = comments?.first as Map?; - final judgment = glyph != null && comment != null - ? ( - name: _nagToJugdmentName(glyph['id'] as int), - comment: comment['text'] as String, - ) - : null; + final judgment = + glyph != null && comment != null + ? (name: _nagToJugdmentName(glyph['id'] as int), comment: comment['text'] as String) + : null; final variation = nextVariation; @@ -243,29 +230,19 @@ ServerEvalEvent _serverEvalEventFromPick(RequiredPick pick) { ), ), isAnalysisComplete: !isAnalysisIncomplete, - division: pick('division').letOrNull( - (it) => ( - middle: it('middle').asIntOrNull(), - end: it('end').asIntOrNull(), - ), - ), + division: pick( + 'division', + ).letOrNull((it) => (middle: it('middle').asIntOrNull(), end: it('end').asIntOrNull())), ); } String _nagToJugdmentName(int nag) => switch (nag) { - 6 => 'Inaccuracy', - 2 => 'Mistake', - 4 => 'Blunder', - int() => '', - }; + 6 => 'Inaccuracy', + 2 => 'Mistake', + 4 => 'Blunder', + int() => '', +}; -typedef ServerAnalysis = ({ - GameId id, - PlayerAnalysis white, - PlayerAnalysis black, -}); +typedef ServerAnalysis = ({GameId id, PlayerAnalysis white, PlayerAnalysis black}); -typedef GameDivision = ({ - int? middle, - int? end, -}); +typedef GameDivision = ({int? middle, int? end}); diff --git a/lib/src/model/game/game_status.dart b/lib/src/model/game/game_status.dart index b091550940..19242a75ca 100644 --- a/lib/src/model/game/game_status.dart +++ b/lib/src/model/game/game_status.dart @@ -55,9 +55,7 @@ extension GameExtension on Pick { return gameStatus; } } - throw PickException( - "value $value at $debugParsingExit can't be casted to GameStatus", - ); + throw PickException("value $value at $debugParsingExit can't be casted to GameStatus"); } GameStatus? asGameStatusOrNull() { diff --git a/lib/src/model/game/game_storage.dart b/lib/src/model/game/game_storage.dart index 5995a5e150..1a5fddc1e7 100644 --- a/lib/src/model/game/game_storage.dart +++ b/lib/src/model/game/game_storage.dart @@ -19,19 +19,13 @@ Future gameStorage(Ref ref) async { const kGameStorageTable = 'game'; -typedef StoredGame = ({ - UserId userId, - DateTime lastModified, - ArchivedGame game, -}); +typedef StoredGame = ({UserId userId, DateTime lastModified, ArchivedGame game}); class GameStorage { const GameStorage(this._db); final Database _db; - Future count({ - UserId? userId, - }) async { + Future count({UserId? userId}) async { final list = await _db.query( kGameStorageTable, where: 'userId = ?', @@ -48,14 +42,8 @@ class GameStorage { }) async { final list = await _db.query( kGameStorageTable, - where: [ - 'userId = ?', - if (until != null) 'lastModified < ?', - ].join(' AND '), - whereArgs: [ - userId ?? kStorageAnonId, - if (until != null) until.toIso8601String(), - ], + where: ['userId = ?', if (until != null) 'lastModified < ?'].join(' AND '), + whereArgs: [userId ?? kStorageAnonId, if (until != null) until.toIso8601String()], orderBy: 'lastModified DESC', limit: max, ); @@ -65,9 +53,7 @@ class GameStorage { final raw = e['data']! as String; final json = jsonDecode(raw); if (json is! Map) { - throw const FormatException( - '[GameStorage] cannot fetch game: expected an object', - ); + throw const FormatException('[GameStorage] cannot fetch game: expected an object'); } return ( userId: UserId(e['userId']! as String), @@ -75,17 +61,12 @@ class GameStorage { game: ArchivedGame.fromJson(json), ); }) - .where( - (e) => - filter.perfs.isEmpty || filter.perfs.contains(e.game.meta.perf), - ) + .where((e) => filter.perfs.isEmpty || filter.perfs.contains(e.game.meta.perf)) .where((e) => filter.side == null || filter.side == e.game.youAre) .toIList(); } - Future fetch({ - required GameId gameId, - }) async { + Future fetch({required GameId gameId}) async { final list = await _db.query( kGameStorageTable, where: 'gameId = ?', @@ -97,9 +78,7 @@ class GameStorage { if (raw != null) { final json = jsonDecode(raw); if (json is! Map) { - throw const FormatException( - '[GameStorage] cannot fetch game: expected an object', - ); + throw const FormatException('[GameStorage] cannot fetch game: expected an object'); } return ArchivedGame.fromJson(json); } @@ -107,23 +86,15 @@ class GameStorage { } Future save(ArchivedGame game) async { - await _db.insert( - kGameStorageTable, - { - 'userId': game.me?.user?.id.toString() ?? kStorageAnonId, - 'gameId': game.id.toString(), - 'lastModified': DateTime.now().toIso8601String(), - 'data': jsonEncode(game.toJson()), - }, - conflictAlgorithm: ConflictAlgorithm.replace, - ); + await _db.insert(kGameStorageTable, { + 'userId': game.me?.user?.id.toString() ?? kStorageAnonId, + 'gameId': game.id.toString(), + 'lastModified': DateTime.now().toIso8601String(), + 'data': jsonEncode(game.toJson()), + }, conflictAlgorithm: ConflictAlgorithm.replace); } Future delete(GameId gameId) async { - await _db.delete( - kGameStorageTable, - where: 'gameId = ?', - whereArgs: [gameId.toString()], - ); + await _db.delete(kGameStorageTable, where: 'gameId = ?', whereArgs: [gameId.toString()]); } } diff --git a/lib/src/model/game/material_diff.dart b/lib/src/model/game/material_diff.dart index 52623a11c0..b0bde7bd79 100644 --- a/lib/src/model/game/material_diff.dart +++ b/lib/src/model/game/material_diff.dart @@ -26,10 +26,8 @@ const IMap pieceScores = IMapConst({ class MaterialDiff with _$MaterialDiff { const MaterialDiff._(); - const factory MaterialDiff({ - required MaterialDiffSide black, - required MaterialDiffSide white, - }) = _MaterialDiff; + const factory MaterialDiff({required MaterialDiffSide black, required MaterialDiffSide white}) = + _MaterialDiff; factory MaterialDiff.fromBoard(Board board, {Board? startingPosition}) { int score = 0; @@ -37,11 +35,9 @@ class MaterialDiff with _$MaterialDiff { final IMap whiteCount = board.materialCount(Side.white); final IMap blackStartingCount = - startingPosition?.materialCount(Side.black) ?? - Board.standard.materialCount(Side.black); + startingPosition?.materialCount(Side.black) ?? Board.standard.materialCount(Side.black); final IMap whiteStartingCount = - startingPosition?.materialCount(Side.white) ?? - Board.standard.materialCount(Side.white); + startingPosition?.materialCount(Side.white) ?? Board.standard.materialCount(Side.white); IMap subtractPieceCounts( IMap startingCount, @@ -52,10 +48,8 @@ class MaterialDiff with _$MaterialDiff { ); } - final IMap blackCapturedPieces = - subtractPieceCounts(whiteStartingCount, whiteCount); - final IMap whiteCapturedPieces = - subtractPieceCounts(blackStartingCount, blackCount); + final IMap blackCapturedPieces = subtractPieceCounts(whiteStartingCount, whiteCount); + final IMap whiteCapturedPieces = subtractPieceCounts(blackStartingCount, blackCount); Map count; Map black; diff --git a/lib/src/model/game/over_the_board_game.dart b/lib/src/model/game/over_the_board_game.dart index daa88b95b0..20633e646e 100644 --- a/lib/src/model/game/over_the_board_game.dart +++ b/lib/src/model/game/over_the_board_game.dart @@ -15,8 +15,7 @@ part 'over_the_board_game.g.dart'; /// /// See [PlayableGame] for a game that is played online. @Freezed(fromJson: true, toJson: true) -abstract class OverTheBoardGame - with _$OverTheBoardGame, BaseGame, IndexableSteps { +abstract class OverTheBoardGame with _$OverTheBoardGame, BaseGame, IndexableSteps { const OverTheBoardGame._(); @override @@ -34,8 +33,7 @@ abstract class OverTheBoardGame @Assert('steps.isNotEmpty') factory OverTheBoardGame({ - @JsonKey(fromJson: stepsFromJson, toJson: stepsToJson) - required IList steps, + @JsonKey(fromJson: stepsFromJson, toJson: stepsToJson) required IList steps, required GameMeta meta, required String? initialFen, required GameStatus status, diff --git a/lib/src/model/game/playable_game.dart b/lib/src/model/game/playable_game.dart index 937ccaf03c..abd6ea85e7 100644 --- a/lib/src/model/game/playable_game.dart +++ b/lib/src/model/game/playable_game.dart @@ -30,9 +30,7 @@ part 'playable_game.freezed.dart'; /// See also: /// - [ArchivedGame] for a game that is finished and not owned by the current user. @freezed -class PlayableGame - with _$PlayableGame, BaseGame, IndexableSteps - implements BaseGame { +class PlayableGame with _$PlayableGame, BaseGame, IndexableSteps implements BaseGame { const PlayableGame._(); @Assert('steps.isNotEmpty') @@ -76,16 +74,18 @@ class PlayableGame } /// Player of the playing point of view. Null if spectating. - Player? get me => youAre == null - ? null - : youAre == Side.white + Player? get me => + youAre == null + ? null + : youAre == Side.white ? white : black; /// Opponent from the playing point of view. Null if spectating. - Player? get opponent => youAre == null - ? null - : youAre == Side.white + Player? get opponent => + youAre == null + ? null + : youAre == Side.white ? black : white; @@ -110,12 +110,8 @@ class PlayableGame (meta.rules == null || !meta.rules!.contains(GameRule.noAbort)); bool get resignable => playable && !abortable; bool get drawable => - playable && - lastPosition.fullmoves >= 2 && - !(me?.offeringDraw == true) && - !hasAI; - bool get rematchable => - meta.rules == null || !meta.rules!.contains(GameRule.noRematch); + playable && lastPosition.fullmoves >= 2 && !(me?.offeringDraw == true) && !hasAI; + bool get rematchable => meta.rules == null || !meta.rules!.contains(GameRule.noRematch); bool get canTakeback => takebackable && playable && @@ -131,8 +127,7 @@ class PlayableGame (meta.rules == null || !meta.rules!.contains(GameRule.noClaimWin)); bool get userAnalysable => - finished && steps.length > 4 || - (playable && (clock == null || youAre == null)); + finished && steps.length > 4 || (playable && (clock == null || youAre == null)); ArchivedGame toArchivedGame({required DateTime finishedAt}) { return ArchivedGame( @@ -153,12 +148,10 @@ class PlayableGame status: status, white: white, black: black, - clock: meta.clock != null - ? ( - initial: meta.clock!.initial, - increment: meta.clock!.increment, - ) - : null, + clock: + meta.clock != null + ? (initial: meta.clock!.initial, increment: meta.clock!.increment) + : null, opening: meta.opening, ), initialFen: initialFen, @@ -224,17 +217,15 @@ PlayableGame _playableGameFromPick(RequiredPick pick) { return PlayableGame( id: requiredGamePick('id').asGameIdOrThrow(), meta: meta, - source: requiredGamePick('source').letOrThrow( - (pick) => - GameSource.nameMap[pick.asStringOrThrow()] ?? GameSource.unknown, - ), + source: requiredGamePick( + 'source', + ).letOrThrow((pick) => GameSource.nameMap[pick.asStringOrThrow()] ?? GameSource.unknown), initialFen: initialFen, steps: steps.toIList(), white: pick('white').letOrThrow(_playerFromUserGamePick), black: pick('black').letOrThrow(_playerFromUserGamePick), clock: pick('clock').letOrNull(_playableClockDataFromPick), - correspondenceClock: - pick('correspondence').letOrNull(_correspondenceClockDataFromPick), + correspondenceClock: pick('correspondence').letOrNull(_correspondenceClockDataFromPick), status: pick('game', 'status').asGameStatusOrThrow(), winner: pick('game', 'winner').asSideOrNull(), boosted: pick('game', 'boosted').asBoolOrNull(), @@ -243,16 +234,14 @@ PlayableGame _playableGameFromPick(RequiredPick pick) { takebackable: pick('takebackable').asBoolOrFalse(), youAre: pick('youAre').asSideOrNull(), prefs: pick('prefs').letOrNull(_gamePrefsFromPick), - expiration: pick('expiration').letOrNull( - (it) { - final idle = it('idleMillis').asDurationFromMilliSecondsOrThrow(); - return ( - idle: idle, - timeToMove: it('millisToMove').asDurationFromMilliSecondsOrThrow(), - movedAt: DateTime.now().subtract(idle), - ); - }, - ), + expiration: pick('expiration').letOrNull((it) { + final idle = it('idleMillis').asDurationFromMilliSecondsOrThrow(); + return ( + idle: idle, + timeToMove: it('millisToMove').asDurationFromMilliSecondsOrThrow(), + movedAt: DateTime.now().subtract(idle), + ); + }), rematch: pick('game', 'rematch').asGameIdOrNull(), ); } @@ -272,14 +261,11 @@ GameMeta _playableGameMetaFromPick(RequiredPick pick) { moreTime: cPick('moretime').asDurationFromSecondsOrNull(), ), ), - daysPerTurn: pick('correspondence') - .letOrNull((ccPick) => ccPick('daysPerTurn').asIntOrThrow()), + daysPerTurn: pick('correspondence').letOrNull((ccPick) => ccPick('daysPerTurn').asIntOrThrow()), startedAtTurn: pick('game', 'startedAtTurn').asIntOrNull(), rules: pick('game', 'rules').letOrNull( (it) => ISet( - pick.asListOrThrow( - (e) => GameRule.nameMap[e.asStringOrThrow()] ?? GameRule.unknown, - ), + pick.asListOrThrow((e) => GameRule.nameMap[e.asStringOrThrow()] ?? GameRule.unknown), ), ), division: pick('division').letOrNull(_divisionFromPick), @@ -317,9 +303,7 @@ PlayableClockData _playableClockDataFromPick(RequiredPick pick) { running: pick('running').asBoolOrThrow(), white: pick('white').asDurationFromSecondsOrThrow(), black: pick('black').asDurationFromSecondsOrThrow(), - lag: pick('lag').letOrNull( - (it) => Duration(milliseconds: it.asIntOrThrow() * 10), - ), + lag: pick('lag').letOrNull((it) => Duration(milliseconds: it.asIntOrThrow() * 10)), at: DateTime.now(), ); } diff --git a/lib/src/model/game/player.dart b/lib/src/model/game/player.dart index e3abfebb38..8607e2a5c6 100644 --- a/lib/src/model/game/player.dart +++ b/lib/src/model/game/player.dart @@ -50,10 +50,7 @@ class Player with _$Player { user?.name ?? name ?? (aiLevel != null - ? context.l10n.aiNameLevelAiLevel( - 'Stockfish', - aiLevel.toString(), - ) + ? context.l10n.aiNameLevelAiLevel('Stockfish', aiLevel.toString()) : context.l10n.anonymous); Player setOnGame(bool onGame) { @@ -77,6 +74,5 @@ class PlayerAnalysis with _$PlayerAnalysis { int? accuracy, }) = _PlayerAnalysis; - factory PlayerAnalysis.fromJson(Map json) => - _$PlayerAnalysisFromJson(json); + factory PlayerAnalysis.fromJson(Map json) => _$PlayerAnalysisFromJson(json); } diff --git a/lib/src/model/lobby/create_game_service.dart b/lib/src/model/lobby/create_game_service.dart index fea652f63a..fef13347f5 100644 --- a/lib/src/model/lobby/create_game_service.dart +++ b/lib/src/model/lobby/create_game_service.dart @@ -17,11 +17,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'create_game_service.g.dart'; -typedef ChallengeResponse = ({ - GameFullId? gameFullId, - Challenge? challenge, - ChallengeDeclineReason? declineReason, -}); +typedef ChallengeResponse = + ({GameFullId? gameFullId, Challenge? challenge, ChallengeDeclineReason? declineReason}); /// A provider for the [CreateGameService]. @riverpod @@ -50,7 +47,8 @@ class CreateGameService { ChallengeId, StreamSubscription, // socket connects events StreamSubscription, // socket events - )? _challengeConnection; + )? + _challengeConnection; Timer? _challengePingTimer; @@ -90,8 +88,7 @@ class CreateGameService { } try { - await LobbyRepository(lichessClient) - .createSeek(actualSeek, sri: socketClient.sri); + await LobbyRepository(lichessClient).createSeek(actualSeek, sri: socketClient.sri); } catch (e) { _log.warning('Failed to create seek', e); // if the completer is not yet completed, complete it with an error @@ -108,10 +105,9 @@ class CreateGameService { _log.info('Creating new correspondence game'); await ref.withClient( - (client) => LobbyRepository(client).createSeek( - seek, - sri: ref.read(preloadedDataProvider).requireValue.sri, - ), + (client) => LobbyRepository( + client, + ).createSeek(seek, sri: ref.read(preloadedDataProvider).requireValue.sri), ); } @@ -120,9 +116,7 @@ class CreateGameService { /// Will listen to the challenge socket and await the response from the destinated user. /// Returns the challenge, along with [GameFullId] if the challenge was accepted, /// or the [ChallengeDeclineReason] if the challenge was declined. - Future newRealTimeChallenge( - ChallengeRequest challengeReq, - ) async { + Future newRealTimeChallenge(ChallengeRequest challengeReq) async { assert(challengeReq.timeControl == ChallengeTimeControlType.clock); if (_challengeConnection != null) { @@ -130,8 +124,7 @@ class CreateGameService { } // ensure the pending connection is closed in any case - final completer = Completer() - ..future.whenComplete(dispose); + final completer = Completer()..future.whenComplete(dispose); try { _log.info('Creating new challenge game'); @@ -162,21 +155,17 @@ class CreateGameService { try { final updatedChallenge = await repo.show(challenge.id); if (updatedChallenge.gameFullId != null) { - completer.complete( - ( - gameFullId: updatedChallenge.gameFullId, - challenge: null, - declineReason: null, - ), - ); + completer.complete(( + gameFullId: updatedChallenge.gameFullId, + challenge: null, + declineReason: null, + )); } else if (updatedChallenge.status == ChallengeStatus.declined) { - completer.complete( - ( - gameFullId: null, - challenge: challenge, - declineReason: updatedChallenge.declineReason, - ), - ); + completer.complete(( + gameFullId: null, + challenge: challenge, + declineReason: updatedChallenge.declineReason, + )); } } catch (e) { _log.warning('Failed to reload challenge', e); @@ -199,16 +188,12 @@ class CreateGameService { /// /// Returns the created challenge immediately. If the challenge is accepted, /// a notification will be sent to the user when the game starts. - Future newCorrespondenceChallenge( - ChallengeRequest challenge, - ) async { + Future newCorrespondenceChallenge(ChallengeRequest challenge) async { assert(challenge.timeControl == ChallengeTimeControlType.correspondence); _log.info('Creating new correspondence challenge'); - return ref.withClient( - (client) => ChallengeRepository(client).create(challenge), - ); + return ref.withClient((client) => ChallengeRepository(client).create(challenge)); } /// Cancel the current game creation. diff --git a/lib/src/model/lobby/game_seek.dart b/lib/src/model/lobby/game_seek.dart index b44d7ce262..ec9e8386b6 100644 --- a/lib/src/model/lobby/game_seek.dart +++ b/lib/src/model/lobby/game_seek.dart @@ -26,10 +26,7 @@ class GameSeek with _$GameSeek { 'ratingDelta == null || ratingRange == null', 'Rating delta and rating range cannot be used together', ) - @Assert( - 'clock != null || days != null', - 'Either clock or days must be set', - ) + @Assert('clock != null || days != null', 'Either clock or days must be set') const factory GameSeek({ (Duration time, Duration increment)? clock, int? days, @@ -46,10 +43,7 @@ class GameSeek with _$GameSeek { /// Construct a game seek from a predefined time control. factory GameSeek.fastPairing(TimeIncrement setup, AuthSessionState? session) { return GameSeek( - clock: ( - Duration(seconds: setup.time), - Duration(seconds: setup.increment), - ), + clock: (Duration(seconds: setup.time), Duration(seconds: setup.increment)), rated: session != null, ); } @@ -63,8 +57,7 @@ class GameSeek with _$GameSeek { ), rated: account != null && setup.customRated, variant: setup.customVariant, - ratingRange: - account != null ? setup.ratingRangeFromCustom(account) : null, + ratingRange: account != null ? setup.ratingRangeFromCustom(account) : null, ); } @@ -74,25 +67,19 @@ class GameSeek with _$GameSeek { days: setup.customDaysPerTurn, rated: account != null && setup.customRated, variant: setup.customVariant, - ratingRange: - account != null ? setup.ratingRangeFromCustom(account) : null, + ratingRange: account != null ? setup.ratingRangeFromCustom(account) : null, ); } /// Construct a game seek from a playable game to find a new opponent, using /// the same time control, variant and rated status. - factory GameSeek.newOpponentFromGame( - PlayableGame game, - GameSetupPrefs setup, - ) { + factory GameSeek.newOpponentFromGame(PlayableGame game, GameSetupPrefs setup) { return GameSeek( - clock: game.meta.clock != null - ? (game.meta.clock!.initial, game.meta.clock!.increment) - : null, + clock: + game.meta.clock != null ? (game.meta.clock!.initial, game.meta.clock!.increment) : null, rated: game.meta.rated, variant: game.meta.variant, - ratingDelta: - game.source == GameSource.lobby ? setup.customRatingDelta : null, + ratingDelta: game.source == GameSource.lobby ? setup.customRatingDelta : null, ); } @@ -109,27 +96,20 @@ class GameSeek with _$GameSeek { return copyWith(ratingRange: range, ratingDelta: null); } - TimeIncrement? get timeIncrement => clock != null - ? TimeIncrement( - clock!.$1.inSeconds, - clock!.$2.inSeconds, - ) - : null; + TimeIncrement? get timeIncrement => + clock != null ? TimeIncrement(clock!.$1.inSeconds, clock!.$2.inSeconds) : null; Perf get perf => Perf.fromVariantAndSpeed( - variant ?? Variant.standard, - timeIncrement != null - ? Speed.fromTimeIncrement(timeIncrement!) - : Speed.correspondence, - ); + variant ?? Variant.standard, + timeIncrement != null ? Speed.fromTimeIncrement(timeIncrement!) : Speed.correspondence, + ); Map get requestBody => { - if (clock != null) 'time': (clock!.$1.inSeconds / 60).toString(), - if (clock != null) 'increment': clock!.$2.inSeconds.toString(), - if (days != null) 'days': days.toString(), - 'rated': rated.toString(), - if (variant != null) 'variant': variant!.name, - if (ratingRange != null) - 'ratingRange': '${ratingRange!.$1}-${ratingRange!.$2}', - }; + if (clock != null) 'time': (clock!.$1.inSeconds / 60).toString(), + if (clock != null) 'increment': clock!.$2.inSeconds.toString(), + if (days != null) 'days': days.toString(), + 'rated': rated.toString(), + if (variant != null) 'variant': variant!.name, + if (ratingRange != null) 'ratingRange': '${ratingRange!.$1}-${ratingRange!.$2}', + }; } diff --git a/lib/src/model/lobby/game_setup_preferences.dart b/lib/src/model/lobby/game_setup_preferences.dart index 6e0103195c..ca9da1e218 100644 --- a/lib/src/model/lobby/game_setup_preferences.dart +++ b/lib/src/model/lobby/game_setup_preferences.dart @@ -22,8 +22,7 @@ class GameSetupPreferences extends _$GameSetupPreferences GameSetupPrefs defaults({LightUser? user}) => GameSetupPrefs.defaults; @override - GameSetupPrefs fromJson(Map json) => - GameSetupPrefs.fromJson(json); + GameSetupPrefs fromJson(Map json) => GameSetupPrefs.fromJson(json); @override GameSetupPrefs build() { @@ -91,17 +90,10 @@ class GameSetupPrefs with _$GameSetupPrefs implements Serializable { customDaysPerTurn: 3, ); - Speed get speedFromCustom => Speed.fromTimeIncrement( - TimeIncrement( - customTimeSeconds, - customIncrementSeconds, - ), - ); + Speed get speedFromCustom => + Speed.fromTimeIncrement(TimeIncrement(customTimeSeconds, customIncrementSeconds)); - Perf get perfFromCustom => Perf.fromVariantAndSpeed( - customVariant, - speedFromCustom, - ); + Perf get perfFromCustom => Perf.fromVariantAndSpeed(customVariant, speedFromCustom); /// Returns the rating range for the custom setup, or null if the user /// doesn't have a rating for the custom setup perf. @@ -123,33 +115,9 @@ class GameSetupPrefs with _$GameSetupPrefs implements Serializable { } } -const kSubtractingRatingRange = [ - -500, - -450, - -400, - -350, - -300, - -250, - -200, - -150, - -100, - -50, - 0, -]; +const kSubtractingRatingRange = [-500, -450, -400, -350, -300, -250, -200, -150, -100, -50, 0]; -const kAddingRatingRange = [ - 0, - 50, - 100, - 150, - 200, - 250, - 300, - 350, - 400, - 450, - 500, -]; +const kAddingRatingRange = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500]; const kAvailableTimesInSeconds = [ 0, diff --git a/lib/src/model/lobby/lobby_numbers.dart b/lib/src/model/lobby/lobby_numbers.dart index b7bf6df761..88b1fea700 100644 --- a/lib/src/model/lobby/lobby_numbers.dart +++ b/lib/src/model/lobby/lobby_numbers.dart @@ -22,10 +22,7 @@ class LobbyNumbers extends _$LobbyNumbers { _socketSubscription = socketGlobalStream.listen((event) { if (event.topic == 'n') { final data = event.data as Map; - state = ( - nbPlayers: data['nbPlayers']!, - nbGames: data['nbGames']!, - ); + state = (nbPlayers: data['nbPlayers']!, nbGames: data['nbGames']!); } }); diff --git a/lib/src/model/lobby/lobby_repository.dart b/lib/src/model/lobby/lobby_repository.dart index db59a23602..4565a91b72 100644 --- a/lib/src/model/lobby/lobby_repository.dart +++ b/lib/src/model/lobby/lobby_repository.dart @@ -15,9 +15,7 @@ part 'lobby_repository.g.dart'; @riverpod Future> correspondenceChallenges(Ref ref) { - return ref.withClient( - (client) => LobbyRepository(client).getCorrespondenceChallenges(), - ); + return ref.withClient((client) => LobbyRepository(client).getCorrespondenceChallenges()); } class LobbyRepository { @@ -29,10 +27,7 @@ class LobbyRepository { final uri = Uri(path: '/api/board/seek', queryParameters: {'sri': sri}); final response = await client.post(uri, body: seek.requestBody); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to create seek: ${response.statusCode}', - uri, - ); + throw http.ClientException('Failed to create seek: ${response.statusCode}', uri); } } @@ -40,10 +35,7 @@ class LobbyRepository { final uri = Uri(path: '/api/board/seek', queryParameters: {'sri': sri}); final response = await client.delete(uri); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to cancel seek: ${response.statusCode}', - uri, - ); + throw http.ClientException('Failed to cancel seek: ${response.statusCode}', uri); } } diff --git a/lib/src/model/notifications/notification_service.dart b/lib/src/model/notifications/notification_service.dart index b49a82a152..01f7119b51 100644 --- a/lib/src/model/notifications/notification_service.dart +++ b/lib/src/model/notifications/notification_service.dart @@ -25,8 +25,7 @@ final _logger = Logger('NotificationService'); /// A provider instance of the [FlutterLocalNotificationsPlugin]. @Riverpod(keepAlive: true) -FlutterLocalNotificationsPlugin notificationDisplay(Ref _) => - FlutterLocalNotificationsPlugin(); +FlutterLocalNotificationsPlugin notificationDisplay(Ref _) => FlutterLocalNotificationsPlugin(); /// A provider instance of the [NotificationService]. @Riverpod(keepAlive: true) @@ -54,12 +53,11 @@ class NotificationService { StreamSubscription? _fcmTokenRefreshSubscription; /// The connectivity changes stream subscription. - ProviderSubscription>? - _connectivitySubscription; + ProviderSubscription>? _connectivitySubscription; /// The stream controller for notification responses. - static final StreamController - _responseStreamController = StreamController.broadcast(); + static final StreamController _responseStreamController = + StreamController.broadcast(); /// The stream subscription for notification responses. StreamSubscription? _responseStreamSubscription; @@ -82,8 +80,7 @@ class NotificationService { /// and after [LichessBinding.initializeNotifications] has been called. Future start() async { // listen for connectivity changes to register device once the app is online - _connectivitySubscription = - _ref.listen(connectivityChangesProvider, (prev, current) async { + _connectivitySubscription = _ref.listen(connectivityChangesProvider, (prev, current) async { if (current.value?.isOnline == true && !_registeredDevice) { try { await registerDevice(); @@ -95,8 +92,7 @@ class NotificationService { }); // Listen for incoming messages while the app is in the foreground. - LichessBinding.instance.firebaseMessagingOnMessage - .listen((RemoteMessage message) { + LichessBinding.instance.firebaseMessagingOnMessage.listen((RemoteMessage message) { _processFcmMessage(message, fromBackground: false); }); @@ -118,9 +114,9 @@ class NotificationService { ); // Listen for token refresh and update the token on the server accordingly. - _fcmTokenRefreshSubscription = LichessBinding - .instance.firebaseMessaging.onTokenRefresh - .listen((String token) { + _fcmTokenRefreshSubscription = LichessBinding.instance.firebaseMessaging.onTokenRefresh.listen(( + String token, + ) { _registerToken(token); }); @@ -134,12 +130,12 @@ class NotificationService { } // Handle any other interaction that caused the app to open when in background. - LichessBinding.instance.firebaseMessagingOnMessageOpenedApp - .listen(_handleFcmMessageOpenedApp); + LichessBinding.instance.firebaseMessagingOnMessageOpenedApp.listen(_handleFcmMessageOpenedApp); // start listening for notification responses - _responseStreamSubscription = - _responseStreamController.stream.listen(_dispatchNotificationResponse); + _responseStreamSubscription = _responseStreamController.stream.listen( + _dispatchNotificationResponse, + ); } /// Shows a notification. @@ -184,22 +180,15 @@ class NotificationService { switch (notification) { case CorresGameUpdateNotification(fullId: final gameFullId): - _ref - .read(correspondenceServiceProvider) - .onNotificationResponse(gameFullId); + _ref.read(correspondenceServiceProvider).onNotificationResponse(gameFullId); case ChallengeNotification(challenge: final challenge): - _ref.read(challengeServiceProvider).onNotificationResponse( - response.actionId, - challenge, - ); + _ref.read(challengeServiceProvider).onNotificationResponse(response.actionId, challenge); } } /// Function called by the notification plugin when a notification has been tapped on. static void onDidReceiveNotificationResponse(NotificationResponse response) { - _logger.fine( - 'received local notification ${response.id} response in foreground.', - ); + _logger.fine('received local notification ${response.id} response in foreground.'); _responseStreamController.add(response); } @@ -214,9 +203,7 @@ class NotificationService { // TODO: handle other notification types case UnhandledFcmMessage(data: final data): - _logger.warning( - 'Received unhandled FCM notification type: ${data['lichess.type']}', - ); + _logger.warning('Received unhandled FCM notification type: ${data['lichess.type']}'); case MalformedFcmMessage(data: final data): _logger.severe('Received malformed FCM message: $data'); @@ -238,6 +225,7 @@ class NotificationService { /// badge count according to the value held by the server. Future _processFcmMessage( RemoteMessage message, { + /// Whether the message was received while the app was in the background. required bool fromBackground, }) async { @@ -249,34 +237,24 @@ class NotificationService { switch (parsedMessage) { case CorresGameUpdateFcmMessage( - fullId: final fullId, - game: final game, - notification: final notification - ): + fullId: final fullId, + game: final game, + notification: final notification, + ): if (game != null) { - await _ref.read(correspondenceServiceProvider).onServerUpdateEvent( - fullId, - game, - fromBackground: fromBackground, - ); + await _ref + .read(correspondenceServiceProvider) + .onServerUpdateEvent(fullId, game, fromBackground: fromBackground); } if (fromBackground == false && notification != null) { - await show( - CorresGameUpdateNotification( - fullId, - notification.title!, - notification.body!, - ), - ); + await show(CorresGameUpdateNotification(fullId, notification.title!, notification.body!)); } // TODO: handle other notification types case UnhandledFcmMessage(data: final data): - _logger.warning( - 'Received unhandled FCM notification type: ${data['lichess.type']}', - ); + _logger.warning('Received unhandled FCM notification type: ${data['lichess.type']}'); case MalformedFcmMessage(data: final data): _logger.severe('Received malformed FCM message: $data'); @@ -316,17 +294,14 @@ class NotificationService { return; } try { - await _ref.withClient( - (client) => client.post(Uri(path: '/mobile/unregister')), - ); + await _ref.withClient((client) => client.post(Uri(path: '/mobile/unregister'))); } catch (e, st) { _logger.severe('could not unregister device; $e', e, st); } } Future _registerToken(String token) async { - final settings = await LichessBinding.instance.firebaseMessaging - .getNotificationSettings(); + final settings = await LichessBinding.instance.firebaseMessaging.getNotificationSettings(); if (settings.authorizationStatus == AuthorizationStatus.denied) { return; } @@ -336,18 +311,14 @@ class NotificationService { return; } try { - await _ref.withClient( - (client) => client.post(Uri(path: '/mobile/register/firebase/$token')), - ); + await _ref.withClient((client) => client.post(Uri(path: '/mobile/register/firebase/$token'))); } catch (e, st) { _logger.severe('could not register device; $e', e, st); } } @pragma('vm:entry-point') - static Future _firebaseMessagingBackgroundHandler( - RemoteMessage message, - ) async { + static Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { // create a new provider scope for the background isolate final ref = ProviderContainer(); @@ -356,10 +327,7 @@ class NotificationService { await ref.read(preloadedDataProvider.future); try { - await ref.read(notificationServiceProvider)._processFcmMessage( - message, - fromBackground: true, - ); + await ref.read(notificationServiceProvider)._processFcmMessage(message, fromBackground: true); ref.dispose(); } catch (e) { diff --git a/lib/src/model/notifications/notifications.dart b/lib/src/model/notifications/notifications.dart index 1b3fdd6dfd..771887faa8 100644 --- a/lib/src/model/notifications/notifications.dart +++ b/lib/src/model/notifications/notifications.dart @@ -48,11 +48,10 @@ sealed class FcmMessage { final round = message.data['lichess.round'] as String?; if (gameFullId != null) { final fullId = GameFullId(gameFullId); - final game = round != null - ? PlayableGame.fromServerJson( - jsonDecode(round) as Map, - ) - : null; + final game = + round != null + ? PlayableGame.fromServerJson(jsonDecode(round) as Map) + : null; return CorresGameUpdateFcmMessage( fullId, game: game, @@ -71,11 +70,7 @@ sealed class FcmMessage { /// An [FcmMessage] that represents a correspondence game update. @immutable class CorresGameUpdateFcmMessage extends FcmMessage { - const CorresGameUpdateFcmMessage( - this.fullId, { - required this.game, - required this.notification, - }); + const CorresGameUpdateFcmMessage(this.fullId, {required this.game, required this.notification}); final GameFullId fullId; final PlayableGame? game; @@ -137,10 +132,7 @@ sealed class LocalNotification { /// /// See [LocalNotification.fromJson] where the [channelId] is used to determine the /// concrete type of the notification, to be able to deserialize it. - Map get payload => { - 'channel': channelId, - ..._concretePayload, - }; + Map get payload => {'channel': channelId, ..._concretePayload}; /// The actual payload of the notification. /// @@ -173,8 +165,8 @@ sealed class LocalNotification { /// are generated server side and are included in the FCM message's [RemoteMessage.notification] field. class CorresGameUpdateNotification extends LocalNotification { const CorresGameUpdateNotification(this.fullId, String title, String body) - : _title = title, - _body = body; + : _title = title, + _body = body; final GameFullId fullId; @@ -196,10 +188,10 @@ class CorresGameUpdateNotification extends LocalNotification { @override Map get _concretePayload => { - 'fullId': fullId.toJson(), - 'title': _title, - 'body': _body, - }; + 'fullId': fullId.toJson(), + 'title': _title, + 'body': _body, + }; @override String title(_) => _title; @@ -209,15 +201,15 @@ class CorresGameUpdateNotification extends LocalNotification { @override NotificationDetails details(AppLocalizations l10n) => NotificationDetails( - android: AndroidNotificationDetails( - channelId, - l10n.preferencesNotifyGameEvent, - importance: Importance.high, - priority: Priority.defaultPriority, - autoCancel: true, - ), - iOS: DarwinNotificationDetails(threadIdentifier: channelId), - ); + android: AndroidNotificationDetails( + channelId, + l10n.preferencesNotifyGameEvent, + importance: Importance.high, + priority: Priority.defaultPriority, + autoCancel: true, + ), + iOS: DarwinNotificationDetails(threadIdentifier: channelId), + ); } /// A notification for a received challenge. @@ -230,8 +222,7 @@ class ChallengeNotification extends LocalNotification { final Challenge challenge; factory ChallengeNotification.fromJson(Map json) { - final challenge = - Challenge.fromJson(json['challenge'] as Map); + final challenge = Challenge.fromJson(json['challenge'] as Map); return ChallengeNotification(challenge); } @@ -242,76 +233,66 @@ class ChallengeNotification extends LocalNotification { int get id => challenge.id.value.hashCode; @override - Map get _concretePayload => { - 'challenge': challenge.toJson(), - }; + Map get _concretePayload => {'challenge': challenge.toJson()}; @override - String title(AppLocalizations _) => - '${challenge.challenger!.user.name} challenges you!'; + String title(AppLocalizations _) => '${challenge.challenger!.user.name} challenges you!'; @override String body(AppLocalizations l10n) => challenge.description(l10n); @override NotificationDetails details(AppLocalizations l10n) => NotificationDetails( - android: AndroidNotificationDetails( - channelId, - l10n.preferencesNotifyChallenge, - importance: Importance.max, - priority: Priority.high, - autoCancel: false, - actions: [ - if (challenge.variant.isPlaySupported) - AndroidNotificationAction( - 'accept', - l10n.accept, - icon: const DrawableResourceAndroidBitmap('tick'), - showsUserInterface: true, - contextual: true, - ), - AndroidNotificationAction( - 'decline', - l10n.decline, - icon: const DrawableResourceAndroidBitmap('cross'), - showsUserInterface: true, - contextual: true, - ), - ], + android: AndroidNotificationDetails( + channelId, + l10n.preferencesNotifyChallenge, + importance: Importance.max, + priority: Priority.high, + autoCancel: false, + actions: [ + if (challenge.variant.isPlaySupported) + AndroidNotificationAction( + 'accept', + l10n.accept, + icon: const DrawableResourceAndroidBitmap('tick'), + showsUserInterface: true, + contextual: true, + ), + AndroidNotificationAction( + 'decline', + l10n.decline, + icon: const DrawableResourceAndroidBitmap('cross'), + showsUserInterface: true, + contextual: true, ), - iOS: DarwinNotificationDetails( - threadIdentifier: channelId, - categoryIdentifier: challenge.variant.isPlaySupported + ], + ), + iOS: DarwinNotificationDetails( + threadIdentifier: channelId, + categoryIdentifier: + challenge.variant.isPlaySupported ? darwinPlayableVariantCategoryId : darwinUnplayableVariantCategoryId, - ), - ); + ), + ); - static const darwinPlayableVariantCategoryId = - 'challenge-notification-playable-variant'; + static const darwinPlayableVariantCategoryId = 'challenge-notification-playable-variant'; - static const darwinUnplayableVariantCategoryId = - 'challenge-notification-unplayable-variant'; + static const darwinUnplayableVariantCategoryId = 'challenge-notification-unplayable-variant'; - static DarwinNotificationCategory darwinPlayableVariantCategory( - AppLocalizations l10n, - ) => + static DarwinNotificationCategory darwinPlayableVariantCategory(AppLocalizations l10n) => DarwinNotificationCategory( darwinPlayableVariantCategoryId, actions: [ DarwinNotificationAction.plain( 'accept', l10n.accept, - options: { - DarwinNotificationActionOption.foreground, - }, + options: {DarwinNotificationActionOption.foreground}, ), DarwinNotificationAction.plain( 'decline', l10n.decline, - options: { - DarwinNotificationActionOption.foreground, - }, + options: {DarwinNotificationActionOption.foreground}, ), ], options: { @@ -319,18 +300,14 @@ class ChallengeNotification extends LocalNotification { }, ); - static DarwinNotificationCategory darwinUnplayableVariantCategory( - AppLocalizations l10n, - ) => + static DarwinNotificationCategory darwinUnplayableVariantCategory(AppLocalizations l10n) => DarwinNotificationCategory( darwinUnplayableVariantCategoryId, actions: [ DarwinNotificationAction.plain( 'decline', l10n.decline, - options: { - DarwinNotificationActionOption.foreground, - }, + options: {DarwinNotificationActionOption.foreground}, ), ], options: { diff --git a/lib/src/model/opening_explorer/opening_explorer.dart b/lib/src/model/opening_explorer/opening_explorer.dart index 6a8b0ca281..4b62f41823 100644 --- a/lib/src/model/opening_explorer/opening_explorer.dart +++ b/lib/src/model/opening_explorer/opening_explorer.dart @@ -9,11 +9,7 @@ import 'package:lichess_mobile/src/model/opening_explorer/opening_explorer_prefe part 'opening_explorer.freezed.dart'; part 'opening_explorer.g.dart'; -enum OpeningDatabase { - master, - lichess, - player, -} +enum OpeningDatabase { master, lichess, player } @Freezed(fromJson: true) class OpeningExplorerEntry with _$OpeningExplorerEntry { @@ -30,12 +26,8 @@ class OpeningExplorerEntry with _$OpeningExplorerEntry { int? queuePosition, }) = _OpeningExplorerEntry; - factory OpeningExplorerEntry.empty() => const OpeningExplorerEntry( - white: 0, - draws: 0, - black: 0, - moves: IList.empty(), - ); + factory OpeningExplorerEntry.empty() => + const OpeningExplorerEntry(white: 0, draws: 0, black: 0, moves: IList.empty()); factory OpeningExplorerEntry.fromJson(Map json) => _$OpeningExplorerEntryFromJson(json); @@ -57,8 +49,7 @@ class OpeningMove with _$OpeningMove { OpeningExplorerGame? game, }) = _OpeningMove; - factory OpeningMove.fromJson(Map json) => - _$OpeningMoveFromJson(json); + factory OpeningMove.fromJson(Map json) => _$OpeningMoveFromJson(json); int get games { return white + draws + black; @@ -86,16 +77,10 @@ class OpeningExplorerGame with _$OpeningExplorerGame { return OpeningExplorerGame( id: pick('id').asGameIdOrThrow(), white: pick('white').letOrThrow( - (pick) => ( - name: pick('name').asStringOrThrow(), - rating: pick('rating').asIntOrThrow() - ), + (pick) => (name: pick('name').asStringOrThrow(), rating: pick('rating').asIntOrThrow()), ), black: pick('black').letOrThrow( - (pick) => ( - name: pick('name').asStringOrThrow(), - rating: pick('rating').asIntOrThrow() - ), + (pick) => (name: pick('name').asStringOrThrow(), rating: pick('rating').asIntOrThrow()), ), uci: pick('uci').asStringOrNull(), winner: pick('winner').asStringOrNull(), @@ -111,10 +96,7 @@ class OpeningExplorerGame with _$OpeningExplorerGame { } } -enum GameMode { - casual, - rated, -} +enum GameMode { casual, rated } @freezed class OpeningExplorerCacheKey with _$OpeningExplorerCacheKey { diff --git a/lib/src/model/opening_explorer/opening_explorer_preferences.dart b/lib/src/model/opening_explorer/opening_explorer_preferences.dart index 833b70af6c..f3dcbc1188 100644 --- a/lib/src/model/opening_explorer/opening_explorer_preferences.dart +++ b/lib/src/model/opening_explorer/opening_explorer_preferences.dart @@ -18,84 +18,76 @@ class OpeningExplorerPreferences extends _$OpeningExplorerPreferences final prefCategory = PrefCategory.openingExplorer; @override - OpeningExplorerPrefs defaults({LightUser? user}) => - OpeningExplorerPrefs.defaults(user: user); + OpeningExplorerPrefs defaults({LightUser? user}) => OpeningExplorerPrefs.defaults(user: user); @override - OpeningExplorerPrefs fromJson(Map json) => - OpeningExplorerPrefs.fromJson(json); + OpeningExplorerPrefs fromJson(Map json) => OpeningExplorerPrefs.fromJson(json); @override OpeningExplorerPrefs build() { return fetch(); } - Future setDatabase(OpeningDatabase db) => save( - state.copyWith(db: db), - ); + Future setDatabase(OpeningDatabase db) => save(state.copyWith(db: db)); Future setMasterDbSince(int year) => save(state.copyWith(masterDb: state.masterDb.copyWith(sinceYear: year))); Future toggleLichessDbSpeed(Speed speed) => save( - state.copyWith( - lichessDb: state.lichessDb.copyWith( - speeds: state.lichessDb.speeds.contains(speed) + state.copyWith( + lichessDb: state.lichessDb.copyWith( + speeds: + state.lichessDb.speeds.contains(speed) ? state.lichessDb.speeds.remove(speed) : state.lichessDb.speeds.add(speed), - ), - ), - ); + ), + ), + ); Future toggleLichessDbRating(int rating) => save( - state.copyWith( - lichessDb: state.lichessDb.copyWith( - ratings: state.lichessDb.ratings.contains(rating) + state.copyWith( + lichessDb: state.lichessDb.copyWith( + ratings: + state.lichessDb.ratings.contains(rating) ? state.lichessDb.ratings.remove(rating) : state.lichessDb.ratings.add(rating), - ), - ), - ); - - Future setLichessDbSince(DateTime since) => save( - state.copyWith(lichessDb: state.lichessDb.copyWith(since: since)), - ); - - Future setPlayerDbUsernameOrId(String username) => save( - state.copyWith( - playerDb: state.playerDb.copyWith( - username: username, - ), - ), - ); - - Future setPlayerDbSide(Side side) => save( - state.copyWith(playerDb: state.playerDb.copyWith(side: side)), - ); + ), + ), + ); + + Future setLichessDbSince(DateTime since) => + save(state.copyWith(lichessDb: state.lichessDb.copyWith(since: since))); + + Future setPlayerDbUsernameOrId(String username) => + save(state.copyWith(playerDb: state.playerDb.copyWith(username: username))); + + Future setPlayerDbSide(Side side) => + save(state.copyWith(playerDb: state.playerDb.copyWith(side: side))); Future togglePlayerDbSpeed(Speed speed) => save( - state.copyWith( - playerDb: state.playerDb.copyWith( - speeds: state.playerDb.speeds.contains(speed) + state.copyWith( + playerDb: state.playerDb.copyWith( + speeds: + state.playerDb.speeds.contains(speed) ? state.playerDb.speeds.remove(speed) : state.playerDb.speeds.add(speed), - ), - ), - ); + ), + ), + ); Future togglePlayerDbGameMode(GameMode gameMode) => save( - state.copyWith( - playerDb: state.playerDb.copyWith( - gameModes: state.playerDb.gameModes.contains(gameMode) + state.copyWith( + playerDb: state.playerDb.copyWith( + gameModes: + state.playerDb.gameModes.contains(gameMode) ? state.playerDb.gameModes.remove(gameMode) : state.playerDb.gameModes.add(gameMode), - ), - ), - ); + ), + ), + ); - Future setPlayerDbSince(DateTime since) => save( - state.copyWith(playerDb: state.playerDb.copyWith(since: since)), - ); + Future setPlayerDbSince(DateTime since) => + save(state.copyWith(playerDb: state.playerDb.copyWith(since: since))); } @Freezed(fromJson: true, toJson: true) @@ -109,13 +101,12 @@ class OpeningExplorerPrefs with _$OpeningExplorerPrefs implements Serializable { required PlayerDb playerDb, }) = _OpeningExplorerPrefs; - factory OpeningExplorerPrefs.defaults({LightUser? user}) => - OpeningExplorerPrefs( - db: OpeningDatabase.master, - masterDb: MasterDb.defaults, - lichessDb: LichessDb.defaults, - playerDb: PlayerDb.defaults(user: user), - ); + factory OpeningExplorerPrefs.defaults({LightUser? user}) => OpeningExplorerPrefs( + db: OpeningDatabase.master, + masterDb: MasterDb.defaults, + lichessDb: LichessDb.defaults, + playerDb: PlayerDb.defaults(user: user), + ); factory OpeningExplorerPrefs.fromJson(Map json) { return _$OpeningExplorerPrefsFromJson(json); @@ -126,9 +117,7 @@ class OpeningExplorerPrefs with _$OpeningExplorerPrefs implements Serializable { class MasterDb with _$MasterDb { const MasterDb._(); - const factory MasterDb({ - required int sinceYear, - }) = _MasterDb; + const factory MasterDb({required int sinceYear}) = _MasterDb; static const kEarliestYear = 1952; static final now = DateTime.now(); @@ -163,17 +152,7 @@ class LichessDb with _$LichessDb { Speed.classical, Speed.correspondence, }); - static const kAvailableRatings = ISetConst({ - 400, - 1000, - 1200, - 1400, - 1600, - 1800, - 2000, - 2200, - 2500, - }); + static const kAvailableRatings = ISetConst({400, 1000, 1200, 1400, 1600, 1800, 2000, 2200, 2500}); static final earliestDate = DateTime.utc(2012, 12); static final now = DateTime.now(); static const kDaysInAYear = 365; @@ -223,12 +202,12 @@ class PlayerDb with _$PlayerDb { 'All time': earliestDate, }; factory PlayerDb.defaults({LightUser? user}) => PlayerDb( - username: user?.name, - side: Side.white, - speeds: kAvailableSpeeds, - gameModes: GameMode.values.toISet(), - since: earliestDate, - ); + username: user?.name, + side: Side.white, + speeds: kAvailableSpeeds, + gameModes: GameMode.values.toISet(), + since: earliestDate, + ); factory PlayerDb.fromJson(Map json) { return _$PlayerDbFromJson(json); diff --git a/lib/src/model/opening_explorer/opening_explorer_repository.dart b/lib/src/model/opening_explorer/opening_explorer_repository.dart index a6b0921fc5..3a78c779ac 100644 --- a/lib/src/model/opening_explorer/opening_explorer_repository.dart +++ b/lib/src/model/opening_explorer/opening_explorer_repository.dart @@ -18,9 +18,7 @@ class OpeningExplorer extends _$OpeningExplorer { StreamSubscription? _openingExplorerSubscription; @override - Future<({OpeningExplorerEntry entry, bool isIndexing})?> build({ - required String fen, - }) async { + Future<({OpeningExplorerEntry entry, bool isIndexing})?> build({required String fen}) async { await ref.debounce(const Duration(milliseconds: 300)); ref.onDispose(() { _openingExplorerSubscription?.cancel(); @@ -30,15 +28,12 @@ class OpeningExplorer extends _$OpeningExplorer { final client = ref.read(defaultClientProvider); switch (prefs.db) { case OpeningDatabase.master: - final openingExplorer = - await OpeningExplorerRepository(client).getMasterDatabase( - fen, - since: prefs.masterDb.sinceYear, - ); + final openingExplorer = await OpeningExplorerRepository( + client, + ).getMasterDatabase(fen, since: prefs.masterDb.sinceYear); return (entry: openingExplorer, isIndexing: false); case OpeningDatabase.lichess: - final openingExplorer = - await OpeningExplorerRepository(client).getLichessDatabase( + final openingExplorer = await OpeningExplorerRepository(client).getLichessDatabase( fen, speeds: prefs.lichessDb.speeds, ratings: prefs.lichessDb.ratings, @@ -46,8 +41,7 @@ class OpeningExplorer extends _$OpeningExplorer { ); return (entry: openingExplorer, isIndexing: false); case OpeningDatabase.player: - final openingExplorerStream = - await OpeningExplorerRepository(client).getPlayerDatabase( + final openingExplorerStream = await OpeningExplorerRepository(client).getPlayerDatabase( fen, // null check handled by widget usernameOrId: prefs.playerDb.username!, @@ -58,16 +52,15 @@ class OpeningExplorer extends _$OpeningExplorer { ); _openingExplorerSubscription = openingExplorerStream.listen( - (openingExplorer) => state = - AsyncValue.data((entry: openingExplorer, isIndexing: true)), - onDone: () => state.value != null - ? state = AsyncValue.data( - (entry: state.value!.entry, isIndexing: false), - ) - : state = AsyncValue.error( - 'No opening explorer data returned for player ${prefs.playerDb.username}', - StackTrace.current, - ), + (openingExplorer) => state = AsyncValue.data((entry: openingExplorer, isIndexing: true)), + onDone: + () => + state.value != null + ? state = AsyncValue.data((entry: state.value!.entry, isIndexing: false)) + : state = AsyncValue.error( + 'No opening explorer data returned for player ${prefs.playerDb.username}', + StackTrace.current, + ), ); return null; } @@ -79,19 +72,12 @@ class OpeningExplorerRepository { final Client client; - Future getMasterDatabase( - String fen, { - int? since, - }) { + Future getMasterDatabase(String fen, {int? since}) { return client.readJson( - Uri.https( - kLichessOpeningExplorerHost, - '/masters', - { - 'fen': fen, - if (since != null) 'since': since.toString(), - }, - ), + Uri.https(kLichessOpeningExplorerHost, '/masters', { + 'fen': fen, + if (since != null) 'since': since.toString(), + }), mapper: OpeningExplorerEntry.fromJson, ); } @@ -103,17 +89,12 @@ class OpeningExplorerRepository { DateTime? since, }) { return client.readJson( - Uri.https( - kLichessOpeningExplorerHost, - '/lichess', - { - 'fen': fen, - if (speeds.isNotEmpty) - 'speeds': speeds.map((speed) => speed.name).join(','), - if (ratings.isNotEmpty) 'ratings': ratings.join(','), - if (since != null) 'since': '${since.year}-${since.month}', - }, - ), + Uri.https(kLichessOpeningExplorerHost, '/lichess', { + 'fen': fen, + if (speeds.isNotEmpty) 'speeds': speeds.map((speed) => speed.name).join(','), + if (ratings.isNotEmpty) 'ratings': ratings.join(','), + if (since != null) 'since': '${since.year}-${since.month}', + }), mapper: OpeningExplorerEntry.fromJson, ); } @@ -127,20 +108,14 @@ class OpeningExplorerRepository { DateTime? since, }) { return client.readNdJsonStream( - Uri.https( - kLichessOpeningExplorerHost, - '/player', - { - 'fen': fen, - 'player': usernameOrId, - 'color': color.name, - if (speeds.isNotEmpty) - 'speeds': speeds.map((speed) => speed.name).join(','), - if (gameModes.isNotEmpty) - 'modes': gameModes.map((gameMode) => gameMode.name).join(','), - if (since != null) 'since': '${since.year}-${since.month}', - }, - ), + Uri.https(kLichessOpeningExplorerHost, '/player', { + 'fen': fen, + 'player': usernameOrId, + 'color': color.name, + if (speeds.isNotEmpty) 'speeds': speeds.map((speed) => speed.name).join(','), + if (gameModes.isNotEmpty) 'modes': gameModes.map((gameMode) => gameMode.name).join(','), + if (since != null) 'since': '${since.year}-${since.month}', + }), mapper: OpeningExplorerEntry.fromJson, ); } diff --git a/lib/src/model/over_the_board/over_the_board_clock.dart b/lib/src/model/over_the_board/over_the_board_clock.dart index 2821527d75..390aee8e86 100644 --- a/lib/src/model/over_the_board/over_the_board_clock.dart +++ b/lib/src/model/over_the_board/over_the_board_clock.dart @@ -19,8 +19,7 @@ class OverTheBoardClock extends _$OverTheBoardClock { OverTheBoardClockState build() { _updateTimer = Timer.periodic(const Duration(milliseconds: 100), (_) { if (_stopwatch.isRunning) { - final newTime = - state.timeLeft(state.activeClock!)! - _stopwatch.elapsed; + final newTime = state.timeLeft(state.activeClock!)! - _stopwatch.elapsed; if (state.activeClock == Side.white) { state = state.copyWith(whiteTimeLeft: newTime); @@ -29,9 +28,7 @@ class OverTheBoardClock extends _$OverTheBoardClock { } if (newTime <= Duration.zero) { - state = state.copyWith( - flagSide: state.activeClock, - ); + state = state.copyWith(flagSide: state.activeClock); } _stopwatch.reset(); @@ -43,10 +40,7 @@ class OverTheBoardClock extends _$OverTheBoardClock { }); return OverTheBoardClockState.fromTimeIncrement( - TimeIncrement( - const Duration(minutes: 5).inSeconds, - const Duration(seconds: 3).inSeconds, - ), + TimeIncrement(const Duration(minutes: 5).inSeconds, const Duration(seconds: 3).inSeconds), ); } @@ -64,8 +58,7 @@ class OverTheBoardClock extends _$OverTheBoardClock { void switchSide({required Side newSideToMove, required bool addIncrement}) { if (state.timeIncrement.isInfinite || state.flagSide != null) return; - final increment = - Duration(seconds: addIncrement ? state.timeIncrement.increment : 0); + final increment = Duration(seconds: addIncrement ? state.timeIncrement.increment : 0); if (newSideToMove == Side.black) { state = state.copyWith( whiteTimeLeft: state.whiteTimeLeft! + increment, @@ -88,9 +81,7 @@ class OverTheBoardClock extends _$OverTheBoardClock { void pause() { if (_stopwatch.isRunning) { - state = state.copyWith( - activeClock: null, - ); + state = state.copyWith(activeClock: null); _stopwatch.reset(); _stopwatch.stop(); } @@ -100,9 +91,7 @@ class OverTheBoardClock extends _$OverTheBoardClock { _stopwatch.reset(); _stopwatch.start(); - state = state.copyWith( - activeClock: newSideToMove, - ); + state = state.copyWith(activeClock: newSideToMove); } } @@ -118,12 +107,11 @@ class OverTheBoardClockState with _$OverTheBoardClockState { required Side? flagSide, }) = _OverTheBoardClockState; - factory OverTheBoardClockState.fromTimeIncrement( - TimeIncrement timeIncrement, - ) { - final initialTime = timeIncrement.isInfinite - ? null - : Duration(seconds: max(timeIncrement.time, timeIncrement.increment)); + factory OverTheBoardClockState.fromTimeIncrement(TimeIncrement timeIncrement) { + final initialTime = + timeIncrement.isInfinite + ? null + : Duration(seconds: max(timeIncrement.time, timeIncrement.increment)); return OverTheBoardClockState( timeIncrement: timeIncrement, @@ -136,6 +124,5 @@ class OverTheBoardClockState with _$OverTheBoardClockState { bool get active => activeClock != null || flagSide != null; - Duration? timeLeft(Side side) => - side == Side.white ? whiteTimeLeft : blackTimeLeft; + Duration? timeLeft(Side side) => side == Side.white ? whiteTimeLeft : blackTimeLeft; } diff --git a/lib/src/model/over_the_board/over_the_board_game_controller.dart b/lib/src/model/over_the_board/over_the_board_game_controller.dart index 8f7741059e..5f4e75c7ff 100644 --- a/lib/src/model/over_the_board/over_the_board_game_controller.dart +++ b/lib/src/model/over_the_board/over_the_board_game_controller.dart @@ -20,22 +20,16 @@ part 'over_the_board_game_controller.g.dart'; class OverTheBoardGameController extends _$OverTheBoardGameController { @override OverTheBoardGameState build() => OverTheBoardGameState.fromVariant( - Variant.standard, - Speed.fromTimeIncrement(const TimeIncrement(0, 0)), - ); + Variant.standard, + Speed.fromTimeIncrement(const TimeIncrement(0, 0)), + ); void startNewGame(Variant variant, TimeIncrement timeIncrement) { - state = OverTheBoardGameState.fromVariant( - variant, - Speed.fromTimeIncrement(timeIncrement), - ); + state = OverTheBoardGameState.fromVariant(variant, Speed.fromTimeIncrement(timeIncrement)); } void rematch() { - state = OverTheBoardGameState.fromVariant( - state.game.meta.variant, - state.game.meta.speed, - ); + state = OverTheBoardGameState.fromVariant(state.game.meta.variant, state.game.meta.speed); } void makeMove(NormalMove move) { @@ -44,8 +38,7 @@ class OverTheBoardGameController extends _$OverTheBoardGameController { return; } - final (newPos, newSan) = - state.currentPosition.makeSan(Move.parse(move.uci)!); + final (newPos, newSan) = state.currentPosition.makeSan(Move.parse(move.uci)!); final sanMove = SanMove(newSan, move); final newStep = GameStep( position: newPos, @@ -67,17 +60,10 @@ class OverTheBoardGameController extends _$OverTheBoardGameController { if (state.currentPosition.isCheckmate) { state = state.copyWith( - game: state.game.copyWith( - status: GameStatus.mate, - winner: state.turn.opposite, - ), + game: state.game.copyWith(status: GameStatus.mate, winner: state.turn.opposite), ); } else if (state.currentPosition.isStalemate) { - state = state.copyWith( - game: state.game.copyWith( - status: GameStatus.stalemate, - ), - ); + state = state.copyWith(game: state.game.copyWith(status: GameStatus.stalemate)); } _moveFeedback(sanMove); @@ -98,10 +84,7 @@ class OverTheBoardGameController extends _$OverTheBoardGameController { void onFlag(Side side) { state = state.copyWith( - game: state.game.copyWith( - status: GameStatus.outoftime, - winner: side.opposite, - ), + game: state.game.copyWith(status: GameStatus.outoftime, winner: side.opposite), ); } @@ -113,9 +96,7 @@ class OverTheBoardGameController extends _$OverTheBoardGameController { void goBack() { if (state.canGoBack) { - state = state.copyWith( - stepCursor: state.stepCursor - 1, - ); + state = state.copyWith(stepCursor: state.stepCursor - 1); } } @@ -139,20 +120,12 @@ class OverTheBoardGameState with _$OverTheBoardGameState { @Default(null) NormalMove? promotionMove, }) = _OverTheBoardGameState; - factory OverTheBoardGameState.fromVariant( - Variant variant, - Speed speed, - ) { - final position = variant == Variant.chess960 - ? randomChess960Position() - : variant.initialPosition; + factory OverTheBoardGameState.fromVariant(Variant variant, Speed speed) { + final position = + variant == Variant.chess960 ? randomChess960Position() : variant.initialPosition; return OverTheBoardGameState( game: OverTheBoardGame( - steps: [ - GameStep( - position: position, - ), - ].lock, + steps: [GameStep(position: position)].lock, status: GameStatus.started, initialFen: position.fen, meta: GameMeta( @@ -169,21 +142,17 @@ class OverTheBoardGameState with _$OverTheBoardGameState { Position get currentPosition => game.stepAt(stepCursor).position; Side get turn => currentPosition.turn; bool get finished => game.finished; - NormalMove? get lastMove => stepCursor > 0 - ? NormalMove.fromUci(game.steps[stepCursor].sanMove!.move.uci) - : null; + NormalMove? get lastMove => + stepCursor > 0 ? NormalMove.fromUci(game.steps[stepCursor].sanMove!.move.uci) : null; - IMap> get legalMoves => makeLegalMoves( - currentPosition, - isChess960: game.meta.variant == Variant.chess960, - ); + IMap> get legalMoves => + makeLegalMoves(currentPosition, isChess960: game.meta.variant == Variant.chess960); MaterialDiffSide? currentMaterialDiff(Side side) { return game.steps[stepCursor].diff?.bySide(side); } - List get moves => - game.steps.skip(1).map((e) => e.sanMove!.san).toList(growable: false); + List get moves => game.steps.skip(1).map((e) => e.sanMove!.san).toList(growable: false); bool get canGoForward => stepCursor < game.steps.length - 1; bool get canGoBack => stepCursor > 0; diff --git a/lib/src/model/puzzle/puzzle.dart b/lib/src/model/puzzle/puzzle.dart index d62db26e5b..8b8562e0fa 100644 --- a/lib/src/model/puzzle/puzzle.dart +++ b/lib/src/model/puzzle/puzzle.dart @@ -32,8 +32,7 @@ class Puzzle with _$Puzzle { if (isCheckmate) { return true; } - if (uci != solutionUci && - (!altCastles.containsKey(uci) || altCastles[uci] != solutionUci)) { + if (uci != solutionUci && (!altCastles.containsKey(uci) || altCastles[uci] != solutionUci)) { return false; } } @@ -56,8 +55,7 @@ class PuzzleData with _$PuzzleData { Side get sideToMove => initialPly.isEven ? Side.black : Side.white; - factory PuzzleData.fromJson(Map json) => - _$PuzzleDataFromJson(json); + factory PuzzleData.fromJson(Map json) => _$PuzzleDataFromJson(json); } @Freezed(fromJson: true, toJson: true) @@ -70,17 +68,13 @@ class PuzzleGlicko with _$PuzzleGlicko { bool? provisional, }) = _PuzzleGlicko; - factory PuzzleGlicko.fromJson(Map json) => - _$PuzzleGlickoFromJson(json); + factory PuzzleGlicko.fromJson(Map json) => _$PuzzleGlickoFromJson(json); } @freezed class PuzzleRound with _$PuzzleRound { - const factory PuzzleRound({ - required PuzzleId id, - required int ratingDiff, - required bool win, - }) = _PuzzleRound; + const factory PuzzleRound({required PuzzleId id, required int ratingDiff, required bool win}) = + _PuzzleRound; } @Freezed(fromJson: true, toJson: true) @@ -94,32 +88,23 @@ class PuzzleGame with _$PuzzleGame { required String pgn, }) = _PuzzleGame; - factory PuzzleGame.fromJson(Map json) => - _$PuzzleGameFromJson(json); + factory PuzzleGame.fromJson(Map json) => _$PuzzleGameFromJson(json); } @Freezed(fromJson: true, toJson: true) class PuzzleGamePlayer with _$PuzzleGamePlayer { - const factory PuzzleGamePlayer({ - required Side side, - required String name, - String? title, - }) = _PuzzleGamePlayer; - - factory PuzzleGamePlayer.fromJson(Map json) => - _$PuzzleGamePlayerFromJson(json); + const factory PuzzleGamePlayer({required Side side, required String name, String? title}) = + _PuzzleGamePlayer; + + factory PuzzleGamePlayer.fromJson(Map json) => _$PuzzleGamePlayerFromJson(json); } @Freezed(fromJson: true, toJson: true) class PuzzleSolution with _$PuzzleSolution { - const factory PuzzleSolution({ - required PuzzleId id, - required bool win, - required bool rated, - }) = _PuzzleSolution; + const factory PuzzleSolution({required PuzzleId id, required bool win, required bool rated}) = + _PuzzleSolution; - factory PuzzleSolution.fromJson(Map json) => - _$PuzzleSolutionFromJson(json); + factory PuzzleSolution.fromJson(Map json) => _$PuzzleSolutionFromJson(json); } @freezed @@ -152,8 +137,7 @@ class LitePuzzle with _$LitePuzzle { required int rating, }) = _LitePuzzle; - factory LitePuzzle.fromJson(Map json) => - _$LitePuzzleFromJson(json); + factory LitePuzzle.fromJson(Map json) => _$LitePuzzleFromJson(json); (Side, String, Move) get preview { final pos1 = Chess.fromSetup(Setup.parseFen(fen)); @@ -195,11 +179,7 @@ class PuzzleHistoryEntry with _$PuzzleHistoryEntry { Duration? solvingTime, }) = _PuzzleHistoryEntry; - factory PuzzleHistoryEntry.fromLitePuzzle( - LitePuzzle puzzle, - bool win, - Duration duration, - ) { + factory PuzzleHistoryEntry.fromLitePuzzle(LitePuzzle puzzle, bool win, Duration duration) { final (_, fen, move) = puzzle.preview; return PuzzleHistoryEntry( date: DateTime.now(), @@ -212,6 +192,5 @@ class PuzzleHistoryEntry with _$PuzzleHistoryEntry { ); } - (String, Side, Move) get preview => - (fen, Chess.fromSetup(Setup.parseFen(fen)).turn, lastMove); + (String, Side, Move) get preview => (fen, Chess.fromSetup(Setup.parseFen(fen)).turn, lastMove); } diff --git a/lib/src/model/puzzle/puzzle_activity.dart b/lib/src/model/puzzle/puzzle_activity.dart index 79b245488f..2e4d9a7e25 100644 --- a/lib/src/model/puzzle/puzzle_activity.dart +++ b/lib/src/model/puzzle/puzzle_activity.dart @@ -45,9 +45,7 @@ class PuzzleActivity extends _$PuzzleActivity { ); } - Map> _groupByDay( - Iterable list, - ) { + Map> _groupByDay(Iterable list) { final map = >{}; for (final entry in list) { final date = DateTime(entry.date.year, entry.date.month, entry.date.day); @@ -68,15 +66,12 @@ class PuzzleActivity extends _$PuzzleActivity { state = AsyncData(currentVal.copyWith(isLoading: true)); Result.capture( ref.withClient( - (client) => PuzzleRepository(client) - .puzzleActivity(_nbPerPage, before: _list.last.date), + (client) => PuzzleRepository(client).puzzleActivity(_nbPerPage, before: _list.last.date), ), ).fold( (value) { if (value.isEmpty) { - state = AsyncData( - currentVal.copyWith(hasMore: false, isLoading: false), - ); + state = AsyncData(currentVal.copyWith(hasMore: false, isLoading: false)); return; } _list.addAll(value); @@ -90,8 +85,7 @@ class PuzzleActivity extends _$PuzzleActivity { ); }, (error, stackTrace) { - state = - AsyncData(currentVal.copyWith(isLoading: false, hasError: true)); + state = AsyncData(currentVal.copyWith(isLoading: false, hasError: true)); }, ); } diff --git a/lib/src/model/puzzle/puzzle_batch_storage.dart b/lib/src/model/puzzle/puzzle_batch_storage.dart index b35969535a..15eb38c3b1 100644 --- a/lib/src/model/puzzle/puzzle_batch_storage.dart +++ b/lib/src/model/puzzle/puzzle_batch_storage.dart @@ -42,10 +42,7 @@ class PuzzleBatchStorage { userId = ? AND angle = ? ''', - whereArgs: [ - userId ?? _anonUserKey, - angle.key, - ], + whereArgs: [userId ?? _anonUserKey, angle.key], ); final raw = list.firstOrNull?['data'] as String?; @@ -67,15 +64,11 @@ class PuzzleBatchStorage { required PuzzleBatch data, PuzzleAngle angle = const PuzzleTheme(PuzzleThemeKey.mix), }) async { - await _db.insert( - _tableName, - { - 'userId': userId ?? _anonUserKey, - 'angle': angle.key, - 'data': jsonEncode(data.toJson()), - }, - conflictAlgorithm: ConflictAlgorithm.replace, - ); + await _db.insert(_tableName, { + 'userId': userId ?? _anonUserKey, + 'angle': angle.key, + 'data': jsonEncode(data.toJson()), + }, conflictAlgorithm: ConflictAlgorithm.replace); _ref.invalidateSelf(); } @@ -89,24 +82,17 @@ class PuzzleBatchStorage { userId = ? AND angle = ? ''', - whereArgs: [ - userId ?? _anonUserKey, - angle.key, - ], + whereArgs: [userId ?? _anonUserKey, angle.key], ); _ref.invalidateSelf(); } /// Fetches all saved puzzles batches (except mix) for the given user. - Future> fetchAll({ - required UserId? userId, - }) async { + Future> fetchAll({required UserId? userId}) async { final list = await _db.query( _tableName, where: 'userId = ?', - whereArgs: [ - userId ?? _anonUserKey, - ], + whereArgs: [userId ?? _anonUserKey], orderBy: 'lastModified DESC', ); return list @@ -134,87 +120,74 @@ class PuzzleBatchStorage { .toIList(); } - Future> fetchSavedThemes({ - required UserId? userId, - }) async { + Future> fetchSavedThemes({required UserId? userId}) async { final list = await _db.query( _tableName, where: 'userId = ?', - whereArgs: [ - userId ?? _anonUserKey, - ], + whereArgs: [userId ?? _anonUserKey], ); - return list.fold>( - IMap(const {}), - (acc, map) { - final angle = map['angle'] as String?; - final raw = map['data'] as String?; - - final theme = angle != null ? puzzleThemeNameMap.get(angle) : null; - - if (theme != null) { - int? count; - if (raw != null) { - final json = jsonDecode(raw); - if (json is! Map) { - throw const FormatException( - '[PuzzleBatchStorage] cannot fetch puzzles: expected an object', - ); - } - final data = PuzzleBatch.fromJson(json); - count = data.unsolved.length; + return list.fold>(IMap(const {}), (acc, map) { + final angle = map['angle'] as String?; + final raw = map['data'] as String?; + + final theme = angle != null ? puzzleThemeNameMap.get(angle) : null; + + if (theme != null) { + int? count; + if (raw != null) { + final json = jsonDecode(raw); + if (json is! Map) { + throw const FormatException( + '[PuzzleBatchStorage] cannot fetch puzzles: expected an object', + ); } - return count != null ? acc.add(theme, count) : acc; + final data = PuzzleBatch.fromJson(json); + count = data.unsolved.length; } + return count != null ? acc.add(theme, count) : acc; + } - return acc; - }, - ); + return acc; + }); } - Future> fetchSavedOpenings({ - required UserId? userId, - }) async { + Future> fetchSavedOpenings({required UserId? userId}) async { final list = await _db.query( _tableName, where: 'userId = ?', - whereArgs: [ - userId ?? _anonUserKey, - ], + whereArgs: [userId ?? _anonUserKey], ); - return list.fold>( - IMap(const {}), - (acc, map) { - final angle = map['angle'] as String?; - final raw = map['data'] as String?; + return list.fold>(IMap(const {}), (acc, map) { + final angle = map['angle'] as String?; + final raw = map['data'] as String?; - final openingKey = angle != null - ? switch (PuzzleAngle.fromKey(angle)) { + final openingKey = + angle != null + ? switch (PuzzleAngle.fromKey(angle)) { PuzzleTheme(themeKey: _) => null, PuzzleOpening(key: final key) => key, } - : null; - - if (openingKey != null) { - int? count; - if (raw != null) { - final json = jsonDecode(raw); - if (json is! Map) { - throw const FormatException( - '[PuzzleBatchStorage] cannot fetch puzzles: expected an object', - ); - } - final data = PuzzleBatch.fromJson(json); - count = data.unsolved.length; + : null; + + if (openingKey != null) { + int? count; + if (raw != null) { + final json = jsonDecode(raw); + if (json is! Map) { + throw const FormatException( + '[PuzzleBatchStorage] cannot fetch puzzles: expected an object', + ); } - return count != null ? acc.add(openingKey, count) : acc; + final data = PuzzleBatch.fromJson(json); + count = data.unsolved.length; } + return count != null ? acc.add(openingKey, count) : acc; + } - return acc; - }, - ); + return acc; + }); } } @@ -225,6 +198,5 @@ class PuzzleBatch with _$PuzzleBatch { required IList unsolved, }) = _PuzzleBatch; - factory PuzzleBatch.fromJson(Map json) => - _$PuzzleBatchFromJson(json); + factory PuzzleBatch.fromJson(Map json) => _$PuzzleBatchFromJson(json); } diff --git a/lib/src/model/puzzle/puzzle_controller.dart b/lib/src/model/puzzle/puzzle_controller.dart index b590cea4ee..0353e41c9a 100644 --- a/lib/src/model/puzzle/puzzle_controller.dart +++ b/lib/src/model/puzzle/puzzle_controller.dart @@ -42,14 +42,10 @@ class PuzzleController extends _$PuzzleController { final _engineEvalDebounce = Debouncer(const Duration(milliseconds: 100)); - Future get _service => ref.read(puzzleServiceFactoryProvider)( - queueLength: kPuzzleLocalQueueLength, - ); + Future get _service => + ref.read(puzzleServiceFactoryProvider)(queueLength: kPuzzleLocalQueueLength); @override - PuzzleState build( - PuzzleContext initialContext, { - PuzzleStreak? initialStreak, - }) { + PuzzleState build(PuzzleContext initialContext, {PuzzleStreak? initialStreak}) { final evaluationService = ref.read(evaluationServiceProvider); ref.onDispose(() { @@ -69,27 +65,19 @@ class PuzzleController extends _$PuzzleController { return _loadNewContext(initialContext, initialStreak); } - PuzzleRepository _repository(LichessClient client) => - PuzzleRepository(client); + PuzzleRepository _repository(LichessClient client) => PuzzleRepository(client); Future _updateUserRating() async { try { - final data = await ref.withClient( - (client) => _repository(client).selectBatch(nb: 0), - ); + final data = await ref.withClient((client) => _repository(client).selectBatch(nb: 0)); final glicko = data.glicko; if (glicko != null) { - state = state.copyWith( - glicko: glicko, - ); + state = state.copyWith(glicko: glicko); } } catch (_) {} } - PuzzleState _loadNewContext( - PuzzleContext context, - PuzzleStreak? streak, - ) { + PuzzleState _loadNewContext(PuzzleContext context, PuzzleStreak? streak) { final root = Root.fromPgnMoves(context.puzzle.game.pgn); _gameTree = root.nodeAt(root.mainlinePath.penultimate) as Branch; @@ -100,9 +88,7 @@ class PuzzleController extends _$PuzzleController { // enable solution button after 4 seconds _enableSolutionButtonTimer = Timer(const Duration(seconds: 4), () { - state = state.copyWith( - canViewSolution: true, - ); + state = state.copyWith(canViewSolution: true); }); final initialPath = UciPath.fromId(_gameTree.children.first.id); @@ -120,9 +106,7 @@ class PuzzleController extends _$PuzzleController { initialPath: initialPath, currentPath: UciPath.empty, node: _gameTree.view, - pov: _gameTree.nodeAt(initialPath).position.ply.isEven - ? Side.white - : Side.black, + pov: _gameTree.nodeAt(initialPath).position.ply.isEven ? Side.white : Side.black, canViewSolution: false, resultSent: false, isChangingDifficulty: false, @@ -144,19 +128,15 @@ class PuzzleController extends _$PuzzleController { if (state.mode == PuzzleMode.play) { final nodeList = _gameTree.branchesOn(state.currentPath).toList(); - final movesToTest = - nodeList.sublist(state.initialPath.size).map((e) => e.sanMove); + final movesToTest = nodeList.sublist(state.initialPath.size).map((e) => e.sanMove); final isGoodMove = state.puzzle.testSolution(movesToTest); if (isGoodMove) { - state = state.copyWith( - feedback: PuzzleFeedback.good, - ); + state = state.copyWith(feedback: PuzzleFeedback.good); final isCheckmate = movesToTest.last.san.endsWith('#'); - final nextUci = - state.puzzle.puzzle.solution.getOrNull(movesToTest.length); + final nextUci = state.puzzle.puzzle.solution.getOrNull(movesToTest.length); // checkmate is always a win if (isCheckmate) { _completePuzzle(); @@ -171,9 +151,7 @@ class PuzzleController extends _$PuzzleController { _completePuzzle(); } } else { - state = state.copyWith( - feedback: PuzzleFeedback.bad, - ); + state = state.copyWith(feedback: PuzzleFeedback.bad); _onFailOrWin(PuzzleResult.lose); if (initialStreak == null) { await Future.delayed(const Duration(milliseconds: 500)); @@ -198,17 +176,13 @@ class PuzzleController extends _$PuzzleController { void userNext() { _viewSolutionTimer?.cancel(); _goToNextNode(replaying: true); - state = state.copyWith( - viewedSolutionRecently: false, - ); + state = state.copyWith(viewedSolutionRecently: false); } void userPrevious() { _viewSolutionTimer?.cancel(); _goToPreviousNode(replaying: true); - state = state.copyWith( - viewedSolutionRecently: false, - ); + state = state.copyWith(viewedSolutionRecently: false); } void viewSolution() { @@ -216,15 +190,11 @@ class PuzzleController extends _$PuzzleController { _mergeSolution(); - state = state.copyWith( - node: _gameTree.branchAt(state.currentPath).view, - ); + state = state.copyWith(node: _gameTree.branchAt(state.currentPath).view); _onFailOrWin(PuzzleResult.lose); - state = state.copyWith( - mode: PuzzleMode.view, - ); + state = state.copyWith(mode: PuzzleMode.view); Timer(const Duration(milliseconds: 800), () { _goToNextNode(); @@ -248,22 +218,16 @@ class PuzzleController extends _$PuzzleController { } Future changeDifficulty(PuzzleDifficulty difficulty) async { - state = state.copyWith( - isChangingDifficulty: true, - ); + state = state.copyWith(isChangingDifficulty: true); - await ref - .read(puzzlePreferencesProvider.notifier) - .setDifficulty(difficulty); + await ref.read(puzzlePreferencesProvider.notifier).setDifficulty(difficulty); final nextPuzzle = (await _service).resetBatch( userId: initialContext.userId, angle: initialContext.angle, ); - state = state.copyWith( - isChangingDifficulty: false, - ); + state = state.copyWith(isChangingDifficulty: false); return nextPuzzle; } @@ -275,9 +239,7 @@ class PuzzleController extends _$PuzzleController { } void saveStreakResultLocally() { - ref.read(streakStorageProvider(initialContext.userId)).saveActiveStreak( - state.streak!, - ); + ref.read(streakStorageProvider(initialContext.userId)).saveActiveStreak(state.streak!); } void _sendStreakResult() { @@ -286,42 +248,25 @@ class PuzzleController extends _$PuzzleController { if (initialContext.userId != null) { final streak = state.streak?.index; if (streak != null && streak > 0) { - ref.withClient( - (client) => _repository(client).postStreakRun(streak), - ); + ref.withClient((client) => _repository(client).postStreakRun(streak)); } } } - FutureResult retryFetchNextStreakPuzzle( - PuzzleStreak streak, - ) async { - state = state.copyWith( - nextPuzzleStreakFetchIsRetrying: true, - ); + FutureResult retryFetchNextStreakPuzzle(PuzzleStreak streak) async { + state = state.copyWith(nextPuzzleStreakFetchIsRetrying: true); final result = await _fetchNextStreakPuzzle(streak); - state = state.copyWith( - nextPuzzleStreakFetchIsRetrying: false, - ); + state = state.copyWith(nextPuzzleStreakFetchIsRetrying: false); result.match( onSuccess: (nextContext) { if (nextContext != null) { - state = state.copyWith( - streak: streak.copyWith( - index: streak.index + 1, - ), - ); + state = state.copyWith(streak: streak.copyWith(index: streak.index + 1)); } else { // no more puzzle - state = state.copyWith( - streak: streak.copyWith( - index: streak.index + 1, - finished: true, - ), - ); + state = state.copyWith(streak: streak.copyWith(index: streak.index + 1, finished: true)); } }, ); @@ -332,25 +277,24 @@ class PuzzleController extends _$PuzzleController { FutureResult _fetchNextStreakPuzzle(PuzzleStreak streak) { return streak.nextId != null ? Result.capture( - ref.withClient( - (client) => _repository(client).fetch(streak.nextId!).then( - (puzzle) => PuzzleContext( - angle: const PuzzleTheme(PuzzleThemeKey.mix), - puzzle: puzzle, - userId: initialContext.userId, - ), + ref.withClient( + (client) => _repository(client) + .fetch(streak.nextId!) + .then( + (puzzle) => PuzzleContext( + angle: const PuzzleTheme(PuzzleThemeKey.mix), + puzzle: puzzle, + userId: initialContext.userId, ), - ), - ) + ), + ), + ) : Future.value(Result.value(null)); } void _goToNextNode({bool replaying = false}) { if (state.node.children.isEmpty) return; - _setPath( - state.currentPath + state.node.children.first.id, - replaying: replaying, - ); + _setPath(state.currentPath + state.node.children.first.id, replaying: replaying); } void _goToPreviousNode({bool replaying = false}) { @@ -358,19 +302,14 @@ class PuzzleController extends _$PuzzleController { } Future _completePuzzle() async { - state = state.copyWith( - mode: PuzzleMode.view, - ); + state = state.copyWith(mode: PuzzleMode.view); await _onFailOrWin(state.result ?? PuzzleResult.win); } Future _onFailOrWin(PuzzleResult result) async { if (state.resultSent) return; - state = state.copyWith( - result: result, - resultSent: true, - ); + state = state.copyWith(result: result, resultSent: true); final soundService = ref.read(soundServiceProvider); @@ -386,27 +325,16 @@ class PuzzleController extends _$PuzzleController { ), ); - state = state.copyWith( - nextContext: next, - ); + state = state.copyWith(nextContext: next); ref - .read( - puzzleSessionProvider(initialContext.userId, initialContext.angle) - .notifier, - ) - .addAttempt( - state.puzzle.puzzle.id, - win: result == PuzzleResult.win, - ); + .read(puzzleSessionProvider(initialContext.userId, 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(initialContext.userId, initialContext.angle).notifier) .setRatingDiffs(rounds); } @@ -425,9 +353,7 @@ class PuzzleController extends _$PuzzleController { state = state.copyWith( mode: PuzzleMode.view, node: _gameTree.branchAt(state.currentPath).view, - streak: state.streak!.copyWith( - finished: true, - ), + streak: state.streak!.copyWith(finished: true), ); _sendStreakResult(); } else { @@ -442,20 +368,15 @@ class PuzzleController extends _$PuzzleController { soundService.play(Sound.confirmation); loadPuzzle( nextContext, - nextStreak: - state.streak!.copyWith(index: state.streak!.index + 1), + nextStreak: state.streak!.copyWith(index: state.streak!.index + 1), ); } else { // no more puzzle - state = state.copyWith.streak!( - finished: true, - ); + state = state.copyWith.streak!(finished: true); } }, onError: (error, _) { - state = state.copyWith( - nextPuzzleStreakFetchError: true, - ); + state = state.copyWith(nextPuzzleStreakFetchError: true); }, ); } @@ -463,11 +384,7 @@ class PuzzleController extends _$PuzzleController { } } - void _setPath( - UciPath path, { - bool replaying = false, - bool firstMove = false, - }) { + void _setPath(UciPath path, {bool replaying = false, bool firstMove = false}) { final pathChange = state.currentPath != path; final newNode = _gameTree.branchAt(path).view; final sanMove = newNode.sanMove; @@ -504,9 +421,7 @@ class PuzzleController extends _$PuzzleController { } void toggleLocalEvaluation() { - state = state.copyWith( - isLocalEvalEnabled: !state.isLocalEvalEnabled, - ); + state = state.copyWith(isLocalEvalEnabled: !state.isLocalEvalEnabled); if (state.isLocalEvalEnabled) { ref.read(evaluationServiceProvider).initEngine(state.evaluationContext); _startEngineEval(); @@ -518,8 +433,7 @@ class PuzzleController extends _$PuzzleController { String makePgn() { final initPosition = _gameTree.nodeAt(state.initialPath).position; var currentPosition = initPosition; - final pgnMoves = state.puzzle.puzzle.solution.fold>([], - (List acc, move) { + final pgnMoves = state.puzzle.puzzle.solution.fold>([], (List acc, move) { final moveObj = Move.parse(move); if (moveObj != null) { final String san; @@ -545,11 +459,11 @@ class PuzzleController extends _$PuzzleController { shouldEmit: (work) => work.path == state.currentPath, ) ?.forEach((t) { - final (work, eval) = t; - _gameTree.updateAt(work.path, (node) { - node.eval = eval; - }); - }), + final (work, eval) = t; + _gameTree.updateAt(work.path, (node) { + node.eval = eval; + }); + }), ); } @@ -572,15 +486,7 @@ class PuzzleController extends _$PuzzleController { final move = Move.parse(uci); final (pos, nodes) = previous; final (newPos, newSan) = pos.makeSan(move!); - return ( - newPos, - nodes.add( - Branch( - position: newPos, - sanMove: SanMove(newSan, move), - ), - ), - ); + return (newPos, nodes.add(Branch(position: newPos, sanMove: SanMove(newSan, move)))); }, ); _gameTree.addNodesAt(state.initialPath, newNodes, prepend: true); @@ -627,16 +533,13 @@ class PuzzleState with _$PuzzleState { return mode == PuzzleMode.view && isLocalEvalEnabled; } - EvaluationContext get evaluationContext => EvaluationContext( - variant: Variant.standard, - initialPosition: initialPosition, - ); + EvaluationContext get evaluationContext => + EvaluationContext(variant: Variant.standard, initialPosition: initialPosition); Position get position => node.position; String get fen => node.position.fen; bool get canGoNext => mode == PuzzleMode.view && node.children.isNotEmpty; - bool get canGoBack => - mode == PuzzleMode.view && currentPath.size > initialPath.size; + bool get canGoBack => mode == PuzzleMode.view && currentPath.size > initialPath.size; IMap> get validMoves => makeLegalMoves(position); } diff --git a/lib/src/model/puzzle/puzzle_difficulty.dart b/lib/src/model/puzzle/puzzle_difficulty.dart index bcda61e0f5..06012507e9 100644 --- a/lib/src/model/puzzle/puzzle_difficulty.dart +++ b/lib/src/model/puzzle/puzzle_difficulty.dart @@ -13,8 +13,9 @@ enum PuzzleDifficulty { const PuzzleDifficulty(this.ratingDelta); } -final IMap puzzleDifficultyNameMap = - IMap(PuzzleDifficulty.values.asNameMap()); +final IMap puzzleDifficultyNameMap = IMap( + PuzzleDifficulty.values.asNameMap(), +); String puzzleDifficultyL10n(BuildContext context, PuzzleDifficulty difficulty) { switch (difficulty) { diff --git a/lib/src/model/puzzle/puzzle_opening.dart b/lib/src/model/puzzle/puzzle_opening.dart index 51858a28a0..6c3a88cab1 100644 --- a/lib/src/model/puzzle/puzzle_opening.dart +++ b/lib/src/model/puzzle/puzzle_opening.dart @@ -5,18 +5,10 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'puzzle_opening.g.dart'; -typedef PuzzleOpeningFamily = ({ - String key, - String name, - int count, - IList openings, -}); +typedef PuzzleOpeningFamily = + ({String key, String name, int count, IList openings}); -typedef PuzzleOpeningData = ({ - String key, - String name, - int count, -}); +typedef PuzzleOpeningData = ({String key, String name, int count}); /// Returns a flattened list of openings with their respective counts. @riverpod @@ -26,13 +18,7 @@ Future> flatOpeningsList(Ref ref) async { .map( (f) => [ (key: f.key, name: f.name, count: f.count), - ...f.openings.map( - (o) => ( - key: o.key, - name: '${f.name}: ${o.name}', - count: o.count, - ), - ), + ...f.openings.map((o) => (key: o.key, name: '${f.name}: ${o.name}', count: o.count)), ], ) .expand((e) => e) diff --git a/lib/src/model/puzzle/puzzle_preferences.dart b/lib/src/model/puzzle/puzzle_preferences.dart index 25cf6be0b9..3f71177c44 100644 --- a/lib/src/model/puzzle/puzzle_preferences.dart +++ b/lib/src/model/puzzle/puzzle_preferences.dart @@ -9,8 +9,7 @@ part 'puzzle_preferences.freezed.dart'; part 'puzzle_preferences.g.dart'; @riverpod -class PuzzlePreferences extends _$PuzzlePreferences - with SessionPreferencesStorage { +class PuzzlePreferences extends _$PuzzlePreferences with SessionPreferencesStorage { // ignore: avoid_public_notifier_properties @override final prefCategory = PrefCategory.puzzle; @@ -47,12 +46,8 @@ class PuzzlePrefs with _$PuzzlePrefs implements Serializable { @Default(false) bool autoNext, }) = _PuzzlePrefs; - factory PuzzlePrefs.defaults({UserId? id}) => PuzzlePrefs( - id: id, - difficulty: PuzzleDifficulty.normal, - autoNext: false, - ); + factory PuzzlePrefs.defaults({UserId? id}) => + PuzzlePrefs(id: id, difficulty: PuzzleDifficulty.normal, autoNext: false); - factory PuzzlePrefs.fromJson(Map json) => - _$PuzzlePrefsFromJson(json); + factory PuzzlePrefs.fromJson(Map json) => _$PuzzlePrefsFromJson(json); } diff --git a/lib/src/model/puzzle/puzzle_providers.dart b/lib/src/model/puzzle/puzzle_providers.dart index b742ffdf2a..7923986f87 100644 --- a/lib/src/model/puzzle/puzzle_providers.dart +++ b/lib/src/model/puzzle/puzzle_providers.dart @@ -31,16 +31,10 @@ Future nextPuzzle(Ref ref, PuzzleAngle angle) async { // be invalidated multiple times when the user scrolls the list) ref.cacheFor(const Duration(minutes: 1)); - return puzzleService.nextPuzzle( - userId: session?.user.id, - angle: angle, - ); + return puzzleService.nextPuzzle(userId: session?.user.id, angle: angle); } -typedef InitialStreak = ({ - PuzzleStreak streak, - Puzzle puzzle, -}); +typedef InitialStreak = ({PuzzleStreak streak, Puzzle puzzle}); /// Fetches the active streak from the local storage if available, otherwise fetches it from the server. @riverpod @@ -49,17 +43,12 @@ Future streak(Ref ref) async { final streakStorage = ref.watch(streakStorageProvider(session?.user.id)); final activeStreak = await streakStorage.loadActiveStreak(); if (activeStreak != null) { - final puzzle = await ref - .read(puzzleProvider(activeStreak.streak[activeStreak.index]).future); + final puzzle = await ref.read(puzzleProvider(activeStreak.streak[activeStreak.index]).future); - return ( - streak: activeStreak, - puzzle: puzzle, - ); + return (streak: activeStreak, puzzle: puzzle); } - final rsp = - await ref.withClient((client) => PuzzleRepository(client).streak()); + final rsp = await ref.withClient((client) => PuzzleRepository(client).streak()); return ( streak: PuzzleStreak( @@ -110,19 +99,14 @@ Future> savedThemeBatches(Ref ref) async { } @riverpod -Future> savedOpeningBatches( - Ref ref, -) async { +Future> savedOpeningBatches(Ref ref) async { final session = ref.watch(authSessionProvider); final storage = await ref.watch(puzzleBatchStorageProvider.future); return storage.fetchSavedOpenings(userId: session?.user.id); } @riverpod -Future puzzleDashboard( - Ref ref, - int days, -) async { +Future puzzleDashboard(Ref ref, int days) async { final session = ref.watch(authSessionProvider); if (session == null) return null; return ref.withClientCacheFor( @@ -143,9 +127,7 @@ Future?> puzzleRecentActivity(Ref ref) async { @riverpod Future stormDashboard(Ref ref, UserId id) async { - return ref.withClient( - (client) => PuzzleRepository(client).stormDashboard(id), - ); + return ref.withClient((client) => PuzzleRepository(client).stormDashboard(id)); } @riverpod diff --git a/lib/src/model/puzzle/puzzle_repository.dart b/lib/src/model/puzzle/puzzle_repository.dart index 19929d9d1a..ca334d58a4 100644 --- a/lib/src/model/puzzle/puzzle_repository.dart +++ b/lib/src/model/puzzle/puzzle_repository.dart @@ -36,10 +36,7 @@ class PuzzleRepository { return client.readJson( Uri( path: '/api/puzzle/batch/${angle.key}', - queryParameters: { - 'nb': nb.toString(), - 'difficulty': difficulty.name, - }, + queryParameters: {'nb': nb.toString(), 'difficulty': difficulty.name}, ), mapper: _decodeBatchResponse, ); @@ -54,32 +51,18 @@ class PuzzleRepository { return client.postReadJson( Uri( path: '/api/puzzle/batch/${angle.key}', - queryParameters: { - 'nb': nb.toString(), - 'difficulty': difficulty.name, - }, + queryParameters: {'nb': nb.toString(), 'difficulty': difficulty.name}, ), headers: {'Content-type': 'application/json'}, body: jsonEncode({ - 'solutions': solved - .map( - (e) => { - 'id': e.id, - 'win': e.win, - 'rated': e.rated, - }, - ) - .toList(), + 'solutions': solved.map((e) => {'id': e.id, 'win': e.win, 'rated': e.rated}).toList(), }), mapper: _decodeBatchResponse, ); } Future fetch(PuzzleId id) { - return client.readJson( - Uri(path: '/api/puzzle/$id'), - mapper: _puzzleFromJson, - ); + return client.readJson(Uri(path: '/api/puzzle/$id'), mapper: _puzzleFromJson); } Future streak() { @@ -88,11 +71,7 @@ class PuzzleRepository { mapper: (Map json) { return PuzzleStreakResponse( puzzle: _puzzleFromPick(pick(json).required()), - streak: IList( - pick(json['streak']).asStringOrThrow().split(' ').map( - (e) => PuzzleId(e), - ), - ), + streak: IList(pick(json['streak']).asStringOrThrow().split(' ').map((e) => PuzzleId(e))), timestamp: DateTime.now(), ); }, @@ -103,10 +82,7 @@ class PuzzleRepository { final uri = Uri(path: '/api/streak/$run'); final response = await client.post(uri); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to post streak run: ${response.statusCode}', - uri, - ); + throw http.ClientException('Failed to post streak run: ${response.statusCode}', uri); } } @@ -115,9 +91,7 @@ class PuzzleRepository { Uri(path: '/api/storm'), mapper: (Map json) { return PuzzleStormResponse( - puzzles: IList( - pick(json['puzzles']).asListOrThrow(_litePuzzleFromPick), - ), + puzzles: IList(pick(json['puzzles']).asListOrThrow(_litePuzzleFromPick)), highscore: pick(json['high']).letOrNull(_stormHighScoreFromPick), key: pick(json['key']).asStringOrNull(), timestamp: DateTime.now(), @@ -155,15 +129,8 @@ class PuzzleRepository { Future daily() { return client - .readJson( - Uri(path: '/api/puzzle/daily'), - mapper: _puzzleFromJson, - ) - .then( - (puzzle) => puzzle.copyWith( - isDailyPuzzle: true, - ), - ); + .readJson(Uri(path: '/api/puzzle/daily'), mapper: _puzzleFromJson) + .then((puzzle) => puzzle.copyWith(isDailyPuzzle: true)); } Future puzzleDashboard(int days) { @@ -173,17 +140,13 @@ class PuzzleRepository { ); } - Future> puzzleActivity( - int max, { - DateTime? before, - }) { + Future> puzzleActivity(int max, {DateTime? before}) { return client.readNdJsonList( Uri( path: '/api/puzzle/activity', queryParameters: { 'max': max.toString(), - if (before != null) - 'before': before.millisecondsSinceEpoch.toString(), + if (before != null) 'before': before.millisecondsSinceEpoch.toString(), }, ), mapper: _puzzleActivityFromJson, @@ -228,13 +191,9 @@ class PuzzleRepository { }), ), glicko: pick(json['glicko']).letOrNull(_puzzleGlickoFromPick), - rounds: pick(json['rounds']).letOrNull( - (p0) => IList( - p0.asListOrNull( - (p1) => _puzzleRoundFromPick(p1), - ), - ), - ), + rounds: pick( + json['rounds'], + ).letOrNull((p0) => IList(p0.asListOrNull((p1) => _puzzleRoundFromPick(p1)))), ); } } @@ -284,15 +243,12 @@ class PuzzleStormResponse with _$PuzzleStormResponse { PuzzleHistoryEntry _puzzleActivityFromJson(Map json) => _historyPuzzleFromPick(pick(json).required()); -Puzzle _puzzleFromJson(Map json) => - _puzzleFromPick(pick(json).required()); +Puzzle _puzzleFromJson(Map json) => _puzzleFromPick(pick(json).required()); PuzzleDashboard _puzzleDashboardFromJson(Map json) => _puzzleDashboardFromPick(pick(json).required()); -IMap _puzzleThemeFromJson( - Map json, -) => +IMap _puzzleThemeFromJson(Map json) => _puzzleThemeFromPick(pick(json).required()); IList _puzzleOpeningFromJson(Map json) => @@ -317,20 +273,17 @@ StormDashboard _stormDashboardFromPick(RequiredPick pick) { month: pick('high', 'month').asIntOrThrow(), week: pick('high', 'week').asIntOrThrow(), ), - dayHighscores: pick('days') - .asListOrThrow((p0) => _stormDayFromPick(p0, dateFormat)) - .toIList(), + dayHighscores: pick('days').asListOrThrow((p0) => _stormDayFromPick(p0, dateFormat)).toIList(), ); } -StormDayScore _stormDayFromPick(RequiredPick pick, DateFormat format) => - StormDayScore( - runs: pick('runs').asIntOrThrow(), - score: pick('score').asIntOrThrow(), - time: pick('time').asIntOrThrow(), - highest: pick('highest').asIntOrThrow(), - day: format.parse(pick('_id').asStringOrThrow()), - ); +StormDayScore _stormDayFromPick(RequiredPick pick, DateFormat format) => StormDayScore( + runs: pick('runs').asIntOrThrow(), + score: pick('score').asIntOrThrow(), + time: pick('time').asIntOrThrow(), + highest: pick('highest').asIntOrThrow(), + day: format.parse(pick('_id').asStringOrThrow()), +); LitePuzzle _litePuzzleFromPick(RequiredPick pick) { return LitePuzzle( @@ -357,8 +310,7 @@ PuzzleData _puzzleDatafromPick(RequiredPick pick) { plays: pick('plays').asIntOrThrow(), initialPly: pick('initialPly').asIntOrThrow(), solution: pick('solution').asListOrThrow((p0) => p0.asStringOrThrow()).lock, - themes: - pick('themes').asListOrThrow((p0) => p0.asStringOrThrow()).toSet().lock, + themes: pick('themes').asListOrThrow((p0) => p0.asStringOrThrow()).toSet().lock, ); } @@ -384,14 +336,10 @@ PuzzleGame _puzzleGameFromPick(RequiredPick pick) { perf: pick('perf', 'key').asPerfOrThrow(), rated: pick('rated').asBoolOrThrow(), white: pick('players').letOrThrow( - (it) => it - .asListOrThrow(_puzzlePlayerFromPick) - .firstWhere((p) => p.side == Side.white), + (it) => it.asListOrThrow(_puzzlePlayerFromPick).firstWhere((p) => p.side == Side.white), ), black: pick('players').letOrThrow( - (it) => it - .asListOrThrow(_puzzlePlayerFromPick) - .firstWhere((p) => p.side == Side.black), + (it) => it.asListOrThrow(_puzzlePlayerFromPick).firstWhere((p) => p.side == Side.black), ), pgn: pick('pgn').asStringOrThrow(), ); @@ -417,29 +365,24 @@ PuzzleHistoryEntry _historyPuzzleFromPick(RequiredPick pick) { } PuzzleDashboard _puzzleDashboardFromPick(RequiredPick pick) => PuzzleDashboard( - global: PuzzleDashboardData( - nb: pick('global')('nb').asIntOrThrow(), - firstWins: pick('global')('firstWins').asIntOrThrow(), - replayWins: pick('global')('replayWins').asIntOrThrow(), - performance: pick('global')('performance').asIntOrThrow(), - theme: PuzzleThemeKey.mix, - ), - themes: pick('themes') + global: PuzzleDashboardData( + nb: pick('global')('nb').asIntOrThrow(), + firstWins: pick('global')('firstWins').asIntOrThrow(), + replayWins: pick('global')('replayWins').asIntOrThrow(), + performance: pick('global')('performance').asIntOrThrow(), + theme: PuzzleThemeKey.mix, + ), + themes: + pick('themes') .asMapOrThrow>() .keys .map( - (key) => _puzzleDashboardDataFromPick( - pick('themes')(key)('results').required(), - key, - ), + (key) => _puzzleDashboardDataFromPick(pick('themes')(key)('results').required(), key), ) .toIList(), - ); +); -PuzzleDashboardData _puzzleDashboardDataFromPick( - RequiredPick results, - String themeKey, -) => +PuzzleDashboardData _puzzleDashboardDataFromPick(RequiredPick results, String themeKey) => PuzzleDashboardData( nb: results('nb').asIntOrThrow(), firstWins: results('firstWins').asIntOrThrow(), @@ -457,8 +400,7 @@ IMap _puzzleThemeFromPick(RequiredPick pick) { return PuzzleThemeData( count: listPick('count').asIntOrThrow(), desc: listPick('desc').asStringOrThrow(), - key: themeMap[listPick('key').asStringOrThrow()] ?? - PuzzleThemeKey.unsupported, + key: themeMap[listPick('key').asStringOrThrow()] ?? PuzzleThemeKey.unsupported, name: listPick('name').asStringOrThrow(), ); }) @@ -486,9 +428,7 @@ IList _puzzleOpeningFromPick(RequiredPick pick) { key: familyPick('key').asStringOrThrow(), name: familyPick('name').asStringOrThrow(), count: familyPick('count').asIntOrThrow(), - openings: openings != null - ? openings.toIList() - : IList(const []), + openings: openings != null ? openings.toIList() : IList(const []), ); }).toIList(); } diff --git a/lib/src/model/puzzle/puzzle_service.dart b/lib/src/model/puzzle/puzzle_service.dart index c5d7cb04bf..9dd9196076 100644 --- a/lib/src/model/puzzle/puzzle_service.dart +++ b/lib/src/model/puzzle/puzzle_service.dart @@ -26,9 +26,7 @@ const kPuzzleLocalQueueLength = 50; @Riverpod(keepAlive: true) Future puzzleService(Ref ref) { - return ref.read(puzzleServiceFactoryProvider)( - queueLength: kPuzzleLocalQueueLength, - ); + return ref.read(puzzleServiceFactoryProvider)(queueLength: kPuzzleLocalQueueLength); } @Riverpod(keepAlive: true) @@ -90,15 +88,16 @@ class PuzzleService { }) { return Result.release( _syncAndLoadData(userId, angle).map( - (data) => data.$1 != null && data.$1!.unsolved.isNotEmpty - ? PuzzleContext( - puzzle: data.$1!.unsolved[0], - angle: angle, - userId: userId, - glicko: data.$2, - rounds: data.$3, - ) - : null, + (data) => + data.$1 != null && data.$1!.unsolved.isNotEmpty + ? PuzzleContext( + puzzle: data.$1!.unsolved[0], + angle: angle, + userId: userId, + glicko: data.$2, + rounds: data.$3, + ) + : null, ), ); } @@ -114,18 +113,14 @@ class PuzzleService { PuzzleAngle angle = const PuzzleTheme(PuzzleThemeKey.mix), }) async { puzzleStorage.save(puzzle: puzzle); - final data = await batchStorage.fetch( - userId: userId, - angle: angle, - ); + final data = await batchStorage.fetch(userId: userId, angle: angle); if (data != null) { await batchStorage.save( userId: userId, angle: angle, data: PuzzleBatch( solved: IList([...data.solved, solution]), - unsolved: - data.unsolved.removeWhere((e) => e.puzzle.id == solution.id), + unsolved: data.unsolved.removeWhere((e) => e.puzzle.id == solution.id), ), ); } @@ -142,10 +137,7 @@ class PuzzleService { } /// Deletes the puzzle batch of [angle] from the local storage. - Future deleteBatch({ - required UserId? userId, - required PuzzleAngle angle, - }) async { + Future deleteBatch({required UserId? userId, required PuzzleAngle angle}) async { await batchStorage.delete(userId: userId, angle: angle); } @@ -157,15 +149,11 @@ class PuzzleService { /// /// This method should never fail, as if the network is down it will fallback /// to the local database. - FutureResult<(PuzzleBatch?, PuzzleGlicko?, IList?)> - _syncAndLoadData( + FutureResult<(PuzzleBatch?, PuzzleGlicko?, IList?)> _syncAndLoadData( UserId? userId, PuzzleAngle angle, ) async { - final data = await batchStorage.fetch( - userId: userId, - angle: angle, - ); + final data = await batchStorage.fetch(userId: userId, angle: angle); final unsolved = data?.unsolved ?? IList(const []); final solved = data?.solved ?? IList(const []); @@ -182,48 +170,37 @@ class PuzzleService { final batchResponse = _ref.withClient( (client) => Result.capture( solved.isNotEmpty && userId != null - ? PuzzleRepository(client).solveBatch( - nb: deficit, - solved: solved, - angle: angle, - difficulty: difficulty, - ) - : PuzzleRepository(client).selectBatch( - nb: deficit, - angle: angle, - difficulty: difficulty, - ), + ? PuzzleRepository( + client, + ).solveBatch(nb: deficit, solved: solved, angle: angle, difficulty: difficulty) + : PuzzleRepository( + client, + ).selectBatch(nb: deficit, angle: angle, difficulty: difficulty), ), ); return batchResponse .fold( - (value) => Result.value( - ( - PuzzleBatch( - solved: IList(const []), - unsolved: IList([...unsolved, ...value.puzzles]), - ), - value.glicko, - value.rounds, - true, // should save the batch - ), - ), - - // we don't need to save the batch if the request failed - (_, __) => Result.value((data, null, null, false)), - ) + (value) => Result.value(( + PuzzleBatch( + solved: IList(const []), + unsolved: IList([...unsolved, ...value.puzzles]), + ), + value.glicko, + value.rounds, + true, // should save the batch + )), + + // we don't need to save the batch if the request failed + (_, __) => Result.value((data, null, null, false)), + ) .flatMap((tuple) async { - final (newBatch, glicko, rounds, shouldSave) = tuple; - if (newBatch != null && shouldSave) { - await batchStorage.save( - userId: userId, - angle: angle, - data: newBatch, - ); - } - return Result.value((newBatch, glicko, rounds)); - }); + final (newBatch, glicko, rounds, shouldSave) = tuple; + if (newBatch != null && shouldSave) { + await batchStorage.save(userId: userId, angle: angle, data: newBatch); + } + return Result.value((newBatch, glicko, rounds)); + }); } return Result.value((data, null, null)); diff --git a/lib/src/model/puzzle/puzzle_session.dart b/lib/src/model/puzzle/puzzle_session.dart index 5e1aee26ea..572d302ffc 100644 --- a/lib/src/model/puzzle/puzzle_session.dart +++ b/lib/src/model/puzzle/puzzle_session.dart @@ -38,8 +38,7 @@ class PuzzleSession extends _$PuzzleSession { addIfNotFound: true, ); final newState = d.copyWith( - attempts: - newAttempts.length > maxSize ? newAttempts.sublist(1) : newAttempts, + attempts: newAttempts.length > maxSize ? newAttempts.sublist(1) : newAttempts, lastUpdatedAt: DateTime.now(), ); state = newState; @@ -50,19 +49,18 @@ class PuzzleSession extends _$PuzzleSession { Future setRatingDiffs(Iterable rounds) async { await _update((d) { final newState = d.copyWith( - attempts: d.attempts.map((a) { - final round = rounds.firstWhereOrNull((r) => r.id == a.id); - return round != null ? a.copyWith(ratingDiff: round.ratingDiff) : a; - }).toIList(), + attempts: + d.attempts.map((a) { + final round = rounds.firstWhereOrNull((r) => r.id == a.id); + return round != null ? a.copyWith(ratingDiff: round.ratingDiff) : a; + }).toIList(), ); state = newState; return newState; }); } - Future _update( - PuzzleSessionData Function(PuzzleSessionData d) update, - ) async { + Future _update(PuzzleSessionData Function(PuzzleSessionData d) update) async { await _store.setString(_storageKey, jsonEncode(update(state).toJson())); } @@ -71,13 +69,10 @@ class PuzzleSession extends _$PuzzleSession { if (stored == null) { return PuzzleSessionData.initial(angle: angle); } - return PuzzleSessionData.fromJson( - jsonDecode(stored) as Map, - ); + return PuzzleSessionData.fromJson(jsonDecode(stored) as Map); } - SharedPreferencesWithCache get _store => - LichessBinding.instance.sharedPreferences; + SharedPreferencesWithCache get _store => LichessBinding.instance.sharedPreferences; String get _storageKey => 'puzzle_session.${userId ?? '**anon**'}'; } @@ -90,9 +85,7 @@ class PuzzleSessionData with _$PuzzleSessionData { required DateTime lastUpdatedAt, }) = _PuzzleSession; - factory PuzzleSessionData.initial({ - required PuzzleAngle angle, - }) { + factory PuzzleSessionData.initial({required PuzzleAngle angle}) { return PuzzleSessionData( angle: angle, attempts: IList(const []), @@ -104,9 +97,7 @@ class PuzzleSessionData with _$PuzzleSessionData { try { return _$PuzzleSessionDataFromJson(json); } catch (e) { - return PuzzleSessionData.initial( - angle: const PuzzleTheme(PuzzleThemeKey.mix), - ); + return PuzzleSessionData.initial(angle: const PuzzleTheme(PuzzleThemeKey.mix)); } } } @@ -115,14 +106,10 @@ class PuzzleSessionData with _$PuzzleSessionData { class PuzzleAttempt with _$PuzzleAttempt { const PuzzleAttempt._(); - const factory PuzzleAttempt({ - required PuzzleId id, - required bool win, - int? ratingDiff, - }) = _PuzzleAttempt; + const factory PuzzleAttempt({required PuzzleId id, required bool win, int? ratingDiff}) = + _PuzzleAttempt; - factory PuzzleAttempt.fromJson(Map json) => - _$PuzzleAttemptFromJson(json); + factory PuzzleAttempt.fromJson(Map json) => _$PuzzleAttemptFromJson(json); String? get ratingDiffString { if (ratingDiff == null) return null; diff --git a/lib/src/model/puzzle/puzzle_storage.dart b/lib/src/model/puzzle/puzzle_storage.dart index acd3426f92..c11fc70ae0 100644 --- a/lib/src/model/puzzle/puzzle_storage.dart +++ b/lib/src/model/puzzle/puzzle_storage.dart @@ -22,9 +22,7 @@ class PuzzleStorage { const PuzzleStorage(this._db); final Database _db; - Future fetch({ - required PuzzleId puzzleId, - }) async { + Future fetch({required PuzzleId puzzleId}) async { final list = await _db.query( _tableName, where: 'puzzleId = ?', @@ -45,17 +43,11 @@ class PuzzleStorage { return null; } - Future save({ - required Puzzle puzzle, - }) async { - await _db.insert( - _tableName, - { - 'puzzleId': puzzle.puzzle.id.toString(), - 'lastModified': DateTime.now().toIso8601String(), - 'data': jsonEncode(puzzle.toJson()), - }, - conflictAlgorithm: ConflictAlgorithm.replace, - ); + Future save({required Puzzle puzzle}) async { + await _db.insert(_tableName, { + 'puzzleId': puzzle.puzzle.id.toString(), + 'lastModified': DateTime.now().toIso8601String(), + 'data': jsonEncode(puzzle.toJson()), + }, conflictAlgorithm: ConflictAlgorithm.replace); } } diff --git a/lib/src/model/puzzle/puzzle_streak.dart b/lib/src/model/puzzle/puzzle_streak.dart index c950bc9681..68a03913dc 100644 --- a/lib/src/model/puzzle/puzzle_streak.dart +++ b/lib/src/model/puzzle/puzzle_streak.dart @@ -21,6 +21,5 @@ class PuzzleStreak with _$PuzzleStreak { PuzzleId? get nextId => streak.getOrNull(index + 1); - factory PuzzleStreak.fromJson(Map json) => - _$PuzzleStreakFromJson(json); + factory PuzzleStreak.fromJson(Map json) => _$PuzzleStreakFromJson(json); } diff --git a/lib/src/model/puzzle/puzzle_theme.dart b/lib/src/model/puzzle/puzzle_theme.dart index 0ddf5236e3..0a223fbb01 100644 --- a/lib/src/model/puzzle/puzzle_theme.dart +++ b/lib/src/model/puzzle/puzzle_theme.dart @@ -402,8 +402,7 @@ enum PuzzleThemeKey { } } -final IMap puzzleThemeNameMap = - IMap(PuzzleThemeKey.values.asNameMap()); +final IMap puzzleThemeNameMap = IMap(PuzzleThemeKey.values.asNameMap()); typedef PuzzleThemeCategory = (String, List); @@ -412,12 +411,7 @@ IList puzzleThemeCategories(Ref ref) { final l10n = ref.watch(localizationsProvider); return IList([ - ( - l10n.strings.puzzleRecommended, - [ - PuzzleThemeKey.mix, - ], - ), + (l10n.strings.puzzleRecommended, [PuzzleThemeKey.mix]), ( l10n.strings.puzzlePhases, [ @@ -504,20 +498,11 @@ IList puzzleThemeCategories(Ref ref) { ), ( l10n.strings.puzzleLengths, - [ - PuzzleThemeKey.oneMove, - PuzzleThemeKey.short, - PuzzleThemeKey.long, - PuzzleThemeKey.veryLong, - ], + [PuzzleThemeKey.oneMove, PuzzleThemeKey.short, PuzzleThemeKey.long, PuzzleThemeKey.veryLong], ), ( l10n.strings.puzzleOrigin, - [ - PuzzleThemeKey.master, - PuzzleThemeKey.masterVsMaster, - PuzzleThemeKey.superGM, - ], + [PuzzleThemeKey.master, PuzzleThemeKey.masterVsMaster, PuzzleThemeKey.superGM], ), ]); } diff --git a/lib/src/model/puzzle/storm.dart b/lib/src/model/puzzle/storm.dart index 105a1c4093..949b8a9840 100644 --- a/lib/src/model/puzzle/storm.dart +++ b/lib/src/model/puzzle/storm.dart @@ -36,11 +36,12 @@ class StormRunStats with _$StormRunStats { IList historyFilter(StormFilter filter) { return history .where( - (e) => (filter.slow && filter.failed) - ? (!e.win && slowPuzzleIds.any((id) => id == e.id)) - : (filter.slow - ? slowPuzzleIds.any((id) => id == e.id) - : (!filter.failed || !e.win)), + (e) => + (filter.slow && filter.failed) + ? (!e.win && slowPuzzleIds.any((id) => id == e.id)) + : (filter.slow + ? slowPuzzleIds.any((id) => id == e.id) + : (!filter.failed || !e.win)), ) .toIList(); } @@ -56,12 +57,7 @@ class StormFilter { StormFilter(slow: slow ?? this.slow, failed: failed ?? this.failed); } -enum StormNewHighType { - day, - week, - month, - allTime, -} +enum StormNewHighType { day, week, month, allTime } @freezed class StormDashboard with _$StormDashboard { @@ -84,14 +80,12 @@ class StormDayScore with _$StormDayScore { @freezed class StormNewHigh with _$StormNewHigh { - const factory StormNewHigh({ - required StormNewHighType key, - required int prev, - }) = _StormNewHigh; + const factory StormNewHigh({required StormNewHighType key, required int prev}) = _StormNewHigh; } -final IMap stormNewHighTypeMap = - IMap(StormNewHighType.values.asNameMap()); +final IMap stormNewHighTypeMap = IMap( + StormNewHighType.values.asNameMap(), +); extension StormExtension on Pick { StormNewHighType asStormNewHighTypeOrThrow() { @@ -104,9 +98,7 @@ extension StormExtension on Pick { return stormNewHighTypeMap[value]!; } } - throw PickException( - "value $value at $debugParsingExit can't be casted to StormNewHighType", - ); + throw PickException("value $value at $debugParsingExit can't be casted to StormNewHighType"); } StormNewHighType? asStormNewHighTypeOrNull() { diff --git a/lib/src/model/puzzle/storm_controller.dart b/lib/src/model/puzzle/storm_controller.dart index 544c890db9..edd18949f8 100644 --- a/lib/src/model/puzzle/storm_controller.dart +++ b/lib/src/model/puzzle/storm_controller.dart @@ -105,12 +105,7 @@ class StormController extends _$StormController { } await Future.delayed(moveDelay); - _addMove( - state.expectedMove!, - ComboState.increase, - runStarted: true, - userMove: false, - ); + _addMove(state.expectedMove!, ComboState.increase, runStarted: true, userMove: false); } else { state = state.copyWith(errors: state.errors + 1); ref.read(soundServiceProvider).play(Sound.error); @@ -149,16 +144,11 @@ class StormController extends _$StormController { if (session != null) { final res = await ref.withClient( (client) => Result.capture( - PuzzleRepository(client) - .postStormRun(stats) - .timeout(const Duration(seconds: 2)), + PuzzleRepository(client).postStormRun(stats).timeout(const Duration(seconds: 2)), ), ); - final newState = state.copyWith( - stats: stats, - mode: StormMode.ended, - ); + final newState = state.copyWith(stats: stats, mode: StormMode.ended); res.match( onSuccess: (newHigh) { @@ -173,10 +163,7 @@ class StormController extends _$StormController { }, ); } else { - state = state.copyWith( - stats: stats, - mode: StormMode.ended, - ); + state = state.copyWith(stats: stats, mode: StormMode.ended); } } @@ -206,12 +193,7 @@ class StormController extends _$StormController { ), ); await Future.delayed(moveDelay); - _addMove( - state.expectedMove!, - ComboState.noChange, - runStarted: true, - userMove: false, - ); + _addMove(state.expectedMove!, ComboState.noChange, runStarted: true, userMove: false); } void _addMove( @@ -242,24 +224,18 @@ class StormController extends _$StormController { ), promotionMove: null, ); - Future.delayed( - userMove ? Duration.zero : const Duration(milliseconds: 250), () { + Future.delayed(userMove ? Duration.zero : const Duration(milliseconds: 250), () { if (pos.board.pieceAt(move.to) != null) { - ref - .read(moveFeedbackServiceProvider) - .captureFeedback(check: state.position.isCheck); + ref.read(moveFeedbackServiceProvider).captureFeedback(check: state.position.isCheck); } else { - ref - .read(moveFeedbackServiceProvider) - .moveFeedback(check: state.position.isCheck); + ref.read(moveFeedbackServiceProvider).moveFeedback(check: state.position.isCheck); } }); } StormRunStats _getStats() { final wins = state.history.where((e) => e.win == true).toList(); - final mean = state.history.sumBy((e) => e.solvingTime!.inSeconds) / - state.history.length; + final mean = state.history.sumBy((e) => e.solvingTime!.inSeconds) / state.history.length; final threshold = mean * 1.5; return StormRunStats( moves: state.moves, @@ -268,23 +244,26 @@ class StormController extends _$StormController { comboBest: state.combo.best, time: state.clock.endAt!, timePerMove: mean, - highest: wins.isNotEmpty - ? wins.map((e) => e.rating).reduce( - (maxRating, rating) => rating > maxRating ? rating : maxRating, - ) - : 0, + highest: + wins.isNotEmpty + ? wins + .map((e) => e.rating) + .reduce((maxRating, rating) => rating > maxRating ? rating : maxRating) + : 0, history: state.history, - slowPuzzleIds: state.history - .where((e) => e.solvingTime!.inSeconds > threshold) - .map((e) => e.id) - .toIList(), + slowPuzzleIds: + state.history + .where((e) => e.solvingTime!.inSeconds > threshold) + .map((e) => e.id) + .toIList(), ); } void _pushToHistory({required bool success}) { - final timeTaken = state.lastSolvedTime != null - ? DateTime.now().difference(state.lastSolvedTime!) - : DateTime.now().difference(state.clock.startAt!); + final timeTaken = + state.lastSolvedTime != null + ? DateTime.now().difference(state.lastSolvedTime!) + : DateTime.now().difference(state.clock.startAt!); state = state.copyWith( history: state.history.add( PuzzleHistoryEntry.fromLitePuzzle(state.puzzle, success, timeTaken), @@ -353,8 +332,7 @@ class StormState with _$StormState { Move? get expectedMove => Move.parse(puzzle.solution[moveIndex + 1]); - Move? get lastMove => - moveIndex == -1 ? null : Move.parse(puzzle.solution[moveIndex]); + Move? get lastMove => moveIndex == -1 ? null : Move.parse(puzzle.solution[moveIndex]); bool get isOver => moveIndex >= puzzle.solution.length - 1; @@ -370,10 +348,7 @@ enum ComboState { increase, reset, noChange } class StormCombo with _$StormCombo { const StormCombo._(); - const factory StormCombo({ - required int current, - required int best, - }) = _StormCombo; + const factory StormCombo({required int current, required int best}) = _StormCombo; /// List representing the bonus awared at each level static const levelBonus = [3, 5, 6, 10]; @@ -394,8 +369,7 @@ class StormCombo with _$StormCombo { /// Returns the level of the `current + 1` combo count int nextLevel() { - final lvl = - levelsAndBonus.indexWhere((element) => element.level > current + 1); + final lvl = levelsAndBonus.indexWhere((element) => element.level > current + 1); return lvl >= 0 ? lvl - 1 : levelsAndBonus.length - 1; } @@ -407,8 +381,7 @@ class StormCombo with _$StormCombo { final lvl = getNext ? nextLevel() : currentLevel(); final lastLevel = levelsAndBonus.last; if (lvl >= levelsAndBonus.length - 1) { - final range = - lastLevel.level - levelsAndBonus[levelsAndBonus.length - 2].level; + final range = lastLevel.level - levelsAndBonus[levelsAndBonus.length - 2].level; return (((currentCombo - lastLevel.level) / range) * 100) % 100; } final bounds = [levelsAndBonus[lvl].level, levelsAndBonus[lvl + 1].level]; diff --git a/lib/src/model/puzzle/streak_storage.dart b/lib/src/model/puzzle/streak_storage.dart index dbe0101a90..a28e6519a5 100644 --- a/lib/src/model/puzzle/streak_storage.dart +++ b/lib/src/model/puzzle/streak_storage.dart @@ -26,24 +26,18 @@ class StreakStorage { return null; } - return PuzzleStreak.fromJson( - jsonDecode(stored) as Map, - ); + return PuzzleStreak.fromJson(jsonDecode(stored) as Map); } Future saveActiveStreak(PuzzleStreak streak) async { - await _store.setString( - _storageKey, - jsonEncode(streak), - ); + await _store.setString(_storageKey, jsonEncode(streak)); } Future clearActiveStreak() async { await _store.remove(_storageKey); } - SharedPreferencesWithCache get _store => - LichessBinding.instance.sharedPreferences; + SharedPreferencesWithCache get _store => LichessBinding.instance.sharedPreferences; String get _storageKey => 'puzzle_streak.${userId ?? '**anon**'}'; } diff --git a/lib/src/model/relation/online_friends.dart b/lib/src/model/relation/online_friends.dart index 8f1f4a3980..be5f41498f 100644 --- a/lib/src/model/relation/online_friends.dart +++ b/lib/src/model/relation/online_friends.dart @@ -22,9 +22,7 @@ class OnlineFriends extends _$OnlineFriends { final state = _socketClient.stream .firstWhere((e) => e.topic == 'following_onlines') - .then( - (event) => _parseFriendsList(event.data as List), - ); + .then((event) => _parseFriendsList(event.data as List)); await _socketClient.firstConnection; @@ -70,9 +68,7 @@ class OnlineFriends extends _$OnlineFriends { switch (event.topic) { case 'following_onlines': - state = AsyncValue.data( - _parseFriendsList(event.data as List), - ); + state = AsyncValue.data(_parseFriendsList(event.data as List)); case 'following_enters': final data = _parseFriend(event.data.toString()); @@ -80,9 +76,7 @@ class OnlineFriends extends _$OnlineFriends { case 'following_leaves': final data = _parseFriend(event.data.toString()); - state = AsyncValue.data( - state.requireValue.removeWhere((v) => v.id == data.id), - ); + state = AsyncValue.data(state.requireValue.removeWhere((v) => v.id == data.id)); } } @@ -92,11 +86,7 @@ class OnlineFriends extends _$OnlineFriends { final splitted = friend.split(' '); final name = splitted.length > 1 ? splitted[1] : splitted[0]; final title = splitted.length > 1 ? splitted[0] : null; - return LightUser( - id: UserId.fromUserName(name), - name: name, - title: title, - ); + return LightUser(id: UserId.fromUserName(name), name: name, title: title); } IList _parseFriendsList(List friends) { diff --git a/lib/src/model/relation/relation_repository.dart b/lib/src/model/relation/relation_repository.dart index b82ee33325..218babb3d3 100644 --- a/lib/src/model/relation/relation_repository.dart +++ b/lib/src/model/relation/relation_repository.dart @@ -22,10 +22,7 @@ class RelationRepository { final response = await client.post(uri); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to follow user: ${response.statusCode}', - uri, - ); + throw http.ClientException('Failed to follow user: ${response.statusCode}', uri); } } @@ -34,10 +31,7 @@ class RelationRepository { final response = await client.post(uri); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to unfollow user: ${response.statusCode}', - uri, - ); + throw http.ClientException('Failed to unfollow user: ${response.statusCode}', uri); } } @@ -46,10 +40,7 @@ class RelationRepository { final response = await client.post(uri); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to block user: ${response.statusCode}', - uri, - ); + throw http.ClientException('Failed to block user: ${response.statusCode}', uri); } } @@ -58,10 +49,7 @@ class RelationRepository { final response = await client.post(uri); if (response.statusCode >= 400) { - throw http.ClientException( - 'Failed to unblock user: ${response.statusCode}', - uri, - ); + throw http.ClientException('Failed to unblock user: ${response.statusCode}', uri); } } } diff --git a/lib/src/model/settings/board_preferences.dart b/lib/src/model/settings/board_preferences.dart index 1c4f87319e..65dd2b3f1b 100644 --- a/lib/src/model/settings/board_preferences.dart +++ b/lib/src/model/settings/board_preferences.dart @@ -12,8 +12,7 @@ part 'board_preferences.freezed.dart'; part 'board_preferences.g.dart'; @riverpod -class BoardPreferences extends _$BoardPreferences - with PreferencesStorage { +class BoardPreferences extends _$BoardPreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties @override PrefCategory get prefCategory => PrefCategory.board; @@ -48,9 +47,7 @@ class BoardPreferences extends _$BoardPreferences Future toggleImmersiveModeWhilePlaying() { return save( - state.copyWith( - immersiveModeWhilePlaying: !(state.immersiveModeWhilePlaying ?? false), - ), + state.copyWith(immersiveModeWhilePlaying: !(state.immersiveModeWhilePlaying ?? false)), ); } @@ -75,35 +72,23 @@ class BoardPreferences extends _$BoardPreferences } Future toggleMagnifyDraggedPiece() { - return save( - state.copyWith( - magnifyDraggedPiece: !state.magnifyDraggedPiece, - ), - ); + return save(state.copyWith(magnifyDraggedPiece: !state.magnifyDraggedPiece)); } Future setDragTargetKind(DragTargetKind dragTargetKind) { return save(state.copyWith(dragTargetKind: dragTargetKind)); } - Future setMaterialDifferenceFormat( - MaterialDifferenceFormat materialDifferenceFormat, - ) { - return save( - state.copyWith(materialDifferenceFormat: materialDifferenceFormat), - ); + Future setMaterialDifferenceFormat(MaterialDifferenceFormat materialDifferenceFormat) { + return save(state.copyWith(materialDifferenceFormat: materialDifferenceFormat)); } Future setClockPosition(ClockPosition clockPosition) { - return save( - state.copyWith(clockPosition: clockPosition), - ); + return save(state.copyWith(clockPosition: clockPosition)); } Future toggleEnableShapeDrawings() { - return save( - state.copyWith(enableShapeDrawings: !state.enableShapeDrawings), - ); + return save(state.copyWith(enableShapeDrawings: !state.enableShapeDrawings)); } Future setShapeColor(ShapeColor shapeColor) { @@ -130,24 +115,15 @@ class BoardPrefs with _$BoardPrefs implements Serializable { ) required MaterialDifferenceFormat materialDifferenceFormat, required ClockPosition clockPosition, - @JsonKey( - defaultValue: PieceShiftMethod.either, - unknownEnumValue: PieceShiftMethod.either, - ) + @JsonKey(defaultValue: PieceShiftMethod.either, unknownEnumValue: PieceShiftMethod.either) required PieceShiftMethod pieceShiftMethod, /// Whether to enable shape drawings on the board for games and puzzles. @JsonKey(defaultValue: true) required bool enableShapeDrawings, @JsonKey(defaultValue: true) required bool magnifyDraggedPiece, - @JsonKey( - defaultValue: DragTargetKind.circle, - unknownEnumValue: DragTargetKind.circle, - ) + @JsonKey(defaultValue: DragTargetKind.circle, unknownEnumValue: DragTargetKind.circle) required DragTargetKind dragTargetKind, - @JsonKey( - defaultValue: ShapeColor.green, - unknownEnumValue: ShapeColor.green, - ) + @JsonKey(defaultValue: ShapeColor.green, unknownEnumValue: ShapeColor.green) required ShapeColor shapeColor, @JsonKey(defaultValue: false) required bool showBorder, }) = _BoardPrefs; @@ -175,12 +151,10 @@ class BoardPrefs with _$BoardPrefs implements Serializable { return ChessboardSettings( pieceAssets: pieceSet.assets, colorScheme: boardTheme.colors, - border: showBorder - ? BoardBorder( - color: darken(boardTheme.colors.darkSquare, 0.2), - width: 16.0, - ) - : null, + border: + showBorder + ? BoardBorder(color: darken(boardTheme.colors.darkSquare, 0.2), width: 16.0) + : null, showValidMoves: showLegalMoves, showLastMove: boardHighlights, enableCoordinates: coordinates, @@ -189,10 +163,7 @@ class BoardPrefs with _$BoardPrefs implements Serializable { dragFeedbackOffset: Offset(0.0, magnifyDraggedPiece ? -1.0 : 0.0), dragTargetKind: dragTargetKind, pieceShiftMethod: pieceShiftMethod, - drawShape: DrawShapeOptions( - enable: enableShapeDrawings, - newShapeColor: shapeColor.color, - ), + drawShape: DrawShapeOptions(enable: enableShapeDrawings, newShapeColor: shapeColor.color), ); } @@ -211,14 +182,12 @@ enum ShapeColor { blue, yellow; - Color get color => Color( - switch (this) { - ShapeColor.green => 0x15781B, - ShapeColor.red => 0x882020, - ShapeColor.blue => 0x003088, - ShapeColor.yellow => 0xe68f00, - }, - ).withAlpha(0xAA); + Color get color => Color(switch (this) { + ShapeColor.green => 0x15781B, + ShapeColor.red => 0x882020, + ShapeColor.blue => 0x003088, + ShapeColor.yellow => 0xe68f00, + }).withAlpha(0xAA); } /// The chessboard theme. @@ -306,27 +275,29 @@ enum BoardTheme { } } - Widget get thumbnail => this == BoardTheme.system - ? SizedBox( - height: 44, - width: 44 * 6, - child: Row( - children: [ - for (final c in const [1, 2, 3, 4, 5, 6]) - Container( - width: 44, - color: c.isEven - ? BoardTheme.system.colors.darkSquare - : BoardTheme.system.colors.lightSquare, - ), - ], - ), - ) - : Image.asset( - 'assets/board-thumbnails/$name.jpg', - height: 44, - errorBuilder: (context, o, st) => const SizedBox.shrink(), - ); + Widget get thumbnail => + this == BoardTheme.system + ? SizedBox( + height: 44, + width: 44 * 6, + child: Row( + children: [ + for (final c in const [1, 2, 3, 4, 5, 6]) + Container( + width: 44, + color: + c.isEven + ? BoardTheme.system.colors.darkSquare + : BoardTheme.system.colors.lightSquare, + ), + ], + ), + ) + : Image.asset( + 'assets/board-thumbnails/$name.jpg', + height: 44, + errorBuilder: (context, o, st) => const SizedBox.shrink(), + ); } enum MaterialDifferenceFormat { @@ -334,20 +305,18 @@ enum MaterialDifferenceFormat { capturedPieces(label: 'Captured pieces'), hidden(label: 'Hidden'); - const MaterialDifferenceFormat({ - required this.label, - }); + const MaterialDifferenceFormat({required this.label}); final String label; bool get visible => this != MaterialDifferenceFormat.hidden; String l10n(AppLocalizations l10n) => switch (this) { - //TODO: Add l10n - MaterialDifferenceFormat.materialDifference => materialDifference.label, - MaterialDifferenceFormat.capturedPieces => capturedPieces.label, - MaterialDifferenceFormat.hidden => hidden.label, - }; + //TODO: Add l10n + MaterialDifferenceFormat.materialDifference => materialDifference.label, + MaterialDifferenceFormat.capturedPieces => capturedPieces.label, + MaterialDifferenceFormat.hidden => hidden.label, + }; } enum ClockPosition { @@ -356,13 +325,13 @@ enum ClockPosition { // TODO: l10n String get label => switch (this) { - ClockPosition.left => 'Left', - ClockPosition.right => 'Right', - }; + ClockPosition.left => 'Left', + ClockPosition.right => 'Right', + }; } String dragTargetKindLabel(DragTargetKind kind) => switch (kind) { - DragTargetKind.circle => 'Circle', - DragTargetKind.square => 'Square', - DragTargetKind.none => 'None', - }; + DragTargetKind.circle => 'Circle', + DragTargetKind.square => 'Square', + DragTargetKind.none => 'None', +}; diff --git a/lib/src/model/settings/brightness.dart b/lib/src/model/settings/brightness.dart index f232bbc6b8..6cc6f93b6c 100644 --- a/lib/src/model/settings/brightness.dart +++ b/lib/src/model/settings/brightness.dart @@ -9,14 +9,9 @@ part 'brightness.g.dart'; class CurrentBrightness extends _$CurrentBrightness { @override Brightness build() { - final themeMode = ref.watch( - generalPreferencesProvider.select( - (state) => state.themeMode, - ), - ); + final themeMode = ref.watch(generalPreferencesProvider.select((state) => state.themeMode)); - WidgetsBinding.instance.platformDispatcher.onPlatformBrightnessChanged = - () { + WidgetsBinding.instance.platformDispatcher.onPlatformBrightnessChanged = () { WidgetsBinding.instance.handlePlatformBrightnessChanged(); if (themeMode == BackgroundThemeMode.system) { state = WidgetsBinding.instance.platformDispatcher.platformBrightness; diff --git a/lib/src/model/settings/general_preferences.dart b/lib/src/model/settings/general_preferences.dart index 3e87d9c6cf..70f18c3e69 100644 --- a/lib/src/model/settings/general_preferences.dart +++ b/lib/src/model/settings/general_preferences.dart @@ -9,8 +9,7 @@ part 'general_preferences.freezed.dart'; part 'general_preferences.g.dart'; @riverpod -class GeneralPreferences extends _$GeneralPreferences - with PreferencesStorage { +class GeneralPreferences extends _$GeneralPreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties @override final prefCategory = PrefCategory.general; @@ -20,8 +19,7 @@ class GeneralPreferences extends _$GeneralPreferences GeneralPrefs get defaults => GeneralPrefs.defaults; @override - GeneralPrefs fromJson(Map json) => - GeneralPrefs.fromJson(json); + GeneralPrefs fromJson(Map json) => GeneralPrefs.fromJson(json); @override GeneralPrefs build() { @@ -53,14 +51,10 @@ class GeneralPreferences extends _$GeneralPreferences if (state.systemColors == false) { final boardTheme = ref.read(boardPreferencesProvider).boardTheme; if (boardTheme == BoardTheme.system) { - await ref - .read(boardPreferencesProvider.notifier) - .setBoardTheme(BoardTheme.brown); + await ref.read(boardPreferencesProvider.notifier).setBoardTheme(BoardTheme.brown); } } else { - await ref - .read(boardPreferencesProvider.notifier) - .setBoardTheme(BoardTheme.system); + await ref.read(boardPreferencesProvider.notifier).setBoardTheme(BoardTheme.system); } } } @@ -68,10 +62,10 @@ class GeneralPreferences extends _$GeneralPreferences Map? _localeToJson(Locale? locale) { return locale != null ? { - 'languageCode': locale.languageCode, - 'countryCode': locale.countryCode, - 'scriptCode': locale.scriptCode, - } + 'languageCode': locale.languageCode, + 'countryCode': locale.countryCode, + 'scriptCode': locale.scriptCode, + } : null; } @@ -89,14 +83,10 @@ Locale? _localeFromJson(Map? json) { @Freezed(fromJson: true, toJson: true) class GeneralPrefs with _$GeneralPrefs implements Serializable { const factory GeneralPrefs({ - @JsonKey( - unknownEnumValue: BackgroundThemeMode.system, - defaultValue: BackgroundThemeMode.system, - ) + @JsonKey(unknownEnumValue: BackgroundThemeMode.system, defaultValue: BackgroundThemeMode.system) required BackgroundThemeMode themeMode, required bool isSoundEnabled, - @JsonKey(unknownEnumValue: SoundTheme.standard) - required SoundTheme soundTheme, + @JsonKey(unknownEnumValue: SoundTheme.standard) required SoundTheme soundTheme, @JsonKey(defaultValue: 0.8) required double masterVolume, /// Should enable system color palette (android 12+ only) diff --git a/lib/src/model/settings/home_preferences.dart b/lib/src/model/settings/home_preferences.dart index 31e2eefff0..ff9fa70910 100644 --- a/lib/src/model/settings/home_preferences.dart +++ b/lib/src/model/settings/home_preferences.dart @@ -6,8 +6,7 @@ part 'home_preferences.freezed.dart'; part 'home_preferences.g.dart'; @riverpod -class HomePreferences extends _$HomePreferences - with PreferencesStorage { +class HomePreferences extends _$HomePreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties @override PrefCategory get prefCategory => PrefCategory.home; @@ -26,32 +25,23 @@ class HomePreferences extends _$HomePreferences Future toggleWidget(EnabledWidget widget) { final newState = state.copyWith( - enabledWidgets: state.enabledWidgets.contains(widget) - ? state.enabledWidgets.difference({widget}) - : state.enabledWidgets.union({widget}), + enabledWidgets: + state.enabledWidgets.contains(widget) + ? state.enabledWidgets.difference({widget}) + : state.enabledWidgets.union({widget}), ); return save(newState); } } -enum EnabledWidget { - hello, - perfCards, - quickPairing, -} +enum EnabledWidget { hello, perfCards, quickPairing } @Freezed(fromJson: true, toJson: true) class HomePrefs with _$HomePrefs implements Serializable { - const factory HomePrefs({ - required Set enabledWidgets, - }) = _HomePrefs; + const factory HomePrefs({required Set enabledWidgets}) = _HomePrefs; static const defaults = HomePrefs( - enabledWidgets: { - EnabledWidget.hello, - EnabledWidget.perfCards, - EnabledWidget.quickPairing, - }, + enabledWidgets: {EnabledWidget.hello, EnabledWidget.perfCards, EnabledWidget.quickPairing}, ); factory HomePrefs.fromJson(Map json) { diff --git a/lib/src/model/settings/over_the_board_preferences.dart b/lib/src/model/settings/over_the_board_preferences.dart index 7565119845..4fe2b72f7c 100644 --- a/lib/src/model/settings/over_the_board_preferences.dart +++ b/lib/src/model/settings/over_the_board_preferences.dart @@ -17,8 +17,7 @@ class OverTheBoardPreferences extends _$OverTheBoardPreferences OverTheBoardPrefs get defaults => OverTheBoardPrefs.defaults; @override - OverTheBoardPrefs fromJson(Map json) => - OverTheBoardPrefs.fromJson(json); + OverTheBoardPrefs fromJson(Map json) => OverTheBoardPrefs.fromJson(json); @override OverTheBoardPrefs build() { @@ -26,15 +25,11 @@ class OverTheBoardPreferences extends _$OverTheBoardPreferences } Future toggleFlipPiecesAfterMove() { - return save( - state.copyWith(flipPiecesAfterMove: !state.flipPiecesAfterMove), - ); + return save(state.copyWith(flipPiecesAfterMove: !state.flipPiecesAfterMove)); } Future toggleSymmetricPieces() { - return save( - state.copyWith(symmetricPieces: !state.symmetricPieces), - ); + return save(state.copyWith(symmetricPieces: !state.symmetricPieces)); } } @@ -47,10 +42,7 @@ class OverTheBoardPrefs with _$OverTheBoardPrefs implements Serializable { required bool symmetricPieces, }) = _OverTheBoardPrefs; - static const defaults = OverTheBoardPrefs( - flipPiecesAfterMove: false, - symmetricPieces: false, - ); + static const defaults = OverTheBoardPrefs(flipPiecesAfterMove: false, symmetricPieces: false); factory OverTheBoardPrefs.fromJson(Map json) { return _$OverTheBoardPrefsFromJson(json); diff --git a/lib/src/model/settings/preferences_storage.dart b/lib/src/model/settings/preferences_storage.dart index a167695c54..9234927cde 100644 --- a/lib/src/model/settings/preferences_storage.dart +++ b/lib/src/model/settings/preferences_storage.dart @@ -41,22 +41,21 @@ mixin PreferencesStorage on AutoDisposeNotifier { PrefCategory get prefCategory; Future save(T value) async { - await LichessBinding.instance.sharedPreferences - .setString(prefCategory.storageKey, jsonEncode(value.toJson())); + await LichessBinding.instance.sharedPreferences.setString( + prefCategory.storageKey, + jsonEncode(value.toJson()), + ); state = value; } T fetch() { - final stored = LichessBinding.instance.sharedPreferences - .getString(prefCategory.storageKey); + final stored = LichessBinding.instance.sharedPreferences.getString(prefCategory.storageKey); if (stored == null) { return defaults; } try { - return fromJson( - jsonDecode(stored) as Map, - ); + return fromJson(jsonDecode(stored) as Map); } catch (e) { _logger.warning('Failed to decode $prefCategory preferences: $e'); return defaults; @@ -65,8 +64,7 @@ mixin PreferencesStorage on AutoDisposeNotifier { } /// A [Notifier] mixin to provide a way to store and retrieve preferences per session. -mixin SessionPreferencesStorage - on AutoDisposeNotifier { +mixin SessionPreferencesStorage on AutoDisposeNotifier { T fromJson(Map json); T defaults({LightUser? user}); @@ -84,15 +82,14 @@ mixin SessionPreferencesStorage T fetch() { final session = ref.watch(authSessionProvider); - final stored = LichessBinding.instance.sharedPreferences - .getString(key(prefCategory.storageKey, session)); + final stored = LichessBinding.instance.sharedPreferences.getString( + key(prefCategory.storageKey, session), + ); if (stored == null) { return defaults(user: session?.user); } try { - return fromJson( - jsonDecode(stored) as Map, - ); + return fromJson(jsonDecode(stored) as Map); } catch (e) { _logger.warning('Failed to decode $prefCategory preferences: $e'); return defaults(user: session?.user); diff --git a/lib/src/model/study/study.dart b/lib/src/model/study/study.dart index ae939d4761..2a45906358 100644 --- a/lib/src/model/study/study.dart +++ b/lib/src/model/study/study.dart @@ -35,8 +35,7 @@ class Study with _$Study { required IList deviationComments, }) = _Study; - StudyChapterMeta get currentChapterMeta => - chapters.firstWhere((c) => c.id == chapter.id); + StudyChapterMeta get currentChapterMeta => chapters.firstWhere((c) => c.id == chapter.id); factory Study.fromServerJson(Map json) { return _studyFromPick(pick(json).required()); @@ -66,22 +65,18 @@ Study _studyFromPick(RequiredPick pick) { chat: study('features', 'chat').asBoolOrFalse(), sticky: study('features', 'sticky').asBoolOrFalse(), ), - topics: - study('topics').asListOrThrow((pick) => pick.asStringOrThrow()).lock, - chapters: study('chapters') - .asListOrThrow((pick) => StudyChapterMeta.fromJson(pick.asMapOrThrow())) - .lock, + topics: study('topics').asListOrThrow((pick) => pick.asStringOrThrow()).lock, + chapters: + study( + 'chapters', + ).asListOrThrow((pick) => StudyChapterMeta.fromJson(pick.asMapOrThrow())).lock, chapter: StudyChapter.fromJson(study('chapter').asMapOrThrow()), hints: hints.lock, deviationComments: deviationComments.lock, ); } -typedef StudyFeatures = ({ - bool cloneable, - bool chat, - bool sticky, -}); +typedef StudyFeatures = ({bool cloneable, bool chat, bool sticky}); @Freezed(fromJson: true) class StudyChapter with _$StudyChapter { @@ -93,18 +88,13 @@ class StudyChapter with _$StudyChapter { @JsonKey(defaultValue: false) required bool practise, required int? conceal, @JsonKey(defaultValue: false) required bool gamebook, - @JsonKey(fromJson: studyChapterFeaturesFromJson) - required StudyChapterFeatures features, + @JsonKey(fromJson: studyChapterFeaturesFromJson) required StudyChapterFeatures features, }) = _StudyChapter; - factory StudyChapter.fromJson(Map json) => - _$StudyChapterFromJson(json); + factory StudyChapter.fromJson(Map json) => _$StudyChapterFromJson(json); } -typedef StudyChapterFeatures = ({ - bool computer, - bool explorer, -}); +typedef StudyChapterFeatures = ({bool computer, bool explorer}); StudyChapterFeatures studyChapterFeaturesFromJson(Map json) { return ( @@ -129,9 +119,7 @@ class StudyChapterSetup with _$StudyChapterSetup { } Variant _variantFromJson(Map json) { - return Variant.values.firstWhereOrNull( - (v) => v.name == json['key'], - )!; + return Variant.values.firstWhereOrNull((v) => v.name == json['key'])!; } @Freezed(fromJson: true) @@ -144,8 +132,7 @@ class StudyChapterMeta with _$StudyChapterMeta { required String? fen, }) = _StudyChapterMeta; - factory StudyChapterMeta.fromJson(Map json) => - _$StudyChapterMetaFromJson(json); + factory StudyChapterMeta.fromJson(Map json) => _$StudyChapterMetaFromJson(json); } @Freezed(fromJson: true) @@ -157,8 +144,7 @@ class StudyPageData with _$StudyPageData { required String name, required bool liked, required int likes, - @JsonKey(fromJson: DateTime.fromMillisecondsSinceEpoch) - required DateTime updatedAt, + @JsonKey(fromJson: DateTime.fromMillisecondsSinceEpoch) required DateTime updatedAt, required LightUser? owner, required IList topics, required IList members, @@ -166,19 +152,14 @@ class StudyPageData with _$StudyPageData { required String? flair, }) = _StudyPageData; - factory StudyPageData.fromJson(Map json) => - _$StudyPageDataFromJson(json); + factory StudyPageData.fromJson(Map json) => _$StudyPageDataFromJson(json); } @Freezed(fromJson: true) class StudyMember with _$StudyMember { const StudyMember._(); - const factory StudyMember({ - required LightUser user, - required String role, - }) = _StudyMember; + const factory StudyMember({required LightUser user, required String role}) = _StudyMember; - factory StudyMember.fromJson(Map json) => - _$StudyMemberFromJson(json); + factory StudyMember.fromJson(Map json) => _$StudyMemberFromJson(json); } diff --git a/lib/src/model/study/study_controller.dart b/lib/src/model/study/study_controller.dart index c77ccf64f4..6e2c2e6e73 100644 --- a/lib/src/model/study/study_controller.dart +++ b/lib/src/model/study/study_controller.dart @@ -72,19 +72,11 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { } Future goToChapter(StudyChapterId chapterId) async { - state = AsyncValue.data( - await _fetchChapter( - state.requireValue.study.id, - chapterId: chapterId, - ), - ); + state = AsyncValue.data(await _fetchChapter(state.requireValue.study.id, chapterId: chapterId)); _ensureItsOurTurnIfGamebook(); } - Future _fetchChapter( - StudyId id, { - StudyChapterId? chapterId, - }) async { + Future _fetchChapter(StudyId id, {StudyChapterId? chapterId}) async { final (study, pgn) = await ref .read(studyRepositoryProvider) .getStudy(id: id, chapterId: chapterId); @@ -134,8 +126,7 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { pgnRootComments: rootComments, lastMove: lastMove, pov: orientation, - isComputerAnalysisAllowed: - study.chapter.features.computer && !study.chapter.gamebook, + isComputerAnalysisAllowed: study.chapter.features.computer && !study.chapter.gamebook, isLocalEvaluationEnabled: prefs.enableLocalEvaluation, gamebookActive: study.chapter.gamebook, pgn: pgn, @@ -147,18 +138,18 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { evaluationService .initEngine( - _evaluationContext(studyState.variant), - options: EvaluationOptions( - multiPv: prefs.numEvalLines, - cores: prefs.numEngineCores, - searchTime: ref.read(analysisPreferencesProvider).engineSearchTime, - ), - ) + _evaluationContext(studyState.variant), + options: EvaluationOptions( + multiPv: prefs.numEvalLines, + cores: prefs.numEngineCores, + searchTime: ref.read(analysisPreferencesProvider).engineSearchTime, + ), + ) .then((_) { - _startEngineEvalTimer = Timer(const Duration(milliseconds: 250), () { - _startEngineEval(); - }); - }); + _startEngineEvalTimer = Timer(const Duration(milliseconds: 250), () { + _startEngineEval(); + }); + }); } return studyState; @@ -170,9 +161,7 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { final liked = state.requireValue.study.liked; _socketClient.send('like', {'liked': !liked}); state = AsyncValue.data( - state.requireValue.copyWith( - study: state.requireValue.study.copyWith(liked: !liked), - ), + state.requireValue.copyWith(study: state.requireValue.study.copyWith(liked: !liked)), ); }); } @@ -184,14 +173,12 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { } switch (event.topic) { case 'liking': - final data = - (event.data as Map)['l'] as Map; + final data = (event.data as Map)['l'] as Map; final likes = data['likes'] as int; final bool meLiked = data['me'] as bool; state = AsyncValue.data( state.requireValue.copyWith( - study: - state.requireValue.study.copyWith(liked: meLiked, likes: likes), + study: state.requireValue.study.copyWith(liked: meLiked, likes: likes), ), ); } @@ -210,10 +197,8 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { } } - EvaluationContext _evaluationContext(Variant variant) => EvaluationContext( - variant: variant, - initialPosition: _root.position, - ); + EvaluationContext _evaluationContext(Variant variant) => + EvaluationContext(variant: variant, initialPosition: _root.position); void onUserMove(NormalMove move) { if (!state.hasValue || state.requireValue.position == null) return; @@ -225,14 +210,9 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { return; } - final (newPath, isNewNode) = - _root.addMoveAt(state.requireValue.currentPath, move); + final (newPath, isNewNode) = _root.addMoveAt(state.requireValue.currentPath, move); if (newPath != null) { - _setPath( - newPath, - shouldRecomputeRootView: isNewNode, - shouldForceShowVariation: true, - ); + _setPath(newPath, shouldRecomputeRootView: isNewNode, shouldForceShowVariation: true); } if (state.requireValue.gamebookActive) { @@ -332,8 +312,7 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { final node = _root.nodeAt(path); - final childrenToShow = - _root.isOnMainline(path) ? node.children.skip(1) : node.children; + final childrenToShow = _root.isOnMainline(path) ? node.children.skip(1) : node.children; for (final child in childrenToShow) { child.isCollapsed = false; @@ -363,10 +342,7 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { if (state == null) return; _root.promoteAt(path, toMainline: toMainline); this.state = AsyncValue.data( - state.copyWith( - isOnMainline: _root.isOnMainline(state.currentPath), - root: _root.view, - ), + state.copyWith(isOnMainline: _root.isOnMainline(state.currentPath), root: _root.view), ); } @@ -381,9 +357,7 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { Future toggleLocalEvaluation() async { if (!state.hasValue) return; - ref - .read(analysisPreferencesProvider.notifier) - .toggleEnableLocalEvaluation(); + ref.read(analysisPreferencesProvider.notifier).toggleEnableLocalEvaluation(); state = AsyncValue.data( state.requireValue.copyWith( @@ -393,13 +367,14 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { if (state.requireValue.isEngineAvailable) { final prefs = ref.read(analysisPreferencesProvider); - await ref.read(evaluationServiceProvider).initEngine( + await ref + .read(evaluationServiceProvider) + .initEngine( _evaluationContext(state.requireValue.variant), options: EvaluationOptions( multiPv: prefs.numEvalLines, cores: prefs.numEngineCores, - searchTime: - ref.read(analysisPreferencesProvider).engineSearchTime, + searchTime: ref.read(analysisPreferencesProvider).engineSearchTime, ), ); _startEngineEval(); @@ -412,11 +387,11 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { void setNumEvalLines(int numEvalLines) { if (!state.hasValue) return; - ref - .read(analysisPreferencesProvider.notifier) - .setNumEvalLines(numEvalLines); + ref.read(analysisPreferencesProvider.notifier).setNumEvalLines(numEvalLines); - ref.read(evaluationServiceProvider).setOptions( + ref + .read(evaluationServiceProvider) + .setOptions( EvaluationOptions( multiPv: numEvalLines, cores: ref.read(analysisPreferencesProvider).numEngineCores, @@ -428,9 +403,7 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { state = AsyncValue.data( state.requireValue.copyWith( - currentNode: StudyCurrentNode.fromNode( - _root.nodeAt(state.requireValue.currentPath), - ), + currentNode: StudyCurrentNode.fromNode(_root.nodeAt(state.requireValue.currentPath)), ), ); @@ -438,11 +411,11 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { } void setEngineCores(int numEngineCores) { - ref - .read(analysisPreferencesProvider.notifier) - .setEngineCores(numEngineCores); + ref.read(analysisPreferencesProvider.notifier).setEngineCores(numEngineCores); - ref.read(evaluationServiceProvider).setOptions( + ref + .read(evaluationServiceProvider) + .setOptions( EvaluationOptions( multiPv: ref.read(analysisPreferencesProvider).numEvalLines, cores: numEngineCores, @@ -454,11 +427,11 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { } void setEngineSearchTime(Duration searchTime) { - ref - .read(analysisPreferencesProvider.notifier) - .setEngineSearchTime(searchTime); + ref.read(analysisPreferencesProvider.notifier).setEngineSearchTime(searchTime); - ref.read(evaluationServiceProvider).setOptions( + ref + .read(evaluationServiceProvider) + .setOptions( EvaluationOptions( multiPv: ref.read(analysisPreferencesProvider).numEvalLines, cores: ref.read(analysisPreferencesProvider).numEngineCores, @@ -482,9 +455,7 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { final currentNode = _root.nodeAt(path); // always show variation if the user plays a move - if (shouldForceShowVariation && - currentNode is Branch && - currentNode.isCollapsed) { + if (shouldForceShowVariation && currentNode is Branch && currentNode.isCollapsed) { _root.updateAt(path, (node) { if (node is Branch) node.isCollapsed = false; }); @@ -493,9 +464,7 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { // root view is only used to display move list, so we need to // recompute the root view only when the nodelist length changes // or a variation is hidden/shown - final rootView = shouldForceShowVariation || shouldRecomputeRootView - ? _root.view - : state.root; + final rootView = shouldForceShowVariation || shouldRecomputeRootView ? _root.view : state.root; final isForward = path.size > state.currentPath.size; if (currentNode is Branch) { @@ -503,9 +472,7 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { if (isForward) { final isCheck = currentNode.sanMove.isCheck; if (currentNode.sanMove.isCapture) { - ref - .read(moveFeedbackServiceProvider) - .captureFeedback(check: isCheck); + ref.read(moveFeedbackServiceProvider).captureFeedback(check: isCheck); } else { ref.read(moveFeedbackServiceProvider).moveFeedback(check: isCheck); } @@ -558,12 +525,9 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { _root.branchesOn(state.currentPath).map(Step.fromNode), // Note: AnalysisController passes _root.eval as initialPositionEval here, // but for studies this leads to false positive cache hits when switching between chapters. - shouldEmit: (work) => - work.path == this.state.valueOrNull?.currentPath, + shouldEmit: (work) => work.path == this.state.valueOrNull?.currentPath, ) - ?.forEach( - (t) => _root.updateAt(t.$1.path, (node) => node.eval = t.$2), - ); + ?.forEach((t) => _root.updateAt(t.$1.path, (node) => node.eval = t.$2)); } void _debouncedStartEngineEval() { @@ -580,21 +544,13 @@ class StudyController extends _$StudyController implements PgnTreeNotifier { // update the current node with last cached eval state = AsyncValue.data( state.requireValue.copyWith( - currentNode: StudyCurrentNode.fromNode( - _root.nodeAt(state.requireValue.currentPath), - ), + currentNode: StudyCurrentNode.fromNode(_root.nodeAt(state.requireValue.currentPath)), ), ); } } -enum GamebookState { - startLesson, - findTheMove, - correctMove, - incorrectMove, - lessonComplete -} +enum GamebookState { startLesson, findTheMove, correctMove, incorrectMove, lessonComplete } @freezed class StudyState with _$StudyState { @@ -645,9 +601,8 @@ class StudyState with _$StudyState { IList? pgnRootComments, }) = _StudyState; - IMap> get validMoves => currentNode.position != null - ? makeLegalMoves(currentNode.position!) - : const IMap.empty(); + IMap> get validMoves => + currentNode.position != null ? makeLegalMoves(currentNode.position!) : const IMap.empty(); /// Whether the engine is available for evaluation bool get isEngineAvailable => @@ -655,28 +610,25 @@ class StudyState with _$StudyState { engineSupportedVariants.contains(variant) && isLocalEvaluationEnabled; - bool get isOpeningExplorerAvailable => - !gamebookActive && study.chapter.features.explorer; + bool get isOpeningExplorerAvailable => !gamebookActive && study.chapter.features.explorer; - EngineGaugeParams? get engineGaugeParams => isEngineAvailable - ? ( - orientation: pov, - isLocalEngineAvailable: isEngineAvailable, - position: position!, - savedEval: currentNode.eval, - ) - : null; + EngineGaugeParams? get engineGaugeParams => + isEngineAvailable + ? ( + orientation: pov, + isLocalEngineAvailable: isEngineAvailable, + position: position!, + savedEval: currentNode.eval, + ) + : null; Position? get position => currentNode.position; StudyChapter get currentChapter => study.chapter; bool get canGoNext => currentNode.children.isNotEmpty; bool get canGoBack => currentPath.size > UciPath.empty.size; - String get currentChapterTitle => study.chapters - .firstWhere( - (chapter) => chapter.id == currentChapter.id, - ) - .name; + String get currentChapterTitle => + study.chapters.firstWhere((chapter) => chapter.id == currentChapter.id).name; bool get hasNextChapter => study.chapter.id != study.chapters.last.id; bool get isAtEndOfChapter => isOnMainline && currentNode.children.isEmpty; @@ -684,22 +636,20 @@ class StudyState with _$StudyState { bool get isAtStartOfChapter => currentPath.isEmpty; String? get gamebookComment { - final comment = - (currentNode.isRoot ? pgnRootComments : currentNode.comments) - ?.map((comment) => comment.text) - .nonNulls - .join('\n'); + final comment = (currentNode.isRoot ? pgnRootComments : currentNode.comments) + ?.map((comment) => comment.text) + .nonNulls + .join('\n'); return comment?.isNotEmpty == true ? comment : gamebookState == GamebookState.incorrectMove - ? gamebookDeviationComment - : null; + ? gamebookDeviationComment + : null; } String? get gamebookHint => study.hints.getOrNull(currentPath.size); - String? get gamebookDeviationComment => - study.deviationComments.getOrNull(currentPath.size); + String? get gamebookDeviationComment => study.deviationComments.getOrNull(currentPath.size); GamebookState get gamebookState { if (isAtEndOfChapter) return GamebookState.lessonComplete; @@ -710,22 +660,20 @@ class StudyState with _$StudyState { return myTurn ? GamebookState.findTheMove : isOnMainline - ? GamebookState.correctMove - : GamebookState.incorrectMove; + ? GamebookState.correctMove + : GamebookState.incorrectMove; } - bool get isIntroductoryChapter => - currentNode.isRoot && currentNode.children.isEmpty; + bool get isIntroductoryChapter => currentNode.isRoot && currentNode.children.isEmpty; IList get pgnShapes => IList( - (currentNode.isRoot ? pgnRootComments : currentNode.comments) - ?.map((comment) => comment.shapes) - .flattened, - ); + (currentNode.isRoot ? pgnRootComments : currentNode.comments) + ?.map((comment) => comment.shapes) + .flattened, + ); - PlayerSide get playerSide => gamebookActive - ? (pov == Side.white ? PlayerSide.white : PlayerSide.black) - : PlayerSide.both; + PlayerSide get playerSide => + gamebookActive ? (pov == Side.white ? PlayerSide.white : PlayerSide.black) : PlayerSide.both; } @freezed @@ -745,11 +693,7 @@ class StudyCurrentNode with _$StudyCurrentNode { }) = _StudyCurrentNode; factory StudyCurrentNode.illegalPosition() { - return const StudyCurrentNode( - position: null, - children: [], - isRoot: true, - ); + return const StudyCurrentNode(position: null, children: [], isRoot: true); } factory StudyCurrentNode.fromNode(Node node) { diff --git a/lib/src/model/study/study_filter.dart b/lib/src/model/study/study_filter.dart index 45246f3dcf..7cefc08f35 100644 --- a/lib/src/model/study/study_filter.dart +++ b/lib/src/model/study/study_filter.dart @@ -14,13 +14,13 @@ enum StudyCategory { likes; String l10n(AppLocalizations l10n) => switch (this) { - StudyCategory.all => l10n.studyAllStudies, - StudyCategory.mine => l10n.studyMyStudies, - StudyCategory.member => l10n.studyStudiesIContributeTo, - StudyCategory.public => l10n.studyMyPublicStudies, - StudyCategory.private => l10n.studyMyPrivateStudies, - StudyCategory.likes => l10n.studyMyFavoriteStudies, - }; + StudyCategory.all => l10n.studyAllStudies, + StudyCategory.mine => l10n.studyMyStudies, + StudyCategory.member => l10n.studyStudiesIContributeTo, + StudyCategory.public => l10n.studyMyPublicStudies, + StudyCategory.private => l10n.studyMyPrivateStudies, + StudyCategory.likes => l10n.studyMyFavoriteStudies, + }; } enum StudyListOrder { @@ -31,12 +31,12 @@ enum StudyListOrder { updated; String l10n(AppLocalizations l10n) => switch (this) { - StudyListOrder.hot => l10n.studyHot, - StudyListOrder.newest => l10n.studyDateAddedNewest, - StudyListOrder.oldest => l10n.studyDateAddedOldest, - StudyListOrder.updated => l10n.studyRecentlyUpdated, - StudyListOrder.popular => l10n.studyMostPopular, - }; + StudyListOrder.hot => l10n.studyHot, + StudyListOrder.newest => l10n.studyDateAddedNewest, + StudyListOrder.oldest => l10n.studyDateAddedOldest, + StudyListOrder.updated => l10n.studyRecentlyUpdated, + StudyListOrder.popular => l10n.studyMostPopular, + }; } @riverpod @@ -44,8 +44,7 @@ class StudyFilter extends _$StudyFilter { @override StudyFilterState build() => const StudyFilterState(); - void setCategory(StudyCategory category) => - state = state.copyWith(category: category); + void setCategory(StudyCategory category) => state = state.copyWith(category: category); void setOrder(StudyListOrder order) => state = state.copyWith(order: order); } diff --git a/lib/src/model/study/study_list_paginator.dart b/lib/src/model/study/study_list_paginator.dart index 4684a3ecd0..0a24a1999d 100644 --- a/lib/src/model/study/study_list_paginator.dart +++ b/lib/src/model/study/study_list_paginator.dart @@ -12,10 +12,7 @@ typedef StudyList = ({IList studies, int? nextPage}); @riverpod class StudyListPaginator extends _$StudyListPaginator { @override - Future build({ - required StudyFilterState filter, - String? search, - }) async { + Future build({required StudyFilterState filter, String? search}) async { return _nextPage(); } @@ -25,12 +22,10 @@ class StudyListPaginator extends _$StudyListPaginator { final newStudyPage = await _nextPage(); - state = AsyncData( - ( - nextPage: newStudyPage.nextPage, - studies: studyList.studies.addAll(newStudyPage.studies), - ), - ); + state = AsyncData(( + nextPage: newStudyPage.nextPage, + studies: studyList.studies.addAll(newStudyPage.studies), + )); } Future _nextPage() async { @@ -38,14 +33,7 @@ class StudyListPaginator extends _$StudyListPaginator { final repo = ref.read(studyRepositoryProvider); return search == null - ? repo.getStudies( - category: filter.category, - order: filter.order, - page: nextPage, - ) - : repo.searchStudies( - query: search!, - page: nextPage, - ); + ? repo.getStudies(category: filter.category, order: filter.order, page: nextPage) + : repo.searchStudies(query: search!, page: nextPage); } } diff --git a/lib/src/model/study/study_preferences.dart b/lib/src/model/study/study_preferences.dart index fe3fb2f7fa..d309eeb7ab 100644 --- a/lib/src/model/study/study_preferences.dart +++ b/lib/src/model/study/study_preferences.dart @@ -6,8 +6,7 @@ part 'study_preferences.freezed.dart'; part 'study_preferences.g.dart'; @riverpod -class StudyPreferences extends _$StudyPreferences - with PreferencesStorage { +class StudyPreferences extends _$StudyPreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties @override final prefCategory = PrefCategory.study; @@ -25,11 +24,7 @@ class StudyPreferences extends _$StudyPreferences } Future toggleShowVariationArrows() { - return save( - state.copyWith( - showVariationArrows: !state.showVariationArrows, - ), - ); + return save(state.copyWith(showVariationArrows: !state.showVariationArrows)); } } @@ -37,13 +32,9 @@ class StudyPreferences extends _$StudyPreferences class StudyPrefs with _$StudyPrefs implements Serializable { const StudyPrefs._(); - const factory StudyPrefs({ - required bool showVariationArrows, - }) = _StudyPrefs; + const factory StudyPrefs({required bool showVariationArrows}) = _StudyPrefs; - static const defaults = StudyPrefs( - showVariationArrows: false, - ); + static const defaults = StudyPrefs(showVariationArrows: false); factory StudyPrefs.fromJson(Map json) { return _$StudyPrefsFromJson(json); diff --git a/lib/src/model/study/study_repository.dart b/lib/src/model/study/study_repository.dart index c0c2cc4886..19cad44f07 100644 --- a/lib/src/model/study/study_repository.dart +++ b/lib/src/model/study/study_repository.dart @@ -35,14 +35,8 @@ class StudyRepository { ); } - Future searchStudies({ - required String query, - int page = 1, - }) { - return _requestStudies( - path: 'search', - queryParameters: {'page': page.toString(), 'q': query}, - ); + Future searchStudies({required String query, int page = 1}) { + return _requestStudies(path: 'search', queryParameters: {'page': page.toString(), 'q': query}); } Future _requestStudies({ @@ -50,22 +44,18 @@ class StudyRepository { required Map queryParameters, }) { return client.readJson( - Uri( - path: '/study/$path', - queryParameters: queryParameters, - ), + Uri(path: '/study/$path', queryParameters: queryParameters), headers: {'Accept': 'application/json'}, mapper: (Map json) { - final paginator = - pick(json, 'paginator').asMapOrThrow(); + final paginator = pick(json, 'paginator').asMapOrThrow(); return ( - studies: pick(paginator, 'currentPageResults') - .asListOrThrow( - (pick) => StudyPageData.fromJson(pick.asMapOrThrow()), - ) - .toIList(), - nextPage: pick(paginator, 'nextPage').asIntOrNull() + studies: + pick( + paginator, + 'currentPageResults', + ).asListOrThrow((pick) => StudyPageData.fromJson(pick.asMapOrThrow())).toIList(), + nextPage: pick(paginator, 'nextPage').asIntOrNull(), ); }, ); @@ -78,9 +68,7 @@ class StudyRepository { final study = await client.readJson( Uri( path: (chapterId != null) ? '/study/$id/$chapterId' : '/study/$id', - queryParameters: { - 'chapters': '1', - }, + queryParameters: {'chapters': '1'}, ), headers: {'Accept': 'application/json'}, mapper: Study.fromServerJson, diff --git a/lib/src/model/tv/featured_player.dart b/lib/src/model/tv/featured_player.dart index aa1f431395..070b3171cc 100644 --- a/lib/src/model/tv/featured_player.dart +++ b/lib/src/model/tv/featured_player.dart @@ -29,8 +29,7 @@ class FeaturedPlayer with _$FeaturedPlayer { } Player get asPlayer => Player( - user: - LightUser(id: UserId(name.toLowerCase()), name: name, title: title), - rating: rating, - ); + user: LightUser(id: UserId(name.toLowerCase()), name: name, title: title), + rating: rating, + ); } diff --git a/lib/src/model/tv/live_tv_channels.dart b/lib/src/model/tv/live_tv_channels.dart index 95c77bb3ff..e7df4cf9e2 100644 --- a/lib/src/model/tv/live_tv_channels.dart +++ b/lib/src/model/tv/live_tv_channels.dart @@ -48,19 +48,16 @@ class LiveTvChannels extends _$LiveTvChannels { } Future> _doStartWatching() async { - final repoGames = - await ref.withClient((client) => TvRepository(client).channels()); + final repoGames = await ref.withClient((client) => TvRepository(client).channels()); - _socketClient = - ref.read(socketPoolProvider).open(Uri(path: kDefaultSocketRoute)); + _socketClient = ref.read(socketPoolProvider).open(Uri(path: kDefaultSocketRoute)); await _socketClient.firstConnection; _socketWatch(repoGames); _socketReadySubscription?.cancel(); _socketReadySubscription = _socketClient.connectedStream.listen((_) async { - final repoGames = - await ref.withClient((client) => TvRepository(client).channels()); + final repoGames = await ref.withClient((client) => TvRepository(client).channels()); _socketWatch(repoGames); }); @@ -89,19 +86,13 @@ class LiveTvChannels extends _$LiveTvChannels { _socketClient.send('startWatchingTvChannels', null); _socketClient.send( 'startWatching', - games.entries - .where((e) => TvChannel.values.contains(e.key)) - .map((e) => e.value.id) - .join(' '), + games.entries.where((e) => TvChannel.values.contains(e.key)).map((e) => e.value.id).join(' '), ); } void _handleSocketEvent(SocketEvent event) { if (!state.hasValue) { - assert( - false, - 'received a SocketEvent while LiveTvChannels state is null', - ); + assert(false, 'received a SocketEvent while LiveTvChannels state is null'); return; } @@ -109,18 +100,15 @@ class LiveTvChannels extends _$LiveTvChannels { case 'fen': final json = event.data as Map; final fenEvent = FenSocketEvent.fromJson(json); - final snapshots = - state.requireValue.values.where((s) => s.id == fenEvent.id); + final snapshots = state.requireValue.values.where((s) => s.id == fenEvent.id); if (snapshots.isNotEmpty) { state = AsyncValue.data( state.requireValue.updateAll( - (key, value) => value.id == fenEvent.id - ? value.copyWith( - fen: fenEvent.fen, - lastMove: fenEvent.lastMove, - ) - : value, + (key, value) => + value.id == fenEvent.id + ? value.copyWith(fen: fenEvent.fen, lastMove: fenEvent.lastMove) + : value, ), ); } diff --git a/lib/src/model/tv/tv_channel.dart b/lib/src/model/tv/tv_channel.dart index 3f70ea4203..72aa16e279 100644 --- a/lib/src/model/tv/tv_channel.dart +++ b/lib/src/model/tv/tv_channel.dart @@ -26,8 +26,7 @@ enum TvChannel { final String label; final IconData icon; - static final IMap nameMap = - IMap(TvChannel.values.asNameMap()); + static final IMap nameMap = IMap(TvChannel.values.asNameMap()); } extension TvChannelExtension on Pick { @@ -41,9 +40,7 @@ extension TvChannelExtension on Pick { return TvChannel.nameMap[value]!; } } - throw PickException( - "value $value at $debugParsingExit can't be casted to TvChannel", - ); + throw PickException("value $value at $debugParsingExit can't be casted to TvChannel"); } TvChannel? asTvChannelOrNull() { diff --git a/lib/src/model/tv/tv_controller.dart b/lib/src/model/tv/tv_controller.dart index 78b6eeacc4..c445844d75 100644 --- a/lib/src/model/tv/tv_controller.dart +++ b/lib/src/model/tv/tv_controller.dart @@ -30,10 +30,7 @@ class TvController extends _$TvController { int? _socketEventVersion; @override - Future build( - TvChannel channel, - (GameId id, Side orientation)? initialGame, - ) async { + Future build(TvChannel channel, (GameId id, Side orientation)? initialGame) async { ref.onDispose(() { _socketSubscription?.cancel(); }); @@ -52,9 +49,7 @@ class TvController extends _$TvController { _socketSubscription?.cancel(); } - Future _connectWebsocket( - (GameId id, Side orientation)? game, - ) async { + Future _connectWebsocket((GameId id, Side orientation)? game) async { GameId id; Side orientation; @@ -62,29 +57,22 @@ class TvController extends _$TvController { id = game.$1; orientation = game.$2; } else { - final channels = - await ref.withClient((client) => TvRepository(client).channels()); + final channels = await ref.withClient((client) => TvRepository(client).channels()); final channelGame = channels[channel]!; id = channelGame.id; orientation = channelGame.side ?? Side.white; } - final socketClient = ref.read(socketPoolProvider).open( - Uri( - path: '/watch/$id/${orientation.name}/v6', - ), - forceReconnect: true, - ); + final socketClient = ref + .read(socketPoolProvider) + .open(Uri(path: '/watch/$id/${orientation.name}/v6'), forceReconnect: true); _socketSubscription?.cancel(); _socketEventVersion = null; _socketSubscription = socketClient.stream.listen(_handleSocketEvent); - return socketClient.stream - .firstWhere((e) => e.topic == 'full') - .then((event) { - final fullEvent = - GameFullEvent.fromJson(event.data as Map); + return socketClient.stream.firstWhere((e) => e.topic == 'full').then((event) { + final fullEvent = GameFullEvent.fromJson(event.data as Map); _socketEventVersion = fullEvent.socketEventVersion; @@ -101,21 +89,15 @@ class TvController extends _$TvController { state = AsyncValue.data(newState); } - bool canGoBack() => - state.mapOrNull(data: (d) => d.value.stepCursor > 0) ?? false; + bool canGoBack() => state.mapOrNull(data: (d) => d.value.stepCursor > 0) ?? false; bool canGoForward() => - state.mapOrNull( - data: (d) => d.value.stepCursor < d.value.game.steps.length - 1, - ) ?? - false; + state.mapOrNull(data: (d) => d.value.stepCursor < d.value.game.steps.length - 1) ?? false; void toggleBoard() { if (state.hasValue) { final curState = state.requireValue; - state = AsyncValue.data( - curState.copyWith(orientation: curState.orientation.opposite), - ); + state = AsyncValue.data(curState.copyWith(orientation: curState.orientation.opposite)); } } @@ -123,9 +105,7 @@ class TvController extends _$TvController { if (state.hasValue) { final curState = state.requireValue; if (curState.stepCursor < curState.game.steps.length - 1) { - state = AsyncValue.data( - curState.copyWith(stepCursor: curState.stepCursor + 1), - ); + state = AsyncValue.data(curState.copyWith(stepCursor: curState.stepCursor + 1)); final san = curState.game.stepAt(curState.stepCursor + 1).sanMove?.san; if (san != null) { _playReplayMoveSound(san); @@ -138,9 +118,7 @@ class TvController extends _$TvController { if (state.hasValue) { final curState = state.requireValue; if (curState.stepCursor > 0) { - state = AsyncValue.data( - curState.copyWith(stepCursor: curState.stepCursor - 1), - ); + state = AsyncValue.data(curState.copyWith(stepCursor: curState.stepCursor - 1)); final san = curState.game.stepAt(curState.stepCursor - 1).sanMove?.san; if (san != null) { _playReplayMoveSound(san); @@ -181,10 +159,7 @@ class TvController extends _$TvController { void _handleSocketTopic(SocketEvent event) { if (!state.hasValue) { - assert( - false, - 'received a game SocketEvent while TvState is null', - ); + assert(false, 'received a game SocketEvent while TvState is null'); return; } @@ -203,9 +178,7 @@ class TvController extends _$TvController { ); TvState newState = curState.copyWith( - game: curState.game.copyWith( - steps: curState.game.steps.add(newStep), - ), + game: curState.game.copyWith(steps: curState.game.steps.add(newStep)), ); if (newState.game.clock != null && data.clock != null) { @@ -217,9 +190,7 @@ class TvController extends _$TvController { ); } if (!curState.isReplaying) { - newState = newState.copyWith( - stepCursor: newState.stepCursor + 1, - ); + newState = newState.copyWith(stepCursor: newState.stepCursor + 1); if (data.san.contains('x')) { _soundService.play(Sound.capture); @@ -231,13 +202,9 @@ class TvController extends _$TvController { state = AsyncData(newState); case 'endData': - final endData = - GameEndEvent.fromJson(event.data as Map); + final endData = GameEndEvent.fromJson(event.data as Map); TvState newState = state.requireValue.copyWith( - game: state.requireValue.game.copyWith( - status: endData.status, - winner: endData.winner, - ), + game: state.requireValue.game.copyWith(status: endData.status, winner: endData.winner), ); if (endData.clock != null) { newState = newState.copyWith.game.clock!( diff --git a/lib/src/model/tv/tv_repository.dart b/lib/src/model/tv/tv_repository.dart index aaff5f5914..480c1c721d 100644 --- a/lib/src/model/tv/tv_repository.dart +++ b/lib/src/model/tv/tv_repository.dart @@ -17,10 +17,7 @@ class TvRepository { final http.Client client; Future channels() { - return client.readJson( - Uri(path: '/api/tv/channels'), - mapper: _tvGamesFromJson, - ); + return client.readJson(Uri(path: '/api/tv/channels'), mapper: _tvGamesFromJson); } } @@ -33,12 +30,11 @@ TvChannels _tvGamesFromJson(Map json) { }); } -TvGame _tvGameFromJson(Map json) => - _tvGameFromPick(pick(json).required()); +TvGame _tvGameFromJson(Map json) => _tvGameFromPick(pick(json).required()); TvGame _tvGameFromPick(RequiredPick pick) => TvGame( - user: pick('user').asLightUserOrThrow(), - rating: pick('rating').asIntOrNull(), - id: pick('gameId').asGameIdOrThrow(), - side: pick('color').asSideOrNull(), - ); + user: pick('user').asLightUserOrThrow(), + rating: pick('rating').asIntOrNull(), + id: pick('gameId').asGameIdOrThrow(), + side: pick('color').asSideOrNull(), +); diff --git a/lib/src/model/user/leaderboard.dart b/lib/src/model/user/leaderboard.dart index dc4b73d822..1df7f17760 100644 --- a/lib/src/model/user/leaderboard.dart +++ b/lib/src/model/user/leaderboard.dart @@ -39,11 +39,6 @@ class LeaderboardUser with _$LeaderboardUser { required int progress, }) = _LeaderboardUser; - LightUser get lightUser => LightUser( - id: id, - name: username, - title: title, - flair: flair, - isPatron: patron, - ); + LightUser get lightUser => + LightUser(id: id, name: username, title: title, flair: flair, isPatron: patron); } diff --git a/lib/src/model/user/profile.dart b/lib/src/model/user/profile.dart index 20ae4ace7f..e737faa535 100644 --- a/lib/src/model/user/profile.dart +++ b/lib/src/model/user/profile.dart @@ -28,55 +28,49 @@ class Profile with _$Profile { factory Profile.fromPick(RequiredPick pick) { const lineSplitter = LineSplitter(); - final rawLinks = pick('links') - .letOrNull((e) => lineSplitter.convert(e.asStringOrThrow())); + final rawLinks = pick('links').letOrNull((e) => lineSplitter.convert(e.asStringOrThrow())); return Profile( - country: - pick('flag').asStringOrNull() ?? pick('country').asStringOrNull(), + country: pick('flag').asStringOrNull() ?? pick('country').asStringOrNull(), location: pick('location').asStringOrNull(), bio: pick('bio').asStringOrNull(), realName: pick('realName').asStringOrNull(), fideRating: pick('fideRating').asIntOrNull(), uscfRating: pick('uscfRating').asIntOrNull(), ecfRating: pick('ecfRating').asIntOrNull(), - links: rawLinks - ?.where((e) => e.trim().isNotEmpty) - .map((e) { - final link = SocialLink.fromUrl(e); - if (link == null) { - final uri = Uri.tryParse(e); - if (uri != null) { - return SocialLink(site: null, url: uri); - } - return null; - } - return link; - }) - .nonNulls - .toIList(), + links: + rawLinks + ?.where((e) => e.trim().isNotEmpty) + .map((e) { + final link = SocialLink.fromUrl(e); + if (link == null) { + final uri = Uri.tryParse(e); + if (uri != null) { + return SocialLink(site: null, url: uri); + } + return null; + } + return link; + }) + .nonNulls + .toIList(), ); } } @freezed class SocialLink with _$SocialLink { - const factory SocialLink({ - required LinkSite? site, - required Uri url, - }) = _SocialLink; + const factory SocialLink({required LinkSite? site, required Uri url}) = _SocialLink; const SocialLink._(); static SocialLink? fromUrl(String url) { - final updatedUrl = url.startsWith('http://') || url.startsWith('https://') - ? url - : 'https://$url'; + final updatedUrl = + url.startsWith('http://') || url.startsWith('https://') ? url : 'https://$url'; final uri = Uri.tryParse(updatedUrl); if (uri == null) return null; final host = uri.host.replaceAll(RegExp(r'www\.'), ''); - final site = - LinkSite.values.firstWhereOrNull((e) => e.domains.contains(host)); + final site = LinkSite.values.firstWhereOrNull((e) => e.domains.contains(host)); return site != null ? SocialLink(site: site, url: uri) : null; } diff --git a/lib/src/model/user/search_history.dart b/lib/src/model/user/search_history.dart index 52f2047cd4..70f5e6b6a8 100644 --- a/lib/src/model/user/search_history.dart +++ b/lib/src/model/user/search_history.dart @@ -17,8 +17,7 @@ class SearchHistory extends _$SearchHistory { String _storageKey(AuthSessionState? session) => 'search.history.${session?.user.id ?? '**anon**'}'; - SharedPreferencesWithCache get _prefs => - LichessBinding.instance.sharedPreferences; + SharedPreferencesWithCache get _prefs => LichessBinding.instance.sharedPreferences; @override SearchHistoryState build() { @@ -26,9 +25,7 @@ class SearchHistory extends _$SearchHistory { final stored = _prefs.getString(_storageKey(session)); return stored != null - ? SearchHistoryState.fromJson( - jsonDecode(stored) as Map, - ) + ? SearchHistoryState.fromJson(jsonDecode(stored) as Map) : SearchHistoryState(history: IList()); } @@ -57,9 +54,7 @@ class SearchHistory extends _$SearchHistory { @Freezed(fromJson: true, toJson: true) class SearchHistoryState with _$SearchHistoryState { - const factory SearchHistoryState({ - required IList history, - }) = _SearchHistoryState; + const factory SearchHistoryState({required IList history}) = _SearchHistoryState; factory SearchHistoryState.fromJson(Map json) => _$SearchHistoryStateFromJson(json); diff --git a/lib/src/model/user/user.dart b/lib/src/model/user/user.dart index 1f71c8bf9a..4df1287c7c 100644 --- a/lib/src/model/user/user.dart +++ b/lib/src/model/user/user.dart @@ -22,8 +22,7 @@ class LightUser with _$LightUser { bool? isOnline, }) = _LightUser; - factory LightUser.fromJson(Map json) => - _$LightUserFromJson(json); + factory LightUser.fromJson(Map json) => _$LightUserFromJson(json); } extension LightUserExtension on Pick { @@ -34,8 +33,8 @@ extension LightUserExtension on Pick { return value; } if (value is Map) { - final name = requiredPick('username').asStringOrNull() ?? - requiredPick('name').asStringOrThrow(); + final name = + requiredPick('username').asStringOrNull() ?? requiredPick('name').asStringOrThrow(); return LightUser( id: requiredPick('id').asUserIdOrThrow(), @@ -46,9 +45,7 @@ extension LightUserExtension on Pick { isOnline: requiredPick('online').asBoolOrNull(), ); } - throw PickException( - "value $value at $debugParsingExit can't be casted to LightUser", - ); + throw PickException("value $value at $debugParsingExit can't be casted to LightUser"); } LightUser? asLightUserOrNull() { @@ -88,20 +85,13 @@ class User with _$User { bool? canChallenge, }) = _User; - LightUser get lightUser => LightUser( - id: id, - name: username, - title: title, - isPatron: isPatron, - flair: flair, - ); + LightUser get lightUser => + LightUser(id: id, name: username, title: title, isPatron: isPatron, flair: flair); - factory User.fromServerJson(Map json) => - User.fromPick(pick(json).required()); + factory User.fromServerJson(Map json) => User.fromPick(pick(json).required()); factory User.fromPick(RequiredPick pick) { - final receivedPerfsMap = - pick('perfs').asMapOrEmpty>(); + final receivedPerfsMap = pick('perfs').asMapOrEmpty>(); return User( id: pick('id').asUserIdOrThrow(), username: pick('username').asStringOrThrow(), @@ -155,32 +145,28 @@ class UserGameCount with _$UserGameCount { UserGameCount.fromPick(pick(json).required()); factory UserGameCount.fromPick(RequiredPick pick) => UserGameCount( - all: pick('all').asIntOrThrow(), - // TODO(#454): enable rest of fields when needed for filtering - // rated: pick('rated').asIntOrThrow(), - // ai: pick('ai').asIntOrThrow(), - // draw: pick('draw').asIntOrThrow(), - // drawH: pick('drawH').asIntOrThrow(), - // win: pick('win').asIntOrThrow(), - // winH: pick('winH').asIntOrThrow(), - // loss: pick('loss').asIntOrThrow(), - // lossH: pick('lossH').asIntOrThrow(), - // bookmark: pick('bookmark').asIntOrThrow(), - // playing: pick('playing').asIntOrThrow(), - // imported: pick('import').asIntOrThrow(), - // me: pick('me').asIntOrThrow(), - ); + all: pick('all').asIntOrThrow(), + // TODO(#454): enable rest of fields when needed for filtering + // rated: pick('rated').asIntOrThrow(), + // ai: pick('ai').asIntOrThrow(), + // draw: pick('draw').asIntOrThrow(), + // drawH: pick('drawH').asIntOrThrow(), + // win: pick('win').asIntOrThrow(), + // winH: pick('winH').asIntOrThrow(), + // loss: pick('loss').asIntOrThrow(), + // lossH: pick('lossH').asIntOrThrow(), + // bookmark: pick('bookmark').asIntOrThrow(), + // playing: pick('playing').asIntOrThrow(), + // imported: pick('import').asIntOrThrow(), + // me: pick('me').asIntOrThrow(), + ); } @freezed class PlayTime with _$PlayTime { - const factory PlayTime({ - required Duration total, - required Duration tv, - }) = _PlayTime; + const factory PlayTime({required Duration total, required Duration tv}) = _PlayTime; - factory PlayTime.fromJson(Map json) => - PlayTime.fromPick(pick(json).required()); + factory PlayTime.fromJson(Map json) => PlayTime.fromPick(pick(json).required()); factory PlayTime.fromPick(RequiredPick pick) { return PlayTime( @@ -203,25 +189,24 @@ class UserPerf with _$UserPerf { bool? provisional, }) = _UserPerf; - factory UserPerf.fromJson(Map json) => - UserPerf.fromPick(pick(json).required()); + factory UserPerf.fromJson(Map json) => UserPerf.fromPick(pick(json).required()); factory UserPerf.fromPick(RequiredPick pick) => UserPerf( - rating: pick('rating').asIntOrThrow(), - ratingDeviation: pick('rd').asIntOrThrow(), - progression: pick('prog').asIntOrThrow(), - games: pick('games').asIntOrNull(), - runs: pick('runs').asIntOrNull(), - provisional: pick('prov').asBoolOrNull(), - ); + rating: pick('rating').asIntOrThrow(), + ratingDeviation: pick('rd').asIntOrThrow(), + progression: pick('prog').asIntOrThrow(), + games: pick('games').asIntOrNull(), + runs: pick('runs').asIntOrNull(), + provisional: pick('prov').asBoolOrNull(), + ); factory UserPerf.fromJsonStreak(Map json) => UserPerf( - rating: UserActivityStreak.fromJson(json).score, - ratingDeviation: 0, - progression: 0, - runs: UserActivityStreak.fromJson(json).runs, - provisional: null, - ); + rating: UserActivityStreak.fromJson(json).score, + ratingDeviation: 0, + progression: 0, + runs: UserActivityStreak.fromJson(json).runs, + provisional: null, + ); int get numberOfGamesOrRuns => games ?? runs ?? 0; } @@ -239,11 +224,11 @@ class UserStatus with _$UserStatus { UserStatus.fromPick(pick(json).required()); factory UserStatus.fromPick(RequiredPick pick) => UserStatus( - id: pick('id').asUserIdOrThrow(), - name: pick('name').asStringOrThrow(), - online: pick('online').asBoolOrNull(), - playing: pick('playing').asBoolOrNull(), - ); + id: pick('id').asUserIdOrThrow(), + name: pick('name').asStringOrThrow(), + online: pick('online').asBoolOrNull(), + playing: pick('playing').asBoolOrNull(), + ); } @freezed @@ -260,31 +245,25 @@ class UserActivityTournament with _$UserActivityTournament { factory UserActivityTournament.fromJson(Map json) => UserActivityTournament.fromPick(pick(json).required()); - factory UserActivityTournament.fromPick(RequiredPick pick) => - UserActivityTournament( - id: pick('tournament', 'id').asStringOrThrow(), - name: pick('tournament', 'name').asStringOrThrow(), - nbGames: pick('nbGames').asIntOrThrow(), - score: pick('score').asIntOrThrow(), - rank: pick('rank').asIntOrThrow(), - rankPercent: pick('rankPercent').asIntOrThrow(), - ); + factory UserActivityTournament.fromPick(RequiredPick pick) => UserActivityTournament( + id: pick('tournament', 'id').asStringOrThrow(), + name: pick('tournament', 'name').asStringOrThrow(), + nbGames: pick('nbGames').asIntOrThrow(), + score: pick('score').asIntOrThrow(), + rank: pick('rank').asIntOrThrow(), + rankPercent: pick('rankPercent').asIntOrThrow(), + ); } @freezed class UserActivityStreak with _$UserActivityStreak { - const factory UserActivityStreak({ - required int runs, - required int score, - }) = _UserActivityStreak; + const factory UserActivityStreak({required int runs, required int score}) = _UserActivityStreak; factory UserActivityStreak.fromJson(Map json) => UserActivityStreak.fromPick(pick(json).required()); - factory UserActivityStreak.fromPick(RequiredPick pick) => UserActivityStreak( - runs: pick('runs').asIntOrThrow(), - score: pick('score').asIntOrThrow(), - ); + factory UserActivityStreak.fromPick(RequiredPick pick) => + UserActivityStreak(runs: pick('runs').asIntOrThrow(), score: pick('score').asIntOrThrow()); } @freezed @@ -301,12 +280,12 @@ class UserActivityScore with _$UserActivityScore { UserActivityScore.fromPick(pick(json).required()); factory UserActivityScore.fromPick(RequiredPick pick) => UserActivityScore( - win: pick('win').asIntOrThrow(), - loss: pick('loss').asIntOrThrow(), - draw: pick('draw').asIntOrThrow(), - ratingBefore: pick('rp', 'before').asIntOrThrow(), - ratingAfter: pick('rp', 'after').asIntOrThrow(), - ); + win: pick('win').asIntOrThrow(), + loss: pick('loss').asIntOrThrow(), + draw: pick('draw').asIntOrThrow(), + ratingBefore: pick('rp', 'before').asIntOrThrow(), + ratingAfter: pick('rp', 'after').asIntOrThrow(), + ); } @freezed @@ -410,13 +389,10 @@ class UserPerfGame with _$UserPerfGame { String? opponentTitle, }) = _UserPerfGame; - LightUser? get opponent => opponentId != null && opponentName != null - ? LightUser( - id: UserId(opponentId!), - name: opponentName!, - title: opponentTitle, - ) - : null; + LightUser? get opponent => + opponentId != null && opponentName != null + ? LightUser(id: UserId(opponentId!), name: opponentName!, title: opponentTitle) + : null; } @immutable @@ -424,10 +400,7 @@ class UserRatingHistoryPerf { final Perf perf; final IList points; - const UserRatingHistoryPerf({ - required this.perf, - required this.points, - }); + const UserRatingHistoryPerf({required this.perf, required this.points}); } @immutable @@ -435,8 +408,5 @@ class UserRatingHistoryPoint { final DateTime date; final int elo; - const UserRatingHistoryPoint({ - required this.date, - required this.elo, - }); + const UserRatingHistoryPoint({required this.date, required this.elo}); } diff --git a/lib/src/model/user/user_repository.dart b/lib/src/model/user/user_repository.dart index 858ff76695..0fc6598b88 100644 --- a/lib/src/model/user/user_repository.dart +++ b/lib/src/model/user/user_repository.dart @@ -17,19 +17,13 @@ class UserRepository { Future getUser(UserId id, {bool withCanChallenge = false}) { return client.readJson( - Uri( - path: '/api/user/$id', - queryParameters: withCanChallenge ? {'challenge': 'true'} : null, - ), + Uri(path: '/api/user/$id', queryParameters: withCanChallenge ? {'challenge': 'true'} : null), mapper: User.fromServerJson, ); } Future> getOnlineBots() { - return client.readNdJsonList( - Uri(path: '/api/bot/online'), - mapper: User.fromServerJson, - ); + return client.readNdJsonList(Uri(path: '/api/bot/online'), mapper: User.fromServerJson); } Future getPerfStats(UserId id, Perf perf) { @@ -41,40 +35,25 @@ class UserRepository { Future> getUsersStatuses(ISet ids) { return client.readJsonList( - Uri( - path: '/api/users/status', - queryParameters: {'ids': ids.join(',')}, - ), + Uri(path: '/api/users/status', queryParameters: {'ids': ids.join(',')}), mapper: UserStatus.fromJson, ); } Future> getActivity(UserId id) { - return client.readJsonList( - Uri(path: '/api/user/$id/activity'), - mapper: _userActivityFromJson, - ); + return client.readJsonList(Uri(path: '/api/user/$id/activity'), mapper: _userActivityFromJson); } Future> getLiveStreamers() { - return client.readJsonList( - Uri(path: '/api/streamer/live'), - mapper: _streamersFromJson, - ); + return client.readJsonList(Uri(path: '/api/streamer/live'), mapper: _streamersFromJson); } Future> getTop1() { - return client.readJson( - Uri(path: '/api/player/top/1/standard'), - mapper: _top1FromJson, - ); + return client.readJson(Uri(path: '/api/player/top/1/standard'), mapper: _top1FromJson); } Future getLeaderboard() { - return client.readJson( - Uri(path: '/api/player'), - mapper: _leaderboardFromJson, - ); + return client.readJson(Uri(path: '/api/player'), mapper: _leaderboardFromJson); } Future> autocompleteUser(String term) { @@ -95,14 +74,10 @@ class UserRepository { } } -UserRatingHistoryPerf? _ratingHistoryFromJson( - Map json, -) => +UserRatingHistoryPerf? _ratingHistoryFromJson(Map json) => _ratingHistoryFromPick(pick(json).required()); -UserRatingHistoryPerf? _ratingHistoryFromPick( - RequiredPick pick, -) { +UserRatingHistoryPerf? _ratingHistoryFromPick(RequiredPick pick) { final perf = pick('name').asPerfOrNull(); if (perf == null) { @@ -111,17 +86,14 @@ UserRatingHistoryPerf? _ratingHistoryFromPick( return UserRatingHistoryPerf( perf: perf, - points: pick('points').asListOrThrow((point) { - final values = point.asListOrThrow((point) => point.asIntOrThrow()); - return UserRatingHistoryPoint( - date: DateTime.utc( - values[0], - values[1] + 1, - values[2], - ), - elo: values[3], - ); - }).toIList(), + points: + pick('points').asListOrThrow((point) { + final values = point.asListOrThrow((point) => point.asIntOrThrow()); + return UserRatingHistoryPoint( + date: DateTime.utc(values[0], values[1] + 1, values[2]), + elo: values[3], + ); + }).toIList(), ); } @@ -130,17 +102,14 @@ IList _autocompleteFromJson(Map json) => _autocompleteFromPick(pick(json).required()); IList _autocompleteFromPick(RequiredPick pick) { - return pick('result') - .asListOrThrow((userPick) => userPick.asLightUserOrThrow()) - .toIList(); + return pick('result').asListOrThrow((userPick) => userPick.asLightUserOrThrow()).toIList(); } UserActivity _userActivityFromJson(Map json) => _userActivityFromPick(pick(json).required()); UserActivity _userActivityFromPick(RequiredPick pick) { - final receivedGamesMap = - pick('games').asMapOrEmpty>(); + final receivedGamesMap = pick('games').asMapOrEmpty>(); final games = IMap({ for (final entry in receivedGamesMap.entries) @@ -148,8 +117,10 @@ UserActivity _userActivityFromPick(RequiredPick pick) { Perf.nameMap.get(entry.key)!: UserActivityScore.fromJson(entry.value), }); - final bestTour = pick('tournaments', 'best') - .asListOrNull((p0) => UserActivityTournament.fromPick(p0)); + final bestTour = pick( + 'tournaments', + 'best', + ).asListOrNull((p0) => UserActivityTournament.fromPick(p0)); return UserActivity( startTime: pick('interval', 'start').asDateTimeFromMillisecondsOrThrow(), @@ -162,12 +133,10 @@ UserActivity _userActivityFromPick(RequiredPick pick) { puzzles: pick('puzzles', 'score').letOrNull(UserActivityScore.fromPick), streak: pick('streak').letOrNull(UserActivityStreak.fromPick), storm: pick('storm').letOrNull(UserActivityStreak.fromPick), - correspondenceEnds: pick('correspondenceEnds', 'score') - .letOrNull(UserActivityScore.fromPick), + correspondenceEnds: pick('correspondenceEnds', 'score').letOrNull(UserActivityScore.fromPick), correspondenceMovesNb: pick('correspondenceMoves', 'nb').asIntOrNull(), - correspondenceGamesNb: pick('correspondenceMoves', 'games') - .asListOrNull((p) => p('id').asStringOrThrow()) - ?.length, + correspondenceGamesNb: + pick('correspondenceMoves', 'games').asListOrNull((p) => p('id').asStringOrThrow())?.length, ); } @@ -214,11 +183,8 @@ UserPerfStats _userPerfStatsFromPick(RequiredPick pick) { maxPlayStreak: playStreak('nb', 'max').letOrNull(_userStreakFromPick), curTimeStreak: playStreak('time', 'cur').letOrNull(_userStreakFromPick), maxTimeStreak: playStreak('time', 'max').letOrNull(_userStreakFromPick), - worstLosses: IList( - stat('worstLosses', 'results').asListOrNull(_userPerfGameFromPick), - ), - bestWins: - IList(stat('bestWins', 'results').asListOrNull(_userPerfGameFromPick)), + worstLosses: IList(stat('worstLosses', 'results').asListOrNull(_userPerfGameFromPick)), + bestWins: IList(stat('bestWins', 'results').asListOrNull(_userPerfGameFromPick)), ); } @@ -272,8 +238,7 @@ UserPerfGame _userPerfGameFromPick(RequiredPick pick) { ); } -Streamer _streamersFromJson(Map json) => - _streamersFromPick(pick(json).required()); +Streamer _streamersFromJson(Map json) => _streamersFromPick(pick(json).required()); Streamer _streamersFromPick(RequiredPick pick) { final stream = pick('stream'); @@ -306,8 +271,7 @@ Leaderboard _leaderBoardFromPick(RequiredPick pick) { ultrabullet: pick('ultraBullet').asListOrEmpty(_leaderboardUserFromPick), crazyhouse: pick('crazyhouse').asListOrEmpty(_leaderboardUserFromPick), chess960: pick('chess960').asListOrEmpty(_leaderboardUserFromPick), - kingOfThehill: - pick('kingOfTheHill').asListOrEmpty(_leaderboardUserFromPick), + kingOfThehill: pick('kingOfTheHill').asListOrEmpty(_leaderboardUserFromPick), threeCheck: pick('threeCheck').asListOrEmpty(_leaderboardUserFromPick), antichess: pick('antichess').asListOrEmpty(_leaderboardUserFromPick), atomic: pick('atomic').asListOrEmpty(_leaderboardUserFromPick), @@ -326,14 +290,14 @@ LeaderboardUser _leaderboardUserFromPick(RequiredPick pick) { flair: pick('flair').asStringOrNull(), patron: pick('patron').asBoolOrNull(), online: pick('online').asBoolOrNull(), - rating: pick('perfs') - .letOrThrow((perfsPick) => perfsPick(prefMap.keys.first, 'rating')) - .asIntOrThrow(), - progress: pick('perfs') - .letOrThrow( - (prefsPick) => prefsPick(prefMap.keys.first, 'progress'), - ) - .asIntOrThrow(), + rating: + pick( + 'perfs', + ).letOrThrow((perfsPick) => perfsPick(prefMap.keys.first, 'rating')).asIntOrThrow(), + progress: + pick( + 'perfs', + ).letOrThrow((prefsPick) => prefsPick(prefMap.keys.first, 'progress')).asIntOrThrow(), ); } diff --git a/lib/src/model/user/user_repository_providers.dart b/lib/src/model/user/user_repository_providers.dart index a1221aa58a..c950b4d469 100644 --- a/lib/src/model/user/user_repository_providers.dart +++ b/lib/src/model/user/user_repository_providers.dart @@ -16,9 +16,7 @@ const _kAutoCompleteDebounceTimer = Duration(milliseconds: 300); @riverpod Future user(Ref ref, {required UserId id}) async { - return ref.withClient( - (client) => UserRepository(client).getUser(id), - ); + return ref.withClient((client) => UserRepository(client).getUser(id)); } @riverpod @@ -36,41 +34,23 @@ Future> userActivity(Ref ref, {required UserId id}) async { @riverpod Future<(User, UserStatus)> userAndStatus(Ref ref, {required UserId id}) async { - return ref.withClient( - (client) async { - final repo = UserRepository(client); - return Future.wait( - [ - repo.getUser(id, withCanChallenge: true), - repo.getUsersStatuses({id}.lock), - ], - eagerError: true, - ).then( - (value) => (value[0] as User, (value[1] as IList).first), - ); - }, - ); + return ref.withClient((client) async { + final repo = UserRepository(client); + return Future.wait([ + repo.getUser(id, withCanChallenge: true), + repo.getUsersStatuses({id}.lock), + ], eagerError: true).then((value) => (value[0] as User, (value[1] as IList).first)); + }); } @riverpod -Future userPerfStats( - Ref ref, { - required UserId id, - required Perf perf, -}) async { - return ref.withClient( - (client) => UserRepository(client).getPerfStats(id, perf), - ); +Future userPerfStats(Ref ref, {required UserId id, required Perf perf}) async { + return ref.withClient((client) => UserRepository(client).getPerfStats(id, perf)); } @riverpod -Future> userStatuses( - Ref ref, { - required ISet ids, -}) async { - return ref.withClient( - (client) => UserRepository(client).getUsersStatuses(ids), - ); +Future> userStatuses(Ref ref, {required ISet ids}) async { + return ref.withClient((client) => UserRepository(client).getUsersStatuses(ids)); } @riverpod @@ -107,16 +87,11 @@ Future> autoCompleteUser(Ref ref, String term) async { throw Exception('Cancelled'); } - return ref.withClient( - (client) => UserRepository(client).autocompleteUser(term), - ); + return ref.withClient((client) => UserRepository(client).autocompleteUser(term)); } @riverpod -Future> userRatingHistory( - Ref ref, { - required UserId id, -}) async { +Future> userRatingHistory(Ref ref, {required UserId id}) async { return ref.withClientCacheFor( (client) => UserRepository(client).getRatingHistory(id), const Duration(minutes: 1), diff --git a/lib/src/navigation.dart b/lib/src/navigation.dart index be4c4b8581..26ff184dd6 100644 --- a/lib/src/navigation.dart +++ b/lib/src/navigation.dart @@ -64,8 +64,7 @@ enum BottomTab { } } -final currentBottomTabProvider = - StateProvider((ref) => BottomTab.home); +final currentBottomTabProvider = StateProvider((ref) => BottomTab.home); final currentNavigatorKeyProvider = Provider>((ref) { final currentTab = ref.watch(currentBottomTabProvider); @@ -111,8 +110,7 @@ final toolsScrollController = ScrollController(debugLabel: 'ToolsScroll'); final watchScrollController = ScrollController(debugLabel: 'WatchScroll'); final settingsScrollController = ScrollController(debugLabel: 'SettingsScroll'); -final RouteObserver> rootNavPageRouteObserver = - RouteObserver>(); +final RouteObserver> rootNavPageRouteObserver = RouteObserver>(); final _cupertinoTabController = CupertinoTabController(); @@ -132,17 +130,10 @@ class BottomNavScaffold extends ConsumerWidget { switch (Theme.of(context).platform) { case TargetPlatform.android: return Scaffold( - body: _TabSwitchingView( - currentTab: currentTab, - tabBuilder: _androidTabBuilder, - ), + body: _TabSwitchingView(currentTab: currentTab, tabBuilder: _androidTabBuilder), bottomNavigationBar: Consumer( builder: (context, ref, _) { - final isOnline = ref - .watch(connectivityChangesProvider) - .valueOrNull - ?.isOnline ?? - true; + final isOnline = ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? true; return NavigationBar( selectedIndex: currentTab.index, destinations: [ @@ -152,16 +143,13 @@ class BottomNavScaffold extends ConsumerWidget { label: tab.label(context.l10n), ), ], - onDestinationSelected: (i) => - _onItemTapped(ref, i, isOnline: isOnline), + onDestinationSelected: (i) => _onItemTapped(ref, i, isOnline: isOnline), ); }, ), ); case TargetPlatform.iOS: - final isOnline = - ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? - true; + final isOnline = ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? true; return CupertinoTabScaffold( tabBuilder: _iOSTabBuilder, controller: _cupertinoTabController, @@ -191,11 +179,7 @@ class BottomNavScaffold extends ConsumerWidget { void _onItemTapped(WidgetRef ref, int index, {required bool isOnline}) { if (index == BottomTab.watch.index && !isOnline) { _cupertinoTabController.index = ref.read(currentBottomTabProvider).index; - showPlatformSnackbar( - ref.context, - 'Not available in offline mode', - type: SnackBarType.info, - ); + showPlatformSnackbar(ref.context, 'Not available in offline mode', type: SnackBarType.info); return; } @@ -306,10 +290,7 @@ Widget _iOSTabBuilder(BuildContext context, int index) { /// A widget laying out multiple tabs with only one active tab being built /// at a time and on stage. Off stage tabs' animations are stopped. class _TabSwitchingView extends StatefulWidget { - const _TabSwitchingView({ - required this.currentTab, - required this.tabBuilder, - }); + const _TabSwitchingView({required this.currentTab, required this.tabBuilder}); final BottomTab currentTab; final IndexedWidgetBuilder tabBuilder; @@ -352,24 +333,19 @@ class _TabSwitchingViewState extends State<_TabSwitchingView> { if (tabFocusNodes.length != BottomTab.values.length) { if (tabFocusNodes.length > BottomTab.values.length) { discardedNodes.addAll(tabFocusNodes.sublist(BottomTab.values.length)); - tabFocusNodes.removeRange( - BottomTab.values.length, - tabFocusNodes.length, - ); + tabFocusNodes.removeRange(BottomTab.values.length, tabFocusNodes.length); } else { tabFocusNodes.addAll( List.generate( BottomTab.values.length - tabFocusNodes.length, (int index) => FocusScopeNode( - debugLabel: - '$BottomNavScaffold Tab ${index + tabFocusNodes.length}', + debugLabel: '$BottomNavScaffold Tab ${index + tabFocusNodes.length}', ), ), ); } } - FocusScope.of(context) - .setFirstFocus(tabFocusNodes[widget.currentTab.index]); + FocusScope.of(context).setFirstFocus(tabFocusNodes[widget.currentTab.index]); } @override @@ -401,9 +377,7 @@ class _TabSwitchingViewState extends State<_TabSwitchingView> { node: tabFocusNodes[index], child: Builder( builder: (BuildContext context) { - return shouldBuildTab[index] - ? widget.tabBuilder(context, index) - : Container(); + return shouldBuildTab[index] ? widget.tabBuilder(context, index) : Container(); }, ), ), @@ -490,11 +464,12 @@ class _MaterialTabViewState extends ConsumerState<_MaterialTabView> { final currentTab = ref.watch(currentBottomTabProvider); final enablePopHandler = currentTab == widget.tab; return NavigatorPopHandler( - onPopWithResult: enablePopHandler - ? (_) { - widget.navigatorKey?.currentState?.maybePop(); - } - : null, + onPopWithResult: + enablePopHandler + ? (_) { + widget.navigatorKey?.currentState?.maybePop(); + } + : null, enabled: enablePopHandler, child: Navigator( key: widget.navigatorKey, @@ -515,10 +490,7 @@ class _MaterialTabViewState extends ConsumerState<_MaterialTabView> { routeBuilder = widget.routes![name]; } if (routeBuilder != null) { - return MaterialPageRoute( - builder: routeBuilder, - settings: settings, - ); + return MaterialPageRoute(builder: routeBuilder, settings: settings); } if (widget.onGenerateRoute != null) { return widget.onGenerateRoute!(settings); diff --git a/lib/src/network/connectivity.dart b/lib/src/network/connectivity.dart index bf340e467a..f85dbce3cf 100644 --- a/lib/src/network/connectivity.dart +++ b/lib/src/network/connectivity.dart @@ -42,20 +42,15 @@ class ConnectivityChanges extends _$ConnectivityChanges { }); _connectivitySubscription?.cancel(); - _connectivitySubscription = - _connectivity.onConnectivityChanged.listen((result) { + _connectivitySubscription = _connectivity.onConnectivityChanged.listen((result) { _connectivityChangesDebouncer(() => _onConnectivityChange(result)); }); final AppLifecycleState? appState = WidgetsBinding.instance.lifecycleState; - _appLifecycleListener = AppLifecycleListener( - onStateChange: _onAppLifecycleChange, - ); + _appLifecycleListener = AppLifecycleListener(onStateChange: _onAppLifecycleChange); - return _connectivity - .checkConnectivity() - .then((r) => _getConnectivityStatus(r, appState)); + return _connectivity.checkConnectivity().then((r) => _getConnectivityStatus(r, appState)); } Future _onAppLifecycleChange(AppLifecycleState appState) async { @@ -64,9 +59,9 @@ class ConnectivityChanges extends _$ConnectivityChanges { } if (appState == AppLifecycleState.resumed) { - final newConn = await _connectivity - .checkConnectivity() - .then((r) => _getConnectivityStatus(r, appState)); + final newConn = await _connectivity.checkConnectivity().then( + (r) => _getConnectivityStatus(r, appState), + ); state = AsyncValue.data(newConn); } else { @@ -88,12 +83,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.valueOrNull?.appState)); } } @@ -101,19 +91,13 @@ class ConnectivityChanges extends _$ConnectivityChanges { List result, AppLifecycleState? appState, ) async { - final status = ( - isOnline: await isOnline(_defaultClient), - appState: appState, - ); + final status = (isOnline: await isOnline(_defaultClient), appState: appState); _logger.info('Connectivity status: $result, isOnline: ${status.isOnline}'); return status; } } -typedef ConnectivityStatus = ({ - bool isOnline, - AppLifecycleState? appState, -}); +typedef ConnectivityStatus = ({bool isOnline, AppLifecycleState? appState}); final _internetCheckUris = [ Uri.parse('https://www.gstatic.com/generate_204'), @@ -126,10 +110,10 @@ Future isOnline(Client client) { try { int remaining = _internetCheckUris.length; final futures = _internetCheckUris.map( - (uri) => client.head(uri).timeout(const Duration(seconds: 10)).then( - (response) => true, - onError: (_) => false, - ), + (uri) => client + .head(uri) + .timeout(const Duration(seconds: 10)) + .then((response) => true, onError: (_) => false), ); for (final future in futures) { future.then((value) { @@ -169,10 +153,7 @@ extension AsyncValueConnectivity on AsyncValue { /// offline: () => 'Offline', /// ); /// ``` - R whenIs({ - required R Function() online, - required R Function() offline, - }) { + R whenIs({required R Function() online, required R Function() offline}) { return maybeWhen( skipLoadingOnReload: true, data: (status) => status.isOnline ? online() : offline(), diff --git a/lib/src/network/http.dart b/lib/src/network/http.dart index 4c24ab6cb5..fb0c115f04 100644 --- a/lib/src/network/http.dart +++ b/lib/src/network/http.dart @@ -58,9 +58,10 @@ class HttpClientFactory { } if (Platform.isIOS || Platform.isMacOS) { - final config = URLSessionConfiguration.ephemeralSessionConfiguration() - ..cache = URLCache.withCapacity(memoryCapacity: _maxCacheSize) - ..httpAdditionalHeaders = {'User-Agent': userAgent}; + final config = + URLSessionConfiguration.ephemeralSessionConfiguration() + ..cache = URLCache.withCapacity(memoryCapacity: _maxCacheSize) + ..httpAdditionalHeaders = {'User-Agent': userAgent}; return CupertinoClient.fromSessionConfiguration(config); } @@ -118,14 +119,8 @@ String userAgent(Ref ref) { } /// 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, -) { - final base = - 'Lichess Mobile/${info.version} as:${user?.id ?? 'anon'} sri:$sri'; +String makeUserAgent(PackageInfo info, BaseDeviceInfo deviceInfo, String sri, LightUser? user) { + final base = 'Lichess Mobile/${info.version} as:${user?.id ?? 'anon'} sri:$sri'; if (deviceInfo is AndroidDeviceInfo) { return '$base os:Android/${deviceInfo.version.release} dev:${deviceInfo.model}'; @@ -180,9 +175,7 @@ class LichessClient implements Client { session?.user, ); - _logger.info( - '${request.method} ${request.url} ${request.headers['User-Agent']}', - ); + _logger.info('${request.method} ${request.url} ${request.headers['User-Agent']}'); try { final response = await _inner.send(request); @@ -204,11 +197,7 @@ class LichessClient implements Client { Future _checkSessionToken(AuthSessionState session) async { final defaultClient = _ref.read(defaultClientProvider); final data = await defaultClient - .postReadJson( - lichessUri('/api/token/test'), - mapper: (json) => json, - body: session.token, - ) + .postReadJson(lichessUri('/api/token/test'), mapper: (json) => json, body: session.token) .timeout(const Duration(seconds: 5)); if (data[session.token] == null) { _logger.fine('Session is not active. Deleting it.'); @@ -233,17 +222,11 @@ class LichessClient implements Client { } @override - Future head( - Uri url, { - Map? headers, - }) => + Future head(Uri url, {Map? headers}) => _sendUnstreamed('HEAD', url, headers); @override - Future get( - Uri url, { - Map? headers, - }) => + Future get(Uri url, {Map? headers}) => _sendUnstreamed('GET', url, headers); @override @@ -252,16 +235,10 @@ class LichessClient implements Client { Map? headers, Object? body, Encoding? encoding, - }) => - _sendUnstreamed('POST', url, headers, body, encoding); + }) => _sendUnstreamed('POST', url, headers, body, encoding); @override - Future put( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => + Future put(Uri url, {Map? headers, Object? body, Encoding? encoding}) => _sendUnstreamed('PUT', url, headers, body, encoding); @override @@ -270,8 +247,7 @@ class LichessClient implements Client { Map? headers, Object? body, Encoding? encoding, - }) => - _sendUnstreamed('PATCH', url, headers, body, encoding); + }) => _sendUnstreamed('PATCH', url, headers, body, encoding); @override Future delete( @@ -279,8 +255,7 @@ class LichessClient implements Client { Map? headers, Object? body, Encoding? encoding, - }) => - _sendUnstreamed('DELETE', url, headers, body, encoding); + }) => _sendUnstreamed('DELETE', url, headers, body, encoding); @override Future read(Uri url, {Map? headers}) async { @@ -332,12 +307,7 @@ class ServerException extends ClientException { final int statusCode; final Map? jsonError; - ServerException( - this.statusCode, - super.message, - Uri super.url, - this.jsonError, - ); + ServerException(this.statusCode, super.message, Uri super.url, this.jsonError); } /// Throws an error if [response] is not successful. @@ -387,19 +357,13 @@ extension ClientExtension on Client { final json = jsonUtf8Decoder.convert(response.bodyBytes); if (json is! Map) { _logger.severe('Could not read JSON object as $T: expected an object.'); - throw ClientException( - 'Could not read JSON object as $T: expected an object.', - url, - ); + throw ClientException('Could not read JSON object as $T: expected an object.', url); } try { return mapper(json); } catch (e, st) { _logger.severe('Could not read JSON object as $T: $e', e, st); - throw ClientException( - 'Could not read JSON object as $T: $e\n$st', - url, - ); + throw ClientException('Could not read JSON object as $T: $e\n$st', url); } } @@ -419,20 +383,14 @@ extension ClientExtension on Client { final json = jsonUtf8Decoder.convert(response.bodyBytes); if (json is! List) { _logger.severe('Could not read JSON object as List: expected a list.'); - throw ClientException( - 'Could not read JSON object as List: expected a list.', - url, - ); + throw ClientException('Could not read JSON object as List: expected a list.', url); } final List list = []; for (final e in json) { if (e is! Map) { _logger.severe('Could not read JSON object as $T: expected an object.'); - throw ClientException( - 'Could not read JSON object as $T: expected an object.', - url, - ); + throw ClientException('Could not read JSON object as $T: expected an object.', url); } try { final mapped = mapper(e); @@ -486,19 +444,13 @@ extension ClientExtension on Client { throw ServerException(response.statusCode, '$message.', url, null); } try { - return response.stream - .map(utf8.decode) - .where((e) => e.isNotEmpty && e != '\n') - .map((e) { + return response.stream.map(utf8.decode).where((e) => e.isNotEmpty && e != '\n').map((e) { final json = jsonDecode(e) as Map; return mapper(json); }); } catch (e) { _logger.severe('Could not read nd-json object as $T.'); - throw ClientException( - 'Could not read nd-json object as $T: $e', - url, - ); + throw ClientException('Could not read nd-json object as $T: $e', url); } } @@ -515,25 +467,18 @@ extension ClientExtension on Client { Encoding? encoding, required T Function(Map) mapper, }) async { - final response = - await post(url, headers: headers, body: body, encoding: encoding); + final response = await post(url, headers: headers, body: body, encoding: encoding); _checkResponseSuccess(url, response); final json = jsonUtf8Decoder.convert(response.bodyBytes); if (json is! Map) { _logger.severe('Could not read json object as $T: expected an object.'); - throw ClientException( - 'Could not read json object as $T: expected an object.', - url, - ); + throw ClientException('Could not read json object as $T: expected an object.', url); } try { return mapper(json); } catch (e, st) { _logger.severe('Could not read json as $T: $e', e, st); - throw ClientException( - 'Could not read json as $T: $e', - url, - ); + throw ClientException('Could not read json as $T: $e', url); } } @@ -550,21 +495,17 @@ extension ClientExtension on Client { Encoding? encoding, required T Function(Map) mapper, }) async { - final response = - await post(url, headers: headers, body: body, encoding: encoding); + final response = await post(url, headers: headers, body: body, encoding: encoding); _checkResponseSuccess(url, response); return _readNdJsonList(response, mapper); } - IList _readNdJsonList( - Response response, - T Function(Map) mapper, - ) { + IList _readNdJsonList(Response response, T Function(Map) mapper) { try { return IList( - LineSplitter.split(utf8.decode(response.bodyBytes)) - .where((e) => e.isNotEmpty && e != '\n') - .map((e) { + LineSplitter.split( + utf8.decode(response.bodyBytes), + ).where((e) => e.isNotEmpty && e != '\n').map((e) { final json = jsonDecode(e) as Map; return mapper(json); }), @@ -600,10 +541,7 @@ extension ClientRefExtension on Ref { /// /// If [fn] throws with a [SocketException], the provider is not kept alive, this /// allows to retry the request later. - Future withClientCacheFor( - Future Function(LichessClient) fn, - Duration duration, - ) async { + Future withClientCacheFor(Future Function(LichessClient) fn, Duration duration) async { final link = keepAlive(); final timer = Timer(duration, link.close); final client = read(lichessClientProvider); diff --git a/lib/src/network/socket.dart b/lib/src/network/socket.dart index fdb9d7eed0..93aacb93b2 100644 --- a/lib/src/network/socket.dart +++ b/lib/src/network/socket.dart @@ -34,11 +34,7 @@ const _kDisconnectOnBackgroundTimeout = Duration(minutes: 5); final _logger = Logger('Socket'); /// Set of topics that are allowed to be broadcasted to the global stream. -const _globalSocketStreamAllowedTopics = { - 'n', - 'message', - 'challenges', -}; +const _globalSocketStreamAllowedTopics = {'n', 'message', 'challenges'}; final _globalStreamController = StreamController.broadcast(); @@ -51,24 +47,21 @@ final _globalStreamController = StreamController.broadcast(); final socketGlobalStream = _globalStreamController.stream; /// Creates a WebSocket URI for the lichess server. -Uri lichessWSUri( - String unencodedPath, [ - Map? queryParameters, -]) => +Uri lichessWSUri(String unencodedPath, [Map? queryParameters]) => kLichessWSHost.startsWith('localhost') ? Uri( - scheme: 'ws', - host: kLichessWSHost.split(':')[0], - port: int.parse(kLichessWSHost.split(':')[1]), - path: unencodedPath, - queryParameters: queryParameters, - ) + scheme: 'ws', + host: kLichessWSHost.split(':')[0], + port: int.parse(kLichessWSHost.split(':')[1]), + path: unencodedPath, + queryParameters: queryParameters, + ) : Uri( - scheme: 'wss', - host: kLichessWSHost, - path: unencodedPath, - queryParameters: queryParameters, - ); + scheme: 'wss', + host: kLichessWSHost, + path: unencodedPath, + queryParameters: queryParameters, + ); /// A lichess WebSocket client. /// @@ -127,13 +120,9 @@ class SocketClient { final VoidCallback? onStreamCancel; late final StreamController _streamController = - StreamController.broadcast( - onListen: onStreamListen, - onCancel: onStreamCancel, - ); + StreamController.broadcast(onListen: onStreamListen, onCancel: onStreamCancel); - late final StreamController _socketOpenController = - StreamController.broadcast(); + late final StreamController _socketOpenController = StreamController.broadcast(); Completer _firstConnection = Completer(); @@ -203,13 +192,9 @@ class SocketClient { final session = getSession(); final uri = lichessWSUri(route.path); - final Map headers = session != null - ? { - 'Authorization': 'Bearer ${signBearerToken(session.token)}', - } - : {}; - WebSocket.userAgent = - makeUserAgent(packageInfo, deviceInfo, sri, session?.user); + final Map headers = + session != null ? {'Authorization': 'Bearer ${signBearerToken(session.token)}'} : {}; + WebSocket.userAgent = makeUserAgent(packageInfo, deviceInfo, sri, session?.user); _logger.info('Creating WebSocket connection to $route'); @@ -225,14 +210,14 @@ class SocketClient { _channel = channel; _socketStreamSubscription?.cancel(); - _socketStreamSubscription = channel.stream.map((raw) { - if (raw == '0') { - return SocketEvent.pong; - } - return SocketEvent.fromJson( - jsonDecode(raw as String) as Map, - ); - }).listen(_handleEvent); + _socketStreamSubscription = channel.stream + .map((raw) { + if (raw == '0') { + return SocketEvent.pong; + } + return SocketEvent.fromJson(jsonDecode(raw as String) as Map); + }) + .listen(_handleEvent); _logger.fine('WebSocket connection to $route established.'); @@ -258,12 +243,7 @@ class SocketClient { } /// Sends a message to the websocket. - void send( - String topic, - Object? data, { - bool? ackable, - bool? withLag, - }) { + void send(String topic, Object? data, {bool? ackable, bool? withLag}) { Map message; if (ackable == true) { @@ -281,10 +261,7 @@ class SocketClient { message = { 't': topic, if (data != null && data is Map) - 'd': { - ...data, - if (withLag == true) 'l': _averageLag.value.inMilliseconds, - } + 'd': {...data, if (withLag == true) 'l': _averageLag.value.inMilliseconds} else if (data != null) 'd': data, }; @@ -323,22 +300,23 @@ class SocketClient { /// /// Returns a [Future] that completes when the connection is closed. Future _disconnect() { - final future = _sink?.close().then((_) { - _logger.fine('WebSocket connection to $route was properly closed.'); - if (isDisposed) { - return; - } - _averageLag.value = Duration.zero; - }).catchError((Object? error) { - _logger.warning( - 'WebSocket connection to $route could not be closed: $error', - error, - ); - if (isDisposed) { - return; - } - _averageLag.value = Duration.zero; - }) ?? + final future = + _sink + ?.close() + .then((_) { + _logger.fine('WebSocket connection to $route was properly closed.'); + if (isDisposed) { + return; + } + _averageLag.value = Duration.zero; + }) + .catchError((Object? error) { + _logger.warning('WebSocket connection to $route could not be closed: $error', error); + if (isDisposed) { + return; + } + _averageLag.value = Duration.zero; + }) ?? Future.value(); _channel = null; _socketStreamSubscription?.cancel(); @@ -378,10 +356,7 @@ class SocketClient { void _sendPing() { _sink?.add( _pongCount % 10 == 2 - ? jsonEncode({ - 't': 'p', - 'l': (_averageLag.value.inMilliseconds * 0.1).round(), - }) + ? jsonEncode({'t': 'p', 'l': (_averageLag.value.inMilliseconds * 0.1).round()}) : 'p', ); _lastPing = DateTime.now(); @@ -396,8 +371,7 @@ class SocketClient { _schedulePing(pingDelay); _pongCount++; final currentLag = Duration( - milliseconds: - math.min(DateTime.now().difference(_lastPing).inMilliseconds, 10000), + milliseconds: math.min(DateTime.now().difference(_lastPing).inMilliseconds, 10000), ); // Average first 4 pings, then switch to decaying average. @@ -413,9 +387,7 @@ class SocketClient { _averageLag.value = Duration.zero; connect(); } else { - _logger.warning( - 'Scheduled reconnect after $delay failed since client is disposed.', - ); + _logger.warning('Scheduled reconnect after $delay failed since client is disposed.'); } }); } @@ -428,8 +400,7 @@ class SocketClient { } void _resendAcks() { - final resendCutoff = - DateTime.now().subtract(const Duration(milliseconds: 2500)); + final resendCutoff = DateTime.now().subtract(const Duration(milliseconds: 2500)); for (final (at, _, ack) in _acks) { if (at.isBefore(resendCutoff)) { _sink?.add(jsonEncode(ack)); @@ -452,10 +423,7 @@ class SocketClient { /// When a requested client is disposed, the pool will automatically reconnect /// the default client. class SocketPool { - SocketPool( - this._ref, { - this.idleTimeout = _kIdleTimeout, - }) { + SocketPool(this._ref, {this.idleTimeout = _kIdleTimeout}) { // Create a default socket client. This one is never disposed. final client = SocketClient( _currentRoute, @@ -503,10 +471,7 @@ class SocketPool { /// It will use an existing connection if it is already active, unless /// [forceReconnect] is set to true. /// Any other active connection will be closed. - SocketClient open( - Uri route, { - bool? forceReconnect, - }) { + SocketClient open(Uri route, {bool? forceReconnect}) { _currentRoute = route; if (_pool[route] == null) { @@ -579,15 +544,12 @@ SocketPool socketPool(Ref ref) { final appLifecycleListener = AppLifecycleListener( onHide: () { closeInBackgroundTimer?.cancel(); - closeInBackgroundTimer = Timer( - _kDisconnectOnBackgroundTimeout, - () { - _logger.info( - 'App is in background for ${_kDisconnectOnBackgroundTimeout.inMinutes}m, closing socket.', - ); - pool.currentClient.close(); - }, - ); + closeInBackgroundTimer = Timer(_kDisconnectOnBackgroundTimeout, () { + _logger.info( + 'App is in background for ${_kDisconnectOnBackgroundTimeout.inMinutes}m, closing socket.', + ); + pool.currentClient.close(); + }); }, onShow: () { closeInBackgroundTimer?.cancel(); @@ -650,8 +612,7 @@ class WebSocketChannelFactory { Map? headers, Duration timeout = const Duration(seconds: 10), }) async { - final socket = - await WebSocket.connect(url, headers: headers).timeout(timeout); + final socket = await WebSocket.connect(url, headers: headers).timeout(timeout); return IOWebSocketChannel(socket); } diff --git a/lib/src/styles/lichess_colors.dart b/lib/src/styles/lichess_colors.dart index d082947f4c..83e8a0ecaf 100644 --- a/lib/src/styles/lichess_colors.dart +++ b/lib/src/styles/lichess_colors.dart @@ -9,8 +9,7 @@ class LichessColors { // http://mmbitson.com // primary: blue - static const MaterialColor primary = - MaterialColor(_primaryPrimaryValue, { + static const MaterialColor primary = MaterialColor(_primaryPrimaryValue, { 50: Color(0xFFE4EFF9), 100: Color(0xFFBBD7F1), 200: Color(0xFF8DBCE8), @@ -25,8 +24,7 @@ class LichessColors { static const int _primaryPrimaryValue = 0xFF1B78D0; // secondary: green - static const MaterialColor secondary = - MaterialColor(_secondaryPrimaryValue, { + static const MaterialColor secondary = MaterialColor(_secondaryPrimaryValue, { 50: Color(0xFFECF3E5), 100: Color(0xFFD0E0BD), 200: Color(0xFFB1CC92), @@ -41,8 +39,7 @@ class LichessColors { static const int _secondaryPrimaryValue = 0xFF629924; // accent: orange - static const MaterialColor accent = - MaterialColor(_accentPrimaryValue, { + static const MaterialColor accent = MaterialColor(_accentPrimaryValue, { 50: Color(0xFFFAEAE0), 100: Color(0xFFF3CAB3), 200: Color(0xFFEBA780), diff --git a/lib/src/styles/lichess_icons.dart b/lib/src/styles/lichess_icons.dart index 12ed2cb177..67675461d1 100644 --- a/lib/src/styles/lichess_icons.dart +++ b/lib/src/styles/lichess_icons.dart @@ -11,7 +11,7 @@ /// fonts: /// - asset: fonts/LichessIcons.ttf /// -/// +/// /// * Font Awesome 4, Copyright (C) 2016 by Dave Gandy /// Author: Dave Gandy /// License: SIL () @@ -33,6 +33,7 @@ class LichessIcons { static const _kFontFam = 'LichessIcons'; static const String? _kFontPkg = null; + // dart format off static const IconData patron = IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData target = IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData blitz = IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg); diff --git a/lib/src/styles/puzzle_icons.dart b/lib/src/styles/puzzle_icons.dart index 04d37141b7..2405640dad 100644 --- a/lib/src/styles/puzzle_icons.dart +++ b/lib/src/styles/puzzle_icons.dart @@ -6,118 +6,189 @@ class PuzzleIcons { static const _kFontFam = 'LichessPuzzleIcons'; static const String? _kFontPkg = null; - static const IconData clearance = - IconData(0xe000, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData queensideAttack = - IconData(0xe001, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData bishopEndgame = - IconData(0xe002, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData short = - IconData(0xe003, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData backRankMate = - IconData(0xe004, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData advancedPawn = - IconData(0xe005, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData doubleCheck = - IconData(0xe006, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData mate = - IconData(0xe007, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData rookEndgame = - IconData(0xe008, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData sacrifice = - IconData(0xe009, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData promotion = - IconData(0xe00a, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData knightEndgame = - IconData(0xe00b, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData skewer = - IconData(0xe00c, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData master = - IconData(0xe00d, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData hookMate = - IconData(0xe00e, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData exposedKing = - IconData(0xe00f, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData intermezzo = - IconData(0xe010, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData interference = - IconData(0xe011, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData doubleBishopMate = - IconData(0xe012, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData hangingPiece = - IconData(0xe013, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData superGM = - IconData(0xe014, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData equality = - IconData(0xe015, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData castling = - IconData(0xe016, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData underPromotion = - IconData(0xe017, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData discoveredAttack = - IconData(0xe018, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData pin = - IconData(0xe019, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData endgame = - IconData(0xe01a, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData defensiveMove = - IconData(0xe01b, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData advantage = - IconData(0xe01c, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData mix = - IconData(0xe01d, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData oneMove = - IconData(0xe01e, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData anastasiaMate = - IconData(0xe01f, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData middlegame = - IconData(0xe020, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData fork = - IconData(0xe021, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData masterVsMaster = - IconData(0xe022, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData quietMove = - IconData(0xe023, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData long = - IconData(0xe024, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData smotheredMate = - IconData(0xe025, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData bodenMate = - IconData(0xe026, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData kingsideAttack = - IconData(0xe027, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData playerGames = - IconData(0xe028, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData pawnEndgame = - IconData(0xe029, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData zugzwang = - IconData(0xe02a, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData deflection = - IconData(0xe02b, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData trappedPiece = - IconData(0xe02c, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData capturingDefender = - IconData(0xe02d, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData queenEndgame = - IconData(0xe02e, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData arabianMate = - IconData(0xe02f, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData enPassant = - IconData(0xe030, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData attackingF2F7 = - IconData(0xe031, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData veryLong = - IconData(0xe032, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData xRayAttack = - IconData(0xe033, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData queenRookEndgame = - IconData(0xe034, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData attraction = - IconData(0xe035, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData crushing = - IconData(0xe036, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData opening = - IconData(0xe037, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData dovetailMate = - IconData(0xe038, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData clearance = IconData(0xe000, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData queensideAttack = IconData( + 0xe001, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData bishopEndgame = IconData( + 0xe002, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData short = IconData(0xe003, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData backRankMate = IconData( + 0xe004, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData advancedPawn = IconData( + 0xe005, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData doubleCheck = IconData( + 0xe006, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData mate = IconData(0xe007, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData rookEndgame = IconData( + 0xe008, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData sacrifice = IconData(0xe009, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData promotion = IconData(0xe00a, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData knightEndgame = IconData( + 0xe00b, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData skewer = IconData(0xe00c, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData master = IconData(0xe00d, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData hookMate = IconData(0xe00e, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData exposedKing = IconData( + 0xe00f, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData intermezzo = IconData( + 0xe010, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData interference = IconData( + 0xe011, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData doubleBishopMate = IconData( + 0xe012, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData hangingPiece = IconData( + 0xe013, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData superGM = IconData(0xe014, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData equality = IconData(0xe015, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData castling = IconData(0xe016, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData underPromotion = IconData( + 0xe017, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData discoveredAttack = IconData( + 0xe018, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData pin = IconData(0xe019, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData endgame = IconData(0xe01a, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData defensiveMove = IconData( + 0xe01b, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData advantage = IconData(0xe01c, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData mix = IconData(0xe01d, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData oneMove = IconData(0xe01e, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData anastasiaMate = IconData( + 0xe01f, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData middlegame = IconData( + 0xe020, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData fork = IconData(0xe021, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData masterVsMaster = IconData( + 0xe022, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData quietMove = IconData(0xe023, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData long = IconData(0xe024, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData smotheredMate = IconData( + 0xe025, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData bodenMate = IconData(0xe026, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData kingsideAttack = IconData( + 0xe027, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData playerGames = IconData( + 0xe028, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData pawnEndgame = IconData( + 0xe029, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData zugzwang = IconData(0xe02a, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData deflection = IconData( + 0xe02b, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData trappedPiece = IconData( + 0xe02c, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData capturingDefender = IconData( + 0xe02d, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData queenEndgame = IconData( + 0xe02e, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData arabianMate = IconData( + 0xe02f, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData enPassant = IconData(0xe030, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData attackingF2F7 = IconData( + 0xe031, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData veryLong = IconData(0xe032, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData xRayAttack = IconData( + 0xe033, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData queenRookEndgame = IconData( + 0xe034, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData attraction = IconData( + 0xe035, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); + static const IconData crushing = IconData(0xe036, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData opening = IconData(0xe037, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData dovetailMate = IconData( + 0xe038, + fontFamily: _kFontFam, + fontPackage: _kFontPkg, + ); } diff --git a/lib/src/styles/social_icons.dart b/lib/src/styles/social_icons.dart index 7712fe8c39..9363012338 100644 --- a/lib/src/styles/social_icons.dart +++ b/lib/src/styles/social_icons.dart @@ -29,8 +29,6 @@ class SocialIcons { static const _kFontFam = 'SocialIcons'; static const String? _kFontPkg = null; - static const IconData youtube = - IconData(0xf167, fontFamily: _kFontFam, fontPackage: _kFontPkg); - static const IconData twitch = - IconData(0xf1e8, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData youtube = IconData(0xf167, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData twitch = IconData(0xf1e8, fontFamily: _kFontFam, fontPackage: _kFontPkg); } diff --git a/lib/src/styles/styles.dart b/lib/src/styles/styles.dart index 697c86a33d..6959840530 100644 --- a/lib/src/styles/styles.dart +++ b/lib/src/styles/styles.dart @@ -8,14 +8,8 @@ import 'package:lichess_mobile/src/styles/lichess_colors.dart'; abstract class Styles { // text static const bold = TextStyle(fontWeight: FontWeight.bold); - static const title = TextStyle( - fontSize: 20.0, - fontWeight: FontWeight.bold, - ); - static const subtitle = TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ); + static const title = TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold); + static const subtitle = TextStyle(fontSize: 16, fontWeight: FontWeight.w500); static final callout = TextStyle( fontSize: defaultTargetPlatform == TargetPlatform.iOS ? 20 : 18, letterSpacing: defaultTargetPlatform == TargetPlatform.iOS ? -0.41 : null, @@ -32,29 +26,16 @@ abstract class Styles { letterSpacing: defaultTargetPlatform == TargetPlatform.iOS ? -0.41 : null, fontWeight: FontWeight.bold, ); - static const boardPreviewTitle = TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ); + static const boardPreviewTitle = TextStyle(fontSize: 16, fontWeight: FontWeight.bold); static const subtitleOpacity = 0.7; - static const timeControl = TextStyle( - letterSpacing: 1.2, - ); - static const formLabel = TextStyle( - fontWeight: FontWeight.bold, - ); - static const formError = TextStyle( - color: LichessColors.red, - ); + static const timeControl = TextStyle(letterSpacing: 1.2); + static const formLabel = TextStyle(fontWeight: FontWeight.bold); + static const formError = TextStyle(color: LichessColors.red); static const formDescription = TextStyle(fontSize: 12); // padding - static const cupertinoAppBarTrailingWidgetPadding = - EdgeInsetsDirectional.only( - end: 8.0, - ); - static const bodyPadding = - EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0); + static const cupertinoAppBarTrailingWidgetPadding = EdgeInsetsDirectional.only(end: 8.0); + static const bodyPadding = EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0); static const verticalBodyPadding = EdgeInsets.symmetric(vertical: 16.0); static const horizontalBodyPadding = EdgeInsets.symmetric(horizontal: 16.0); static const sectionBottomPadding = EdgeInsets.only(bottom: 16.0); @@ -62,11 +43,7 @@ abstract class Styles { static const bodySectionPadding = EdgeInsets.all(16.0); /// Horizontal and bottom padding for the body section. - static const bodySectionBottomPadding = EdgeInsets.only( - bottom: 16.0, - left: 16.0, - right: 16.0, - ); + static const bodySectionBottomPadding = EdgeInsets.only(bottom: 16.0, left: 16.0, right: 16.0); // colors static Color? expansionTileColor(BuildContext context) => @@ -77,8 +54,7 @@ abstract class Styles { color: Color(0xE6F9F9F9), darkColor: Color.fromARGB(210, 36, 36, 38), ); - static const cupertinoTabletAppBarColor = - CupertinoDynamicColor.withBrightness( + static const cupertinoTabletAppBarColor = CupertinoDynamicColor.withBrightness( color: Color(0xFFF9F9F9), darkColor: Color.fromARGB(255, 36, 36, 36), ); @@ -207,10 +183,7 @@ abstract class Styles { // from: // https://github.com/flutter/flutter/blob/796c8ef79279f9c774545b3771238c3098dbefab/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart#L17 static const CupertinoDynamicColor cupertinoDefaultTabBarBorderColor = - CupertinoDynamicColor.withBrightness( - color: Color(0x4D000000), - darkColor: Color(0x29000000), - ); + CupertinoDynamicColor.withBrightness(color: Color(0x4D000000), darkColor: Color(0x29000000)); } /// Retrieve the default text color and apply an opacity to it. @@ -313,6 +286,5 @@ const lichessCustomColors = CustomColors( ); extension CustomColorsBuildContext on BuildContext { - CustomColors get lichessColors => - Theme.of(this).extension() ?? lichessCustomColors; + CustomColors get lichessColors => Theme.of(this).extension() ?? lichessCustomColors; } diff --git a/lib/src/utils/async_value.dart b/lib/src/utils/async_value.dart index 3dd5db17ca..b7aba0b226 100644 --- a/lib/src/utils/async_value.dart +++ b/lib/src/utils/async_value.dart @@ -7,9 +7,7 @@ extension AsyncValueUI on AsyncValue { if (!isRefreshing && hasError) { switch (Theme.of(context).platform) { case TargetPlatform.android: - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(error.toString())), - ); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error.toString()))); case TargetPlatform.iOS: showCupertinoSnackBar( context: context, diff --git a/lib/src/utils/badge_service.dart b/lib/src/utils/badge_service.dart index 9fb7a8403f..67a8c3e809 100644 --- a/lib/src/utils/badge_service.dart +++ b/lib/src/utils/badge_service.dart @@ -19,9 +19,7 @@ class BadgeService { } try { - await _channel.invokeMethod('setBadge', { - 'badge': value, - }); + await _channel.invokeMethod('setBadge', {'badge': value}); } on PlatformException catch (e) { _log.severe(e); } diff --git a/lib/src/utils/chessboard.dart b/lib/src/utils/chessboard.dart index 3519e92b40..af26a57636 100644 --- a/lib/src/utils/chessboard.dart +++ b/lib/src/utils/chessboard.dart @@ -6,15 +6,13 @@ import 'package:flutter/widgets.dart'; /// This method clears the cache before loading the images. Future precachePieceImages(PieceSet pieceSet) async { try { - final devicePixelRatio = WidgetsBinding - .instance.platformDispatcher.implicitView?.devicePixelRatio ?? - 1.0; + final devicePixelRatio = + WidgetsBinding.instance.platformDispatcher.implicitView?.devicePixelRatio ?? 1.0; ChessgroundImages.instance.clear(); for (final asset in pieceSet.assets.values) { - await ChessgroundImages.instance - .load(asset, devicePixelRatio: devicePixelRatio); + await ChessgroundImages.instance.load(asset, devicePixelRatio: devicePixelRatio); debugPrint('Preloaded piece image: ${asset.assetName}'); } } catch (e) { diff --git a/lib/src/utils/color_palette.dart b/lib/src/utils/color_palette.dart index 4ecbb07e12..6ecdd18d82 100644 --- a/lib/src/utils/color_palette.dart +++ b/lib/src/utils/color_palette.dart @@ -21,10 +21,7 @@ void setCorePalette(CorePalette? palette) { _boardColorScheme = ChessboardColorScheme( darkSquare: darkSquare, lightSquare: lightSquare, - background: SolidColorChessboardBackground( - lightSquare: lightSquare, - darkSquare: darkSquare, - ), + background: SolidColorChessboardBackground(lightSquare: lightSquare, darkSquare: darkSquare), whiteCoordBackground: SolidColorChessboardBackground( lightSquare: lightSquare, darkSquare: darkSquare, diff --git a/lib/src/utils/focus_detector.dart b/lib/src/utils/focus_detector.dart index 40d5800eed..25cc261294 100644 --- a/lib/src/utils/focus_detector.dart +++ b/lib/src/utils/focus_detector.dart @@ -52,8 +52,7 @@ class FocusDetector extends StatefulWidget { _FocusDetectorState createState() => _FocusDetectorState(); } -class _FocusDetectorState extends State - with WidgetsBindingObserver { +class _FocusDetectorState extends State with WidgetsBindingObserver { final _visibilityDetectorKey = UniqueKey(); /// Counter to keep track of the visibility changes. @@ -101,13 +100,13 @@ class _FocusDetectorState extends State @override Widget build(BuildContext context) => VisibilityDetector( - key: _visibilityDetectorKey, - onVisibilityChanged: (visibilityInfo) { - final visibleFraction = visibilityInfo.visibleFraction; - _notifyVisibilityStatusChange(visibleFraction); - }, - child: widget.child, - ); + key: _visibilityDetectorKey, + onVisibilityChanged: (visibilityInfo) { + final visibleFraction = visibilityInfo.visibleFraction; + _notifyVisibilityStatusChange(visibleFraction); + }, + child: widget.child, + ); /// Notifies changes in the widget's visibility. void _notifyVisibilityStatusChange(double newVisibleFraction) { diff --git a/lib/src/utils/gestures_exclusion.dart b/lib/src/utils/gestures_exclusion.dart index bbcb139c6c..b8e0b410a2 100644 --- a/lib/src/utils/gestures_exclusion.dart +++ b/lib/src/utils/gestures_exclusion.dart @@ -47,10 +47,7 @@ class AndroidGesturesExclusionWidget extends StatelessWidget { return FocusDetector( onFocusGained: () { if (shouldExcludeGesturesOnFocusGained?.call() ?? true) { - setAndroidBoardGesturesExclusion( - boardKey, - withImmersiveMode: shouldSetImmersiveMode, - ); + setAndroidBoardGesturesExclusion(boardKey, withImmersiveMode: shouldSetImmersiveMode); } }, onFocusLost: () { @@ -126,8 +123,7 @@ Future clearAndroidBoardGesturesExclusion() async { } class GesturesExclusion { - static const _channel = - MethodChannel('mobile.lichess.org/gestures_exclusion'); + static const _channel = MethodChannel('mobile.lichess.org/gestures_exclusion'); const GesturesExclusion._(); @@ -138,24 +134,23 @@ class GesturesExclusion { return; } - final rectsAsMaps = rects - .map( - (r) => r.hasNaN || r.isInfinite - ? null - : { - 'left': r.left.floor(), - 'top': r.top.floor(), - 'right': r.right.floor(), - 'bottom': r.bottom.floor(), - }, - ) - .toList(); + final rectsAsMaps = + rects + .map( + (r) => + r.hasNaN || r.isInfinite + ? null + : { + 'left': r.left.floor(), + 'top': r.top.floor(), + 'right': r.right.floor(), + 'bottom': r.bottom.floor(), + }, + ) + .toList(); try { - await _channel.invokeMethod( - 'setSystemGestureExclusionRects', - rectsAsMaps, - ); + await _channel.invokeMethod('setSystemGestureExclusionRects', rectsAsMaps); } on PlatformException catch (e) { debugPrint('Failed to set rects: ${e.message}'); } @@ -167,8 +162,7 @@ class GesturesExclusion { } try { - await _channel - .invokeMethod('setSystemGestureExclusionRects', []); + await _channel.invokeMethod('setSystemGestureExclusionRects', []); } on PlatformException catch (e) { debugPrint('Failed to clear rects: ${e.message}'); } diff --git a/lib/src/utils/image.dart b/lib/src/utils/image.dart index 58a31d6b2d..9208bb5e1e 100644 --- a/lib/src/utils/image.dart +++ b/lib/src/utils/image.dart @@ -4,10 +4,7 @@ import 'dart:typed_data'; import 'package:material_color_utilities/material_color_utilities.dart'; -typedef ImageColors = ({ - int primaryContainer, - int onPrimaryContainer, -}); +typedef ImageColors = ({int primaryContainer, int onPrimaryContainer}); /// A worker that quantizes an image and returns a minimal color scheme associated /// with the image. @@ -41,12 +38,7 @@ class ImageColorWorker { final connection = Completer<(ReceivePort, SendPort)>.sync(); initPort.handler = (dynamic initialMessage) { final commandPort = initialMessage as SendPort; - connection.complete( - ( - ReceivePort.fromRawReceivePort(initPort), - commandPort, - ), - ); + connection.complete((ReceivePort.fromRawReceivePort(initPort), commandPort)); }; try { @@ -56,8 +48,7 @@ class ImageColorWorker { rethrow; } - final (ReceivePort receivePort, SendPort sendPort) = - await connection.future; + final (ReceivePort receivePort, SendPort sendPort) = await connection.future; return ImageColorWorker._(receivePort, sendPort); } @@ -79,10 +70,7 @@ class ImageColorWorker { if (_closed && _activeRequests.isEmpty) _responses.close(); } - static void _handleCommandsToIsolate( - ReceivePort receivePort, - SendPort sendPort, - ) { + static void _handleCommandsToIsolate(ReceivePort receivePort, SendPort sendPort) { receivePort.listen((message) async { if (message == 'shutdown') { receivePort.close(); @@ -91,15 +79,14 @@ class ImageColorWorker { final (int id, Uint32List image) = message as (int, Uint32List); try { // final stopwatch0 = Stopwatch()..start(); - final QuantizerResult quantizerResult = - await QuantizerCelebi().quantize(image, 32); + final quantizerResult = await QuantizerCelebi().quantize(image, 32); final Map colorToCount = quantizerResult.colorToCount.map( - (int key, int value) => - MapEntry(_getArgbFromAbgr(key), value), + (int key, int value) => MapEntry(_getArgbFromAbgr(key), value), ); final significantColors = Map.from(colorToCount) ..removeWhere((key, value) => value < 10); - final meanTone = colorToCount.entries.fold( + final meanTone = + colorToCount.entries.fold( 0, (double previousValue, MapEntry element) => previousValue + Hct.fromInt(element.key).tone * element.value, @@ -109,19 +96,18 @@ class ImageColorWorker { (int previousValue, int element) => previousValue + element, ); - final int scoredResult = Score.score( - colorToCount, - desired: 1, - fallbackColorARGB: 0xFFFFFFFF, - filter: false, - ).first; + final int scoredResult = + Score.score( + colorToCount, + desired: 1, + fallbackColorARGB: 0xFFFFFFFF, + filter: false, + ).first; final Hct sourceColor = Hct.fromInt(scoredResult); if ((meanTone - sourceColor.tone).abs() > 20) { sourceColor.tone = meanTone; } - final scheme = (significantColors.length <= 10 - ? SchemeMonochrome.new - : SchemeFidelity.new)( + final scheme = (significantColors.length <= 10 ? SchemeMonochrome.new : SchemeFidelity.new)( sourceColorHct: sourceColor, isDark: sourceColor.tone < 50, contrastLevel: 0.0, diff --git a/lib/src/utils/immersive_mode.dart b/lib/src/utils/immersive_mode.dart index e99b460606..54738fdc94 100644 --- a/lib/src/utils/immersive_mode.dart +++ b/lib/src/utils/immersive_mode.dart @@ -11,11 +11,7 @@ import 'package:wakelock_plus/wakelock_plus.dart'; /// force the device to stay awake. class ImmersiveModeWidget extends StatelessWidget { /// Create a new immersive mode widget, that enables immersive mode when focused. - const ImmersiveModeWidget({ - required this.child, - this.shouldEnableOnFocusGained, - super.key, - }); + const ImmersiveModeWidget({required this.child, this.shouldEnableOnFocusGained, super.key}); final Widget child; @@ -40,11 +36,7 @@ class ImmersiveModeWidget extends StatelessWidget { /// A widget that enables wakelock when focused. class WakelockWidget extends StatelessWidget { /// Create a new wakelock widget, that enables wakelock when focused. - const WakelockWidget({ - required this.child, - this.shouldEnableOnFocusGained, - super.key, - }); + const WakelockWidget({required this.child, this.shouldEnableOnFocusGained, super.key}); final Widget child; @@ -91,17 +83,18 @@ class ImmersiveMode { Future disable() async { final wakeFuture = WakelockPlus.disable(); - final androidInfo = defaultTargetPlatform == TargetPlatform.android - ? await DeviceInfoPlugin().androidInfo - : null; + final androidInfo = + defaultTargetPlatform == TargetPlatform.android + ? await DeviceInfoPlugin().androidInfo + : null; final setUiModeFuture = androidInfo == null || androidInfo.version.sdkInt >= 29 ? SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge) : SystemChrome.setEnabledSystemUIMode( - SystemUiMode.manual, - overlays: SystemUiOverlay.values, - ); + SystemUiMode.manual, + overlays: SystemUiOverlay.values, + ); return Future.wait([wakeFuture, setUiModeFuture]).then((_) {}); } diff --git a/lib/src/utils/json.dart b/lib/src/utils/json.dart index b8c013b980..2bc6852ebe 100644 --- a/lib/src/utils/json.dart +++ b/lib/src/utils/json.dart @@ -25,9 +25,7 @@ extension TimeExtension on Pick { if (value is int) { return DateTime.fromMillisecondsSinceEpoch(value); } - throw PickException( - "value $value at $debugParsingExit can't be casted to DateTime", - ); + throw PickException("value $value at $debugParsingExit can't be casted to DateTime"); } /// Matches a DateTime from milliseconds since unix epoch. @@ -51,9 +49,7 @@ extension TimeExtension on Pick { } else if (value is double) { return Duration(milliseconds: (value * 1000).toInt()); } - throw PickException( - "value $value at $debugParsingExit can't be casted to Duration", - ); + throw PickException("value $value at $debugParsingExit can't be casted to Duration"); } /// Matches a Duration from seconds @@ -75,9 +71,7 @@ extension TimeExtension on Pick { if (value is int) { return Duration(milliseconds: value * 10); } - throw PickException( - "value $value at $debugParsingExit can't be casted to Duration", - ); + throw PickException("value $value at $debugParsingExit can't be casted to Duration"); } Duration? asDurationFromCentiSecondsOrNull() { @@ -98,9 +92,7 @@ extension TimeExtension on Pick { if (value is int) { return Duration(milliseconds: value); } - throw PickException( - "value $value at $debugParsingExit can't be casted to Duration", - ); + throw PickException("value $value at $debugParsingExit can't be casted to Duration"); } Duration? asDurationFromMilliSecondsOrNull() { diff --git a/lib/src/utils/l10n.dart b/lib/src/utils/l10n.dart index bf7b6adc33..2090626a20 100644 --- a/lib/src/utils/l10n.dart +++ b/lib/src/utils/l10n.dart @@ -32,21 +32,16 @@ Text l10nWithWidget( children: [ if (parts[0].isNotEmpty) TextSpan(text: parts[0], style: textStyle), if (parts[0] != localizedStringWithPlaceholder) - WidgetSpan( - child: widget, - alignment: PlaceholderAlignment.middle, - style: textStyle, - ), - if (parts.length > 1 && parts[1].isNotEmpty) - TextSpan(text: parts[1], style: textStyle), + WidgetSpan(child: widget, alignment: PlaceholderAlignment.middle, style: textStyle), + if (parts.length > 1 && parts[1].isNotEmpty) TextSpan(text: parts[1], style: textStyle), ], ), ); } final _dayFormatter = DateFormat.E().add_jm(); -final _monthFormatter = DateFormat.MMMd().add_Hm(); -final _dateFormatterWithYear = DateFormat.yMMMd().add_Hm(); +final _monthFormatter = DateFormat.MMMd(); +final _dateFormatterWithYear = DateFormat.yMMMd(); String relativeDate(DateTime date) { final diff = date.difference(DateTime.now()); @@ -55,11 +50,11 @@ String relativeDate(DateTime date) { ? diff.inHours == 0 ? 'in ${diff.inMinutes} minute${diff.inMinutes > 1 ? 's' : ''}' // TODO translate with https://github.com/lichess-org/lila/blob/65b28ea8e43e0133df6c7ed40e03c2954f247d1e/translation/source/timeago.xml#L8 : 'in ${diff.inHours} hour${diff.inHours > 1 ? 's' : ''}' // TODO translate with https://github.com/lichess-org/lila/blob/65b28ea8e43e0133df6c7ed40e03c2954f247d1e/translation/source/timeago.xml#L12 - : diff.inDays <= 7 - ? _dayFormatter.format(date) - : diff.inDays < 365 - ? _monthFormatter.format(date) - : _dateFormatterWithYear.format(date); + : diff.inDays.abs() <= 7 + ? _dayFormatter.format(date) + : diff.inDays.abs() < 365 + ? _monthFormatter.format(date) + : _dateFormatterWithYear.format(date); } /// Returns a localized locale name. @@ -68,106 +63,106 @@ String relativeDate(DateTime date) { /// /// Not all of these are actually supported in the app currently, but this way we won't have to check the lila code again when we add more languages. String localeToLocalizedName(Locale locale) => switch (locale) { - Locale(languageCode: 'en', countryCode: 'GB') => 'English', - Locale(languageCode: 'af', countryCode: 'ZA') => 'Afrikaans', - Locale(languageCode: 'an', countryCode: 'ES') => 'Aragonés', - Locale(languageCode: 'ar', countryCode: 'SA') => 'العربية', - Locale(languageCode: 'as', countryCode: 'IN') => 'অসমীয়া', - Locale(languageCode: 'av', countryCode: 'DA') => 'авар мацӀ', - Locale(languageCode: 'az', countryCode: 'AZ') => 'Azərbaycanca', - Locale(languageCode: 'be', countryCode: 'BY') => 'Беларуская', - Locale(languageCode: 'bg', countryCode: 'BG') => 'български език', - Locale(languageCode: 'bn', countryCode: 'BD') => 'বাংলা', - Locale(languageCode: 'br', countryCode: 'FR') => 'Brezhoneg', - Locale(languageCode: 'bs', countryCode: 'BA') => 'Bosanski', - Locale(languageCode: 'ca', countryCode: 'ES') => 'Català, valencià', - Locale(languageCode: 'ckb', countryCode: 'IR') => 'کوردی سۆرانی', - Locale(languageCode: 'co', countryCode: 'FR') => 'Corsu', - Locale(languageCode: 'cs', countryCode: 'CZ') => 'Čeština', - Locale(languageCode: 'cv', countryCode: 'CU') => 'чӑваш чӗлхи', - Locale(languageCode: 'cy', countryCode: 'GB') => 'Cymraeg', - Locale(languageCode: 'da', countryCode: 'DK') => 'Dansk', - Locale(languageCode: 'de', countryCode: 'DE') => 'Deutsch', - Locale(languageCode: 'el', countryCode: 'GR') => 'Ελληνικά', - Locale(languageCode: 'en', countryCode: 'US') => 'English (US)', - Locale(languageCode: 'eo', countryCode: 'UY') => 'Esperanto', - Locale(languageCode: 'es', countryCode: 'ES') => 'Español', - Locale(languageCode: 'et', countryCode: 'EE') => 'Eesti keel', - Locale(languageCode: 'eu', countryCode: 'ES') => 'Euskara', - Locale(languageCode: 'fa', countryCode: 'IR') => 'فارسی', - Locale(languageCode: 'fi', countryCode: 'FI') => 'Suomen kieli', - Locale(languageCode: 'fo', countryCode: 'FO') => 'Føroyskt', - Locale(languageCode: 'fr', countryCode: 'FR') => 'Français', - Locale(languageCode: 'frp', countryCode: 'IT') => 'Arpitan', - Locale(languageCode: 'fy', countryCode: 'NL') => 'Frysk', - Locale(languageCode: 'ga', countryCode: 'IE') => 'Gaeilge', - Locale(languageCode: 'gd', countryCode: 'GB') => 'Gàidhlig', - Locale(languageCode: 'gl', countryCode: 'ES') => 'Galego', - Locale(languageCode: 'gsw', countryCode: 'CH') => 'Schwizerdütsch', - Locale(languageCode: 'gu', countryCode: 'IN') => 'ગુજરાતી', - Locale(languageCode: 'he', countryCode: 'IL') => 'עִבְרִית', - Locale(languageCode: 'hi', countryCode: 'IN') => 'हिन्दी, हिंदी', - Locale(languageCode: 'hr', countryCode: 'HR') => 'Hrvatski', - Locale(languageCode: 'hu', countryCode: 'HU') => 'Magyar', - Locale(languageCode: 'hy', countryCode: 'AM') => 'Հայերեն', - Locale(languageCode: 'ia', countryCode: 'IA') => 'Interlingua', - Locale(languageCode: 'id', countryCode: 'ID') => 'Bahasa Indonesia', - Locale(languageCode: 'io', countryCode: 'EN') => 'Ido', - Locale(languageCode: 'is', countryCode: 'IS') => 'Íslenska', - Locale(languageCode: 'it', countryCode: 'IT') => 'Italiano', - Locale(languageCode: 'ja', countryCode: 'JP') => '日本語', - Locale(languageCode: 'jbo', countryCode: 'EN') => 'Lojban', - Locale(languageCode: 'jv', countryCode: 'ID') => 'Basa Jawa', - Locale(languageCode: 'ka', countryCode: 'GE') => 'ქართული', - Locale(languageCode: 'kab', countryCode: 'DZ') => 'Taqvaylit', - Locale(languageCode: 'kk', countryCode: 'KZ') => 'қазақша', - Locale(languageCode: 'kmr', countryCode: 'TR') => 'Kurdî (Kurmancî)', - Locale(languageCode: 'kn', countryCode: 'IN') => 'ಕನ್ನಡ', - Locale(languageCode: 'ko', countryCode: 'KR') => '한국어', - Locale(languageCode: 'ky', countryCode: 'KG') => 'кыргызча', - Locale(languageCode: 'la', countryCode: 'LA') => 'Lingua Latina', - Locale(languageCode: 'lb', countryCode: 'LU') => 'Lëtzebuergesch', - Locale(languageCode: 'lt', countryCode: 'LT') => 'Lietuvių kalba', - Locale(languageCode: 'lv', countryCode: 'LV') => 'Latviešu valoda', - Locale(languageCode: 'mg', countryCode: 'MG') => 'Fiteny malagasy', - Locale(languageCode: 'mk', countryCode: 'MK') => 'македонски јази', - Locale(languageCode: 'ml', countryCode: 'IN') => 'മലയാളം', - Locale(languageCode: 'mn', countryCode: 'MN') => 'монгол', - Locale(languageCode: 'mr', countryCode: 'IN') => 'मराठी', - Locale(languageCode: 'nb', countryCode: 'NO') => 'Norsk bokmål', - Locale(languageCode: 'ne', countryCode: 'NP') => 'नेपाली', - Locale(languageCode: 'nl', countryCode: 'NL') => 'Nederlands', - Locale(languageCode: 'nn', countryCode: 'NO') => 'Norsk nynorsk', - Locale(languageCode: 'pi', countryCode: 'IN') => 'पालि', - Locale(languageCode: 'pl', countryCode: 'PL') => 'Polski', - Locale(languageCode: 'ps', countryCode: 'AF') => 'پښتو', - Locale(languageCode: 'pt', countryCode: 'PT') => 'Português', - Locale(languageCode: 'pt', countryCode: 'BR') => 'Português (BR)', - Locale(languageCode: 'ro', countryCode: 'RO') => 'Română', - Locale(languageCode: 'ru', countryCode: 'RU') => 'русский язык', - Locale(languageCode: 'ry', countryCode: 'UA') => 'Русинська бисїда', - Locale(languageCode: 'sa', countryCode: 'IN') => 'संस्कृत', - Locale(languageCode: 'sk', countryCode: 'SK') => 'Slovenčina', - Locale(languageCode: 'sl', countryCode: 'SI') => 'Slovenščina', - Locale(languageCode: 'so', countryCode: 'SO') => 'Af Soomaali', - Locale(languageCode: 'sq', countryCode: 'AL') => 'Shqip', - Locale(languageCode: 'sr', countryCode: 'SP') => 'Српски језик', - Locale(languageCode: 'sv', countryCode: 'SE') => 'Svenska', - Locale(languageCode: 'sw', countryCode: 'KE') => 'Kiswahili', - Locale(languageCode: 'ta', countryCode: 'IN') => 'தமிழ்', - Locale(languageCode: 'tg', countryCode: 'TJ') => 'тоҷикӣ', - Locale(languageCode: 'th', countryCode: 'TH') => 'ไทย', - Locale(languageCode: 'tk', countryCode: 'TM') => 'Türkmençe', - Locale(languageCode: 'tl', countryCode: 'PH') => 'Tagalog', - Locale(languageCode: 'tp', countryCode: 'TP') => 'Toki pona', - Locale(languageCode: 'tr', countryCode: 'TR') => 'Türkçe', - Locale(languageCode: 'uk', countryCode: 'UA') => 'українська', - Locale(languageCode: 'ur', countryCode: 'PK') => 'اُردُو', - Locale(languageCode: 'uz', countryCode: 'UZ') => 'oʻzbekcha', - Locale(languageCode: 'vi', countryCode: 'VN') => 'Tiếng Việt', - Locale(languageCode: 'yo', countryCode: 'NG') => 'Yorùbá', - Locale(languageCode: 'zh', countryCode: 'CN') => '中文', - Locale(languageCode: 'zh', countryCode: 'TW') => '繁體中文', - Locale(languageCode: 'zu', countryCode: 'ZA') => 'isiZulu', - _ => locale.toString(), - }; + Locale(languageCode: 'en', countryCode: 'GB') => 'English', + Locale(languageCode: 'af', countryCode: 'ZA') => 'Afrikaans', + Locale(languageCode: 'an', countryCode: 'ES') => 'Aragonés', + Locale(languageCode: 'ar', countryCode: 'SA') => 'العربية', + Locale(languageCode: 'as', countryCode: 'IN') => 'অসমীয়া', + Locale(languageCode: 'av', countryCode: 'DA') => 'авар мацӀ', + Locale(languageCode: 'az', countryCode: 'AZ') => 'Azərbaycanca', + Locale(languageCode: 'be', countryCode: 'BY') => 'Беларуская', + Locale(languageCode: 'bg', countryCode: 'BG') => 'български език', + Locale(languageCode: 'bn', countryCode: 'BD') => 'বাংলা', + Locale(languageCode: 'br', countryCode: 'FR') => 'Brezhoneg', + Locale(languageCode: 'bs', countryCode: 'BA') => 'Bosanski', + Locale(languageCode: 'ca', countryCode: 'ES') => 'Català, valencià', + Locale(languageCode: 'ckb', countryCode: 'IR') => 'کوردی سۆرانی', + Locale(languageCode: 'co', countryCode: 'FR') => 'Corsu', + Locale(languageCode: 'cs', countryCode: 'CZ') => 'Čeština', + Locale(languageCode: 'cv', countryCode: 'CU') => 'чӑваш чӗлхи', + Locale(languageCode: 'cy', countryCode: 'GB') => 'Cymraeg', + Locale(languageCode: 'da', countryCode: 'DK') => 'Dansk', + Locale(languageCode: 'de', countryCode: 'DE') => 'Deutsch', + Locale(languageCode: 'el', countryCode: 'GR') => 'Ελληνικά', + Locale(languageCode: 'en', countryCode: 'US') => 'English (US)', + Locale(languageCode: 'eo', countryCode: 'UY') => 'Esperanto', + Locale(languageCode: 'es', countryCode: 'ES') => 'Español', + Locale(languageCode: 'et', countryCode: 'EE') => 'Eesti keel', + Locale(languageCode: 'eu', countryCode: 'ES') => 'Euskara', + Locale(languageCode: 'fa', countryCode: 'IR') => 'فارسی', + Locale(languageCode: 'fi', countryCode: 'FI') => 'Suomen kieli', + Locale(languageCode: 'fo', countryCode: 'FO') => 'Føroyskt', + Locale(languageCode: 'fr', countryCode: 'FR') => 'Français', + Locale(languageCode: 'frp', countryCode: 'IT') => 'Arpitan', + Locale(languageCode: 'fy', countryCode: 'NL') => 'Frysk', + Locale(languageCode: 'ga', countryCode: 'IE') => 'Gaeilge', + Locale(languageCode: 'gd', countryCode: 'GB') => 'Gàidhlig', + Locale(languageCode: 'gl', countryCode: 'ES') => 'Galego', + Locale(languageCode: 'gsw', countryCode: 'CH') => 'Schwizerdütsch', + Locale(languageCode: 'gu', countryCode: 'IN') => 'ગુજરાતી', + Locale(languageCode: 'he', countryCode: 'IL') => 'עִבְרִית', + Locale(languageCode: 'hi', countryCode: 'IN') => 'हिन्दी, हिंदी', + Locale(languageCode: 'hr', countryCode: 'HR') => 'Hrvatski', + Locale(languageCode: 'hu', countryCode: 'HU') => 'Magyar', + Locale(languageCode: 'hy', countryCode: 'AM') => 'Հայերեն', + Locale(languageCode: 'ia', countryCode: 'IA') => 'Interlingua', + Locale(languageCode: 'id', countryCode: 'ID') => 'Bahasa Indonesia', + Locale(languageCode: 'io', countryCode: 'EN') => 'Ido', + Locale(languageCode: 'is', countryCode: 'IS') => 'Íslenska', + Locale(languageCode: 'it', countryCode: 'IT') => 'Italiano', + Locale(languageCode: 'ja', countryCode: 'JP') => '日本語', + Locale(languageCode: 'jbo', countryCode: 'EN') => 'Lojban', + Locale(languageCode: 'jv', countryCode: 'ID') => 'Basa Jawa', + Locale(languageCode: 'ka', countryCode: 'GE') => 'ქართული', + Locale(languageCode: 'kab', countryCode: 'DZ') => 'Taqvaylit', + Locale(languageCode: 'kk', countryCode: 'KZ') => 'қазақша', + Locale(languageCode: 'kmr', countryCode: 'TR') => 'Kurdî (Kurmancî)', + Locale(languageCode: 'kn', countryCode: 'IN') => 'ಕನ್ನಡ', + Locale(languageCode: 'ko', countryCode: 'KR') => '한국어', + Locale(languageCode: 'ky', countryCode: 'KG') => 'кыргызча', + Locale(languageCode: 'la', countryCode: 'LA') => 'Lingua Latina', + Locale(languageCode: 'lb', countryCode: 'LU') => 'Lëtzebuergesch', + Locale(languageCode: 'lt', countryCode: 'LT') => 'Lietuvių kalba', + Locale(languageCode: 'lv', countryCode: 'LV') => 'Latviešu valoda', + Locale(languageCode: 'mg', countryCode: 'MG') => 'Fiteny malagasy', + Locale(languageCode: 'mk', countryCode: 'MK') => 'македонски јази', + Locale(languageCode: 'ml', countryCode: 'IN') => 'മലയാളം', + Locale(languageCode: 'mn', countryCode: 'MN') => 'монгол', + Locale(languageCode: 'mr', countryCode: 'IN') => 'मराठी', + Locale(languageCode: 'nb', countryCode: 'NO') => 'Norsk bokmål', + Locale(languageCode: 'ne', countryCode: 'NP') => 'नेपाली', + Locale(languageCode: 'nl', countryCode: 'NL') => 'Nederlands', + Locale(languageCode: 'nn', countryCode: 'NO') => 'Norsk nynorsk', + Locale(languageCode: 'pi', countryCode: 'IN') => 'पालि', + Locale(languageCode: 'pl', countryCode: 'PL') => 'Polski', + Locale(languageCode: 'ps', countryCode: 'AF') => 'پښتو', + Locale(languageCode: 'pt', countryCode: 'PT') => 'Português', + Locale(languageCode: 'pt', countryCode: 'BR') => 'Português (BR)', + Locale(languageCode: 'ro', countryCode: 'RO') => 'Română', + Locale(languageCode: 'ru', countryCode: 'RU') => 'русский язык', + Locale(languageCode: 'ry', countryCode: 'UA') => 'Русинська бисїда', + Locale(languageCode: 'sa', countryCode: 'IN') => 'संस्कृत', + Locale(languageCode: 'sk', countryCode: 'SK') => 'Slovenčina', + Locale(languageCode: 'sl', countryCode: 'SI') => 'Slovenščina', + Locale(languageCode: 'so', countryCode: 'SO') => 'Af Soomaali', + Locale(languageCode: 'sq', countryCode: 'AL') => 'Shqip', + Locale(languageCode: 'sr', countryCode: 'SP') => 'Српски језик', + Locale(languageCode: 'sv', countryCode: 'SE') => 'Svenska', + Locale(languageCode: 'sw', countryCode: 'KE') => 'Kiswahili', + Locale(languageCode: 'ta', countryCode: 'IN') => 'தமிழ்', + Locale(languageCode: 'tg', countryCode: 'TJ') => 'тоҷикӣ', + Locale(languageCode: 'th', countryCode: 'TH') => 'ไทย', + Locale(languageCode: 'tk', countryCode: 'TM') => 'Türkmençe', + Locale(languageCode: 'tl', countryCode: 'PH') => 'Tagalog', + Locale(languageCode: 'tp', countryCode: 'TP') => 'Toki pona', + Locale(languageCode: 'tr', countryCode: 'TR') => 'Türkçe', + Locale(languageCode: 'uk', countryCode: 'UA') => 'українська', + Locale(languageCode: 'ur', countryCode: 'PK') => 'اُردُو', + Locale(languageCode: 'uz', countryCode: 'UZ') => 'oʻzbekcha', + Locale(languageCode: 'vi', countryCode: 'VN') => 'Tiếng Việt', + Locale(languageCode: 'yo', countryCode: 'NG') => 'Yorùbá', + Locale(languageCode: 'zh', countryCode: 'CN') => '中文', + Locale(languageCode: 'zh', countryCode: 'TW') => '繁體中文', + Locale(languageCode: 'zu', countryCode: 'ZA') => 'isiZulu', + _ => locale.toString(), +}; diff --git a/lib/src/utils/navigation.dart b/lib/src/utils/navigation.dart index 80e054b7e8..11dea40fe4 100644 --- a/lib/src/utils/navigation.dart +++ b/lib/src/utils/navigation.dart @@ -60,33 +60,20 @@ Future pushPlatformRoute( bool fullscreenDialog = false, String? title, }) { - assert( - screen != null || builder != null, - 'Either screen or builder must be provided.', - ); + assert(screen != null || builder != null, 'Either screen or builder must be provided.'); return Navigator.of(context, rootNavigator: rootNavigator).push( Theme.of(context).platform == TargetPlatform.iOS ? builder != null - ? CupertinoPageRoute( - builder: builder, - title: title, - fullscreenDialog: fullscreenDialog, - ) + ? CupertinoPageRoute(builder: builder, title: title, fullscreenDialog: fullscreenDialog) : CupertinoScreenRoute( - screen: screen!, - title: title, - fullscreenDialog: fullscreenDialog, - ) + screen: screen!, + title: title, + fullscreenDialog: fullscreenDialog, + ) : builder != null - ? MaterialPageRoute( - builder: builder, - fullscreenDialog: fullscreenDialog, - ) - : MaterialScreenRoute( - screen: screen!, - fullscreenDialog: fullscreenDialog, - ), + ? MaterialPageRoute(builder: builder, fullscreenDialog: fullscreenDialog) + : MaterialScreenRoute(screen: screen!, fullscreenDialog: fullscreenDialog), ); } @@ -107,30 +94,17 @@ Future pushReplacementPlatformRoute( bool fullscreenDialog = false, String? title, }) { - return Navigator.of( - context, - rootNavigator: rootNavigator, - ).pushReplacement( + return Navigator.of(context, rootNavigator: rootNavigator).pushReplacement( Theme.of(context).platform == TargetPlatform.iOS ? builder != null - ? CupertinoPageRoute( - builder: builder, - title: title, - fullscreenDialog: fullscreenDialog, - ) + ? CupertinoPageRoute(builder: builder, title: title, fullscreenDialog: fullscreenDialog) : CupertinoScreenRoute( - screen: screen!, - title: title, - fullscreenDialog: fullscreenDialog, - ) + screen: screen!, + title: title, + fullscreenDialog: fullscreenDialog, + ) : builder != null - ? MaterialPageRoute( - builder: builder, - fullscreenDialog: fullscreenDialog, - ) - : MaterialScreenRoute( - screen: screen!, - fullscreenDialog: fullscreenDialog, - ), + ? MaterialPageRoute(builder: builder, fullscreenDialog: fullscreenDialog) + : MaterialScreenRoute(screen: screen!, fullscreenDialog: fullscreenDialog), ); } diff --git a/lib/src/utils/rate_limit.dart b/lib/src/utils/rate_limit.dart index 5033ea8076..901de4eff3 100644 --- a/lib/src/utils/rate_limit.dart +++ b/lib/src/utils/rate_limit.dart @@ -4,9 +4,7 @@ class Debouncer { final Duration delay; Timer? _timer; - Debouncer( - this.delay, - ); + Debouncer(this.delay); void call(void Function() action) { _timer?.cancel(); diff --git a/lib/src/utils/screen.dart b/lib/src/utils/screen.dart index eb4ba411a3..1266a6e1e5 100644 --- a/lib/src/utils/screen.dart +++ b/lib/src/utils/screen.dart @@ -7,8 +7,7 @@ double estimateRemainingHeightLeftBoard(BuildContext context) { final padding = MediaQuery.paddingOf(context); final safeViewportHeight = size.height - padding.top - padding.bottom; final boardSize = size.width; - final appBarHeight = - Theme.of(context).platform == TargetPlatform.iOS ? 44.0 : 56.0; + final appBarHeight = Theme.of(context).platform == TargetPlatform.iOS ? 44.0 : 56.0; return safeViewportHeight - boardSize - appBarHeight - kBottomBarHeight; } diff --git a/lib/src/utils/share.dart b/lib/src/utils/share.dart index 167290a4cc..1f7a2e33a9 100644 --- a/lib/src/utils/share.dart +++ b/lib/src/utils/share.dart @@ -7,6 +7,7 @@ import 'package:share_plus/share_plus.dart'; /// in order to make it work on iPads. Future launchShareDialog( BuildContext context, { + /// The uri to share. Uri? uri, @@ -26,12 +27,7 @@ Future launchShareDialog( if (uri != null) { return Share.shareUri(uri); } else if (files != null) { - return Share.shareXFiles( - files, - subject: subject, - text: text, - sharePositionOrigin: origin, - ); + return Share.shareXFiles(files, subject: subject, text: text, sharePositionOrigin: origin); } else if (text != null) { return Share.share(text, subject: subject, sharePositionOrigin: origin); } diff --git a/lib/src/utils/system.dart b/lib/src/utils/system.dart index f63a2e5869..77fbb21e7a 100644 --- a/lib/src/utils/system.dart +++ b/lib/src/utils/system.dart @@ -31,8 +31,7 @@ class System { Future clearUserData() async { if (Platform.isAndroid) { try { - final result = - await _channel.invokeMethod('clearApplicationUserData'); + final result = await _channel.invokeMethod('clearApplicationUserData'); return result ?? false; } on PlatformException catch (e) { debugPrint('Failed to clear user data: ${e.message}'); @@ -45,8 +44,7 @@ class System { } /// A provider that returns OS version of an Android device. -final androidVersionProvider = - FutureProvider((ref) async { +final androidVersionProvider = FutureProvider((ref) async { if (!Platform.isAndroid) { return null; } diff --git a/lib/src/view/account/edit_profile_screen.dart b/lib/src/view/account/edit_profile_screen.dart index c567d5e362..d0329a0b23 100644 --- a/lib/src/view/account/edit_profile_screen.dart +++ b/lib/src/view/account/edit_profile_screen.dart @@ -23,9 +23,7 @@ class EditProfileScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.editProfile), - ), + appBar: PlatformAppBar(title: Text(context.l10n.editProfile)), body: _Body(), ); } @@ -38,9 +36,7 @@ class _Body extends ConsumerWidget { return account.when( data: (data) { if (data == null) { - return Center( - child: Text(context.l10n.mobileMustBeLoggedIn), - ); + return Center(child: Text(context.l10n.mobileMustBeLoggedIn)); } return Padding( padding: Styles.bodyPadding, @@ -88,10 +84,7 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> { final _cupertinoTextFieldDecoration = BoxDecoration( color: CupertinoColors.tertiarySystemBackground, - border: Border.all( - color: CupertinoColors.systemGrey4, - width: 1, - ), + border: Border.all(color: CupertinoColors.systemGrey4, width: 1), borderRadius: BorderRadius.circular(8), ); @@ -99,8 +92,7 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> { @override Widget build(BuildContext context) { - final String? initialLinks = - widget.user.profile?.links?.map((e) => e.url).join('\r\n'); + final String? initialLinks = widget.user.profile?.links?.map((e) => e.url).join('\r\n'); return Form( key: _formKey, @@ -110,9 +102,7 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> { label: context.l10n.biography, initialValue: widget.user.profile?.bio, formKey: 'bio', - controller: TextEditingController( - text: widget.user.profile?.bio, - ), + controller: TextEditingController(text: widget.user.profile?.bio), description: context.l10n.biographyDescription, maxLength: 400, maxLines: 6, @@ -133,17 +123,16 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> { AdaptiveAutoComplete( cupertinoDecoration: _cupertinoTextFieldDecoration, textInputAction: TextInputAction.next, - initialValue: field.value != null - ? TextEditingValue(text: countries[field.value]!) - : null, + initialValue: + field.value != null + ? TextEditingValue(text: countries[field.value]!) + : null, optionsBuilder: (TextEditingValue value) { if (value.text.isEmpty) { return const Iterable.empty(); } return _countries.where((String option) { - return option - .toLowerCase() - .contains(value.text.toLowerCase()); + return option.toLowerCase().contains(value.text.toLowerCase()); }); }, onSelected: (String selection) { @@ -161,9 +150,7 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> { _textField( label: context.l10n.location, initialValue: widget.user.profile?.location, - controller: TextEditingController( - text: widget.user.profile?.location, - ), + controller: TextEditingController(text: widget.user.profile?.location), formKey: 'location', maxLength: 80, ), @@ -171,18 +158,14 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> { label: context.l10n.realName, initialValue: widget.user.profile?.realName, formKey: 'realName', - controller: TextEditingController( - text: widget.user.profile?.realName, - ), + controller: TextEditingController(text: widget.user.profile?.realName), maxLength: 20, ), _numericField( label: context.l10n.xRating('FIDE'), initialValue: widget.user.profile?.fideRating, formKey: 'fideRating', - controller: TextEditingController( - text: widget.user.profile?.fideRating?.toString(), - ), + controller: TextEditingController(text: widget.user.profile?.fideRating?.toString()), validator: (value) { if (value != null && (value < 1400 || value > 3000)) { return 'Rating must be between 1400 and 3000'; @@ -194,9 +177,7 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> { label: context.l10n.xRating('USCF'), initialValue: widget.user.profile?.uscfRating, formKey: 'uscfRating', - controller: TextEditingController( - text: widget.user.profile?.uscfRating?.toString(), - ), + controller: TextEditingController(text: widget.user.profile?.uscfRating?.toString()), validator: (value) { if (value != null && (value < 100 || value > 3000)) { return 'Rating must be between 100 and 3000'; @@ -208,9 +189,7 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> { label: context.l10n.xRating('ECF'), initialValue: widget.user.profile?.ecfRating, formKey: 'ecfRating', - controller: TextEditingController( - text: widget.user.profile?.ecfRating?.toString(), - ), + controller: TextEditingController(text: widget.user.profile?.ecfRating?.toString()), textInputAction: TextInputAction.done, validator: (value) { if (value != null && (value < 0 || value > 3000)) { @@ -237,55 +216,52 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> { builder: (context, snapshot) { return FatButton( semanticsLabel: context.l10n.apply, - onPressed: snapshot.connectionState == ConnectionState.waiting - ? null - : () async { - if (_formKey.currentState!.validate()) { - _formKey.currentState!.save(); - _formData.removeWhere((key, value) { - return value == null; - }); - final future = Result.capture( - ref.withClient( - (client) => - AccountRepository(client).saveProfile( - _formData.map( - (key, value) => - MapEntry(key, value.toString()), + onPressed: + snapshot.connectionState == ConnectionState.waiting + ? null + : () async { + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + _formData.removeWhere((key, value) { + return value == null; + }); + final future = Result.capture( + ref.withClient( + (client) => AccountRepository(client).saveProfile( + _formData.map((key, value) => MapEntry(key, value.toString())), ), ), - ), - ); + ); - setState(() { - _pendingSaveProfile = future; - }); + setState(() { + _pendingSaveProfile = future; + }); - final result = await future; + final result = await future; - result.match( - onError: (err, __) { - if (context.mounted) { - showPlatformSnackbar( - context, - 'Something went wrong', - type: SnackBarType.error, - ); - } - }, - onSuccess: (_) { - if (context.mounted) { - ref.invalidate(accountProvider); - showPlatformSnackbar( - context, - context.l10n.success, - type: SnackBarType.success, - ); - } - }, - ); - } - }, + result.match( + onError: (err, __) { + if (context.mounted) { + showPlatformSnackbar( + context, + 'Something went wrong', + type: SnackBarType.error, + ); + } + }, + onSuccess: (_) { + if (context.mounted) { + ref.invalidate(accountProvider); + showPlatformSnackbar( + context, + context.l10n.success, + type: SnackBarType.success, + ); + } + }, + ); + } + }, child: Text(context.l10n.apply), ); }, @@ -324,17 +300,15 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> { maxLines: maxLines, cupertinoDecoration: _cupertinoTextFieldDecoration.copyWith( border: Border.all( - color: field.errorText == null - ? CupertinoColors.systemGrey4 - : context.lichessColors.error, + color: + field.errorText == null + ? CupertinoColors.systemGrey4 + : context.lichessColors.error, width: 1, ), ), - materialDecoration: field.errorText != null - ? InputDecoration( - errorText: field.errorText, - ) - : null, + materialDecoration: + field.errorText != null ? InputDecoration(errorText: field.errorText) : null, textInputAction: textInputAction, controller: controller, onChanged: (value) { @@ -345,14 +319,10 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> { const SizedBox(height: 6.0), Text(description, style: Styles.formDescription), ], - if (Theme.of(context).platform == TargetPlatform.iOS && - field.errorText != null) + if (Theme.of(context).platform == TargetPlatform.iOS && field.errorText != null) Padding( padding: const EdgeInsets.only(top: 6.0), - child: Text( - field.errorText!, - style: Styles.formError, - ), + child: Text(field.errorText!, style: Styles.formError), ), ], ); @@ -387,31 +357,25 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> { keyboardType: TextInputType.number, cupertinoDecoration: _cupertinoTextFieldDecoration.copyWith( border: Border.all( - color: field.errorText == null - ? CupertinoColors.systemGrey4 - : context.lichessColors.error, + color: + field.errorText == null + ? CupertinoColors.systemGrey4 + : context.lichessColors.error, width: 1, ), ), - materialDecoration: field.errorText != null - ? InputDecoration( - errorText: field.errorText, - ) - : null, + materialDecoration: + field.errorText != null ? InputDecoration(errorText: field.errorText) : null, textInputAction: textInputAction, controller: controller, onChanged: (value) { field.didChange(int.tryParse(value)); }, ), - if (Theme.of(context).platform == TargetPlatform.iOS && - field.errorText != null) + if (Theme.of(context).platform == TargetPlatform.iOS && field.errorText != null) Padding( padding: const EdgeInsets.only(top: 6.0), - child: Text( - field.errorText!, - style: Styles.formError, - ), + child: Text(field.errorText!, style: Styles.formError), ), ], ); diff --git a/lib/src/view/account/profile_screen.dart b/lib/src/view/account/profile_screen.dart index e94baae744..805d031e7f 100644 --- a/lib/src/view/account/profile_screen.dart +++ b/lib/src/view/account/profile_screen.dart @@ -24,9 +24,9 @@ class ProfileScreen extends ConsumerWidget { return PlatformScaffold( appBar: PlatformAppBar( title: account.when( - data: (user) => user == null - ? const SizedBox.shrink() - : UserFullNameWidget(user: user.lightUser), + data: + (user) => + user == null ? const SizedBox.shrink() : UserFullNameWidget(user: user.lightUser), loading: () => const SizedBox.shrink(), error: (error, _) => const SizedBox.shrink(), ), @@ -34,19 +34,14 @@ class ProfileScreen extends ConsumerWidget { AppBarIconButton( icon: const Icon(Icons.edit), semanticsLabel: context.l10n.editProfile, - onPressed: () => pushPlatformRoute( - context, - builder: (_) => const EditProfileScreen(), - ), + onPressed: () => pushPlatformRoute(context, builder: (_) => const EditProfileScreen()), ), ], ), body: account.when( data: (user) { if (user == null) { - return Center( - child: Text(context.l10n.mobileMustBeLoggedIn), - ); + return Center(child: Text(context.l10n.mobileMustBeLoggedIn)); } return ListView( children: [ @@ -59,9 +54,7 @@ class ProfileScreen extends ConsumerWidget { }, loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) { - return FullScreenRetryRequest( - onRetry: () => ref.invalidate(accountProvider), - ); + return FullScreenRetryRequest(onRetry: () => ref.invalidate(accountProvider)); }, ), ); @@ -84,31 +77,33 @@ class AccountPerfCards extends ConsumerWidget { return const SizedBox.shrink(); } }, - loading: () => Shimmer( - child: Padding( - padding: padding ?? Styles.bodySectionPadding, - child: SizedBox( - height: 106, - child: ListView.separated( - padding: const EdgeInsets.symmetric(vertical: 3.0), - scrollDirection: Axis.horizontal, - itemCount: 5, - separatorBuilder: (context, index) => const SizedBox(width: 10), - itemBuilder: (context, index) => ShimmerLoading( - isLoading: true, - child: Container( - width: 100, - height: 100, - decoration: BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.circular(10.0), - ), + loading: + () => Shimmer( + child: Padding( + padding: padding ?? Styles.bodySectionPadding, + child: SizedBox( + height: 106, + child: ListView.separated( + padding: const EdgeInsets.symmetric(vertical: 3.0), + scrollDirection: Axis.horizontal, + itemCount: 5, + separatorBuilder: (context, index) => const SizedBox(width: 10), + itemBuilder: + (context, index) => ShimmerLoading( + isLoading: true, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(10.0), + ), + ), + ), ), ), ), ), - ), - ), error: (error, stack) => const SizedBox.shrink(), ); } diff --git a/lib/src/view/account/rating_pref_aware.dart b/lib/src/view/account/rating_pref_aware.dart index 46a0289348..a71faf9acc 100644 --- a/lib/src/view/account/rating_pref_aware.dart +++ b/lib/src/view/account/rating_pref_aware.dart @@ -9,11 +9,7 @@ class RatingPrefAware extends ConsumerWidget { /// in their settings. /// /// Optionally, a different [orElse] widget can be displayed if ratings are disabled. - const RatingPrefAware({ - required this.child, - this.orElse, - super.key, - }); + const RatingPrefAware({required this.child, this.orElse, super.key}); final Widget child; final Widget? orElse; diff --git a/lib/src/view/analysis/analysis_board.dart b/lib/src/view/analysis/analysis_board.dart index 8f37f0fe54..d454dd8ecc 100644 --- a/lib/src/view/analysis/analysis_board.dart +++ b/lib/src/view/analysis/analysis_board.dart @@ -43,35 +43,28 @@ class AnalysisBoardState extends ConsumerState { final boardPrefs = ref.watch(boardPreferencesProvider); final analysisPrefs = ref.watch(analysisPreferencesProvider); final enableComputerAnalysis = analysisPrefs.enableComputerAnalysis; - final showBestMoveArrow = - enableComputerAnalysis && analysisPrefs.showBestMoveArrow; - final showAnnotationsOnBoard = - enableComputerAnalysis && analysisPrefs.showAnnotations; - final evalBestMoves = enableComputerAnalysis - ? ref.watch( - engineEvaluationProvider.select((s) => s.eval?.bestMoves), - ) - : null; + final showBestMoveArrow = enableComputerAnalysis && analysisPrefs.showBestMoveArrow; + final showAnnotationsOnBoard = enableComputerAnalysis && analysisPrefs.showAnnotations; + final evalBestMoves = + enableComputerAnalysis + ? ref.watch(engineEvaluationProvider.select((s) => s.eval?.bestMoves)) + : null; final currentNode = analysisState.currentNode; - final annotation = - showAnnotationsOnBoard ? makeAnnotation(currentNode.nags) : null; + final annotation = showAnnotationsOnBoard ? makeAnnotation(currentNode.nags) : null; - final bestMoves = enableComputerAnalysis - ? evalBestMoves ?? currentNode.eval?.bestMoves - : null; + final bestMoves = enableComputerAnalysis ? evalBestMoves ?? currentNode.eval?.bestMoves : null; final sanMove = currentNode.sanMove; - final ISet bestMoveShapes = showBestMoveArrow && - analysisState.isEngineAvailable && - bestMoves != null - ? computeBestMoveShapes( - bestMoves, - currentNode.position.turn, - boardPrefs.pieceSet.assets, - ) - : ISet(); + final ISet bestMoveShapes = + showBestMoveArrow && analysisState.isEngineAvailable && bestMoves != null + ? computeBestMoveShapes( + bestMoves, + currentNode.position.turn, + boardPrefs.pieceSet.assets, + ) + : ISet(); return Chessboard( size: widget.boardSize, @@ -79,44 +72,39 @@ class AnalysisBoardState extends ConsumerState { lastMove: analysisState.lastMove as NormalMove?, orientation: analysisState.pov, game: GameData( - playerSide: analysisState.position.isGameOver - ? PlayerSide.none - : analysisState.position.turn == Side.white + playerSide: + analysisState.position.isGameOver + ? PlayerSide.none + : analysisState.position.turn == Side.white ? PlayerSide.white : PlayerSide.black, isCheck: boardPrefs.boardHighlights && analysisState.position.isCheck, sideToMove: analysisState.position.turn, validMoves: analysisState.validMoves, promotionMove: analysisState.promotionMove, - onMove: (move, {isDrop, captured}) => - ref.read(ctrlProvider.notifier).onUserMove( - move, - shouldReplace: widget.shouldReplaceChildOnUserMove, - ), - onPromotionSelection: (role) => - ref.read(ctrlProvider.notifier).onPromotionSelection(role), + onMove: + (move, {isDrop, captured}) => ref + .read(ctrlProvider.notifier) + .onUserMove(move, shouldReplace: widget.shouldReplaceChildOnUserMove), + onPromotionSelection: (role) => ref.read(ctrlProvider.notifier).onPromotionSelection(role), ), shapes: userShapes.union(bestMoveShapes), annotations: showAnnotationsOnBoard && sanMove != null && annotation != null ? altCastles.containsKey(sanMove.move.uci) - ? IMap({ - Move.parse(altCastles[sanMove.move.uci]!)!.to: annotation, - }) + ? IMap({Move.parse(altCastles[sanMove.move.uci]!)!.to: annotation}) : IMap({sanMove.move.to: annotation}) : null, settings: boardPrefs.toBoardSettings().copyWith( - borderRadius: widget.borderRadius, - boxShadow: widget.borderRadius != null - ? boardShadows - : const [], - drawShape: DrawShapeOptions( - enable: widget.enableDrawingShapes, - onCompleteShape: _onCompleteShape, - onClearShapes: _onClearShapes, - newShapeColor: boardPrefs.shapeColor.color, - ), - ), + borderRadius: widget.borderRadius, + boxShadow: widget.borderRadius != null ? boardShadows : const [], + drawShape: DrawShapeOptions( + enable: widget.enableDrawingShapes, + onCompleteShape: _onCompleteShape, + onClearShapes: _onClearShapes, + newShapeColor: boardPrefs.shapeColor.color, + ), + ), ); } diff --git a/lib/src/view/analysis/analysis_layout.dart b/lib/src/view/analysis/analysis_layout.dart index 40b9edf93c..ee43d4a8b3 100644 --- a/lib/src/view/analysis/analysis_layout.dart +++ b/lib/src/view/analysis/analysis_layout.dart @@ -11,16 +11,10 @@ import 'package:lichess_mobile/src/widgets/platform.dart'; /// The height of the board header or footer in the analysis layout. const kAnalysisBoardHeaderOrFooterHeight = 26.0; -typedef BoardBuilder = Widget Function( - BuildContext context, - double boardSize, - BorderRadius? boardRadius, -); +typedef BoardBuilder = + Widget Function(BuildContext context, double boardSize, BorderRadius? boardRadius); -typedef EngineGaugeBuilder = Widget Function( - BuildContext context, - Orientation orientation, -); +typedef EngineGaugeBuilder = Widget Function(BuildContext context, Orientation orientation); enum AnalysisTab { opening(Icons.explore), @@ -45,11 +39,7 @@ enum AnalysisTab { /// Indicator for the analysis tab, typically shown in the app bar. class AppBarAnalysisTabIndicator extends StatefulWidget { - const AppBarAnalysisTabIndicator({ - required this.tabs, - required this.controller, - super.key, - }); + const AppBarAnalysisTabIndicator({required this.tabs, required this.controller, super.key}); final TabController controller; @@ -60,12 +50,10 @@ class AppBarAnalysisTabIndicator extends StatefulWidget { final List tabs; @override - State createState() => - _AppBarAnalysisTabIndicatorState(); + State createState() => _AppBarAnalysisTabIndicatorState(); } -class _AppBarAnalysisTabIndicatorState - extends State { +class _AppBarAnalysisTabIndicatorState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); @@ -102,15 +90,16 @@ class _AppBarAnalysisTabIndicatorState onPressed: () { showAdaptiveActionSheet( context: context, - actions: widget.tabs.map((tab) { - return BottomSheetAction( - leading: Icon(tab.icon), - makeLabel: (context) => Text(tab.l10n(context.l10n)), - onPressed: (_) { - widget.controller.animateTo(widget.tabs.indexOf(tab)); - }, - ); - }).toList(), + actions: + widget.tabs.map((tab) { + return BottomSheetAction( + leading: Icon(tab.icon), + makeLabel: (context) => Text(tab.l10n(context.l10n)), + onPressed: (_) { + widget.controller.animateTo(widget.tabs.indexOf(tab)); + }, + ); + }).toList(), ); }, ); @@ -181,25 +170,23 @@ class AnalysisLayout extends StatelessWidget { bottom: false, child: LayoutBuilder( builder: (context, constraints) { - final orientation = constraints.maxWidth > constraints.maxHeight - ? Orientation.landscape - : Orientation.portrait; + final orientation = + constraints.maxWidth > constraints.maxHeight + ? Orientation.landscape + : Orientation.portrait; final isTablet = isTabletOrLarger(context); - const tabletBoardRadius = - BorderRadius.all(Radius.circular(4.0)); + const tabletBoardRadius = BorderRadius.all(Radius.circular(4.0)); if (orientation == Orientation.landscape) { - final headerAndFooterHeight = (boardHeader != null - ? kAnalysisBoardHeaderOrFooterHeight - : 0.0) + - (boardFooter != null - ? kAnalysisBoardHeaderOrFooterHeight - : 0.0); - final sideWidth = constraints.biggest.longestSide - - constraints.biggest.shortestSide; - final defaultBoardSize = constraints.biggest.shortestSide - - (kTabletBoardTableSidePadding * 2); - final boardSize = (sideWidth >= 250 + final headerAndFooterHeight = + (boardHeader != null ? kAnalysisBoardHeaderOrFooterHeight : 0.0) + + (boardFooter != null ? kAnalysisBoardHeaderOrFooterHeight : 0.0); + final sideWidth = + constraints.biggest.longestSide - constraints.biggest.shortestSide; + final defaultBoardSize = + constraints.biggest.shortestSide - (kTabletBoardTableSidePadding * 2); + final boardSize = + (sideWidth >= 250 ? defaultBoardSize : constraints.biggest.longestSide / kGoldenRatio - (kTabletBoardTableSidePadding * 2)) - @@ -214,15 +201,15 @@ class AnalysisLayout extends StatelessWidget { if (boardHeader != null) Container( decoration: BoxDecoration( - borderRadius: isTablet - ? tabletBoardRadius.copyWith( - bottomLeft: Radius.zero, - bottomRight: Radius.zero, - ) - : null, + borderRadius: + isTablet + ? tabletBoardRadius.copyWith( + bottomLeft: Radius.zero, + bottomRight: Radius.zero, + ) + : null, ), - clipBehavior: - isTablet ? Clip.hardEdge : Clip.none, + clipBehavior: isTablet ? Clip.hardEdge : Clip.none, child: SizedBox( height: kAnalysisBoardHeaderOrFooterHeight, width: boardSize, @@ -232,24 +219,22 @@ class AnalysisLayout extends StatelessWidget { boardBuilder( context, boardSize, - isTablet && - boardHeader == null && - boardFooter != null + isTablet && boardHeader == null && boardFooter != null ? tabletBoardRadius : null, ), if (boardFooter != null) Container( decoration: BoxDecoration( - borderRadius: isTablet - ? tabletBoardRadius.copyWith( - topLeft: Radius.zero, - topRight: Radius.zero, - ) - : null, + borderRadius: + isTablet + ? tabletBoardRadius.copyWith( + topLeft: Radius.zero, + topRight: Radius.zero, + ) + : null, ), - clipBehavior: - isTablet ? Clip.hardEdge : Clip.none, + clipBehavior: isTablet ? Clip.hardEdge : Clip.none, height: kAnalysisBoardHeaderOrFooterHeight, width: boardSize, child: boardFooter, @@ -258,10 +243,7 @@ class AnalysisLayout extends StatelessWidget { ), if (engineGaugeBuilder != null) ...[ const SizedBox(width: 4.0), - engineGaugeBuilder!( - context, - Orientation.landscape, - ), + engineGaugeBuilder!(context, Orientation.landscape), ], const SizedBox(width: 16.0), Expanded( @@ -273,14 +255,9 @@ class AnalysisLayout extends StatelessWidget { Expanded( child: PlatformCard( clipBehavior: Clip.hardEdge, - borderRadius: const BorderRadius.all( - Radius.circular(4.0), - ), + borderRadius: const BorderRadius.all(Radius.circular(4.0)), semanticContainer: false, - child: TabBarView( - controller: tabController, - children: children, - ), + child: TabBarView(controller: tabController, children: children), ), ), ], @@ -291,13 +268,12 @@ class AnalysisLayout extends StatelessWidget { ); } else { final defaultBoardSize = constraints.biggest.shortestSide; - final remainingHeight = - constraints.maxHeight - defaultBoardSize; - final isSmallScreen = - remainingHeight < kSmallRemainingHeightLeftBoardThreshold; - final boardSize = isTablet || isSmallScreen - ? defaultBoardSize - kTabletBoardTableSidePadding * 2 - : defaultBoardSize; + final remainingHeight = constraints.maxHeight - defaultBoardSize; + final isSmallScreen = remainingHeight < kSmallRemainingHeightLeftBoardThreshold; + final boardSize = + isTablet || isSmallScreen + ? defaultBoardSize - kTabletBoardTableSidePadding * 2 + : defaultBoardSize; return Column( mainAxisAlignment: MainAxisAlignment.center, @@ -305,55 +281,49 @@ class AnalysisLayout extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ if (engineGaugeBuilder != null) - engineGaugeBuilder!( - context, - Orientation.portrait, - ), + engineGaugeBuilder!(context, Orientation.portrait), if (engineLines != null) engineLines!, Padding( - padding: isTablet - ? const EdgeInsets.all( - kTabletBoardTableSidePadding, - ) - : EdgeInsets.zero, + padding: + isTablet + ? const EdgeInsets.all(kTabletBoardTableSidePadding) + : EdgeInsets.zero, child: Column( children: [ if (boardHeader != null) Container( decoration: BoxDecoration( - borderRadius: isTablet - ? tabletBoardRadius.copyWith( - bottomLeft: Radius.zero, - bottomRight: Radius.zero, - ) - : null, + borderRadius: + isTablet + ? tabletBoardRadius.copyWith( + bottomLeft: Radius.zero, + bottomRight: Radius.zero, + ) + : null, ), - clipBehavior: - isTablet ? Clip.hardEdge : Clip.none, + clipBehavior: isTablet ? Clip.hardEdge : Clip.none, height: kAnalysisBoardHeaderOrFooterHeight, child: boardHeader, ), boardBuilder( context, boardSize, - isTablet && - boardHeader == null && - boardFooter != null + isTablet && boardHeader == null && boardFooter != null ? tabletBoardRadius : null, ), if (boardFooter != null) Container( decoration: BoxDecoration( - borderRadius: isTablet - ? tabletBoardRadius.copyWith( - topLeft: Radius.zero, - topRight: Radius.zero, - ) - : null, + borderRadius: + isTablet + ? tabletBoardRadius.copyWith( + topLeft: Radius.zero, + topRight: Radius.zero, + ) + : null, ), - clipBehavior: - isTablet ? Clip.hardEdge : Clip.none, + clipBehavior: isTablet ? Clip.hardEdge : Clip.none, height: kAnalysisBoardHeaderOrFooterHeight, child: boardFooter, ), @@ -362,15 +332,13 @@ class AnalysisLayout extends StatelessWidget { ), Expanded( child: Padding( - padding: isTablet - ? const EdgeInsets.symmetric( - horizontal: kTabletBoardTableSidePadding, - ) - : EdgeInsets.zero, - child: TabBarView( - controller: tabController, - children: children, - ), + padding: + isTablet + ? const EdgeInsets.symmetric( + horizontal: kTabletBoardTableSidePadding, + ) + : EdgeInsets.zero, + child: TabBarView(controller: tabController, children: children), ), ), ], diff --git a/lib/src/view/analysis/analysis_screen.dart b/lib/src/view/analysis/analysis_screen.dart index edd2ce3d01..24b7517fd5 100644 --- a/lib/src/view/analysis/analysis_screen.dart +++ b/lib/src/view/analysis/analysis_screen.dart @@ -34,10 +34,7 @@ import 'tree_view.dart'; final _logger = Logger('AnalysisScreen'); class AnalysisScreen extends ConsumerStatefulWidget { - const AnalysisScreen({ - required this.options, - this.enableDrawingShapes = true, - }); + const AnalysisScreen({required this.options, this.enableDrawingShapes = true}); final AnalysisOptions options; @@ -62,11 +59,7 @@ class _AnalysisScreenState extends ConsumerState if (widget.options.gameId != null) AnalysisTab.summary, ]; - _tabController = TabController( - vsync: this, - initialIndex: 1, - length: tabs.length, - ); + _tabController = TabController(vsync: this, initialIndex: 1, length: tabs.length); } @override @@ -84,10 +77,7 @@ class _AnalysisScreenState extends ConsumerState final appBarActions = [ if (prefs.enableComputerAnalysis) EngineDepth(defaultEval: asyncState.valueOrNull?.currentNode.eval), - AppBarAnalysisTabIndicator( - tabs: tabs, - controller: _tabController, - ), + AppBarAnalysisTabIndicator(tabs: tabs, controller: _tabController), AppBarIconButton( onPressed: () { pushPlatformRoute( @@ -105,10 +95,7 @@ class _AnalysisScreenState extends ConsumerState case AsyncData(:final value): return PlatformScaffold( resizeToAvoidBottomInset: false, - appBar: PlatformAppBar( - title: _Title(variant: value.variant), - actions: appBarActions, - ), + appBar: PlatformAppBar(title: _Title(variant: value.variant), actions: appBarActions), body: _Body( options: widget.options, controller: _tabController, @@ -146,10 +133,7 @@ class _Title extends StatelessWidget { return Row( mainAxisSize: MainAxisSize.min, children: [ - if (!excludedIcons.contains(variant)) ...[ - Icon(variant.icon), - const SizedBox(width: 5.0), - ], + if (!excludedIcons.contains(variant)) ...[Icon(variant.icon), const SizedBox(width: 5.0)], Flexible( child: AutoSizeText( context.l10n.analysis, @@ -164,11 +148,7 @@ class _Title extends StatelessWidget { } class _Body extends ConsumerWidget { - const _Body({ - required this.options, - required this.controller, - required this.enableDrawingShapes, - }); + const _Body({required this.options, required this.controller, required this.enableDrawingShapes}); final TabController controller; final AnalysisOptions options; @@ -189,51 +169,49 @@ class _Body extends ConsumerWidget { return AnalysisLayout( tabController: controller, - boardBuilder: (context, boardSize, borderRadius) => AnalysisBoard( - options, - boardSize, - borderRadius: borderRadius, - enableDrawingShapes: enableDrawingShapes, - ), - engineGaugeBuilder: hasEval && showEvaluationGauge - ? (context, orientation) { - return orientation == Orientation.portrait - ? EngineGauge( + boardBuilder: + (context, boardSize, borderRadius) => AnalysisBoard( + options, + boardSize, + borderRadius: borderRadius, + enableDrawingShapes: enableDrawingShapes, + ), + engineGaugeBuilder: + hasEval && showEvaluationGauge + ? (context, orientation) { + return orientation == Orientation.portrait + ? EngineGauge( displayMode: EngineGaugeDisplayMode.horizontal, params: analysisState.engineGaugeParams, ) - : Container( + : Container( clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4.0), - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(4.0)), child: EngineGauge( displayMode: EngineGaugeDisplayMode.vertical, params: analysisState.engineGaugeParams, ), ); - } - : null, - engineLines: isEngineAvailable && numEvalLines > 0 - ? EngineLines( - onTapMove: ref.read(ctrlProvider.notifier).onUserMove, - clientEval: currentNode.eval, - isGameOver: currentNode.position.isGameOver, - ) - : null, + } + : null, + engineLines: + isEngineAvailable && numEvalLines > 0 + ? EngineLines( + onTapMove: ref.read(ctrlProvider.notifier).onUserMove, + clientEval: currentNode.eval, + isGameOver: currentNode.position.isGameOver, + ) + : null, bottomBar: _BottomBar(options: options), children: [ OpeningExplorerView( position: currentNode.position, - opening: kOpeningAllowedVariants.contains(analysisState.variant) - ? analysisState.currentNode.isRoot - ? LightOpening( - eco: '', - name: context.l10n.startPosition, - ) - : analysisState.currentNode.opening ?? - analysisState.currentBranchOpening - : null, + opening: + kOpeningAllowedVariants.contains(analysisState.variant) + ? analysisState.currentNode.isRoot + ? LightOpening(eco: '', name: context.l10n.startPosition) + : analysisState.currentNode.opening ?? analysisState.currentBranchOpening + : null, onMoveSelected: (move) { ref.read(ctrlProvider.notifier).onUserMove(move); }, @@ -270,8 +248,7 @@ class _BottomBar extends ConsumerWidget { icon: CupertinoIcons.arrow_2_squarepath, ), RepeatButton( - onLongPress: - analysisState.canGoBack ? () => _moveBackward(ref) : null, + onLongPress: analysisState.canGoBack ? () => _moveBackward(ref) : null, child: BottomBarButton( key: const ValueKey('goto-previous'), onTap: analysisState.canGoBack ? () => _moveBackward(ref) : null, @@ -306,23 +283,18 @@ class _BottomBar extends ConsumerWidget { BottomSheetAction( makeLabel: (context) => Text(context.l10n.flipBoard), onPressed: (context) { - ref - .read(analysisControllerProvider(options).notifier) - .toggleBoard(); + ref.read(analysisControllerProvider(options).notifier).toggleBoard(); }, ), BottomSheetAction( makeLabel: (context) => Text(context.l10n.boardEditor), onPressed: (context) { - final analysisState = - ref.read(analysisControllerProvider(options)).requireValue; + final analysisState = ref.read(analysisControllerProvider(options)).requireValue; final boardFen = analysisState.position.fen; pushPlatformRoute( context, title: context.l10n.boardEditor, - builder: (_) => BoardEditorScreen( - initialFen: boardFen, - ), + builder: (_) => BoardEditorScreen(initialFen: boardFen), ); }, ), @@ -339,45 +311,34 @@ class _BottomBar extends ConsumerWidget { BottomSheetAction( makeLabel: (context) => Text(context.l10n.mobileSharePositionAsFEN), onPressed: (_) { - final analysisState = - ref.read(analysisControllerProvider(options)).requireValue; - launchShareDialog( - context, - text: analysisState.position.fen, - ); + final analysisState = ref.read(analysisControllerProvider(options)).requireValue; + launchShareDialog(context, text: analysisState.position.fen); }, ), if (options.gameId != null) BottomSheetAction( - makeLabel: (context) => - Text(context.l10n.screenshotCurrentPosition), + makeLabel: (context) => Text(context.l10n.screenshotCurrentPosition), onPressed: (_) async { final gameId = options.gameId!; - final analysisState = - ref.read(analysisControllerProvider(options)).requireValue; + final analysisState = ref.read(analysisControllerProvider(options)).requireValue; try { - final image = - await ref.read(gameShareServiceProvider).screenshotPosition( - analysisState.pov, - analysisState.position.fen, - analysisState.lastMove, - ); + final image = await ref + .read(gameShareServiceProvider) + .screenshotPosition( + analysisState.pov, + analysisState.position.fen, + analysisState.lastMove, + ); if (context.mounted) { launchShareDialog( context, files: [image], - subject: context.l10n.puzzleFromGameLink( - lichessUri('/$gameId').toString(), - ), + subject: context.l10n.puzzleFromGameLink(lichessUri('/$gameId').toString()), ); } } catch (e) { if (context.mounted) { - showPlatformSnackbar( - context, - 'Failed to get GIF', - type: SnackBarType.error, - ); + showPlatformSnackbar(context, 'Failed to get GIF', type: SnackBarType.error); } } }, diff --git a/lib/src/view/analysis/analysis_settings.dart b/lib/src/view/analysis/analysis_settings.dart index 6c893a3539..896ac838be 100644 --- a/lib/src/view/analysis/analysis_settings.dart +++ b/lib/src/view/analysis/analysis_settings.dart @@ -29,9 +29,7 @@ class AnalysisSettings extends ConsumerWidget { switch (asyncState) { case AsyncData(:final value): return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.settingsSettings), - ), + appBar: PlatformAppBar(title: Text(context.l10n.settingsSettings)), body: ListView( children: [ ListSection( @@ -46,9 +44,10 @@ class AnalysisSettings extends ConsumerWidget { ), AnimatedCrossFade( duration: const Duration(milliseconds: 300), - crossFadeState: value.isComputerAnalysisAllowedAndEnabled - ? CrossFadeState.showSecond - : CrossFadeState.showFirst, + crossFadeState: + value.isComputerAnalysisAllowedAndEnabled + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, firstChild: const SizedBox.shrink(), secondChild: ListSection( margin: EdgeInsets.zero, @@ -58,30 +57,38 @@ class AnalysisSettings extends ConsumerWidget { SwitchSettingTile( title: Text(context.l10n.evaluationGauge), value: prefs.showEvaluationGauge, - onChanged: (value) => ref - .read(analysisPreferencesProvider.notifier) - .toggleShowEvaluationGauge(), + onChanged: + (value) => + ref + .read(analysisPreferencesProvider.notifier) + .toggleShowEvaluationGauge(), ), SwitchSettingTile( title: Text(context.l10n.toggleGlyphAnnotations), value: prefs.showAnnotations, - onChanged: (_) => ref - .read(analysisPreferencesProvider.notifier) - .toggleAnnotations(), + onChanged: + (_) => + ref + .read(analysisPreferencesProvider.notifier) + .toggleAnnotations(), ), SwitchSettingTile( title: Text(context.l10n.mobileShowComments), value: prefs.showPgnComments, - onChanged: (_) => ref - .read(analysisPreferencesProvider.notifier) - .togglePgnComments(), + onChanged: + (_) => + ref + .read(analysisPreferencesProvider.notifier) + .togglePgnComments(), ), SwitchSettingTile( title: Text(context.l10n.bestMoveArrow), value: prefs.showBestMoveArrow, - onChanged: (value) => ref - .read(analysisPreferencesProvider.notifier) - .toggleShowBestMoveArrow(), + onChanged: + (value) => + ref + .read(analysisPreferencesProvider.notifier) + .toggleShowBestMoveArrow(), ), ], ), @@ -90,9 +97,10 @@ class AnalysisSettings extends ConsumerWidget { ), AnimatedCrossFade( duration: const Duration(milliseconds: 300), - crossFadeState: value.isComputerAnalysisAllowedAndEnabled - ? CrossFadeState.showSecond - : CrossFadeState.showFirst, + crossFadeState: + value.isComputerAnalysisAllowedAndEnabled + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, firstChild: const SizedBox.shrink(), secondChild: StockfishSettingsWidget( onToggleLocalEvaluation: () { @@ -113,21 +121,20 @@ class AnalysisSettings extends ConsumerWidget { children: [ PlatformListTile( title: Text(context.l10n.openingExplorer), - onTap: () => showAdaptiveBottomSheet( - context: context, - isScrollControlled: true, - showDragHandle: true, - isDismissible: true, - builder: (_) => const OpeningExplorerSettings(), - ), + onTap: + () => showAdaptiveBottomSheet( + context: context, + isScrollControlled: true, + showDragHandle: true, + isDismissible: true, + builder: (_) => const OpeningExplorerSettings(), + ), ), SwitchSettingTile( title: Text(context.l10n.sound), value: isSoundEnabled, onChanged: (value) { - ref - .read(generalPreferencesProvider.notifier) - .toggleSoundEnabled(); + ref.read(generalPreferencesProvider.notifier).toggleSoundEnabled(); }, ), ], diff --git a/lib/src/view/analysis/analysis_share_screen.dart b/lib/src/view/analysis/analysis_share_screen.dart index 63552ffe2e..24ed1b3170 100644 --- a/lib/src/view/analysis/analysis_share_screen.dart +++ b/lib/src/view/analysis/analysis_share_screen.dart @@ -24,20 +24,13 @@ class AnalysisShareScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.studyShareAndExport), - ), + appBar: PlatformAppBar(title: Text(context.l10n.studyShareAndExport)), body: _EditPgnTagsForm(options), ); } } -const Set _ratingHeaders = { - 'WhiteElo', - 'BlackElo', - 'WhiteRatingDiff', - 'BlackRatingDiff', -}; +const Set _ratingHeaders = {'WhiteElo', 'BlackElo', 'WhiteRatingDiff', 'BlackRatingDiff'}; class _EditPgnTagsForm extends ConsumerStatefulWidget { const _EditPgnTagsForm(this.options); @@ -63,10 +56,7 @@ class _EditPgnTagsFormState extends ConsumerState<_EditPgnTagsForm> { _focusNodes[entry.key] = FocusNode(); _focusNodes[entry.key]!.addListener(() { if (!_focusNodes[entry.key]!.hasFocus) { - ref.read(ctrlProvider.notifier).updatePgnHeader( - entry.key, - _controllers[entry.key]!.text, - ); + ref.read(ctrlProvider.notifier).updatePgnHeader(entry.key, _controllers[entry.key]!.text); } }); } @@ -86,8 +76,7 @@ class _EditPgnTagsFormState extends ConsumerState<_EditPgnTagsForm> { @override Widget build(BuildContext context) { final ctrlProvider = analysisControllerProvider(widget.options); - final pgnHeaders = - ref.watch(ctrlProvider.select((c) => c.requireValue.pgnHeaders)); + final pgnHeaders = ref.watch(ctrlProvider.select((c) => c.requireValue.pgnHeaders)); final showRatingAsync = ref.watch(showRatingsPrefProvider); void focusAndSelectNextField(int index, IMap pgnHeaders) { @@ -127,46 +116,44 @@ class _EditPgnTagsFormState extends ConsumerState<_EditPgnTagsForm> { child: ListView( children: [ Column( - children: pgnHeaders.entries - .where( - (e) => showRatings || !_ratingHeaders.contains(e.key), - ) - .mapIndexed((index, e) { - return _EditablePgnField( - entry: e, - controller: _controllers[e.key]!, - focusNode: _focusNodes[e.key]!, - onTap: () { - _controllers[e.key]!.selection = TextSelection( - baseOffset: 0, - extentOffset: _controllers[e.key]!.text.length, - ); - if (e.key == 'Result') { - _showResultChoicePicker( - e, - context: context, - onEntryChanged: () { - focusAndSelectNextField(index, pgnHeaders); - }, - ); - } else if (e.key == 'Date') { - _showDatePicker( - e, - context: context, - onEntryChanged: () { - focusAndSelectNextField(index, pgnHeaders); - }, - ); - } - }, - onSubmitted: (value) { - ref - .read(ctrlProvider.notifier) - .updatePgnHeader(e.key, value); - focusAndSelectNextField(index, pgnHeaders); - }, - ); - }).toList(), + children: + pgnHeaders.entries + .where((e) => showRatings || !_ratingHeaders.contains(e.key)) + .mapIndexed((index, e) { + return _EditablePgnField( + entry: e, + controller: _controllers[e.key]!, + focusNode: _focusNodes[e.key]!, + onTap: () { + _controllers[e.key]!.selection = TextSelection( + baseOffset: 0, + extentOffset: _controllers[e.key]!.text.length, + ); + if (e.key == 'Result') { + _showResultChoicePicker( + e, + context: context, + onEntryChanged: () { + focusAndSelectNextField(index, pgnHeaders); + }, + ); + } else if (e.key == 'Date') { + _showDatePicker( + e, + context: context, + onEntryChanged: () { + focusAndSelectNextField(index, pgnHeaders); + }, + ); + } + }, + onSubmitted: (value) { + ref.read(ctrlProvider.notifier).updatePgnHeader(e.key, value); + focusAndSelectNextField(index, pgnHeaders); + }, + ); + }) + .toList(), ), Padding( padding: const EdgeInsets.all(24.0), @@ -177,13 +164,10 @@ class _EditPgnTagsFormState extends ConsumerState<_EditPgnTagsForm> { onPressed: () { launchShareDialog( context, - text: ref - .read( - analysisControllerProvider( - widget.options, - ).notifier, - ) - .makeExportPgn(), + text: + ref + .read(analysisControllerProvider(widget.options).notifier) + .makeExportPgn(), ); }, child: Text(context.l10n.mobileShareGamePGN), @@ -209,51 +193,47 @@ class _EditPgnTagsFormState extends ConsumerState<_EditPgnTagsForm> { if (Theme.of(context).platform == TargetPlatform.iOS) { return showCupertinoModalPopup( context: context, - builder: (BuildContext context) => Container( - height: 216, - padding: const EdgeInsets.only(top: 6.0), - margin: EdgeInsets.only( - bottom: MediaQuery.viewInsetsOf(context).bottom, - ), - color: CupertinoColors.systemBackground.resolveFrom(context), - child: SafeArea( - top: false, - child: CupertinoDatePicker( - mode: CupertinoDatePickerMode.date, - initialDateTime: entry.value.isNotEmpty - ? _dateFormatter.parse(entry.value) - : DateTime.now(), - onDateTimeChanged: (DateTime newDateTime) { - final newDate = _dateFormatter.format(newDateTime); - ref.read(ctrlProvider.notifier).updatePgnHeader( - entry.key, - newDate, - ); - _controllers[entry.key]!.text = newDate; - }, + builder: + (BuildContext context) => Container( + height: 216, + padding: const EdgeInsets.only(top: 6.0), + margin: EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(context).bottom), + color: CupertinoColors.systemBackground.resolveFrom(context), + child: SafeArea( + top: false, + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.date, + initialDateTime: + entry.value.isNotEmpty ? _dateFormatter.parse(entry.value) : DateTime.now(), + onDateTimeChanged: (DateTime newDateTime) { + final newDate = _dateFormatter.format(newDateTime); + ref.read(ctrlProvider.notifier).updatePgnHeader(entry.key, newDate); + _controllers[entry.key]!.text = newDate; + }, + ), + ), ), - ), - ), ).then((_) { onEntryChanged(); }); } else { return showDatePicker( - context: context, - initialDate: entry.value.isNotEmpty - ? _dateFormatter.parse(entry.value) - : DateTime.now(), - firstDate: DateTime(1900), - lastDate: DateTime(2100), - ).then((date) { - if (date != null) { - final formatted = _dateFormatter.format(date); - ref.read(ctrlProvider.notifier).updatePgnHeader(entry.key, formatted); - _controllers[entry.key]!.text = formatted; - } - }).then((_) { - onEntryChanged(); - }); + context: context, + initialDate: + entry.value.isNotEmpty ? _dateFormatter.parse(entry.value) : DateTime.now(), + firstDate: DateTime(1900), + lastDate: DateTime(2100), + ) + .then((date) { + if (date != null) { + final formatted = _dateFormatter.format(date); + ref.read(ctrlProvider.notifier).updatePgnHeader(entry.key, formatted); + _controllers[entry.key]!.text = formatted; + } + }) + .then((_) { + onEntryChanged(); + }); } } @@ -269,13 +249,8 @@ class _EditPgnTagsFormState extends ConsumerState<_EditPgnTagsForm> { labelBuilder: (choice) => Text(choice), onSelectedItemChanged: (choice) { ref - .read( - analysisControllerProvider(widget.options).notifier, - ) - .updatePgnHeader( - entry.key, - choice, - ); + .read(analysisControllerProvider(widget.options).notifier) + .updatePgnHeader(entry.key, choice); _controllers[entry.key]!.text = choice; }, ).then((_) { @@ -302,18 +277,14 @@ class _EditablePgnField extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: - Styles.horizontalBodyPadding.add(const EdgeInsets.only(bottom: 8.0)), + padding: Styles.horizontalBodyPadding.add(const EdgeInsets.only(bottom: 8.0)), child: Row( children: [ SizedBox( width: 110, child: Text( entry.key, - style: const TextStyle( - fontWeight: FontWeight.bold, - overflow: TextOverflow.ellipsis, - ), + style: const TextStyle(fontWeight: FontWeight.bold, overflow: TextOverflow.ellipsis), ), ), const SizedBox(width: 8), @@ -322,17 +293,15 @@ class _EditablePgnField extends StatelessWidget { focusNode: focusNode, cupertinoDecoration: BoxDecoration( color: CupertinoColors.tertiarySystemBackground, - border: Border.all( - color: CupertinoColors.systemGrey4, - width: 1, - ), + border: Border.all(color: CupertinoColors.systemGrey4, width: 1), borderRadius: BorderRadius.circular(8), ), controller: controller, textInputAction: TextInputAction.next, - keyboardType: entry.key == 'WhiteElo' || entry.key == 'BlackElo' - ? TextInputType.number - : TextInputType.text, + keyboardType: + entry.key == 'WhiteElo' || entry.key == 'BlackElo' + ? TextInputType.number + : TextInputType.text, onTap: onTap, onSubmitted: onSubmitted, ), diff --git a/lib/src/view/analysis/server_analysis.dart b/lib/src/view/analysis/server_analysis.dart index bf9ee77493..729ee8aa8f 100644 --- a/lib/src/view/analysis/server_analysis.dart +++ b/lib/src/view/analysis/server_analysis.dart @@ -31,8 +31,7 @@ class ServerAnalysisSummary extends ConsumerWidget { final canShowGameSummary = ref.watch( ctrlProvider.select((value) => value.requireValue.canShowGameSummary), ); - final pgnHeaders = ref - .watch(ctrlProvider.select((value) => value.requireValue.pgnHeaders)); + final pgnHeaders = ref.watch(ctrlProvider.select((value) => value.requireValue.pgnHeaders)); final currentGameAnalysis = ref.watch(currentAnalysisProvider); if (analysisPrefs.enableComputerAnalysis == false || !canShowGameSummary) { @@ -61,179 +60,138 @@ class ServerAnalysisSummary extends ConsumerWidget { return playersAnalysis != null ? ListView( - children: [ - if (currentGameAnalysis == options.gameId) - const Padding( - padding: EdgeInsets.only(top: 16.0), - child: WaitingForServerAnalysis(), - ), - AcplChart(options), - Center( - child: SizedBox( - width: math.min(MediaQuery.sizeOf(context).width, 500), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Table( - defaultVerticalAlignment: - TableCellVerticalAlignment.middle, - columnWidths: const { - 0: FlexColumnWidth(1), - 1: FlexColumnWidth(1), - 2: FlexColumnWidth(1), - }, - children: [ - TableRow( - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide(color: Colors.grey), + children: [ + if (currentGameAnalysis == options.gameId) + const Padding(padding: EdgeInsets.only(top: 16.0), child: WaitingForServerAnalysis()), + AcplChart(options), + Center( + child: SizedBox( + width: math.min(MediaQuery.sizeOf(context).width, 500), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Table( + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + columnWidths: const { + 0: FlexColumnWidth(1), + 1: FlexColumnWidth(1), + 2: FlexColumnWidth(1), + }, + children: [ + TableRow( + decoration: const BoxDecoration( + border: Border(bottom: BorderSide(color: Colors.grey)), + ), + children: [ + _SummaryPlayerName(Side.white, pgnHeaders), + Center( + child: Text( + pgnHeaders.get('Result') ?? '', + style: const TextStyle(fontWeight: FontWeight.bold), ), ), + _SummaryPlayerName(Side.black, pgnHeaders), + ], + ), + if (playersAnalysis.white.accuracy != null && + playersAnalysis.black.accuracy != null) + TableRow( children: [ - _SummaryPlayerName(Side.white, pgnHeaders), + _SummaryNumber('${playersAnalysis.white.accuracy}%'), Center( + heightFactor: 1.8, + child: Text(context.l10n.accuracy, softWrap: true), + ), + _SummaryNumber('${playersAnalysis.black.accuracy}%'), + ], + ), + for (final item in [ + ( + playersAnalysis.white.inaccuracies.toString(), + context.l10n.nbInaccuracies(2).replaceAll('2', '').trim().capitalize(), + playersAnalysis.black.inaccuracies.toString(), + ), + ( + playersAnalysis.white.mistakes.toString(), + context.l10n.nbMistakes(2).replaceAll('2', '').trim().capitalize(), + playersAnalysis.black.mistakes.toString(), + ), + ( + playersAnalysis.white.blunders.toString(), + context.l10n.nbBlunders(2).replaceAll('2', '').trim().capitalize(), + playersAnalysis.black.blunders.toString(), + ), + ]) + TableRow( + children: [ + _SummaryNumber(item.$1), + Center(heightFactor: 1.2, child: Text(item.$2, softWrap: true)), + _SummaryNumber(item.$3), + ], + ), + if (playersAnalysis.white.acpl != null && playersAnalysis.black.acpl != null) + TableRow( + children: [ + _SummaryNumber(playersAnalysis.white.acpl.toString()), + Center( + heightFactor: 1.5, child: Text( - pgnHeaders.get('Result') ?? '', - style: const TextStyle( - fontWeight: FontWeight.bold, - ), + context.l10n.averageCentipawnLoss, + softWrap: true, + textAlign: TextAlign.center, ), ), - _SummaryPlayerName(Side.black, pgnHeaders), + _SummaryNumber(playersAnalysis.black.acpl.toString()), ], ), - if (playersAnalysis.white.accuracy != null && - playersAnalysis.black.accuracy != null) - TableRow( - children: [ - _SummaryNumber( - '${playersAnalysis.white.accuracy}%', - ), - Center( - heightFactor: 1.8, - child: Text( - context.l10n.accuracy, - softWrap: true, - ), - ), - _SummaryNumber( - '${playersAnalysis.black.accuracy}%', - ), - ], - ), - for (final item in [ - ( - playersAnalysis.white.inaccuracies.toString(), - context.l10n - .nbInaccuracies(2) - .replaceAll('2', '') - .trim() - .capitalize(), - playersAnalysis.black.inaccuracies.toString() - ), - ( - playersAnalysis.white.mistakes.toString(), - context.l10n - .nbMistakes(2) - .replaceAll('2', '') - .trim() - .capitalize(), - playersAnalysis.black.mistakes.toString() - ), - ( - playersAnalysis.white.blunders.toString(), - context.l10n - .nbBlunders(2) - .replaceAll('2', '') - .trim() - .capitalize(), - playersAnalysis.black.blunders.toString() - ), - ]) - TableRow( - children: [ - _SummaryNumber(item.$1), - Center( - heightFactor: 1.2, - child: Text( - item.$2, - softWrap: true, - ), - ), - _SummaryNumber(item.$3), - ], - ), - if (playersAnalysis.white.acpl != null && - playersAnalysis.black.acpl != null) - TableRow( - children: [ - _SummaryNumber( - playersAnalysis.white.acpl.toString(), - ), - Center( - heightFactor: 1.5, - child: Text( - context.l10n.averageCentipawnLoss, - softWrap: true, - textAlign: TextAlign.center, - ), - ), - _SummaryNumber( - playersAnalysis.black.acpl.toString(), - ), - ], - ), - ], - ), + ], ), ), ), - ], - ) + ), + ], + ) : Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Spacer(), - if (currentGameAnalysis == options.gameId) - const Center( - child: Padding( - padding: EdgeInsets.symmetric(vertical: 16.0), - child: WaitingForServerAnalysis(), - ), - ) - else - Center( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: Builder( - builder: (context) { - Future? pendingRequest; - return StatefulBuilder( - builder: (context, setState) { - return FutureBuilder( - future: pendingRequest, - builder: (context, snapshot) { - return SecondaryButton( - semanticsLabel: - context.l10n.requestAComputerAnalysis, - onPressed: ref.watch(authSessionProvider) == - null - ? () { + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Spacer(), + if (currentGameAnalysis == options.gameId) + const Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 16.0), + child: WaitingForServerAnalysis(), + ), + ) + else + Center( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Builder( + builder: (context) { + Future? pendingRequest; + return StatefulBuilder( + builder: (context, setState) { + return FutureBuilder( + future: pendingRequest, + builder: (context, snapshot) { + return SecondaryButton( + semanticsLabel: context.l10n.requestAComputerAnalysis, + onPressed: + ref.watch(authSessionProvider) == null + ? () { showPlatformSnackbar( context, - context - .l10n.youNeedAnAccountToDoThat, + context.l10n.youNeedAnAccountToDoThat, ); } - : snapshot.connectionState == - ConnectionState.waiting - ? null - : () { - setState(() { - pendingRequest = ref - .read(ctrlProvider.notifier) - .requestServerAnalysis() - .catchError((Object e) { + : snapshot.connectionState == ConnectionState.waiting + ? null + : () { + setState(() { + pendingRequest = ref + .read(ctrlProvider.notifier) + .requestServerAnalysis() + .catchError((Object e) { if (context.mounted) { showPlatformSnackbar( context, @@ -242,23 +200,21 @@ class ServerAnalysisSummary extends ConsumerWidget { ); } }); - }); - }, - child: Text( - context.l10n.requestAComputerAnalysis, - ), - ); - }, - ); - }, - ); - }, - ), + }); + }, + child: Text(context.l10n.requestAComputerAnalysis), + ); + }, + ); + }, + ); + }, ), ), - const Spacer(), - ], - ); + ), + const Spacer(), + ], + ); } } @@ -271,11 +227,7 @@ class WaitingForServerAnalysis extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ - Image.asset( - 'assets/images/stockfish/icon.png', - width: 30, - height: 30, - ), + Image.asset('assets/images/stockfish/icon.png', width: 30, height: 30), const SizedBox(width: 8.0), Text(context.l10n.waitingForAnalysis), const SizedBox(width: 8.0), @@ -291,12 +243,7 @@ class _SummaryNumber extends StatelessWidget { @override Widget build(BuildContext context) { - return Center( - child: Text( - data, - softWrap: true, - ), - ); + return Center(child: Text(data, softWrap: true)); } } @@ -307,12 +254,12 @@ class _SummaryPlayerName extends StatelessWidget { @override Widget build(BuildContext context) { - final playerTitle = side == Side.white - ? pgnHeaders.get('WhiteTitle') - : pgnHeaders.get('BlackTitle'); - final playerName = side == Side.white - ? pgnHeaders.get('White') ?? context.l10n.white - : pgnHeaders.get('Black') ?? context.l10n.black; + final playerTitle = + side == Side.white ? pgnHeaders.get('WhiteTitle') : pgnHeaders.get('BlackTitle'); + final playerName = + side == Side.white + ? pgnHeaders.get('White') ?? context.l10n.white + : pgnHeaders.get('Black') ?? context.l10n.black; final brightness = Theme.of(context).brightness; @@ -329,15 +276,13 @@ class _SummaryPlayerName extends StatelessWidget { ? CupertinoIcons.circle : CupertinoIcons.circle_filled : brightness == Brightness.light - ? CupertinoIcons.circle_filled - : CupertinoIcons.circle, + ? CupertinoIcons.circle_filled + : CupertinoIcons.circle, size: 14, ), Text( '${playerTitle != null ? '$playerTitle ' : ''}$playerName', - style: const TextStyle( - fontWeight: FontWeight.bold, - ), + style: const TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, softWrap: true, ), @@ -366,25 +311,21 @@ class AcplChart extends ConsumerWidget { final aboveLineColor = brightness == Brightness.light ? black : white; VerticalLine phaseVerticalBar(double x, String label) => VerticalLine( - x: x, - color: const Color(0xFF707070), - strokeWidth: 0.5, - label: VerticalLineLabel( - style: TextStyle( - fontSize: 10, - color: Theme.of(context) - .textTheme - .labelMedium - ?.color - ?.withValues(alpha: 0.3), - ), - labelResolver: (line) => label, - padding: const EdgeInsets.only(right: 1), - alignment: Alignment.topRight, - direction: LabelDirection.vertical, - show: true, - ), - ); + x: x, + color: const Color(0xFF707070), + strokeWidth: 0.5, + label: VerticalLineLabel( + style: TextStyle( + fontSize: 10, + color: Theme.of(context).textTheme.labelMedium?.color?.withValues(alpha: 0.3), + ), + labelResolver: (line) => label, + padding: const EdgeInsets.only(right: 1), + alignment: Alignment.topRight, + direction: LabelDirection.vertical, + show: true, + ), + ); final state = ref.watch(analysisControllerProvider(options)).requireValue; final data = state.acplChartData; @@ -397,9 +338,7 @@ class AcplChart extends ConsumerWidget { } final spots = data - .mapIndexed( - (i, e) => FlSpot(i.toDouble(), e.winningChances(Side.white)), - ) + .mapIndexed((i, e) => FlSpot(i.toDouble(), e.winningChances(Side.white))) .toList(growable: false); final divisionLines = []; @@ -408,10 +347,7 @@ class AcplChart extends ConsumerWidget { if (state.division!.middlegame! > 0) { divisionLines.add(phaseVerticalBar(0.0, context.l10n.opening)); divisionLines.add( - phaseVerticalBar( - state.division!.middlegame! - 1, - context.l10n.middlegame, - ), + phaseVerticalBar(state.division!.middlegame! - 1, context.l10n.middlegame), ); } else { divisionLines.add(phaseVerticalBar(0.0, context.l10n.middlegame)); @@ -420,19 +356,9 @@ class AcplChart extends ConsumerWidget { if (state.division?.endgame != null) { if (state.division!.endgame! > 0) { - divisionLines.add( - phaseVerticalBar( - state.division!.endgame! - 1, - context.l10n.endgame, - ), - ); + divisionLines.add(phaseVerticalBar(state.division!.endgame! - 1, context.l10n.endgame)); } else { - divisionLines.add( - phaseVerticalBar( - 0.0, - context.l10n.endgame, - ), - ); + divisionLines.add(phaseVerticalBar(0.0, context.l10n.endgame)); } } return Center( @@ -444,23 +370,19 @@ class AcplChart extends ConsumerWidget { LineChartData( lineTouchData: LineTouchData( enabled: false, - touchCallback: - (FlTouchEvent event, LineTouchResponse? touchResponse) { + touchCallback: (FlTouchEvent event, LineTouchResponse? touchResponse) { if (event is FlTapDownEvent || event is FlPanUpdateEvent || event is FlLongPressMoveUpdate) { final touchX = event.localPosition!.dx; - final chartWidth = context.size!.width - - 32; // Insets on both sides of the chart of 16 + final chartWidth = + context.size!.width - 32; // Insets on both sides of the chart of 16 final minX = spots.first.x; final maxX = spots.last.x; - final touchXDataValue = - minX + (touchX / chartWidth) * (maxX - minX); + final touchXDataValue = minX + (touchX / chartWidth) * (maxX - minX); final closestSpot = spots.reduce( - (a, b) => (a.x - touchXDataValue).abs() < - (b.x - touchXDataValue).abs() - ? a - : b, + (a, b) => + (a.x - touchXDataValue).abs() < (b.x - touchXDataValue).abs() ? a : b, ); final closestNodeIndex = closestSpot.x.round(); ref @@ -477,19 +399,9 @@ class AcplChart extends ConsumerWidget { isCurved: false, barWidth: 1, color: mainLineColor.withValues(alpha: 0.7), - aboveBarData: BarAreaData( - show: true, - color: aboveLineColor, - applyCutOffY: true, - ), - belowBarData: BarAreaData( - show: true, - color: belowLineColor, - applyCutOffY: true, - ), - dotData: const FlDotData( - show: false, - ), + aboveBarData: BarAreaData(show: true, color: aboveLineColor, applyCutOffY: true), + belowBarData: BarAreaData(show: true, color: belowLineColor, applyCutOffY: true), + dotData: const FlDotData(show: false), ), ], extraLinesData: ExtraLinesData( diff --git a/lib/src/view/analysis/stockfish_settings.dart b/lib/src/view/analysis/stockfish_settings.dart index a76795dad9..7448e430a6 100644 --- a/lib/src/view/analysis/stockfish_settings.dart +++ b/lib/src/view/analysis/stockfish_settings.dart @@ -39,18 +39,14 @@ class StockfishSettingsWidget extends ConsumerWidget { title: Text.rich( TextSpan( text: 'Search time: ', - style: const TextStyle( - fontWeight: FontWeight.normal, - ), + style: const TextStyle(fontWeight: FontWeight.normal), children: [ TextSpan( - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - text: prefs.engineSearchTime.inSeconds == 3600 - ? '∞' - : '${prefs.engineSearchTime.inSeconds}s', + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), + text: + prefs.engineSearchTime.inSeconds == 3600 + ? '∞' + : '${prefs.engineSearchTime.inSeconds}s', ), ], ), @@ -58,25 +54,18 @@ class StockfishSettingsWidget extends ConsumerWidget { subtitle: NonLinearSlider( labelBuilder: (value) => value == 3600 ? '∞' : '${value}s', value: prefs.engineSearchTime.inSeconds, - values: - kAvailableEngineSearchTimes.map((e) => e.inSeconds).toList(), - onChangeEnd: (value) => - onSetEngineSearchTime(Duration(seconds: value.toInt())), + values: kAvailableEngineSearchTimes.map((e) => e.inSeconds).toList(), + onChangeEnd: (value) => onSetEngineSearchTime(Duration(seconds: value.toInt())), ), ), PlatformListTile( title: Text.rich( TextSpan( text: '${context.l10n.multipleLines}: ', - style: const TextStyle( - fontWeight: FontWeight.normal, - ), + style: const TextStyle(fontWeight: FontWeight.normal), children: [ TextSpan( - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), text: prefs.numEvalLines.toString(), ), ], @@ -93,15 +82,10 @@ class StockfishSettingsWidget extends ConsumerWidget { title: Text.rich( TextSpan( text: '${context.l10n.cpus}: ', - style: const TextStyle( - fontWeight: FontWeight.normal, - ), + style: const TextStyle(fontWeight: FontWeight.normal), children: [ TextSpan( - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), text: prefs.numEngineCores.toString(), ), ], @@ -109,10 +93,7 @@ class StockfishSettingsWidget extends ConsumerWidget { ), subtitle: NonLinearSlider( value: prefs.numEngineCores, - values: List.generate( - maxEngineCores, - (index) => index + 1, - ), + values: List.generate(maxEngineCores, (index) => index + 1), onChangeEnd: (value) => onSetEngineCores(value.toInt()), ), ), diff --git a/lib/src/view/analysis/tree_view.dart b/lib/src/view/analysis/tree_view.dart index 7087ecba0d..983cff10dd 100644 --- a/lib/src/view/analysis/tree_view.dart +++ b/lib/src/view/analysis/tree_view.dart @@ -15,17 +15,14 @@ class AnalysisTreeView extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final ctrlProvider = analysisControllerProvider(options); - final root = - ref.watch(ctrlProvider.select((value) => value.requireValue.root)); - final currentPath = ref - .watch(ctrlProvider.select((value) => value.requireValue.currentPath)); + final root = ref.watch(ctrlProvider.select((value) => value.requireValue.root)); + final currentPath = ref.watch(ctrlProvider.select((value) => value.requireValue.currentPath)); final pgnRootComments = ref.watch( ctrlProvider.select((value) => value.requireValue.pgnRootComments), ); final prefs = ref.watch(analysisPreferencesProvider); // enable computer analysis takes effect here only if it's a lichess game - final enableComputerAnalysis = - !options.isLichessGameAnalysis || prefs.enableComputerAnalysis; + final enableComputerAnalysis = !options.isLichessGameAnalysis || prefs.enableComputerAnalysis; return ListView( padding: EdgeInsets.zero, @@ -37,8 +34,7 @@ class AnalysisTreeView extends ConsumerWidget { notifier: ref.read(ctrlProvider.notifier), shouldShowComputerVariations: enableComputerAnalysis, shouldShowComments: enableComputerAnalysis && prefs.showPgnComments, - shouldShowAnnotations: - enableComputerAnalysis && prefs.showAnnotations, + shouldShowAnnotations: enableComputerAnalysis && prefs.showAnnotations, ), ], ); diff --git a/lib/src/view/board_editor/board_editor_menu.dart b/lib/src/view/board_editor/board_editor_menu.dart index 6f35d17525..a8e60ec4cc 100644 --- a/lib/src/view/board_editor/board_editor_menu.dart +++ b/lib/src/view/board_editor/board_editor_menu.dart @@ -23,21 +23,20 @@ class BoardEditorMenu extends ConsumerWidget { padding: Styles.horizontalBodyPadding, child: Wrap( spacing: 8.0, - children: Side.values.map((side) { - return ChoiceChip( - label: Text( - side == Side.white - ? context.l10n.whitePlays - : context.l10n.blackPlays, - ), - selected: editorState.sideToPlay == side, - onSelected: (selected) { - if (selected) { - ref.read(editorController.notifier).setSideToPlay(side); - } - }, - ); - }).toList(), + children: + Side.values.map((side) { + return ChoiceChip( + label: Text( + side == Side.white ? context.l10n.whitePlays : context.l10n.blackPlays, + ), + selected: editorState.sideToPlay == side, + onSelected: (selected) { + if (selected) { + ref.read(editorController.notifier).setSideToPlay(side); + } + }, + ); + }).toList(), ), ), Padding( @@ -53,25 +52,17 @@ class BoardEditorMenu extends ConsumerWidget { SizedBox( width: 100.0, child: Text( - side == Side.white - ? context.l10n.white - : context.l10n.black, + side == Side.white ? context.l10n.white : context.l10n.black, maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ...[CastlingSide.king, CastlingSide.queen].map((castlingSide) { return ChoiceChip( - label: Text( - castlingSide == CastlingSide.king ? 'O-O' : 'O-O-O', - ), + label: Text(castlingSide == CastlingSide.king ? 'O-O' : 'O-O-O'), selected: editorState.isCastlingAllowed(side, castlingSide), onSelected: (selected) { - ref.read(editorController.notifier).setCastling( - side, - castlingSide, - selected, - ); + ref.read(editorController.notifier).setCastling(side, castlingSide, selected); }, ); }), @@ -86,17 +77,16 @@ class BoardEditorMenu extends ConsumerWidget { ), Wrap( spacing: 8.0, - children: editorState.enPassantOptions.squares.map((square) { - return ChoiceChip( - label: Text(square.name), - selected: editorState.enPassantSquare == square, - onSelected: (selected) { - ref - .read(editorController.notifier) - .toggleEnPassantSquare(square); - }, - ); - }).toList(), + children: + editorState.enPassantOptions.squares.map((square) { + return ChoiceChip( + label: Text(square.name), + selected: editorState.enPassantSquare == square, + onSelected: (selected) { + ref.read(editorController.notifier).toggleEnPassantSquare(square); + }, + ); + }).toList(), ), ], ], diff --git a/lib/src/view/board_editor/board_editor_screen.dart b/lib/src/view/board_editor/board_editor_screen.dart index e5a66bd187..2a06aae43c 100644 --- a/lib/src/view/board_editor/board_editor_screen.dart +++ b/lib/src/view/board_editor/board_editor_screen.dart @@ -28,9 +28,7 @@ class BoardEditorScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.boardEditor), - ), + appBar: PlatformAppBar(title: Text(context.l10n.boardEditor)), body: _Body(initialFen), ); } @@ -43,8 +41,7 @@ class _Body extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final boardEditorState = - ref.watch(boardEditorControllerProvider(initialFen)); + final boardEditorState = ref.watch(boardEditorControllerProvider(initialFen)); return Column( children: [ @@ -57,16 +54,14 @@ class _Body extends ConsumerWidget { final defaultBoardSize = constraints.biggest.shortestSide; final isTablet = isTabletOrLarger(context); - final remainingHeight = - constraints.maxHeight - defaultBoardSize; - final isSmallScreen = - remainingHeight < kSmallRemainingHeightLeftBoardThreshold; - final boardSize = isTablet || isSmallScreen - ? defaultBoardSize - kTabletBoardTableSidePadding * 2 - : defaultBoardSize; + final remainingHeight = constraints.maxHeight - defaultBoardSize; + final isSmallScreen = remainingHeight < kSmallRemainingHeightLeftBoardThreshold; + final boardSize = + isTablet || isSmallScreen + ? defaultBoardSize - kTabletBoardTableSidePadding * 2 + : defaultBoardSize; - final direction = - aspectRatio > 1 ? Axis.horizontal : Axis.vertical; + final direction = aspectRatio > 1 ? Axis.horizontal : Axis.vertical; return Flex( direction: direction, @@ -133,21 +128,20 @@ class _BoardEditor extends ConsumerWidget { pieces: pieces, orientation: orientation, settings: boardPrefs.toBoardSettings().copyWith( - borderRadius: isTablet - ? const BorderRadius.all(Radius.circular(4.0)) - : BorderRadius.zero, - boxShadow: isTablet ? boardShadows : const [], - ), + borderRadius: isTablet ? const BorderRadius.all(Radius.circular(4.0)) : BorderRadius.zero, + boxShadow: isTablet ? boardShadows : const [], + ), pointerMode: editorState.editorPointerMode, - onDiscardedPiece: (Square square) => ref - .read(boardEditorControllerProvider(initialFen).notifier) - .discardPiece(square), - onDroppedPiece: (Square? origin, Square dest, Piece piece) => ref - .read(boardEditorControllerProvider(initialFen).notifier) - .movePiece(origin, dest, piece), - onEditedSquare: (Square square) => ref - .read(boardEditorControllerProvider(initialFen).notifier) - .editSquare(square), + onDiscardedPiece: + (Square square) => + ref.read(boardEditorControllerProvider(initialFen).notifier).discardPiece(square), + onDroppedPiece: + (Square? origin, Square dest, Piece piece) => ref + .read(boardEditorControllerProvider(initialFen).notifier) + .movePiece(origin, dest, piece), + onEditedSquare: + (Square square) => + ref.read(boardEditorControllerProvider(initialFen).notifier).editSquare(square), ); } } @@ -187,9 +181,8 @@ class _PieceMenuState extends ConsumerState<_PieceMenu> { return Container( clipBehavior: Clip.hardEdge, decoration: BoxDecoration( - borderRadius: widget.isTablet - ? const BorderRadius.all(Radius.circular(4.0)) - : BorderRadius.zero, + borderRadius: + widget.isTablet ? const BorderRadius.all(Radius.circular(4.0)) : BorderRadius.zero, boxShadow: widget.isTablet ? boardShadows : const [], ), child: ColoredBox( @@ -204,81 +197,69 @@ class _PieceMenuState extends ConsumerState<_PieceMenu> { height: squareSize, child: ColoredBox( key: Key('drag-button-${widget.side.name}'), - color: editorState.editorPointerMode == EditorPointerMode.drag - ? context.lichessColors.good - : Colors.transparent, + color: + editorState.editorPointerMode == EditorPointerMode.drag + ? context.lichessColors.good + : Colors.transparent, child: GestureDetector( - onTap: () => ref - .read(editorController.notifier) - .updateMode(EditorPointerMode.drag), - child: Icon( - CupertinoIcons.hand_draw, - size: 0.9 * squareSize, - ), + onTap: + () => ref.read(editorController.notifier).updateMode(EditorPointerMode.drag), + child: Icon(CupertinoIcons.hand_draw, size: 0.9 * squareSize), ), ), ), - ...Role.values.map( - (role) { - final piece = Piece(role: role, color: widget.side); - final pieceWidget = PieceWidget( - piece: piece, - size: squareSize, - pieceAssets: boardPrefs.pieceSet.assets, - ); + ...Role.values.map((role) { + final piece = Piece(role: role, color: widget.side); + final pieceWidget = PieceWidget( + piece: piece, + size: squareSize, + pieceAssets: boardPrefs.pieceSet.assets, + ); - return ColoredBox( - key: Key( - 'piece-button-${piece.color.name}-${piece.role.name}', - ), - color: ref - .read( - boardEditorControllerProvider( - widget.initialFen, - ), - ) - .activePieceOnEdit == - piece - ? Theme.of(context).colorScheme.primary - : Colors.transparent, - child: GestureDetector( - child: Draggable( - data: Piece(role: role, color: widget.side), - feedback: PieceDragFeedback( - piece: piece, - squareSize: squareSize, - pieceAssets: boardPrefs.pieceSet.assets, - ), - child: pieceWidget, - onDragEnd: (_) => ref - .read(editorController.notifier) - .updateMode(EditorPointerMode.drag), + return ColoredBox( + key: Key('piece-button-${piece.color.name}-${piece.role.name}'), + color: + ref.read(boardEditorControllerProvider(widget.initialFen)).activePieceOnEdit == + piece + ? Theme.of(context).colorScheme.primary + : Colors.transparent, + child: GestureDetector( + child: Draggable( + data: Piece(role: role, color: widget.side), + feedback: PieceDragFeedback( + piece: piece, + squareSize: squareSize, + pieceAssets: boardPrefs.pieceSet.assets, ), - onTap: () => ref - .read(editorController.notifier) - .updateMode(EditorPointerMode.edit, piece), + child: pieceWidget, + onDragEnd: + (_) => + ref.read(editorController.notifier).updateMode(EditorPointerMode.drag), ), - ); - }, - ), + onTap: + () => ref + .read(editorController.notifier) + .updateMode(EditorPointerMode.edit, piece), + ), + ); + }), SizedBox( key: Key('delete-button-${widget.side.name}'), width: squareSize, height: squareSize, child: ColoredBox( - color: editorState.deletePiecesActive - ? context.lichessColors.error - : Colors.transparent, + color: + editorState.deletePiecesActive + ? context.lichessColors.error + : Colors.transparent, child: GestureDetector( - onTap: () => { - ref - .read(editorController.notifier) - .updateMode(EditorPointerMode.edit, null), - }, - child: Icon( - CupertinoIcons.delete, - size: 0.8 * squareSize, - ), + onTap: + () => { + ref + .read(editorController.notifier) + .updateMode(EditorPointerMode.edit, null), + }, + child: Icon(CupertinoIcons.delete, size: 0.8 * squareSize), ), ), ), @@ -303,61 +284,54 @@ class _BottomBar extends ConsumerWidget { children: [ BottomBarButton( label: context.l10n.menu, - onTap: () => showAdaptiveBottomSheet( - context: context, - builder: (BuildContext context) => BoardEditorMenu( - initialFen: initialFen, - ), - showDragHandle: true, - constraints: BoxConstraints( - minHeight: MediaQuery.sizeOf(context).height * 0.5, - ), - ), + onTap: + () => showAdaptiveBottomSheet( + context: context, + builder: (BuildContext context) => BoardEditorMenu(initialFen: initialFen), + showDragHandle: true, + constraints: BoxConstraints(minHeight: MediaQuery.sizeOf(context).height * 0.5), + ), icon: Icons.tune, ), BottomBarButton( key: const Key('flip-button'), label: context.l10n.flipBoard, - onTap: ref - .read(boardEditorControllerProvider(initialFen).notifier) - .flipBoard, + onTap: ref.read(boardEditorControllerProvider(initialFen).notifier).flipBoard, icon: CupertinoIcons.arrow_2_squarepath, ), BottomBarButton( label: context.l10n.analysis, key: const Key('analysis-board-button'), - onTap: editorState.pgn != null && - // 1 condition (of many) where stockfish segfaults - pieceCount > 0 && - pieceCount <= 32 - ? () { - pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => AnalysisScreen( - options: AnalysisOptions( - orientation: editorState.orientation, - standalone: ( - pgn: editorState.pgn!, - isComputerAnalysisAllowed: true, - variant: Variant.fromPosition, - ), - ), - ), - ); - } - : null, + onTap: + editorState.pgn != null && + // 1 condition (of many) where stockfish segfaults + pieceCount > 0 && + pieceCount <= 32 + ? () { + pushPlatformRoute( + context, + rootNavigator: true, + builder: + (context) => AnalysisScreen( + options: AnalysisOptions( + orientation: editorState.orientation, + standalone: ( + pgn: editorState.pgn!, + isComputerAnalysisAllowed: true, + variant: Variant.fromPosition, + ), + ), + ), + ); + } + : null, icon: Icons.biotech, ), BottomBarButton( label: context.l10n.mobileSharePositionAsFEN, - onTap: () => launchShareDialog( - context, - text: editorState.fen, - ), - icon: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoIcons.share - : Icons.share, + onTap: () => launchShareDialog(context, text: editorState.fen), + icon: + Theme.of(context).platform == TargetPlatform.iOS ? CupertinoIcons.share : Icons.share, ), ], ); diff --git a/lib/src/view/broadcast/broadcast_boards_tab.dart b/lib/src/view/broadcast/broadcast_boards_tab.dart index 7f0df075f7..cb55aa1e23 100644 --- a/lib/src/view/broadcast/broadcast_boards_tab.dart +++ b/lib/src/view/broadcast/broadcast_boards_tab.dart @@ -36,7 +36,8 @@ class BroadcastBoardsTab extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final edgeInsets = MediaQuery.paddingOf(context) - + final edgeInsets = + MediaQuery.paddingOf(context) - (Theme.of(context).platform == TargetPlatform.iOS ? EdgeInsets.only(top: MediaQuery.paddingOf(context).top) : EdgeInsets.zero) + @@ -46,8 +47,9 @@ class BroadcastBoardsTab extends ConsumerWidget { return SliverPadding( padding: edgeInsets, sliver: switch (round) { - AsyncData(:final value) => value.games.isEmpty - ? SliverPadding( + AsyncData(:final value) => + value.games.isEmpty + ? SliverPadding( padding: const EdgeInsets.only(top: 16.0), sliver: SliverToBoxAdapter( child: Column( @@ -59,7 +61,7 @@ class BroadcastBoardsTab extends ConsumerWidget { ), ), ) - : BroadcastPreview( + : BroadcastPreview( games: value.games.values.toIList(), tournamentId: tournamentId, roundId: roundId, @@ -68,15 +70,9 @@ class BroadcastBoardsTab extends ConsumerWidget { roundSlug: value.round.slug, ), AsyncError(:final error) => SliverFillRemaining( - child: Center( - child: Text('Could not load broadcast: $error'), - ), - ), - _ => const SliverFillRemaining( - child: Center( - child: CircularProgressIndicator.adaptive(), - ), - ), + child: Center(child: Text('Could not load broadcast: $error')), + ), + _ => const SliverFillRemaining(child: Center(child: CircularProgressIndicator.adaptive())), }, ); } @@ -93,12 +89,12 @@ class BroadcastPreview extends StatelessWidget { }); const BroadcastPreview.loading() - : tournamentId = const BroadcastTournamentId(''), - roundId = const BroadcastRoundId(''), - games = null, - title = '', - tournamentSlug = '', - roundSlug = ''; + : tournamentId = const BroadcastTournamentId(''), + roundId = const BroadcastRoundId(''), + games = null, + title = '', + tournamentSlug = '', + roundSlug = ''; final BroadcastTournamentId tournamentId; final BroadcastRoundId roundId; @@ -118,7 +114,8 @@ class BroadcastPreview extends StatelessWidget { final headerAndFooterHeight = textHeight + _kPlayerWidgetPadding.vertical; final numberOfBoardsByRow = isTabletOrLarger(context) ? 3 : 2; final screenWidth = MediaQuery.sizeOf(context).width; - final boardWidth = (screenWidth - + final boardWidth = + (screenWidth - Styles.horizontalBodyPadding.horizontal - (numberOfBoardsByRow - 1) * boardSpacing) / numberOfBoardsByRow; @@ -153,14 +150,15 @@ class BroadcastPreview extends StatelessWidget { pushPlatformRoute( context, title: title, - builder: (context) => BroadcastGameScreen( - tournamentId: tournamentId, - roundId: roundId, - gameId: game.id, - tournamentSlug: tournamentSlug, - roundSlug: roundSlug, - title: title, - ), + builder: + (context) => BroadcastGameScreen( + tournamentId: tournamentId, + roundId: roundId, + gameId: game.id, + tournamentSlug: tournamentSlug, + roundSlug: roundSlug, + title: title, + ), ); }, orientation: Side.white, @@ -187,9 +185,7 @@ class BroadcastPreview extends StatelessWidget { } class _PlayerWidgetLoading extends StatelessWidget { - const _PlayerWidgetLoading({ - required this.width, - }); + const _PlayerWidgetLoading({required this.width}); final double width; @@ -201,10 +197,7 @@ class _PlayerWidgetLoading extends StatelessWidget { padding: _kPlayerWidgetPadding, child: Container( height: _kPlayerWidgetTextStyle.fontSize, - decoration: BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.circular(5), - ), + decoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.circular(5)), ), ), ); @@ -252,26 +245,26 @@ class _PlayerWidget extends StatelessWidget { (gameStatus == BroadcastResult.draw) ? '½' : (gameStatus == BroadcastResult.whiteWins) - ? side == Side.white - ? '1' - : '0' - : side == Side.black - ? '1' - : '0', - style: - const TextStyle().copyWith(fontWeight: FontWeight.bold), + ? side == Side.white + ? '1' + : '0' + : side == Side.black + ? '1' + : '0', + style: const TextStyle().copyWith(fontWeight: FontWeight.bold), ) else if (player.clock != null) CountdownClockBuilder( timeLeft: player.clock!, active: side == playingSide, - builder: (context, timeLeft) => Text( - timeLeft.toHoursMinutesSeconds(), - style: TextStyle( - color: (side == playingSide) ? Colors.orange[900] : null, - fontFeatures: const [FontFeature.tabularFigures()], - ), - ), + builder: + (context, timeLeft) => Text( + timeLeft.toHoursMinutesSeconds(), + style: TextStyle( + color: (side == playingSide) ? Colors.orange[900] : null, + fontFeatures: const [FontFeature.tabularFigures()], + ), + ), tickInterval: const Duration(seconds: 1), clockUpdatedAt: game.updatedClockAt, ), diff --git a/lib/src/view/broadcast/broadcast_game_bottom_bar.dart b/lib/src/view/broadcast/broadcast_game_bottom_bar.dart index e408303f91..4283bfcc54 100644 --- a/lib/src/view/broadcast/broadcast_game_bottom_bar.dart +++ b/lib/src/view/broadcast/broadcast_game_bottom_bar.dart @@ -42,14 +42,11 @@ class BroadcastGameBottomBar extends ConsumerWidget { actions: [ if (tournamentSlug != null && roundSlug != null) BottomSheetAction( - makeLabel: (context) => - Text(context.l10n.mobileShareGameURL), + makeLabel: (context) => Text(context.l10n.mobileShareGameURL), onPressed: (context) async { launchShareDialog( context, - uri: lichessUri( - '/broadcast/$tournamentSlug/$roundSlug/$roundId/$gameId', - ), + uri: lichessUri('/broadcast/$tournamentSlug/$roundSlug/$roundId/$gameId'), ); }, ), @@ -58,14 +55,10 @@ class BroadcastGameBottomBar extends ConsumerWidget { onPressed: (context) async { try { final pgn = await ref.withClient( - (client) => BroadcastRepository(client) - .getGamePgn(roundId, gameId), + (client) => BroadcastRepository(client).getGamePgn(roundId, gameId), ); if (context.mounted) { - launchShareDialog( - context, - text: pgn, - ); + launchShareDialog(context, text: pgn); } } catch (e) { if (context.mounted) { @@ -82,16 +75,11 @@ class BroadcastGameBottomBar extends ConsumerWidget { makeLabel: (context) => const Text('GIF'), onPressed: (_) async { try { - final gif = - await ref.read(gameShareServiceProvider).chapterGif( - roundId, - gameId, - ); + final gif = await ref + .read(gameShareServiceProvider) + .chapterGif(roundId, gameId); if (context.mounted) { - launchShareDialog( - context, - files: [gif], - ); + launchShareDialog(context, files: [gif]); } } catch (e) { debugPrint(e.toString()); @@ -108,42 +96,33 @@ class BroadcastGameBottomBar extends ConsumerWidget { ], ); }, - icon: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoIcons.share - : Icons.share, + icon: + Theme.of(context).platform == TargetPlatform.iOS ? CupertinoIcons.share : Icons.share, ), BottomBarButton( label: context.l10n.flipBoard, onTap: () { - ref - .read( - ctrlProvider.notifier, - ) - .toggleBoard(); + ref.read(ctrlProvider.notifier).toggleBoard(); }, icon: CupertinoIcons.arrow_2_squarepath, ), RepeatButton( - onLongPress: - broadcastGameState.canGoBack ? () => _moveBackward(ref) : null, + onLongPress: broadcastGameState.canGoBack ? () => _moveBackward(ref) : null, child: BottomBarButton( key: const ValueKey('goto-previous'), - onTap: - broadcastGameState.canGoBack ? () => _moveBackward(ref) : null, + onTap: broadcastGameState.canGoBack ? () => _moveBackward(ref) : null, label: 'Previous', icon: CupertinoIcons.chevron_back, showTooltip: false, ), ), RepeatButton( - onLongPress: - broadcastGameState.canGoNext ? () => _moveForward(ref) : null, + onLongPress: broadcastGameState.canGoNext ? () => _moveForward(ref) : null, child: BottomBarButton( key: const ValueKey('goto-next'), icon: CupertinoIcons.chevron_forward, label: context.l10n.next, - onTap: - broadcastGameState.canGoNext ? () => _moveForward(ref) : null, + onTap: broadcastGameState.canGoNext ? () => _moveForward(ref) : null, showTooltip: false, ), ), @@ -151,10 +130,8 @@ class BroadcastGameBottomBar extends ConsumerWidget { ); } - void _moveForward(WidgetRef ref) => ref - .read(broadcastGameControllerProvider(roundId, gameId).notifier) - .userNext(); - void _moveBackward(WidgetRef ref) => ref - .read(broadcastGameControllerProvider(roundId, gameId).notifier) - .userPrevious(); + void _moveForward(WidgetRef ref) => + ref.read(broadcastGameControllerProvider(roundId, gameId).notifier).userNext(); + void _moveBackward(WidgetRef ref) => + ref.read(broadcastGameControllerProvider(roundId, gameId).notifier).userPrevious(); } diff --git a/lib/src/view/broadcast/broadcast_game_screen.dart b/lib/src/view/broadcast/broadcast_game_screen.dart index 9883ad9c3e..52cdf4d0db 100644 --- a/lib/src/view/broadcast/broadcast_game_screen.dart +++ b/lib/src/view/broadcast/broadcast_game_screen.dart @@ -49,8 +49,7 @@ class BroadcastGameScreen extends ConsumerStatefulWidget { }); @override - ConsumerState createState() => - _BroadcastGameScreenState(); + ConsumerState createState() => _BroadcastGameScreenState(); } class _BroadcastGameScreenState extends ConsumerState @@ -62,16 +61,9 @@ class _BroadcastGameScreenState extends ConsumerState void initState() { super.initState(); - tabs = [ - AnalysisTab.opening, - AnalysisTab.moves, - ]; + tabs = [AnalysisTab.opening, AnalysisTab.moves]; - _tabController = TabController( - vsync: this, - initialIndex: 1, - length: tabs.length, - ); + _tabController = TabController(vsync: this, initialIndex: 1, length: tabs.length); } @override @@ -85,43 +77,36 @@ class _BroadcastGameScreenState extends ConsumerState final broadcastRoundGameState = ref.watch( broadcastRoundGameProvider(widget.roundId, widget.gameId), ); - final broadcastGameState = ref - .watch(broadcastGameControllerProvider(widget.roundId, widget.gameId)); - final title = (widget.title != null) - ? Text( - widget.title!, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ) - : switch (ref.watch(broadcastGameScreenTitleProvider(widget.roundId))) { - AsyncData(value: final title) => Text( + final broadcastGameState = ref.watch( + broadcastGameControllerProvider(widget.roundId, widget.gameId), + ); + final title = + (widget.title != null) + ? Text(widget.title!, overflow: TextOverflow.ellipsis, maxLines: 1) + : switch (ref.watch(broadcastGameScreenTitleProvider(widget.roundId))) { + AsyncData(value: final title) => Text( title, overflow: TextOverflow.ellipsis, maxLines: 1, ), - _ => const SizedBox.shrink(), - }; + _ => const SizedBox.shrink(), + }; return PlatformScaffold( appBar: PlatformAppBar( title: title, actions: [ - AppBarAnalysisTabIndicator( - tabs: tabs, - controller: _tabController, - ), + AppBarAnalysisTabIndicator(tabs: tabs, controller: _tabController), AppBarIconButton( - onPressed: (broadcastGameState.hasValue) - ? () { - pushPlatformRoute( - context, - screen: BroadcastGameSettings( - widget.roundId, - widget.gameId, - ), - ); - } - : null, + onPressed: + (broadcastGameState.hasValue) + ? () { + pushPlatformRoute( + context, + screen: BroadcastGameSettings(widget.roundId, widget.gameId), + ); + } + : null, semanticsLabel: context.l10n.settingsSettings, icon: const Icon(Icons.settings), ), @@ -129,19 +114,15 @@ class _BroadcastGameScreenState extends ConsumerState ), body: switch ((broadcastRoundGameState, broadcastGameState)) { (AsyncData(), AsyncData()) => _Body( - widget.tournamentId, - widget.roundId, - widget.gameId, - widget.tournamentSlug, - widget.roundSlug, - tabController: _tabController, - ), - (AsyncError(:final error), _) => Center( - child: Text('Cannot load broadcast game: $error'), - ), - (_, AsyncError(:final error)) => Center( - child: Text('Cannot load broadcast game: $error'), - ), + widget.tournamentId, + widget.roundId, + widget.gameId, + widget.tournamentSlug, + widget.roundSlug, + tabController: _tabController, + ), + (AsyncError(:final error), _) => Center(child: Text('Cannot load broadcast game: $error')), + (_, AsyncError(:final error)) => Center(child: Text('Cannot load broadcast game: $error')), _ => const Center(child: CircularProgressIndicator.adaptive()), }, ); @@ -167,9 +148,7 @@ class _Body extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final broadcastState = ref - .watch(broadcastGameControllerProvider(roundId, gameId)) - .requireValue; + final broadcastState = ref.watch(broadcastGameControllerProvider(roundId, gameId)).requireValue; final analysisPrefs = ref.watch(analysisPreferencesProvider); final showEvaluationGauge = analysisPrefs.showEvaluationGauge; final numEvalLines = analysisPrefs.numEvalLines; @@ -180,12 +159,9 @@ class _Body extends ConsumerWidget { return AnalysisLayout( tabController: tabController, - boardBuilder: (context, boardSize, borderRadius) => _BroadcastBoard( - roundId, - gameId, - boardSize, - borderRadius, - ), + boardBuilder: + (context, boardSize, borderRadius) => + _BroadcastBoard(roundId, gameId, boardSize, borderRadius), boardHeader: _PlayerWidget( tournamentId: tournamentId, roundId: roundId, @@ -198,55 +174,46 @@ class _Body extends ConsumerWidget { gameId: gameId, widgetPosition: _PlayerWidgetPosition.bottom, ), - engineGaugeBuilder: isLocalEvaluationEnabled && showEvaluationGauge - ? (context, orientation) { - return orientation == Orientation.portrait - ? EngineGauge( + engineGaugeBuilder: + isLocalEvaluationEnabled && showEvaluationGauge + ? (context, orientation) { + return orientation == Orientation.portrait + ? EngineGauge( displayMode: EngineGaugeDisplayMode.horizontal, params: engineGaugeParams, ) - : Container( + : Container( clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4.0), - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(4.0)), child: EngineGauge( displayMode: EngineGaugeDisplayMode.vertical, params: engineGaugeParams, ), ); - } - : null, - engineLines: isLocalEvaluationEnabled && numEvalLines > 0 - ? EngineLines( - clientEval: currentNode.eval, - isGameOver: currentNode.position.isGameOver, - onTapMove: ref - .read( - broadcastGameControllerProvider(roundId, gameId).notifier, - ) - .onUserMove, - ) - : null, + } + : null, + engineLines: + isLocalEvaluationEnabled && numEvalLines > 0 + ? EngineLines( + clientEval: currentNode.eval, + isGameOver: currentNode.position.isGameOver, + onTapMove: + ref.read(broadcastGameControllerProvider(roundId, gameId).notifier).onUserMove, + ) + : null, bottomBar: BroadcastGameBottomBar( roundId: roundId, gameId: gameId, tournamentSlug: tournamentSlug, roundSlug: roundSlug, ), - children: [ - _OpeningExplorerTab(roundId, gameId), - BroadcastGameTreeView(roundId, gameId), - ], + children: [_OpeningExplorerTab(roundId, gameId), BroadcastGameTreeView(roundId, gameId)], ); } } class _OpeningExplorerTab extends ConsumerWidget { - const _OpeningExplorerTab( - this.roundId, - this.gameId, - ); + const _OpeningExplorerTab(this.roundId, this.gameId); final BroadcastRoundId roundId; final BroadcastGameId gameId; @@ -264,12 +231,7 @@ class _OpeningExplorerTab extends ConsumerWidget { } class _BroadcastBoard extends ConsumerStatefulWidget { - const _BroadcastBoard( - this.roundId, - this.gameId, - this.boardSize, - this.borderRadius, - ); + const _BroadcastBoard(this.roundId, this.gameId, this.boardSize, this.borderRadius); final BroadcastRoundId roundId; final BroadcastGameId gameId; @@ -285,15 +247,12 @@ class _BroadcastBoardState extends ConsumerState<_BroadcastBoard> { @override Widget build(BuildContext context) { - final ctrlProvider = - broadcastGameControllerProvider(widget.roundId, widget.gameId); + final ctrlProvider = broadcastGameControllerProvider(widget.roundId, widget.gameId); final broadcastAnalysisState = ref.watch(ctrlProvider).requireValue; final boardPrefs = ref.watch(boardPreferencesProvider); final analysisPrefs = ref.watch(analysisPreferencesProvider); - final evalBestMoves = ref.watch( - engineEvaluationProvider.select((s) => s.eval?.bestMoves), - ); + final evalBestMoves = ref.watch(engineEvaluationProvider.select((s) => s.eval?.bestMoves)); final currentNode = broadcastAnalysisState.currentNode; final annotation = makeAnnotation(currentNode.nags); @@ -302,15 +261,16 @@ class _BroadcastBoardState extends ConsumerState<_BroadcastBoard> { final sanMove = currentNode.sanMove; - final ISet bestMoveShapes = analysisPrefs.showBestMoveArrow && - broadcastAnalysisState.isLocalEvaluationEnabled && - bestMoves != null - ? computeBestMoveShapes( - bestMoves, - currentNode.position.turn, - boardPrefs.pieceSet.assets, - ) - : ISet(); + final ISet bestMoveShapes = + analysisPrefs.showBestMoveArrow && + broadcastAnalysisState.isLocalEvaluationEnabled && + bestMoves != null + ? computeBestMoveShapes( + bestMoves, + currentNode.position.turn, + boardPrefs.pieceSet.assets, + ) + : ISet(); return Chessboard( size: widget.boardSize, @@ -318,42 +278,36 @@ class _BroadcastBoardState extends ConsumerState<_BroadcastBoard> { lastMove: broadcastAnalysisState.lastMove as NormalMove?, orientation: broadcastAnalysisState.pov, game: GameData( - playerSide: broadcastAnalysisState.position.isGameOver - ? PlayerSide.none - : broadcastAnalysisState.position.turn == Side.white + playerSide: + broadcastAnalysisState.position.isGameOver + ? PlayerSide.none + : broadcastAnalysisState.position.turn == Side.white ? PlayerSide.white : PlayerSide.black, - isCheck: boardPrefs.boardHighlights && - broadcastAnalysisState.position.isCheck, + isCheck: boardPrefs.boardHighlights && broadcastAnalysisState.position.isCheck, sideToMove: broadcastAnalysisState.position.turn, validMoves: broadcastAnalysisState.validMoves, promotionMove: broadcastAnalysisState.promotionMove, - onMove: (move, {isDrop, captured}) => - ref.read(ctrlProvider.notifier).onUserMove(move), - onPromotionSelection: (role) => - ref.read(ctrlProvider.notifier).onPromotionSelection(role), + onMove: (move, {isDrop, captured}) => ref.read(ctrlProvider.notifier).onUserMove(move), + onPromotionSelection: (role) => ref.read(ctrlProvider.notifier).onPromotionSelection(role), ), shapes: userShapes.union(bestMoveShapes), annotations: analysisPrefs.showAnnotations && sanMove != null && annotation != null ? altCastles.containsKey(sanMove.move.uci) - ? IMap({ - Move.parse(altCastles[sanMove.move.uci]!)!.to: annotation, - }) + ? IMap({Move.parse(altCastles[sanMove.move.uci]!)!.to: annotation}) : IMap({sanMove.move.to: annotation}) : null, settings: boardPrefs.toBoardSettings().copyWith( - borderRadius: widget.borderRadius, - boxShadow: widget.borderRadius != null - ? boardShadows - : const [], - drawShape: DrawShapeOptions( - enable: boardPrefs.enableShapeDrawings, - onCompleteShape: _onCompleteShape, - onClearShapes: _onClearShapes, - newShapeColor: boardPrefs.shapeColor.color, - ), - ), + borderRadius: widget.borderRadius, + boxShadow: widget.borderRadius != null ? boardShadows : const [], + drawShape: DrawShapeOptions( + enable: boardPrefs.enableShapeDrawings, + onCompleteShape: _onCompleteShape, + onClearShapes: _onClearShapes, + newShapeColor: boardPrefs.shapeColor.color, + ), + ), ); } @@ -394,11 +348,9 @@ class _PlayerWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final broadcastGameState = ref - .watch(broadcastGameControllerProvider(roundId, gameId)) - .requireValue; - final game = - ref.watch(broadcastRoundGameProvider(roundId, gameId)).requireValue; + final broadcastGameState = + ref.watch(broadcastGameControllerProvider(roundId, gameId)).requireValue; + final game = ref.watch(broadcastRoundGameProvider(roundId, gameId)).requireValue; final isCursorOnLiveMove = broadcastGameState.currentPath == broadcastGameState.broadcastLivePath; @@ -413,25 +365,26 @@ class _PlayerWidget extends ConsumerWidget { final gameStatus = game.status; final pastClocks = broadcastGameState.clocks; - final pastClock = - (sideToMove == side) ? pastClocks?.parentClock : pastClocks?.clock; + final pastClock = (sideToMove == side) ? pastClocks?.parentClock : pastClocks?.clock; return GestureDetector( onTap: () { pushPlatformRoute( context, - builder: (context) => BroadcastPlayerResultsScreen( - tournamentId, - (player.fideId != null) ? player.fideId!.toString() : player.name, - player.title, - player.name, - ), + builder: + (context) => BroadcastPlayerResultsScreen( + tournamentId, + (player.fideId != null) ? player.fideId!.toString() : player.name, + player.title, + player.name, + ), ); }, child: Container( - color: Theme.of(context).platform == TargetPlatform.iOS - ? Styles.cupertinoCardColor.resolveFrom(context) - : Theme.of(context).colorScheme.surfaceContainer, + color: + Theme.of(context).platform == TargetPlatform.iOS + ? Styles.cupertinoCardColor.resolveFrom(context) + : Theme.of(context).colorScheme.surfaceContainer, padding: const EdgeInsets.only(left: 8.0), child: Row( children: [ @@ -440,12 +393,12 @@ class _PlayerWidget extends ConsumerWidget { (gameStatus == BroadcastResult.draw) ? '½' : (gameStatus == BroadcastResult.whiteWins) - ? side == Side.white - ? '1' - : '0' - : side == Side.black - ? '1' - : '0', + ? side == Side.white + ? '1' + : '0' + : side == Side.black + ? '1' + : '0', style: const TextStyle().copyWith(fontWeight: FontWeight.bold), ), const SizedBox(width: 16.0), @@ -456,38 +409,40 @@ class _PlayerWidget extends ConsumerWidget { title: player.title, name: player.name, rating: player.rating, - textStyle: - const TextStyle().copyWith(fontWeight: FontWeight.bold), + textStyle: const TextStyle().copyWith(fontWeight: FontWeight.bold), ), ), if (liveClock != null || pastClock != null) Container( height: kAnalysisBoardHeaderOrFooterHeight, - color: (side == sideToMove) - ? isCursorOnLiveMove - ? Theme.of(context).colorScheme.tertiaryContainer - : Theme.of(context).colorScheme.secondaryContainer - : Colors.transparent, + color: + (side == sideToMove) + ? isCursorOnLiveMove + ? Theme.of(context).colorScheme.tertiaryContainer + : Theme.of(context).colorScheme.secondaryContainer + : Colors.transparent, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6.0), child: Center( - child: liveClock != null - ? CountdownClockBuilder( - timeLeft: liveClock, - active: side == sideToMove, - builder: (context, timeLeft) => _Clock( - timeLeft: timeLeft, + child: + liveClock != null + ? CountdownClockBuilder( + timeLeft: liveClock, + active: side == sideToMove, + builder: + (context, timeLeft) => _Clock( + timeLeft: timeLeft, + isSideToMove: side == sideToMove, + isLive: true, + ), + tickInterval: const Duration(seconds: 1), + clockUpdatedAt: game.updatedClockAt, + ) + : _Clock( + timeLeft: pastClock!, isSideToMove: side == sideToMove, - isLive: true, + isLive: false, ), - tickInterval: const Duration(seconds: 1), - clockUpdatedAt: game.updatedClockAt, - ) - : _Clock( - timeLeft: pastClock!, - isSideToMove: side == sideToMove, - isLive: false, - ), ), ), ), @@ -499,11 +454,7 @@ class _PlayerWidget extends ConsumerWidget { } class _Clock extends StatelessWidget { - const _Clock({ - required this.timeLeft, - required this.isSideToMove, - required this.isLive, - }); + const _Clock({required this.timeLeft, required this.isSideToMove, required this.isLive}); final Duration timeLeft; final bool isSideToMove; @@ -514,11 +465,12 @@ class _Clock extends StatelessWidget { return Text( timeLeft.toHoursMinutesSeconds(), style: TextStyle( - color: isSideToMove - ? isLive - ? Theme.of(context).colorScheme.onTertiaryContainer - : Theme.of(context).colorScheme.onSecondaryContainer - : null, + color: + isSideToMove + ? isLive + ? Theme.of(context).colorScheme.onTertiaryContainer + : Theme.of(context).colorScheme.onSecondaryContainer + : null, fontFeatures: const [FontFeature.tabularFigures()], ), ); diff --git a/lib/src/view/broadcast/broadcast_game_screen_providers.dart b/lib/src/view/broadcast/broadcast_game_screen_providers.dart index 8490ee3988..288733f47f 100644 --- a/lib/src/view/broadcast/broadcast_game_screen_providers.dart +++ b/lib/src/view/broadcast/broadcast_game_screen_providers.dart @@ -13,18 +13,13 @@ Future broadcastRoundGame( BroadcastGameId gameId, ) { return ref.watch( - broadcastRoundControllerProvider(roundId) - .selectAsync((round) => round.games[gameId]!), + broadcastRoundControllerProvider(roundId).selectAsync((round) => round.games[gameId]!), ); } @riverpod -Future broadcastGameScreenTitle( - Ref ref, - BroadcastRoundId roundId, -) { +Future broadcastGameScreenTitle(Ref ref, BroadcastRoundId roundId) { return ref.watch( - broadcastRoundControllerProvider(roundId) - .selectAsync((round) => round.round.name), + broadcastRoundControllerProvider(roundId).selectAsync((round) => round.round.name), ); } diff --git a/lib/src/view/broadcast/broadcast_game_settings.dart b/lib/src/view/broadcast/broadcast_game_settings.dart index 4fd699e371..10d27db672 100644 --- a/lib/src/view/broadcast/broadcast_game_settings.dart +++ b/lib/src/view/broadcast/broadcast_game_settings.dart @@ -21,8 +21,7 @@ class BroadcastGameSettings extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final broacdcastGameAnalysisController = - broadcastGameControllerProvider(roundId, gameId); + final broacdcastGameAnalysisController = broadcastGameControllerProvider(roundId, gameId); final analysisPrefs = ref.watch(analysisPreferencesProvider); final isSoundEnabled = ref.watch( @@ -34,34 +33,31 @@ class BroadcastGameSettings extends ConsumerWidget { body: ListView( children: [ StockfishSettingsWidget( - onToggleLocalEvaluation: () => ref - .read(broacdcastGameAnalysisController.notifier) - .toggleLocalEvaluation(), - onSetEngineSearchTime: (value) => ref - .read(broacdcastGameAnalysisController.notifier) - .setEngineSearchTime(value), - onSetNumEvalLines: (value) => ref - .read(broacdcastGameAnalysisController.notifier) - .setNumEvalLines(value), - onSetEngineCores: (value) => ref - .read(broacdcastGameAnalysisController.notifier) - .setEngineCores(value), + onToggleLocalEvaluation: + () => ref.read(broacdcastGameAnalysisController.notifier).toggleLocalEvaluation(), + onSetEngineSearchTime: + (value) => + ref.read(broacdcastGameAnalysisController.notifier).setEngineSearchTime(value), + onSetNumEvalLines: + (value) => + ref.read(broacdcastGameAnalysisController.notifier).setNumEvalLines(value), + onSetEngineCores: + (value) => + ref.read(broacdcastGameAnalysisController.notifier).setEngineCores(value), ), ListSection( children: [ SwitchSettingTile( title: Text(context.l10n.toggleGlyphAnnotations), value: analysisPrefs.showAnnotations, - onChanged: (_) => ref - .read(analysisPreferencesProvider.notifier) - .toggleAnnotations(), + onChanged: + (_) => ref.read(analysisPreferencesProvider.notifier).toggleAnnotations(), ), SwitchSettingTile( title: Text(context.l10n.mobileShowComments), value: analysisPrefs.showPgnComments, - onChanged: (_) => ref - .read(analysisPreferencesProvider.notifier) - .togglePgnComments(), + onChanged: + (_) => ref.read(analysisPreferencesProvider.notifier).togglePgnComments(), ), ], ), @@ -69,21 +65,20 @@ class BroadcastGameSettings extends ConsumerWidget { children: [ PlatformListTile( title: Text(context.l10n.openingExplorer), - onTap: () => showAdaptiveBottomSheet( - context: context, - isScrollControlled: true, - showDragHandle: true, - isDismissible: true, - builder: (_) => const OpeningExplorerSettings(), - ), + onTap: + () => showAdaptiveBottomSheet( + context: context, + isScrollControlled: true, + showDragHandle: true, + isDismissible: true, + builder: (_) => const OpeningExplorerSettings(), + ), ), SwitchSettingTile( title: Text(context.l10n.sound), value: isSoundEnabled, onChanged: (value) { - ref - .read(generalPreferencesProvider.notifier) - .toggleSoundEnabled(); + ref.read(generalPreferencesProvider.notifier).toggleSoundEnabled(); }, ), ], diff --git a/lib/src/view/broadcast/broadcast_game_tree_view.dart b/lib/src/view/broadcast/broadcast_game_tree_view.dart index f691d03d5e..81c062ab17 100644 --- a/lib/src/view/broadcast/broadcast_game_tree_view.dart +++ b/lib/src/view/broadcast/broadcast_game_tree_view.dart @@ -8,10 +8,7 @@ import 'package:lichess_mobile/src/widgets/pgn.dart'; const kOpeningHeaderHeight = 32.0; class BroadcastGameTreeView extends ConsumerWidget { - const BroadcastGameTreeView( - this.roundId, - this.gameId, - ); + const BroadcastGameTreeView(this.roundId, this.gameId); final BroadcastRoundId roundId; final BroadcastGameId gameId; diff --git a/lib/src/view/broadcast/broadcast_list_screen.dart b/lib/src/view/broadcast/broadcast_list_screen.dart index 7ab8a3743a..98aee4235c 100644 --- a/lib/src/view/broadcast/broadcast_list_screen.dart +++ b/lib/src/view/broadcast/broadcast_list_screen.dart @@ -37,21 +37,19 @@ class BroadcastListScreen extends StatelessWidget { maxLines: 1, ); return PlatformWidget( - androidBuilder: (_) => Scaffold( - body: const _Body(), - appBar: AppBar(title: title), - ), - iosBuilder: (_) => CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: title, - automaticBackgroundVisibility: false, - backgroundColor: Styles.cupertinoAppBarColor - .resolveFrom(context) - .withValues(alpha: 0.0), - border: null, - ), - child: const _Body(), - ), + androidBuilder: (_) => Scaffold(body: const _Body(), appBar: AppBar(title: title)), + iosBuilder: + (_) => CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: title, + automaticBackgroundVisibility: false, + backgroundColor: Styles.cupertinoAppBarColor + .resolveFrom(context) + .withValues(alpha: 0.0), + border: null, + ), + child: const _Body(), + ), ); } } @@ -67,8 +65,7 @@ class _BodyState extends ConsumerState<_Body> { final ScrollController _scrollController = ScrollController(); ImageColorWorker? _worker; - final GlobalKey _refreshIndicatorKey = - GlobalKey(); + final GlobalKey _refreshIndicatorKey = GlobalKey(); @override void initState() { @@ -95,8 +92,7 @@ class _BodyState extends ConsumerState<_Body> { } void _scrollListener() { - if (_scrollController.position.pixels >= - _scrollController.position.maxScrollExtent - 300) { + if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 300) { final broadcastList = ref.read(broadcastsPaginatorProvider); if (!broadcastList.isLoading) { @@ -110,27 +106,24 @@ class _BodyState extends ConsumerState<_Body> { final broadcasts = ref.watch(broadcastsPaginatorProvider); if (_worker == null || (!broadcasts.hasValue && broadcasts.isLoading)) { - return const Center( - child: CircularProgressIndicator.adaptive(), - ); + return const Center(child: CircularProgressIndicator.adaptive()); } if (!broadcasts.hasValue && broadcasts.isLoading) { - debugPrint( - 'SEVERE: [BroadcastsListScreen] could not load broadcast tournaments', - ); + debugPrint('SEVERE: [BroadcastsListScreen] could not load broadcast tournaments'); return const Center(child: Text('Could not load broadcast tournaments')); } final screenWidth = MediaQuery.sizeOf(context).width; - final itemsByRow = screenWidth >= 1200 - ? 3 - : screenWidth >= 700 + final itemsByRow = + screenWidth >= 1200 + ? 3 + : screenWidth >= 700 ? 2 : 1; const loadingItems = 12; - final pastItemsCount = broadcasts.requireValue.past.length + - (broadcasts.isLoading ? loadingItems : 0); + final pastItemsCount = + broadcasts.requireValue.past.length + (broadcasts.isLoading ? loadingItems : 0); final highTierGridDelegate = SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: itemsByRow, @@ -143,9 +136,10 @@ class _BodyState extends ConsumerState<_Body> { crossAxisCount: itemsByRow + 1, crossAxisSpacing: 16.0, mainAxisSpacing: 16.0, - childAspectRatio: screenWidth >= 1200 - ? 1.4 - : screenWidth >= 700 + childAspectRatio: + screenWidth >= 1200 + ? 1.4 + : screenWidth >= 700 ? 1.3 : 1.0, ); @@ -155,24 +149,21 @@ class _BodyState extends ConsumerState<_Body> { ('past', context.l10n.broadcastCompleted, broadcasts.value!.past), ]; - final activeHighTier = broadcasts.value!.active - .where( - (broadcast) => - broadcast.tour.tier != null && broadcast.tour.tier! >= 4, - ) - .toList(); + final activeHighTier = + broadcasts.value!.active + .where((broadcast) => broadcast.tour.tier != null && broadcast.tour.tier! >= 4) + .toList(); - final activeLowTier = broadcasts.value!.active - .where( - (broadcast) => - broadcast.tour.tier == null || broadcast.tour.tier! < 4, - ) - .toList(); + final activeLowTier = + broadcasts.value!.active + .where((broadcast) => broadcast.tour.tier == null || broadcast.tour.tier! < 4) + .toList(); return RefreshIndicator.adaptive( - edgeOffset: Theme.of(context).platform == TargetPlatform.iOS - ? MediaQuery.paddingOf(context).top + 16.0 - : 0, + edgeOffset: + Theme.of(context).platform == TargetPlatform.iOS + ? MediaQuery.paddingOf(context).top + 16.0 + : 0, key: _refreshIndicatorKey, onRefresh: () async => ref.refresh(broadcastsPaginatorProvider), child: Shimmer( @@ -215,10 +206,9 @@ class _BodyState extends ConsumerState<_Body> { : Styles.bodySectionPadding, sliver: SliverGrid.builder( gridDelegate: highTierGridDelegate, - itemBuilder: (context, index) => BroadcastCard( - worker: _worker!, - broadcast: activeHighTier[index], - ), + itemBuilder: + (context, index) => + BroadcastCard(worker: _worker!, broadcast: activeHighTier[index]), itemCount: activeHighTier.length, ), ), @@ -227,36 +217,37 @@ class _BodyState extends ConsumerState<_Body> { padding: Styles.bodySectionPadding, sliver: SliverGrid.builder( gridDelegate: lowTierGridDelegate, - itemBuilder: (context, index) => BroadcastCard( - worker: _worker!, - broadcast: activeLowTier[index], - ), + itemBuilder: + (context, index) => + BroadcastCard(worker: _worker!, broadcast: activeLowTier[index]), itemCount: activeLowTier.length, ), ), ] else SliverPadding( - padding: Theme.of(context).platform == TargetPlatform.iOS - ? Styles.horizontalBodyPadding - : Styles.bodySectionPadding, + padding: + Theme.of(context).platform == TargetPlatform.iOS + ? Styles.horizontalBodyPadding + : Styles.bodySectionPadding, sliver: SliverGrid.builder( gridDelegate: lowTierGridDelegate, - itemBuilder: (context, index) => - (broadcasts.isLoading && - index >= pastItemsCount - loadingItems) - ? ShimmerLoading( - isLoading: true, - child: BroadcastCard.loading(_worker!), - ) - : BroadcastCard( - worker: _worker!, - broadcast: section.$3[index], - ), + itemBuilder: + (context, index) => + (broadcasts.isLoading && index >= pastItemsCount - loadingItems) + ? ShimmerLoading( + isLoading: true, + child: BroadcastCard.loading(_worker!), + ) + : BroadcastCard(worker: _worker!, broadcast: section.$3[index]), itemCount: section.$3.length, ), ), ], ), + const SliverSafeArea( + top: false, + sliver: SliverToBoxAdapter(child: SizedBox(height: 16.0)), + ), ], ), ), @@ -265,53 +256,46 @@ class _BodyState extends ConsumerState<_Body> { } class BroadcastCard extends StatefulWidget { - const BroadcastCard({ - required this.broadcast, - required this.worker, - super.key, - }); + const BroadcastCard({required this.broadcast, required this.worker, super.key}); final Broadcast broadcast; final ImageColorWorker worker; const BroadcastCard.loading(this.worker) - : broadcast = const Broadcast( - tour: BroadcastTournamentData( - id: BroadcastTournamentId(''), - name: '', - slug: '', - imageUrl: null, - description: '', - information: ( - format: null, - timeControl: null, - players: null, - website: null, - location: null, - dates: null, - ), - ), - round: BroadcastRound( - id: BroadcastRoundId(''), - name: '', - slug: '', - status: RoundStatus.finished, - startsAt: null, - finishedAt: null, - startsAfterPrevious: false, + : broadcast = const Broadcast( + tour: BroadcastTournamentData( + id: BroadcastTournamentId(''), + name: '', + slug: '', + imageUrl: null, + description: '', + information: ( + format: null, + timeControl: null, + players: null, + website: null, + location: null, + dates: null, ), - group: null, - roundToLinkId: BroadcastRoundId(''), - ); + ), + round: BroadcastRound( + id: BroadcastRoundId(''), + name: '', + slug: '', + status: RoundStatus.finished, + startsAt: null, + finishedAt: null, + startsAfterPrevious: false, + ), + group: null, + roundToLinkId: BroadcastRoundId(''), + ); @override State createState() => _BroadcastCartState(); } -typedef _CardColors = ({ - Color primaryContainer, - Color onPrimaryContainer, -}); +typedef _CardColors = ({Color primaryContainer, Color onPrimaryContainer}); final Map _colorsCache = {}; Future<_CardColors?> _computeImageColors( @@ -319,8 +303,7 @@ Future<_CardColors?> _computeImageColors( String imageUrl, ByteData imageBytes, ) async { - final response = - await worker.getImageColors(imageBytes.buffer.asUint32List()); + final response = await worker.getImageColors(imageBytes.buffer.asUint32List()); if (response != null) { final (:primaryContainer, :onPrimaryContainer) = response; final cardColors = ( @@ -364,8 +347,7 @@ class _BroadcastCartState extends State { await precacheImage(provider, context); final ui.Image scaledImage = await _imageProviderToScaled(provider); final imageBytes = await scaledImage.toByteData(); - final response = - await _computeImageColors(widget.worker, provider.url, imageBytes!); + final response = await _computeImageColors(widget.worker, provider.url, imageBytes!); if (response != null) { if (mounted) { setState(() { @@ -390,17 +372,13 @@ class _BroadcastCartState extends State { Theme.of(context).platform == TargetPlatform.iOS ? Styles.cupertinoCardColor.resolveFrom(context) : Theme.of(context).colorScheme.surfaceContainer; - final backgroundColor = - _cardColors?.primaryContainer ?? defaultBackgroundColor; + final backgroundColor = _cardColors?.primaryContainer ?? defaultBackgroundColor; final titleColor = _cardColors?.onPrimaryContainer; final subTitleColor = - _cardColors?.onPrimaryContainer.withValues(alpha: 0.8) ?? - textShade(context, 0.8); + _cardColors?.onPrimaryContainer.withValues(alpha: 0.8) ?? textShade(context, 0.8); final bgHsl = HSLColor.fromColor(backgroundColor); final liveHsl = HSLColor.fromColor(LichessColors.red); - final liveColor = - (bgHsl.lightness <= 0.5 ? liveHsl.withLightness(0.9) : liveHsl) - .toColor(); + final liveColor = (bgHsl.lightness <= 0.5 ? liveHsl.withLightness(0.9) : liveHsl).toColor(); return GestureDetector( onTap: () { @@ -408,8 +386,7 @@ class _BroadcastCartState extends State { context, title: widget.broadcast.title, rootNavigator: true, - builder: (context) => - BroadcastRoundScreen(broadcast: widget.broadcast), + builder: (context) => BroadcastRoundScreen(broadcast: widget.broadcast), ); }, onTapDown: (_) => _onTapDown(), @@ -424,9 +401,8 @@ class _BroadcastCartState extends State { decoration: BoxDecoration( borderRadius: kBroadcastGridItemBorderRadius, color: backgroundColor, - boxShadow: Theme.of(context).platform == TargetPlatform.iOS - ? null - : kElevationToShadow[1], + boxShadow: + Theme.of(context).platform == TargetPlatform.iOS ? null : kElevationToShadow[1], ), child: Stack( children: [ @@ -448,12 +424,7 @@ class _BroadcastCartState extends State { aspectRatio: 2.0, child: Image( image: imageProvider, - frameBuilder: ( - context, - child, - frame, - wasSynchronouslyLoaded, - ) { + frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { if (wasSynchronouslyLoaded) { return child; } @@ -463,8 +434,8 @@ class _BroadcastCartState extends State { child: child, ); }, - errorBuilder: (context, error, stackTrace) => - const Image(image: kDefaultBroadcastImage), + errorBuilder: + (context, error, stackTrace) => const Image(image: kDefaultBroadcastImage), ), ), ), @@ -475,40 +446,56 @@ class _BroadcastCartState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (widget.broadcast.round.startsAt != null) - Padding( - padding: kBroadcastGridItemContentPadding, - child: Row( - children: [ + Padding( + padding: kBroadcastGridItemContentPadding, + child: Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + if (!widget.broadcast.isFinished) ...[ Text( widget.broadcast.round.name, style: TextStyle( - fontSize: 13, + fontSize: 14, color: subTitleColor, + letterSpacing: -0.2, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), - const SizedBox(width: 4.0), - Flexible( + const SizedBox(width: 5.0), + ], + if (widget.broadcast.round.startsAt != null) + Expanded( child: Text( relativeDate(widget.broadcast.round.startsAt!), - style: TextStyle( - fontSize: 13, - color: subTitleColor, - ), + style: TextStyle(fontSize: 12, color: subTitleColor), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), - if (widget.broadcast.isLive) ...[ - const Spacer(flex: 3), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.circle, - size: 16, + if (widget.broadcast.isLive) ...[ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.circle, + size: 16, + color: liveColor, + shadows: const [ + Shadow( + color: Colors.black54, + offset: Offset(0, 1), + blurRadius: 2, + ), + ], + ), + const SizedBox(width: 4.0), + Text( + 'LIVE', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, color: liveColor, shadows: const [ Shadow( @@ -518,29 +505,14 @@ class _BroadcastCartState extends State { ), ], ), - const SizedBox(width: 4.0), - Text( - 'LIVE', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: liveColor, - shadows: const [ - Shadow( - color: Colors.black54, - offset: Offset(0, 1), - blurRadius: 2, - ), - ], - ), - overflow: TextOverflow.ellipsis, - ), - ], - ), - ], + overflow: TextOverflow.ellipsis, + ), + ], + ), ], - ), + ], ), + ), Padding( padding: kBroadcastGridItemContentPadding.add( const EdgeInsets.symmetric(vertical: 3.0), @@ -562,11 +534,7 @@ class _BroadcastCartState extends State { padding: kBroadcastGridItemContentPadding, child: Text( widget.broadcast.tour.information.players!, - style: TextStyle( - fontSize: 12, - color: subTitleColor, - letterSpacing: -0.2, - ), + style: TextStyle(fontSize: 12, color: subTitleColor, letterSpacing: -0.2), overflow: TextOverflow.ellipsis, maxLines: 1, ), @@ -624,10 +592,8 @@ Future _imageProviderToScaled(ImageProvider imageProvider) async { final bool rescale = width > maxDimension || height > maxDimension; if (rescale) { - paintWidth = - (width > height) ? maxDimension : (maxDimension / height) * width; - paintHeight = - (height > width) ? maxDimension : (maxDimension / width) * height; + paintWidth = (width > height) ? maxDimension : (maxDimension / height) * width; + paintHeight = (height > width) ? maxDimension : (maxDimension / width) * height; } final ui.PictureRecorder pictureRecorder = ui.PictureRecorder(); final Canvas canvas = Canvas(pictureRecorder); @@ -639,8 +605,7 @@ Future _imageProviderToScaled(ImageProvider imageProvider) async { ); final ui.Picture picture = pictureRecorder.endRecording(); - scaledImage = - await picture.toImage(paintWidth.toInt(), paintHeight.toInt()); + scaledImage = await picture.toImage(paintWidth.toInt(), paintHeight.toInt()); imageCompleter.complete(info.image); }, onError: (Object exception, StackTrace? stackTrace) { @@ -651,9 +616,7 @@ Future _imageProviderToScaled(ImageProvider imageProvider) async { loadFailureTimeout = Timer(const Duration(seconds: 5), () { stream.removeListener(listener); - imageCompleter.completeError( - TimeoutException('Timeout occurred trying to load image'), - ); + imageCompleter.completeError(TimeoutException('Timeout occurred trying to load image')); }); stream.addListener(listener); diff --git a/lib/src/view/broadcast/broadcast_overview_tab.dart b/lib/src/view/broadcast/broadcast_overview_tab.dart index 0208fed424..36b68cd26e 100644 --- a/lib/src/view/broadcast/broadcast_overview_tab.dart +++ b/lib/src/view/broadcast/broadcast_overview_tab.dart @@ -16,18 +16,15 @@ final _dateFormatter = DateFormat.MMMd(); /// A tab that displays the overview of a broadcast. class BroadcastOverviewTab extends ConsumerWidget { - const BroadcastOverviewTab({ - required this.broadcast, - required this.tournamentId, - super.key, - }); + const BroadcastOverviewTab({required this.broadcast, required this.tournamentId, super.key}); final Broadcast broadcast; final BroadcastTournamentId tournamentId; @override Widget build(BuildContext context, WidgetRef ref) { - final edgeInsets = MediaQuery.paddingOf(context) - + final edgeInsets = + MediaQuery.paddingOf(context) - (Theme.of(context).platform == TargetPlatform.iOS ? EdgeInsets.only(top: MediaQuery.paddingOf(context).top) : EdgeInsets.zero) + @@ -41,62 +38,51 @@ class BroadcastOverviewTab extends ConsumerWidget { return SliverPadding( padding: edgeInsets, sliver: SliverList( - delegate: SliverChildListDelegate( - [ - if (tournament.data.imageUrl != null) ...[ - Image.network(tournament.data.imageUrl!), - const SizedBox(height: 16.0), + delegate: SliverChildListDelegate([ + if (tournament.data.imageUrl != null) ...[ + Image.network(tournament.data.imageUrl!), + const SizedBox(height: 16.0), + ], + Wrap( + alignment: WrapAlignment.center, + children: [ + if (information.dates != null) + _BroadcastOverviewCard( + CupertinoIcons.calendar, + information.dates!.endsAt == null + ? _dateFormatter.format(information.dates!.startsAt) + : '${_dateFormatter.format(information.dates!.startsAt)} - ${_dateFormatter.format(information.dates!.endsAt!)}', + ), + if (information.format != null) + _BroadcastOverviewCard(Icons.emoji_events, '${information.format}'), + if (information.timeControl != null) + _BroadcastOverviewCard( + CupertinoIcons.stopwatch_fill, + '${information.timeControl}', + ), + if (information.location != null) + _BroadcastOverviewCard(Icons.public, '${information.location}'), + if (information.players != null) + _BroadcastOverviewCard(Icons.person, '${information.players}'), + if (information.website != null) + _BroadcastOverviewCard( + Icons.link, + context.l10n.broadcastOfficialWebsite, + information.website, + ), ], - Wrap( - alignment: WrapAlignment.center, - children: [ - if (information.dates != null) - _BroadcastOverviewCard( - CupertinoIcons.calendar, - information.dates!.endsAt == null - ? _dateFormatter.format(information.dates!.startsAt) - : '${_dateFormatter.format(information.dates!.startsAt)} - ${_dateFormatter.format(information.dates!.endsAt!)}', - ), - if (information.format != null) - _BroadcastOverviewCard( - Icons.emoji_events, - '${information.format}', - ), - if (information.timeControl != null) - _BroadcastOverviewCard( - CupertinoIcons.stopwatch_fill, - '${information.timeControl}', - ), - if (information.location != null) - _BroadcastOverviewCard( - Icons.public, - '${information.location}', - ), - if (information.players != null) - _BroadcastOverviewCard( - Icons.person, - '${information.players}', - ), - if (information.website != null) - _BroadcastOverviewCard( - Icons.link, - context.l10n.broadcastOfficialWebsite, - information.website, - ), - ], + ), + if (description != null) ...[ + const SizedBox(height: 16), + MarkdownBody( + data: description, + onTapLink: (text, url, title) { + if (url == null) return; + launchUrl(Uri.parse(url)); + }, ), - if (description != null) ...[ - const SizedBox(height: 16), - MarkdownBody( - data: description, - onTapLink: (text, url, title) { - if (url == null) return; - launchUrl(Uri.parse(url)); - }, - ), - ], ], - ), + ]), ), ); case AsyncError(:final error): @@ -137,20 +123,13 @@ class _BroadcastOverviewCard extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon( - iconData, - color: website != null - ? Theme.of(context).colorScheme.primary - : null, - ), + Icon(iconData, color: website != null ? Theme.of(context).colorScheme.primary : null), const SizedBox(width: 10), Flexible( child: Text( text, style: TextStyle( - color: website != null - ? Theme.of(context).colorScheme.primary - : null, + color: website != null ? Theme.of(context).colorScheme.primary : null, ), ), ), diff --git a/lib/src/view/broadcast/broadcast_player_results_screen.dart b/lib/src/view/broadcast/broadcast_player_results_screen.dart index d867b341c3..21e88e2326 100644 --- a/lib/src/view/broadcast/broadcast_player_results_screen.dart +++ b/lib/src/view/broadcast/broadcast_player_results_screen.dart @@ -36,17 +36,13 @@ class BroadcastPlayerResultsScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: BroadcastPlayerWidget(title: playerTitle, name: playerName), - ), + appBar: PlatformAppBar(title: BroadcastPlayerWidget(title: playerTitle, name: playerName)), body: _Body(tournamentId, playerId), ); } } -const _kTableRowPadding = EdgeInsets.symmetric( - vertical: 12.0, -); +const _kTableRowPadding = EdgeInsets.symmetric(vertical: 12.0); class _Body extends ConsumerWidget { final BroadcastTournamentId tournamentId; @@ -56,24 +52,17 @@ class _Body extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final playersResults = - ref.watch(broadcastPlayerResultProvider(tournamentId, playerId)); + final playersResults = ref.watch(broadcastPlayerResultProvider(tournamentId, playerId)); switch (playersResults) { case AsyncData(value: final playerResults): final player = playerResults.player; final fideData = playerResults.fideData; - final showRatingDiff = - playerResults.games.any((result) => result.ratingDiff != null); - final statWidth = (MediaQuery.sizeOf(context).width - - Styles.bodyPadding.horizontal - - 10 * 2) / - 3; + final showRatingDiff = playerResults.games.any((result) => result.ratingDiff != null); + final statWidth = + (MediaQuery.sizeOf(context).width - Styles.bodyPadding.horizontal - 10 * 2) / 3; const cardSpacing = 10.0; - final indexWidth = max( - 8.0 + playerResults.games.length.toString().length * 10.0, - 28.0, - ); + final indexWidth = max(8.0 + playerResults.games.length.toString().length * 10.0, 28.0); return ListView.builder( itemCount: playerResults.games.length + 1, @@ -129,9 +118,7 @@ class _Body extends ConsumerWidget { width: statWidth, child: StatCard( context.l10n.broadcastAgeThisYear, - value: - (DateTime.now().year - fideData.birthYear!) - .toString(), + value: (DateTime.now().year - fideData.birthYear!).toString(), ), ), if (player.federation != null) @@ -143,20 +130,15 @@ class _Body extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.network( - lichessFideFedSrc( - player.federation!, - ), + lichessFideFedSrc(player.federation!), height: 12, - httpClient: - ref.read(defaultClientProvider), + httpClient: ref.read(defaultClientProvider), ), const SizedBox(width: 5), Flexible( child: Text( federationIdToName[player.federation!]!, - style: const TextStyle( - fontSize: 18.0, - ), + style: const TextStyle(fontSize: 18.0), overflow: TextOverflow.ellipsis, ), ), @@ -167,10 +149,7 @@ class _Body extends ConsumerWidget { if (player.fideId != null) SizedBox( width: statWidth, - child: StatCard( - 'FIDE ID', - value: player.fideId!.toString(), - ), + child: StatCard('FIDE ID', value: player.fideId!.toString()), ), ], ), @@ -200,10 +179,7 @@ class _Body extends ConsumerWidget { width: statWidth, child: StatCard( context.l10n.broadcastRatingDiff, - child: ProgressionWidget( - player.ratingDiff!, - fontSize: 18.0, - ), + child: ProgressionWidget(player.ratingDiff!, fontSize: 18.0), ), ), ], @@ -219,21 +195,21 @@ class _Body extends ConsumerWidget { onTap: () { pushPlatformRoute( context, - builder: (context) => BroadcastGameScreen( - tournamentId: tournamentId, - roundId: playerResult.roundId, - gameId: playerResult.gameId, - ), + builder: + (context) => BroadcastGameScreen( + tournamentId: tournamentId, + roundId: playerResult.roundId, + gameId: playerResult.gameId, + ), ); }, child: ColoredBox( - color: Theme.of(context).platform == TargetPlatform.iOS - ? index.isEven - ? CupertinoColors.secondarySystemBackground - .resolveFrom(context) - : CupertinoColors.tertiarySystemBackground - .resolveFrom(context) - : index.isEven + color: + Theme.of(context).platform == TargetPlatform.iOS + ? index.isEven + ? CupertinoColors.secondarySystemBackground.resolveFrom(context) + : CupertinoColors.tertiarySystemBackground.resolveFrom(context) + : index.isEven ? Theme.of(context).colorScheme.surfaceContainerLow : Theme.of(context).colorScheme.surfaceContainerHigh, child: Padding( @@ -245,9 +221,7 @@ class _Body extends ConsumerWidget { child: Center( child: Text( index.toString(), - style: const TextStyle( - fontWeight: FontWeight.bold, - ), + style: const TextStyle(fontWeight: FontWeight.bold), ), ), ), @@ -261,13 +235,10 @@ class _Body extends ConsumerWidget { ), Expanded( flex: 3, - child: (playerResult.opponent.rating != null) - ? Center( - child: Text( - playerResult.opponent.rating.toString(), - ), - ) - : const SizedBox.shrink(), + child: + (playerResult.opponent.rating != null) + ? Center(child: Text(playerResult.opponent.rating.toString())) + : const SizedBox.shrink(), ), SizedBox( width: 30, @@ -276,24 +247,20 @@ class _Body extends ConsumerWidget { width: 15, height: 15, decoration: BoxDecoration( - border: (Theme.of(context).brightness == - Brightness.light && - playerResult.color == Side.white || - Theme.of(context).brightness == - Brightness.dark && - playerResult.color == Side.black) - ? Border.all( - width: 2.0, - color: - Theme.of(context).colorScheme.outline, - ) - : null, + border: + (Theme.of(context).brightness == Brightness.light && + playerResult.color == Side.white || + Theme.of(context).brightness == Brightness.dark && + playerResult.color == Side.black) + ? Border.all( + width: 2.0, + color: Theme.of(context).colorScheme.outline, + ) + : null, shape: BoxShape.circle, color: switch (playerResult.color) { - Side.white => - Colors.white.withValues(alpha: 0.9), - Side.black => - Colors.black.withValues(alpha: 0.9), + Side.white => Colors.white.withValues(alpha: 0.9), + Side.black => Colors.black.withValues(alpha: 0.9), }, ), ), @@ -307,17 +274,15 @@ class _Body extends ConsumerWidget { BroadcastPoints.one => '1', BroadcastPoints.half => '½', BroadcastPoints.zero => '0', - _ => '*' + _ => '*', }, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: switch (playerResult.points) { - BroadcastPoints.one => - context.lichessColors.good, - BroadcastPoints.zero => - context.lichessColors.error, - _ => null + BroadcastPoints.one => context.lichessColors.good, + BroadcastPoints.zero => context.lichessColors.error, + _ => null, }, ), ), @@ -326,12 +291,10 @@ class _Body extends ConsumerWidget { if (showRatingDiff) SizedBox( width: 38, - child: (playerResult.ratingDiff != null) - ? ProgressionWidget( - playerResult.ratingDiff!, - fontSize: 14, - ) - : null, + child: + (playerResult.ratingDiff != null) + ? ProgressionWidget(playerResult.ratingDiff!, fontSize: 14) + : null, ), ], ), diff --git a/lib/src/view/broadcast/broadcast_player_widget.dart b/lib/src/view/broadcast/broadcast_player_widget.dart index b2f081f63a..363cebcad6 100644 --- a/lib/src/view/broadcast/broadcast_player_widget.dart +++ b/lib/src/view/broadcast/broadcast_player_widget.dart @@ -36,27 +36,16 @@ class BroadcastPlayerWidget extends ConsumerWidget { Text( title!, style: TextStyle( - color: (title == 'BOT') - ? context.lichessColors.fancy - : context.lichessColors.brag, + color: (title == 'BOT') ? context.lichessColors.fancy : context.lichessColors.brag, fontWeight: FontWeight.bold, ), ), const SizedBox(width: 5), ], - Flexible( - child: Text( - name, - style: textStyle, - overflow: TextOverflow.ellipsis, - ), - ), + Flexible(child: Text(name, style: textStyle, overflow: TextOverflow.ellipsis)), if (rating != null) ...[ const SizedBox(width: 5), - Text( - rating.toString(), - overflow: TextOverflow.ellipsis, - ), + Text(rating.toString(), overflow: TextOverflow.ellipsis), ], ], ); diff --git a/lib/src/view/broadcast/broadcast_players_tab.dart b/lib/src/view/broadcast/broadcast_players_tab.dart index fb0255204d..713766979e 100644 --- a/lib/src/view/broadcast/broadcast_players_tab.dart +++ b/lib/src/view/broadcast/broadcast_players_tab.dart @@ -22,7 +22,8 @@ class BroadcastPlayersTab extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final edgeInsets = MediaQuery.paddingOf(context) - + final edgeInsets = + MediaQuery.paddingOf(context) - (Theme.of(context).platform == TargetPlatform.iOS ? EdgeInsets.only(top: MediaQuery.paddingOf(context).top) : EdgeInsets.zero) + @@ -32,16 +33,10 @@ class BroadcastPlayersTab extends ConsumerWidget { return switch (players) { AsyncData(value: final players) => PlayersList(players, tournamentId), AsyncError(:final error) => SliverPadding( - padding: edgeInsets, - sliver: SliverFillRemaining( - child: Center(child: Text('Cannot load players data: $error')), - ), - ), - _ => const SliverFillRemaining( - child: Center( - child: CircularProgressIndicator.adaptive(), - ), - ), + padding: edgeInsets, + sliver: SliverFillRemaining(child: Center(child: Text('Cannot load players data: $error'))), + ), + _ => const SliverFillRemaining(child: Center(child: CircularProgressIndicator.adaptive())), }; } } @@ -54,8 +49,7 @@ const _kTableRowPadding = EdgeInsets.symmetric( horizontal: _kTableRowHorizontalPadding, vertical: _kTableRowVerticalPadding, ); -const _kHeaderTextStyle = - TextStyle(fontWeight: FontWeight.bold, overflow: TextOverflow.ellipsis); +const _kHeaderTextStyle = TextStyle(fontWeight: FontWeight.bold, overflow: TextOverflow.ellipsis); class PlayersList extends ConsumerStatefulWidget { const PlayersList(this.players, this.tournamentId); @@ -75,25 +69,23 @@ class _PlayersListState extends ConsumerState { void sort(_SortingTypes newSort, {bool toggleReverse = false}) { final compare = switch (newSort) { _SortingTypes.player => - (BroadcastPlayerExtended a, BroadcastPlayerExtended b) => - a.name.compareTo(b.name), - _SortingTypes.elo => - (BroadcastPlayerExtended a, BroadcastPlayerExtended b) { - if (a.rating == null) return 1; - if (b.rating == null) return -1; - return b.rating!.compareTo(a.rating!); - }, - _SortingTypes.score => - (BroadcastPlayerExtended a, BroadcastPlayerExtended b) { - if (a.score == null) return 1; - if (b.score == null) return -1; - final value = b.score!.compareTo(a.score!); - if (value == 0) { - return a.played.compareTo(b.played); - } else { - return value; - } + (BroadcastPlayerExtended a, BroadcastPlayerExtended b) => a.name.compareTo(b.name), + _SortingTypes.elo => (BroadcastPlayerExtended a, BroadcastPlayerExtended b) { + if (a.rating == null) return 1; + if (b.rating == null) return -1; + return b.rating!.compareTo(a.rating!); + }, + _SortingTypes.score => (BroadcastPlayerExtended a, BroadcastPlayerExtended b) { + if (a.score == null) return 1; + if (b.score == null) return -1; + + final value = b.score!.compareTo(a.score!); + if (value == 0) { + return a.played.compareTo(b.played); + } else { + return value; } + }, }; setState(() { @@ -138,76 +130,70 @@ class _PlayersListState extends ConsumerState { Expanded( child: _TableTitleCell( title: Text(context.l10n.player, style: _kHeaderTextStyle), - onTap: () => sort( - _SortingTypes.player, - toggleReverse: currentSort == _SortingTypes.player, - ), - sortIcon: (currentSort == _SortingTypes.player) - ? (reverse - ? Icons.keyboard_arrow_up - : Icons.keyboard_arrow_down) - : null, + onTap: + () => sort( + _SortingTypes.player, + toggleReverse: currentSort == _SortingTypes.player, + ), + sortIcon: + (currentSort == _SortingTypes.player) + ? (reverse ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down) + : null, ), ), SizedBox( width: eloWidth, child: _TableTitleCell( title: const Text('Elo', style: _kHeaderTextStyle), - onTap: () => sort( - _SortingTypes.elo, - toggleReverse: currentSort == _SortingTypes.elo, - ), - sortIcon: (currentSort == _SortingTypes.elo) - ? (reverse - ? Icons.keyboard_arrow_up - : Icons.keyboard_arrow_down) - : null, + onTap: + () => + sort(_SortingTypes.elo, toggleReverse: currentSort == _SortingTypes.elo), + sortIcon: + (currentSort == _SortingTypes.elo) + ? (reverse ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down) + : null, ), ), SizedBox( width: scoreWidth, child: _TableTitleCell( - title: Text( - context.l10n.broadcastScore, - style: _kHeaderTextStyle, - ), - onTap: () => sort( - _SortingTypes.score, - toggleReverse: currentSort == _SortingTypes.score, - ), - sortIcon: (currentSort == _SortingTypes.score) - ? (reverse - ? Icons.keyboard_arrow_up - : Icons.keyboard_arrow_down) - : null, + title: Text(context.l10n.broadcastScore, style: _kHeaderTextStyle), + onTap: + () => sort( + _SortingTypes.score, + toggleReverse: currentSort == _SortingTypes.score, + ), + sortIcon: + (currentSort == _SortingTypes.score) + ? (reverse ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down) + : null, ), ), ], ); } else { final player = players[index - 1]; + return GestureDetector( onTap: () { pushPlatformRoute( context, - builder: (context) => BroadcastPlayerResultsScreen( - widget.tournamentId, - player.fideId != null - ? player.fideId.toString() - : player.name, - player.title, - player.name, - ), + builder: + (context) => BroadcastPlayerResultsScreen( + widget.tournamentId, + player.fideId != null ? player.fideId.toString() : player.name, + player.title, + player.name, + ), ); }, child: ColoredBox( - color: Theme.of(context).platform == TargetPlatform.iOS - ? index.isEven - ? CupertinoColors.secondarySystemBackground - .resolveFrom(context) - : CupertinoColors.tertiarySystemBackground - .resolveFrom(context) - : index.isEven + color: + Theme.of(context).platform == TargetPlatform.iOS + ? index.isEven + ? CupertinoColors.secondarySystemBackground.resolveFrom(context) + : CupertinoColors.tertiarySystemBackground.resolveFrom(context) + : index.isEven ? Theme.of(context).colorScheme.surfaceContainerLow : Theme.of(context).colorScheme.surfaceContainerHigh, child: Row( @@ -233,10 +219,7 @@ class _PlayersListState extends ConsumerState { Text(player.rating.toString()), const SizedBox(width: 5), if (player.ratingDiff != null) - ProgressionWidget( - player.ratingDiff!, - fontSize: 14, - ), + ProgressionWidget(player.ratingDiff!, fontSize: 14), ], ], ), @@ -246,14 +229,15 @@ class _PlayersListState extends ConsumerState { width: scoreWidth, child: Padding( padding: _kTableRowPadding, - child: (player.score != null) - ? Align( - alignment: Alignment.centerRight, - child: Text( - '${player.score!.toStringAsFixed((player.score! == player.score!.roundToDouble()) ? 0 : 1)} / ${player.played}', - ), - ) - : const SizedBox.shrink(), + child: + (player.score != null) + ? Align( + alignment: Alignment.centerRight, + child: Text( + '${player.score!.toStringAsFixed((player.score! == player.score!.roundToDouble()) ? 0 : 1)} / ${player.played}', + ), + ) + : null, ), ), ], @@ -267,11 +251,7 @@ class _PlayersListState extends ConsumerState { } class _TableTitleCell extends StatelessWidget { - const _TableTitleCell({ - required this.title, - required this.onTap, - this.sortIcon, - }); + const _TableTitleCell({required this.title, required this.onTap, this.sortIcon}); final Widget title; final void Function() onTap; @@ -289,16 +269,11 @@ class _TableTitleCell extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: title, - ), + Expanded(child: title), if (sortIcon != null) Text( String.fromCharCode(sortIcon!.codePoint), - style: _kHeaderTextStyle.copyWith( - fontSize: 16, - fontFamily: sortIcon!.fontFamily, - ), + style: _kHeaderTextStyle.copyWith(fontSize: 16, fontFamily: sortIcon!.fontFamily), ), ], ), diff --git a/lib/src/view/broadcast/broadcast_round_screen.dart b/lib/src/view/broadcast/broadcast_round_screen.dart index b98465cf54..cafba27587 100644 --- a/lib/src/view/broadcast/broadcast_round_screen.dart +++ b/lib/src/view/broadcast/broadcast_round_screen.dart @@ -106,45 +106,39 @@ class _BroadcastRoundScreenState extends ConsumerState Expanded( child: switch (asyncRound) { AsyncData(value: final _) => switch (selectedTab) { - _CupertinoView.overview => _TabView( - cupertinoTabSwitcher: tabSwitcher, - sliver: BroadcastOverviewTab( - broadcast: widget.broadcast, - tournamentId: _selectedTournamentId, - ), - ), - _CupertinoView.boards => _TabView( - cupertinoTabSwitcher: tabSwitcher, - sliver: switch (asyncTournament) { - AsyncData(:final value) => BroadcastBoardsTab( - tournamentId: _selectedTournamentId, - roundId: _selectedRoundId ?? value.defaultRoundId, - tournamentSlug: widget.broadcast.tour.slug, - ), - _ => const SliverFillRemaining( - child: SizedBox.shrink(), - ), - }, - ), - _CupertinoView.players => _TabView( - cupertinoTabSwitcher: tabSwitcher, - sliver: BroadcastPlayersTab( - tournamentId: _selectedTournamentId, - ), + _CupertinoView.overview => _TabView( + cupertinoTabSwitcher: tabSwitcher, + sliver: BroadcastOverviewTab( + broadcast: widget.broadcast, + tournamentId: _selectedTournamentId, + ), + ), + _CupertinoView.boards => _TabView( + cupertinoTabSwitcher: tabSwitcher, + sliver: switch (asyncTournament) { + AsyncData(:final value) => BroadcastBoardsTab( + tournamentId: _selectedTournamentId, + roundId: _selectedRoundId ?? value.defaultRoundId, + tournamentSlug: widget.broadcast.tour.slug, ), - }, - _ => const Center( - child: CircularProgressIndicator.adaptive(), + _ => const SliverFillRemaining(child: SizedBox.shrink()), + }, ), + _CupertinoView.players => _TabView( + cupertinoTabSwitcher: tabSwitcher, + sliver: BroadcastPlayersTab(tournamentId: _selectedTournamentId), + ), + }, + _ => const Center(child: CircularProgressIndicator.adaptive()), }, ), switch (asyncTournament) { AsyncData(:final value) => _BottomBar( - tournament: value, - roundId: _selectedRoundId ?? value.defaultRoundId, - setTournamentId: setTournamentId, - setRoundId: setRoundId, - ), + tournament: value, + roundId: _selectedRoundId ?? value.defaultRoundId, + setTournamentId: setTournamentId, + setRoundId: setRoundId, + ), _ => const BottomBar.empty(), }, ], @@ -176,44 +170,36 @@ class _BroadcastRoundScreenState extends ConsumerState ), body: switch (asyncRound) { AsyncData(value: final _) => TabBarView( - controller: _tabController, - children: [ - _TabView( - sliver: BroadcastOverviewTab( - broadcast: widget.broadcast, - tournamentId: _selectedTournamentId, - ), - ), - _TabView( - sliver: switch (asyncTournament) { - AsyncData(:final value) => BroadcastBoardsTab( - tournamentId: _selectedTournamentId, - roundId: _selectedRoundId ?? value.defaultRoundId, - tournamentSlug: widget.broadcast.tour.slug, - ), - _ => const SliverFillRemaining( - child: SizedBox.shrink(), - ), - }, + controller: _tabController, + children: [ + _TabView( + sliver: BroadcastOverviewTab( + broadcast: widget.broadcast, + tournamentId: _selectedTournamentId, ), - _TabView( - sliver: BroadcastPlayersTab( + ), + _TabView( + sliver: switch (asyncTournament) { + AsyncData(:final value) => BroadcastBoardsTab( tournamentId: _selectedTournamentId, + roundId: _selectedRoundId ?? value.defaultRoundId, + tournamentSlug: widget.broadcast.tour.slug, ), - ), - ], - ), - _ => const Center( - child: CircularProgressIndicator(), - ) + _ => const SliverFillRemaining(child: SizedBox.shrink()), + }, + ), + _TabView(sliver: BroadcastPlayersTab(tournamentId: _selectedTournamentId)), + ], + ), + _ => const Center(child: CircularProgressIndicator()), }, bottomNavigationBar: switch (asyncTournament) { AsyncData(:final value) => _BottomBar( - tournament: value, - roundId: _selectedRoundId ?? value.defaultRoundId, - setTournamentId: setTournamentId, - setRoundId: setRoundId, - ), + tournament: value, + roundId: _selectedRoundId ?? value.defaultRoundId, + setTournamentId: setTournamentId, + setRoundId: setRoundId, + ), _ => const BottomBar.empty(), }, ); @@ -221,8 +207,7 @@ class _BroadcastRoundScreenState extends ConsumerState @override Widget build(BuildContext context) { - final asyncTour = - ref.watch(broadcastTournamentProvider(_selectedTournamentId)); + final asyncTour = ref.watch(broadcastTournamentProvider(_selectedTournamentId)); const loadingRound = AsyncValue.loading(); @@ -231,15 +216,11 @@ class _BroadcastRoundScreenState extends ConsumerState // Eagerly initalize the round controller so it stays alive when switching tabs // and to know if the round has games to show final round = ref.watch( - broadcastRoundControllerProvider( - _selectedRoundId ?? tournament.defaultRoundId, - ), + broadcastRoundControllerProvider(_selectedRoundId ?? tournament.defaultRoundId), ); ref.listen( - broadcastRoundControllerProvider( - _selectedRoundId ?? tournament.defaultRoundId, - ), + broadcastRoundControllerProvider(_selectedRoundId ?? tournament.defaultRoundId), (_, round) { if (round.hasValue && !roundLoaded) { roundLoaded = true; @@ -255,27 +236,21 @@ class _BroadcastRoundScreenState extends ConsumerState ); return PlatformWidget( - androidBuilder: (context) => - _androidBuilder(context, asyncTour, round), + androidBuilder: (context) => _androidBuilder(context, asyncTour, round), iosBuilder: (context) => _iosBuilder(context, asyncTour, round), ); case _: return PlatformWidget( - androidBuilder: (context) => - _androidBuilder(context, asyncTour, loadingRound), - iosBuilder: (context) => - _iosBuilder(context, asyncTour, loadingRound), + androidBuilder: (context) => _androidBuilder(context, asyncTour, loadingRound), + iosBuilder: (context) => _iosBuilder(context, asyncTour, loadingRound), ); } } } class _TabView extends StatelessWidget { - const _TabView({ - required this.sliver, - this.cupertinoTabSwitcher, - }); + const _TabView({required this.sliver, this.cupertinoTabSwitcher}); final Widget sliver; final Widget? cupertinoTabSwitcher; @@ -287,8 +262,7 @@ class _TabView extends StatelessWidget { slivers: [ if (cupertinoTabSwitcher != null) SliverPadding( - padding: Styles.bodyPadding + - EdgeInsets.only(top: MediaQuery.paddingOf(context).top), + padding: Styles.bodyPadding + EdgeInsets.only(top: MediaQuery.paddingOf(context).top), sliver: SliverToBoxAdapter(child: cupertinoTabSwitcher), ), sliver, @@ -317,78 +291,73 @@ class _BottomBar extends ConsumerWidget { children: [ if (tournament.group != null) AdaptiveTextButton( - onPressed: () => showAdaptiveBottomSheet( - context: context, - showDragHandle: true, - isScrollControlled: true, - isDismissible: true, - builder: (_) => DraggableScrollableSheet( - initialChildSize: 0.4, - maxChildSize: 0.4, - minChildSize: 0.1, - snap: true, - expand: false, - builder: (context, scrollController) { - return _TournamentSelectorMenu( - tournament: tournament, - group: tournament.group!, - scrollController: scrollController, - setTournamentId: setTournamentId, - ); - }, - ), - ), + onPressed: + () => showAdaptiveBottomSheet( + context: context, + showDragHandle: true, + isScrollControlled: true, + isDismissible: true, + builder: + (_) => DraggableScrollableSheet( + initialChildSize: 0.4, + maxChildSize: 0.4, + minChildSize: 0.1, + snap: true, + expand: false, + builder: (context, scrollController) { + return _TournamentSelectorMenu( + tournament: tournament, + group: tournament.group!, + scrollController: scrollController, + setTournamentId: setTournamentId, + ); + }, + ), + ), child: Text( - tournament.group! - .firstWhere((g) => g.id == tournament.data.id) - .name, + tournament.group!.firstWhere((g) => g.id == tournament.data.id).name, maxLines: 1, overflow: TextOverflow.ellipsis, ), ), AdaptiveTextButton( - onPressed: () => showAdaptiveBottomSheet( - context: context, - showDragHandle: true, - isScrollControlled: true, - isDismissible: true, - builder: (_) => DraggableScrollableSheet( - initialChildSize: 0.6, - maxChildSize: 0.6, - snap: true, - expand: false, - builder: (context, scrollController) { - return _RoundSelectorMenu( - selectedRoundId: roundId, - rounds: tournament.rounds, - scrollController: scrollController, - setRoundId: setRoundId, - ); - }, - ), - ), + onPressed: + () => showAdaptiveBottomSheet( + context: context, + showDragHandle: true, + isScrollControlled: true, + isDismissible: true, + builder: + (_) => DraggableScrollableSheet( + initialChildSize: 0.6, + maxChildSize: 0.6, + snap: true, + expand: false, + builder: (context, scrollController) { + return _RoundSelectorMenu( + selectedRoundId: roundId, + rounds: tournament.rounds, + scrollController: scrollController, + setRoundId: setRoundId, + ); + }, + ), + ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Flexible( child: Text( - tournament.rounds - .firstWhere((round) => round.id == roundId) - .name, + tournament.rounds.firstWhere((round) => round.id == roundId).name, maxLines: 1, overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 5.0), - switch (tournament.rounds - .firstWhere((round) => round.id == roundId) - .status) { - RoundStatus.finished => - Icon(Icons.check, color: context.lichessColors.good), - RoundStatus.live => - Icon(Icons.circle, color: context.lichessColors.error), - RoundStatus.upcoming => - const Icon(Icons.calendar_month, color: Colors.grey), + switch (tournament.rounds.firstWhere((round) => round.id == roundId).status) { + RoundStatus.finished => Icon(Icons.check, color: context.lichessColors.good), + RoundStatus.live => Icon(Icons.circle, color: context.lichessColors.error), + RoundStatus.upcoming => const Icon(Icons.calendar_month, color: Colors.grey), }, ], ), @@ -426,10 +395,7 @@ class _RoundSelectorState extends ConsumerState<_RoundSelectorMenu> { // Scroll to the current round WidgetsBinding.instance.addPostFrameCallback((_) { if (currentRoundKey.currentContext != null) { - Scrollable.ensureVisible( - currentRoundKey.currentContext!, - alignment: 0.5, - ); + Scrollable.ensureVisible(currentRoundKey.currentContext!, alignment: 0.5); } }); @@ -447,32 +413,17 @@ class _RoundSelectorState extends ConsumerState<_RoundSelectorMenu> { if (round.startsAt != null || round.startsAfterPrevious) ...[ Text( round.startsAt != null - ? round.startsAt! - .difference(DateTime.now()) - .inDays - .abs() < - 30 + ? round.startsAt!.difference(DateTime.now()).inDays.abs() < 30 ? _dateFormatMonth.format(round.startsAt!) : _dateFormatYearMonth.format(round.startsAt!) - : context.l10n.broadcastStartsAfter( - widget.rounds[index - 1].name, - ), + : context.l10n.broadcastStartsAfter(widget.rounds[index - 1].name), ), const SizedBox(width: 5.0), ], switch (round.status) { - RoundStatus.finished => Icon( - Icons.check, - color: context.lichessColors.good, - ), - RoundStatus.live => Icon( - Icons.circle, - color: context.lichessColors.error, - ), - RoundStatus.upcoming => const Icon( - Icons.calendar_month, - color: Colors.grey, - ), + RoundStatus.finished => Icon(Icons.check, color: context.lichessColors.good), + RoundStatus.live => Icon(Icons.circle, color: context.lichessColors.error), + RoundStatus.upcoming => const Icon(Icons.calendar_month, color: Colors.grey), }, ], ), @@ -500,8 +451,7 @@ class _TournamentSelectorMenu extends ConsumerStatefulWidget { final void Function(BroadcastTournamentId) setTournamentId; @override - ConsumerState<_TournamentSelectorMenu> createState() => - _TournamentSelectorState(); + ConsumerState<_TournamentSelectorMenu> createState() => _TournamentSelectorState(); } class _TournamentSelectorState extends ConsumerState<_TournamentSelectorMenu> { @@ -512,10 +462,7 @@ class _TournamentSelectorState extends ConsumerState<_TournamentSelectorMenu> { // Scroll to the current tournament WidgetsBinding.instance.addPostFrameCallback((_) { if (currentTournamentKey.currentContext != null) { - Scrollable.ensureVisible( - currentTournamentKey.currentContext!, - alignment: 0.5, - ); + Scrollable.ensureVisible(currentTournamentKey.currentContext!, alignment: 0.5); } }); @@ -524,9 +471,7 @@ class _TournamentSelectorState extends ConsumerState<_TournamentSelectorMenu> { children: [ for (final tournament in widget.group) PlatformListTile( - key: tournament.id == widget.tournament.data.id - ? currentTournamentKey - : null, + key: tournament.id == widget.tournament.data.id ? currentTournamentKey : null, selected: tournament.id == widget.tournament.data.id, title: Text(tournament.name), onTap: () { diff --git a/lib/src/view/clock/clock_settings.dart b/lib/src/view/clock/clock_settings.dart index decea94991..3b3e44723f 100644 --- a/lib/src/view/clock/clock_settings.dart +++ b/lib/src/view/clock/clock_settings.dart @@ -24,9 +24,10 @@ class ClockSettings extends ConsumerWidget { ); return Padding( - padding: orientation == Orientation.portrait - ? const EdgeInsets.symmetric(vertical: 10.0) - : const EdgeInsets.symmetric(horizontal: 10.0), + padding: + orientation == Orientation.portrait + ? const EdgeInsets.symmetric(vertical: 10.0) + : const EdgeInsets.symmetric(horizontal: 10.0), child: (orientation == Orientation.portrait ? Row.new : Column.new)( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -34,64 +35,57 @@ class ClockSettings extends ConsumerWidget { IconButton( tooltip: context.l10n.reset, iconSize: _iconSize, - onPressed: buttonsEnabled - ? () { - ref.read(clockToolControllerProvider.notifier).reset(); - } - : null, + onPressed: + buttonsEnabled + ? () { + ref.read(clockToolControllerProvider.notifier).reset(); + } + : null, icon: const Icon(Icons.refresh), ), IconButton( tooltip: context.l10n.settingsSettings, iconSize: _iconSize, - onPressed: buttonsEnabled - ? () { - final double screenHeight = - MediaQuery.sizeOf(context).height; - showAdaptiveBottomSheet( - context: context, - isScrollControlled: true, - showDragHandle: true, - constraints: BoxConstraints( - maxHeight: screenHeight - (screenHeight / 10), - ), - builder: (BuildContext context) { - final options = ref.watch( - clockToolControllerProvider - .select((value) => value.options), - ); - return TimeControlModal( - excludeUltraBullet: true, - value: TimeIncrement( - options.whiteTime.inSeconds, - options.whiteIncrement.inSeconds, - ), - onSelected: (choice) { - ref - .read(clockToolControllerProvider.notifier) - .updateOptions(choice); - }, - ); - }, - ); - } - : null, + onPressed: + buttonsEnabled + ? () { + final double screenHeight = MediaQuery.sizeOf(context).height; + showAdaptiveBottomSheet( + context: context, + isScrollControlled: true, + showDragHandle: true, + constraints: BoxConstraints(maxHeight: screenHeight - (screenHeight / 10)), + builder: (BuildContext context) { + final options = ref.watch( + clockToolControllerProvider.select((value) => value.options), + ); + return TimeControlModal( + excludeUltraBullet: true, + value: TimeIncrement( + options.whiteTime.inSeconds, + options.whiteIncrement.inSeconds, + ), + onSelected: (choice) { + ref.read(clockToolControllerProvider.notifier).updateOptions(choice); + }, + ); + }, + ); + } + : null, icon: const Icon(Icons.settings), ), IconButton( iconSize: _iconSize, // TODO: translate tooltip: 'Toggle sound', - onPressed: () => ref - .read(generalPreferencesProvider.notifier) - .toggleSoundEnabled(), + onPressed: () => ref.read(generalPreferencesProvider.notifier).toggleSoundEnabled(), icon: Icon(isSoundEnabled ? Icons.volume_up : Icons.volume_off), ), IconButton( tooltip: context.l10n.close, iconSize: _iconSize, - onPressed: - buttonsEnabled ? () => Navigator.of(context).pop() : null, + onPressed: buttonsEnabled ? () => Navigator.of(context).pop() : null, icon: const Icon(Icons.home), ), ], diff --git a/lib/src/view/clock/clock_tool_screen.dart b/lib/src/view/clock/clock_tool_screen.dart index b9962a0802..be5b873708 100644 --- a/lib/src/view/clock/clock_tool_screen.dart +++ b/lib/src/view/clock/clock_tool_screen.dart @@ -79,18 +79,20 @@ class ClockTile extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final colorScheme = Theme.of(context).colorScheme; - final backgroundColor = clockState.isFlagged(playerType) - ? context.lichessColors.error - : !clockState.paused && clockState.isPlayersTurn(playerType) + final backgroundColor = + clockState.isFlagged(playerType) + ? context.lichessColors.error + : !clockState.paused && clockState.isPlayersTurn(playerType) ? colorScheme.primary : clockState.activeSide == playerType - ? colorScheme.secondaryContainer - : colorScheme.surfaceContainer; + ? colorScheme.secondaryContainer + : colorScheme.surfaceContainer; final clockStyle = ClockStyle( - textColor: clockState.activeSide == playerType - ? colorScheme.onSecondaryContainer - : colorScheme.onSurface, + textColor: + clockState.activeSide == playerType + ? colorScheme.onSecondaryContainer + : colorScheme.onSurface, activeTextColor: colorScheme.onPrimary, emergencyTextColor: Colors.white, backgroundColor: Colors.transparent, @@ -99,10 +101,7 @@ class ClockTile extends ConsumerWidget { ); return RotatedBox( - quarterTurns: - orientation == Orientation.portrait && position == TilePosition.top - ? 2 - : 0, + quarterTurns: orientation == Orientation.portrait && position == TilePosition.top ? 2 : 0, child: Stack( alignment: Alignment.center, fit: StackFit.expand, @@ -111,25 +110,22 @@ class ClockTile extends ConsumerWidget { color: backgroundColor, child: InkWell( splashFactory: NoSplash.splashFactory, - onTap: !clockState.started - ? () { - ref - .read(clockToolControllerProvider.notifier) - .setBottomPlayer( - position == TilePosition.bottom - ? Side.white - : Side.black, - ); - } - : null, - onTapDown: clockState.started && - clockState.isPlayersMoveAllowed(playerType) - ? (_) { - ref - .read(clockToolControllerProvider.notifier) - .onTap(playerType); - } - : null, + onTap: + !clockState.started + ? () { + ref + .read(clockToolControllerProvider.notifier) + .setBottomPlayer( + position == TilePosition.bottom ? Side.white : Side.black, + ); + } + : null, + onTapDown: + clockState.started && clockState.isPlayersMoveAllowed(playerType) + ? (_) { + ref.read(clockToolControllerProvider.notifier).onTap(playerType); + } + : null, child: Padding( padding: const EdgeInsets.all(40), child: Column( @@ -152,9 +148,10 @@ class ClockTile extends ConsumerWidget { }, ), secondChild: const Icon(Icons.flag), - crossFadeState: clockState.isFlagged(playerType) - ? CrossFadeState.showSecond - : CrossFadeState.showFirst, + crossFadeState: + clockState.isFlagged(playerType) + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, ), ), ], @@ -186,32 +183,31 @@ class ClockTile extends ConsumerWidget { iconSize: 32, icon: Icons.tune, color: clockStyle.textColor, - onTap: clockState.started - ? null - : () => showAdaptiveBottomSheet( + onTap: + clockState.started + ? null + : () => showAdaptiveBottomSheet( context: context, - builder: (BuildContext context) => - CustomClockSettings( - player: playerType, - clock: playerType == Side.white - ? TimeIncrement.fromDurations( - clockState.options.whiteTime, - clockState.options.whiteIncrement, - ) - : TimeIncrement.fromDurations( - clockState.options.blackTime, - clockState.options.blackIncrement, - ), - onSubmit: ( - Side player, - TimeIncrement clock, - ) { - Navigator.of(context).pop(); - ref - .read(clockToolControllerProvider.notifier) - .updateOptionsCustom(clock, player); - }, - ), + builder: + (BuildContext context) => CustomClockSettings( + player: playerType, + clock: + playerType == Side.white + ? TimeIncrement.fromDurations( + clockState.options.whiteTime, + clockState.options.whiteIncrement, + ) + : TimeIncrement.fromDurations( + clockState.options.blackTime, + clockState.options.blackIncrement, + ), + onSubmit: (Side player, TimeIncrement clock) { + Navigator.of(context).pop(); + ref + .read(clockToolControllerProvider.notifier) + .updateOptionsCustom(clock, player); + }, + ), ), ), ), diff --git a/lib/src/view/clock/custom_clock_settings.dart b/lib/src/view/clock/custom_clock_settings.dart index 3ac2172a1a..b1051d5732 100644 --- a/lib/src/view/clock/custom_clock_settings.dart +++ b/lib/src/view/clock/custom_clock_settings.dart @@ -10,11 +10,7 @@ import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/non_linear_slider.dart'; class CustomClockSettings extends StatefulWidget { - const CustomClockSettings({ - required this.onSubmit, - required this.player, - required this.clock, - }); + const CustomClockSettings({required this.onSubmit, required this.player, required this.clock}); final Side player; final TimeIncrement clock; @@ -50,10 +46,7 @@ class _CustomClockSettingsState extends State { child: FatButton( semanticsLabel: context.l10n.apply, child: Text(context.l10n.apply), - onPressed: () => widget.onSubmit( - widget.player, - TimeIncrement(time, increment), - ), + onPressed: () => widget.onSubmit(widget.player, TimeIncrement(time, increment)), ), ), ], @@ -84,29 +77,29 @@ class _PlayerTimeSlider extends StatelessWidget { value: clock.time, values: kAvailableTimesInSeconds, labelBuilder: _clockTimeLabel, - onChange: Theme.of(context).platform == TargetPlatform.iOS - ? (num value) { - updateTime(value.toInt()); - } - : null, + onChange: + Theme.of(context).platform == TargetPlatform.iOS + ? (num value) { + updateTime(value.toInt()); + } + : null, onChangeEnd: (num value) { updateTime(value.toInt()); }, ), ), PlatformListTile( - title: Text( - '${context.l10n.increment}: ${context.l10n.nbSeconds(clock.increment)}', - ), + title: Text('${context.l10n.increment}: ${context.l10n.nbSeconds(clock.increment)}'), subtitle: NonLinearSlider( value: clock.increment, values: kAvailableIncrementsInSeconds, labelBuilder: (num sec) => sec.toString(), - onChange: Theme.of(context).platform == TargetPlatform.iOS - ? (num value) { - updateIncrement(value.toInt()); - } - : null, + onChange: + Theme.of(context).platform == TargetPlatform.iOS + ? (num value) { + updateIncrement(value.toInt()); + } + : null, onChangeEnd: (num value) { updateIncrement(value.toInt()); }, diff --git a/lib/src/view/coordinate_training/coordinate_display.dart b/lib/src/view/coordinate_training/coordinate_display.dart index 9e4275b0ec..b9b9ede150 100644 --- a/lib/src/view/coordinate_training/coordinate_display.dart +++ b/lib/src/view/coordinate_training/coordinate_display.dart @@ -10,18 +10,14 @@ const double _kCurrCoordOpacity = 0.9; const double _kNextCoordOpacity = 0.7; class CoordinateDisplay extends ConsumerStatefulWidget { - const CoordinateDisplay({ - required this.currentCoord, - required this.nextCoord, - }); + const CoordinateDisplay({required this.currentCoord, required this.nextCoord}); final Square currentCoord; final Square nextCoord; @override - ConsumerState createState() => - CoordinateDisplayState(); + ConsumerState createState() => CoordinateDisplayState(); } class CoordinateDisplayState extends ConsumerState @@ -34,34 +30,27 @@ class CoordinateDisplayState extends ConsumerState late final Animation _scaleAnimation = Tween( begin: _kNextCoordScale, end: 1.0, - ).animate( - CurvedAnimation(parent: _controller, curve: Curves.linear), - ); + ).animate(CurvedAnimation(parent: _controller, curve: Curves.linear)); late final Animation _currCoordSlideInAnimation = Tween( begin: _kNextCoordFractionalTranslation, end: Offset.zero, - ).animate( - CurvedAnimation(parent: _controller, curve: Curves.linear), - ); + ).animate(CurvedAnimation(parent: _controller, curve: Curves.linear)); late final Animation _nextCoordSlideInAnimation = Tween( begin: const Offset(0.5, 0), end: Offset.zero, - ).animate( - CurvedAnimation(parent: _controller, curve: Curves.linear), - ); + ).animate(CurvedAnimation(parent: _controller, curve: Curves.linear)); late final Animation _currCoordOpacityAnimation = Tween( begin: _kNextCoordOpacity, end: _kCurrCoordOpacity, - ).animate( - CurvedAnimation(parent: _controller, curve: Curves.linear), - ); + ).animate(CurvedAnimation(parent: _controller, curve: Curves.linear)); - late final Animation _nextCoordFadeInAnimation = - Tween(begin: 0.0, end: _kNextCoordOpacity) - .animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn)); + late final Animation _nextCoordFadeInAnimation = Tween( + begin: 0.0, + end: _kNextCoordOpacity, + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn)); @override Widget build(BuildContext context) { @@ -73,13 +62,7 @@ class CoordinateDisplayState extends ConsumerState color: Colors.white.withValues(alpha: 0.9), fontWeight: FontWeight.bold, fontFeatures: [const FontFeature.tabularFigures()], - shadows: const [ - Shadow( - color: Colors.black, - offset: Offset(0, 5), - blurRadius: 40.0, - ), - ], + shadows: const [Shadow(color: Colors.black, offset: Offset(0, 5), blurRadius: 40.0)], ); return IgnorePointer( @@ -91,10 +74,7 @@ class CoordinateDisplayState extends ConsumerState position: _currCoordSlideInAnimation, child: ScaleTransition( scale: _scaleAnimation, - child: Text( - trainingState.currentCoord?.name ?? '', - style: textStyle, - ), + child: Text(trainingState.currentCoord?.name ?? '', style: textStyle), ), ), ), @@ -106,10 +86,7 @@ class CoordinateDisplayState extends ConsumerState translation: _kNextCoordFractionalTranslation, child: Transform.scale( scale: _kNextCoordScale, - child: Text( - trainingState.nextCoord?.name ?? '', - style: textStyle, - ), + child: Text(trainingState.nextCoord?.name ?? '', style: textStyle), ), ), ), diff --git a/lib/src/view/coordinate_training/coordinate_training_screen.dart b/lib/src/view/coordinate_training/coordinate_training_screen.dart index 02b9b3d7a7..f4d8e65b44 100644 --- a/lib/src/view/coordinate_training/coordinate_training_screen.dart +++ b/lib/src/view/coordinate_training/coordinate_training_screen.dart @@ -36,11 +36,11 @@ class CoordinateTrainingScreen extends StatelessWidget { AppBarIconButton( icon: const Icon(Icons.settings), semanticsLabel: context.l10n.settingsSettings, - onPressed: () => showAdaptiveBottomSheet( - context: context, - builder: (BuildContext context) => - const _CoordinateTrainingMenu(), - ), + onPressed: + () => showAdaptiveBottomSheet( + context: context, + builder: (BuildContext context) => const _CoordinateTrainingMenu(), + ), ), ], ), @@ -74,26 +74,26 @@ class _BodyState extends ConsumerState<_Body> { final IMap squareHighlights = { - if (trainingState.trainingActive) - if (trainingPrefs.mode == TrainingMode.findSquare) ...{ - if (highlightLastGuess != null) ...{ - highlightLastGuess!: SquareHighlight( - details: HighlightDetails( - solidColor: (trainingState.lastGuess == Guess.correct - ? context.lichessColors.good - : context.lichessColors.error) - .withValues(alpha: 0.5), + if (trainingState.trainingActive) + if (trainingPrefs.mode == TrainingMode.findSquare) ...{ + if (highlightLastGuess != null) ...{ + highlightLastGuess!: SquareHighlight( + details: HighlightDetails( + solidColor: (trainingState.lastGuess == Guess.correct + ? context.lichessColors.good + : context.lichessColors.error) + .withValues(alpha: 0.5), + ), + ), + }, + } else ...{ + trainingState.currentCoord!: SquareHighlight( + details: HighlightDetails( + solidColor: context.lichessColors.good.withValues(alpha: 0.5), + ), ), - ), - }, - } else ...{ - trainingState.currentCoord!: SquareHighlight( - details: HighlightDetails( - solidColor: context.lichessColors.good.withValues(alpha: 0.5), - ), - ), - }, - }.lock; + }, + }.lock; return SafeArea( bottom: false, @@ -106,16 +106,14 @@ class _BodyState extends ConsumerState<_Body> { final defaultBoardSize = constraints.biggest.shortestSide; final isTablet = isTabletOrLarger(context); - final remainingHeight = - constraints.maxHeight - defaultBoardSize; - final isSmallScreen = - remainingHeight < kSmallRemainingHeightLeftBoardThreshold; - final boardSize = isTablet || isSmallScreen - ? defaultBoardSize - kTabletBoardTableSidePadding * 2 - : defaultBoardSize; + final remainingHeight = constraints.maxHeight - defaultBoardSize; + final isSmallScreen = remainingHeight < kSmallRemainingHeightLeftBoardThreshold; + final boardSize = + isTablet || isSmallScreen + ? defaultBoardSize - kTabletBoardTableSidePadding * 2 + : defaultBoardSize; - final direction = - aspectRatio > 1 ? Axis.horizontal : Axis.vertical; + final direction = aspectRatio > 1 ? Axis.horizontal : Axis.vertical; return Flex( direction: direction, @@ -127,11 +125,11 @@ class _BodyState extends ConsumerState<_Body> { children: [ _TimeBar( maxWidth: boardSize, - timeFractionElapsed: - trainingState.timeFractionElapsed, - color: trainingState.lastGuess == Guess.incorrect - ? context.lichessColors.error - : context.lichessColors.good, + timeFractionElapsed: trainingState.timeFractionElapsed, + color: + trainingState.lastGuess == Guess.incorrect + ? context.lichessColors.error + : context.lichessColors.good, ), _TrainingBoard( boardSize: boardSize, @@ -146,11 +144,8 @@ class _BodyState extends ConsumerState<_Body> { _ScoreAndTrainingButton( scoreSize: boardSize / 8, score: trainingState.score, - onPressed: ref - .read( - coordinateTrainingControllerProvider.notifier, - ) - .abortTraining, + onPressed: + ref.read(coordinateTrainingControllerProvider.notifier).abortTraining, label: 'Abort Training', ) else if (trainingState.lastScore != null) @@ -159,12 +154,8 @@ class _BodyState extends ConsumerState<_Body> { score: trainingState.lastScore!, onPressed: () { ref - .read( - coordinateTrainingControllerProvider.notifier, - ) - .startTraining( - trainingPrefs.timeChoice.duration, - ); + .read(coordinateTrainingControllerProvider.notifier) + .startTraining(trainingPrefs.timeChoice.duration); }, label: 'New Training', ) @@ -174,13 +165,8 @@ class _BodyState extends ConsumerState<_Body> { child: _Button( onPressed: () { ref - .read( - coordinateTrainingControllerProvider - .notifier, - ) - .startTraining( - trainingPrefs.timeChoice.duration, - ); + .read(coordinateTrainingControllerProvider.notifier) + .startTraining(trainingPrefs.timeChoice.duration); }, label: 'Start Training', ), @@ -212,9 +198,7 @@ class _BodyState extends ConsumerState<_Body> { } void _onGuess(Square square) { - ref - .read(coordinateTrainingControllerProvider.notifier) - .guessCoordinate(square); + ref.read(coordinateTrainingControllerProvider.notifier).guessCoordinate(square); setState(() { highlightLastGuess = square; @@ -230,11 +214,7 @@ class _BodyState extends ConsumerState<_Body> { } class _TimeBar extends StatelessWidget { - const _TimeBar({ - required this.maxWidth, - required this.timeFractionElapsed, - required this.color, - }); + const _TimeBar({required this.maxWidth, required this.timeFractionElapsed, required this.color}); final double maxWidth; final double? timeFractionElapsed; @@ -247,9 +227,7 @@ class _TimeBar extends StatelessWidget { child: SizedBox( width: maxWidth * (timeFractionElapsed ?? 0.0), height: 15.0, - child: ColoredBox( - color: color, - ), + child: ColoredBox(color: color), ), ); } @@ -271,24 +249,18 @@ class _CoordinateTrainingMenu extends ConsumerWidget { children: [ Padding( padding: const EdgeInsets.all(8.0), - child: Text( - context.l10n.preferencesDisplay, - style: Styles.sectionTitle, - ), + child: Text(context.l10n.preferencesDisplay, style: Styles.sectionTitle), ), SwitchSettingTile( title: const Text('Show Coordinates'), value: trainingPrefs.showCoordinates, - onChanged: ref - .read(coordinateTrainingPreferencesProvider.notifier) - .setShowCoordinates, + onChanged: + ref.read(coordinateTrainingPreferencesProvider.notifier).setShowCoordinates, ), SwitchSettingTile( title: const Text('Show Pieces'), value: trainingPrefs.showPieces, - onChanged: ref - .read(coordinateTrainingPreferencesProvider.notifier) - .setShowPieces, + onChanged: ref.read(coordinateTrainingPreferencesProvider.notifier).setShowPieces, ), ], ), @@ -321,14 +293,12 @@ class _ScoreAndTrainingButton extends ConsumerWidget { _Score( score: score, size: scoreSize, - color: trainingState.lastGuess == Guess.incorrect - ? context.lichessColors.error - : context.lichessColors.good, - ), - _Button( - label: label, - onPressed: onPressed, + color: + trainingState.lastGuess == Guess.incorrect + ? context.lichessColors.error + : context.lichessColors.good, ), + _Button(label: label, onPressed: onPressed), ], ), ); @@ -336,11 +306,7 @@ class _ScoreAndTrainingButton extends ConsumerWidget { } class _Score extends StatelessWidget { - const _Score({ - required this.size, - required this.color, - required this.score, - }); + const _Score({required this.size, required this.color, required this.score}); final int score; @@ -351,16 +317,10 @@ class _Score extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.only( - top: 10.0, - left: 10.0, - right: 10.0, - ), + padding: const EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), child: Container( decoration: BoxDecoration( - borderRadius: const BorderRadius.all( - Radius.circular(4.0), - ), + borderRadius: const BorderRadius.all(Radius.circular(4.0)), color: color, ), width: size, @@ -368,10 +328,7 @@ class _Score extends StatelessWidget { child: Center( child: Text( score.toString(), - style: Styles.bold.copyWith( - color: Colors.white, - fontSize: 24.0, - ), + style: Styles.bold.copyWith(color: Colors.white, fontSize: 24.0), ), ), ), @@ -380,10 +337,7 @@ class _Score extends StatelessWidget { } class _Button extends StatelessWidget { - const _Button({ - required this.onPressed, - required this.label, - }); + const _Button({required this.onPressed, required this.label}); final VoidCallback onPressed; final String label; @@ -393,10 +347,7 @@ class _Button extends StatelessWidget { return FatButton( semanticsLabel: label, onPressed: onPressed, - child: Text( - label, - style: Styles.bold, - ), + child: Text(label, style: Styles.bold), ); } } @@ -420,11 +371,7 @@ class SettingsBottomSheet extends ConsumerWidget { choiceLabel: (choice) => Text(choice.label(context.l10n)), onSelected: (choice, selected) { if (selected) { - ref - .read( - coordinateTrainingPreferencesProvider.notifier, - ) - .setSideChoice(choice); + ref.read(coordinateTrainingPreferencesProvider.notifier).setSideChoice(choice); } }, ), @@ -440,11 +387,7 @@ class SettingsBottomSheet extends ConsumerWidget { choiceLabel: (choice) => choice.label(context.l10n), onSelected: (choice, selected) { if (selected) { - ref - .read( - coordinateTrainingPreferencesProvider.notifier, - ) - .setTimeChoice(choice); + ref.read(coordinateTrainingPreferencesProvider.notifier).setTimeChoice(choice); } }, ), @@ -490,29 +433,25 @@ class _TrainingBoardState extends ConsumerState<_TrainingBoard> { children: [ ChessboardEditor( size: widget.boardSize, - pieces: readFen( - trainingPrefs.showPieces ? kInitialFEN : kEmptyFEN, - ), + pieces: readFen(trainingPrefs.showPieces ? kInitialFEN : kEmptyFEN), squareHighlights: widget.squareHighlights, orientation: widget.orientation, settings: boardPrefs.toBoardSettings().copyWith( - enableCoordinates: trainingPrefs.showCoordinates, - borderRadius: widget.isTablet + enableCoordinates: trainingPrefs.showCoordinates, + borderRadius: + widget.isTablet ? const BorderRadius.all(Radius.circular(4.0)) : BorderRadius.zero, - boxShadow: - widget.isTablet ? boardShadows : const [], - ), + boxShadow: widget.isTablet ? boardShadows : const [], + ), pointerMode: EditorPointerMode.edit, onEditedSquare: (square) { - if (trainingState.trainingActive && - trainingPrefs.mode == TrainingMode.findSquare) { + if (trainingState.trainingActive && trainingPrefs.mode == TrainingMode.findSquare) { widget.onGuess(square); } }, ), - if (trainingState.trainingActive && - trainingPrefs.mode == TrainingMode.findSquare) + if (trainingState.trainingActive && trainingPrefs.mode == TrainingMode.findSquare) CoordinateDisplay( currentCoord: trainingState.currentCoord!, nextCoord: trainingState.nextCoord!, @@ -557,21 +496,13 @@ Future _coordinateTrainingInfoDialogBuilder(BuildContext context) { text: ' • You can analyse a game more effectively if you can quickly recognise coordinates.\n', ), - TextSpan( - text: '\n', - ), - TextSpan( - text: 'Find Square\n', - style: TextStyle(fontWeight: FontWeight.bold), - ), + TextSpan(text: '\n'), + TextSpan(text: 'Find Square\n', style: TextStyle(fontWeight: FontWeight.bold)), TextSpan( text: 'A coordinate appears on the board and you must click on the corresponding square.\n', ), - TextSpan( - text: - 'You have 30 seconds to correctly map as many squares as possible!\n', - ), + TextSpan(text: 'You have 30 seconds to correctly map as many squares as possible!\n'), ], ), ), diff --git a/lib/src/view/correspondence/offline_correspondence_game_screen.dart b/lib/src/view/correspondence/offline_correspondence_game_screen.dart index c72c357b11..472a6ed00b 100644 --- a/lib/src/view/correspondence/offline_correspondence_game_screen.dart +++ b/lib/src/view/correspondence/offline_correspondence_game_screen.dart @@ -27,20 +27,15 @@ import 'package:lichess_mobile/src/widgets/buttons.dart'; import 'package:lichess_mobile/src/widgets/platform_scaffold.dart'; class OfflineCorrespondenceGameScreen extends StatefulWidget { - const OfflineCorrespondenceGameScreen({ - required this.initialGame, - super.key, - }); + const OfflineCorrespondenceGameScreen({required this.initialGame, super.key}); final (DateTime, OfflineCorrespondenceGame) initialGame; @override - State createState() => - _OfflineCorrespondenceGameScreenState(); + State createState() => _OfflineCorrespondenceGameScreenState(); } -class _OfflineCorrespondenceGameScreenState - extends State { +class _OfflineCorrespondenceGameScreenState extends State { late (DateTime, OfflineCorrespondenceGame) currentGame; @override @@ -60,11 +55,7 @@ class _OfflineCorrespondenceGameScreenState final (lastModified, game) = currentGame; return PlatformScaffold( appBar: PlatformAppBar(title: _Title(game)), - body: _Body( - game: game, - lastModified: lastModified, - onGameChanged: goToNextGame, - ), + body: _Body(game: game, lastModified: lastModified, onGameChanged: goToNextGame), ); } } @@ -75,20 +66,14 @@ class _Title extends StatelessWidget { @override Widget build(BuildContext context) { - final mode = - game.rated ? ' • ${context.l10n.rated}' : ' • ${context.l10n.casual}'; + final mode = game.rated ? ' • ${context.l10n.rated}' : ' • ${context.l10n.casual}'; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - game.perf.icon, - color: DefaultTextStyle.of(context).style.color, - ), + Icon(game.perf.icon, color: DefaultTextStyle.of(context).style.color), const SizedBox(width: 4.0), if (game.daysPerTurn != null) - Text( - '${context.l10n.nbDays(game.daysPerTurn!)}$mode', - ) + Text('${context.l10n.nbDays(game.daysPerTurn!)}$mode') else Text('∞$mode'), ], @@ -97,11 +82,7 @@ class _Title extends StatelessWidget { } class _Body extends ConsumerStatefulWidget { - const _Body({ - required this.game, - required this.lastModified, - required this.onGameChanged, - }); + const _Body({required this.game, required this.lastModified, required this.onGameChanged}); final OfflineCorrespondenceGame game; final DateTime lastModified; @@ -143,13 +124,10 @@ class _BodyState extends ConsumerState<_Body> { @override Widget build(BuildContext context) { final materialDifference = ref.watch( - boardPreferencesProvider.select( - (prefs) => prefs.materialDifferenceFormat, - ), + boardPreferencesProvider.select((prefs) => prefs.materialDifferenceFormat), ); - final offlineOngoingGames = - ref.watch(offlineOngoingCorrespondenceGamesProvider); + final offlineOngoingGames = ref.watch(offlineOngoingCorrespondenceGamesProvider); final position = game.positionAt(stepCursor); final sideToMove = position.turn; @@ -157,48 +135,39 @@ class _BodyState extends ConsumerState<_Body> { final black = GamePlayer( player: game.black, - materialDiff: materialDifference.visible - ? game.materialDiffAt(stepCursor, Side.black) - : null, + materialDiff: materialDifference.visible ? game.materialDiffAt(stepCursor, Side.black) : null, materialDifferenceFormat: materialDifference, shouldLinkToUserProfile: false, mePlaying: youAre == Side.black, - confirmMoveCallbacks: youAre == Side.black && moveToConfirm != null - ? ( - confirm: confirmMove, - cancel: cancelMove, - ) - : null, - clock: youAre == Side.black && - game.estimatedTimeLeft(Side.black, widget.lastModified) != null - ? CorrespondenceClock( - duration: - game.estimatedTimeLeft(Side.black, widget.lastModified)!, - active: activeClockSide == Side.black, - ) - : null, + confirmMoveCallbacks: + youAre == Side.black && moveToConfirm != null + ? (confirm: confirmMove, cancel: cancelMove) + : null, + clock: + youAre == Side.black && game.estimatedTimeLeft(Side.black, widget.lastModified) != null + ? CorrespondenceClock( + duration: game.estimatedTimeLeft(Side.black, widget.lastModified)!, + active: activeClockSide == Side.black, + ) + : null, ); final white = GamePlayer( player: game.white, - materialDiff: materialDifference.visible - ? game.materialDiffAt(stepCursor, Side.white) - : null, + materialDiff: materialDifference.visible ? game.materialDiffAt(stepCursor, Side.white) : null, materialDifferenceFormat: materialDifference, shouldLinkToUserProfile: false, mePlaying: youAre == Side.white, - confirmMoveCallbacks: youAre == Side.white && moveToConfirm != null - ? ( - confirm: confirmMove, - cancel: cancelMove, - ) - : null, - clock: game.estimatedTimeLeft(Side.white, widget.lastModified) != null - ? CorrespondenceClock( - duration: - game.estimatedTimeLeft(Side.white, widget.lastModified)!, - active: activeClockSide == Side.white, - ) - : null, + confirmMoveCallbacks: + youAre == Side.white && moveToConfirm != null + ? (confirm: confirmMove, cancel: cancelMove) + : null, + clock: + game.estimatedTimeLeft(Side.white, widget.lastModified) != null + ? CorrespondenceClock( + duration: game.estimatedTimeLeft(Side.white, widget.lastModified)!, + active: activeClockSide == Side.white, + ) + : null, ); final topPlayer = youAre == Side.white ? black : white; @@ -214,17 +183,15 @@ class _BodyState extends ConsumerState<_Body> { fen: position.fen, lastMove: game.moveAt(stepCursor) as NormalMove?, gameData: GameData( - playerSide: game.playable && !isReplaying - ? youAre == Side.white - ? PlayerSide.white - : PlayerSide.black - : PlayerSide.none, + playerSide: + game.playable && !isReplaying + ? youAre == Side.white + ? PlayerSide.white + : PlayerSide.black + : PlayerSide.none, isCheck: position.isCheck, sideToMove: sideToMove, - validMoves: makeLegalMoves( - position, - isChess960: game.variant == Variant.chess960, - ), + validMoves: makeLegalMoves(position, isChess960: game.variant == Variant.chess960), promotionMove: promotionMove, onMove: (move, {isDrop, captured}) { onUserMove(move); @@ -233,10 +200,7 @@ class _BodyState extends ConsumerState<_Body> { ), topTable: topPlayer, bottomTable: bottomPlayer, - moves: game.steps - .skip(1) - .map((e) => e.sanMove!.san) - .toList(growable: false), + moves: game.steps.skip(1).map((e) => e.sanMove!.san).toList(growable: false), currentMoveIndex: stepCursor, onSelectMove: (moveIndex) { // ref.read(ctrlProvider.notifier).cursorAt(moveIndex); @@ -260,17 +224,18 @@ class _BodyState extends ConsumerState<_Body> { onTap: () { pushPlatformRoute( context, - builder: (_) => AnalysisScreen( - options: AnalysisOptions( - orientation: game.youAre, - standalone: ( - pgn: game.makePgn(), - isComputerAnalysisAllowed: false, - variant: game.variant, + builder: + (_) => AnalysisScreen( + options: AnalysisOptions( + orientation: game.youAre, + standalone: ( + pgn: game.makePgn(), + isComputerAnalysisAllowed: false, + variant: game.variant, + ), + initialMoveCursor: stepCursor, + ), ), - initialMoveCursor: stepCursor, - ), - ), ); }, icon: Icons.biotech, @@ -285,8 +250,8 @@ class _BodyState extends ConsumerState<_Body> { .firstWhereOrNull((g) => g.$2.isMyTurn); return nextTurn != null ? () { - widget.onGameChanged(nextTurn); - } + widget.onGameChanged(nextTurn); + } : null; }, orElse: () => null, @@ -294,18 +259,17 @@ class _BodyState extends ConsumerState<_Body> { ), BottomBarButton( label: context.l10n.mobileCorrespondenceClearSavedMove, - onTap: game.registeredMoveAtPgn != null - ? () { - showConfirmDialog( - context, - title: Text( - context.l10n.mobileCorrespondenceClearSavedMove, - ), - isDestructiveAction: true, - onConfirm: (_) => deleteRegisteredMove(), - ); - } - : null, + onTap: + game.registeredMoveAtPgn != null + ? () { + showConfirmDialog( + context, + title: Text(context.l10n.mobileCorrespondenceClearSavedMove), + isDestructiveAction: true, + onConfirm: (_) => deleteRegisteredMove(), + ); + } + : null, icon: Icons.save, ), RepeatButton( @@ -370,9 +334,7 @@ class _BodyState extends ConsumerState<_Body> { setState(() { moveToConfirm = (game.sanMoves, move); - game = game.copyWith( - steps: game.steps.add(newStep), - ); + game = game.copyWith(steps: game.steps.add(newStep)); promotionMove = null; stepCursor = stepCursor + 1; }); @@ -395,9 +357,7 @@ class _BodyState extends ConsumerState<_Body> { Future confirmMove() async { setState(() { - game = game.copyWith( - registeredMoveAtPgn: (moveToConfirm!.$1, moveToConfirm!.$2), - ); + game = game.copyWith(registeredMoveAtPgn: (moveToConfirm!.$1, moveToConfirm!.$2)); moveToConfirm = null; }); @@ -409,19 +369,14 @@ class _BodyState extends ConsumerState<_Body> { setState(() { moveToConfirm = null; stepCursor = stepCursor - 1; - game = game.copyWith( - steps: game.steps.removeLast(), - ); + game = game.copyWith(steps: game.steps.removeLast()); }); } Future deleteRegisteredMove() async { setState(() { stepCursor = stepCursor - 1; - game = game.copyWith( - steps: game.steps.removeLast(), - registeredMoveAtPgn: null, - ); + game = game.copyWith(steps: game.steps.removeLast(), registeredMoveAtPgn: null); }); final storage = await ref.read(correspondenceGameStorageProvider.future); diff --git a/lib/src/view/engine/engine_depth.dart b/lib/src/view/engine/engine_depth.dart index 83cec2197f..e9a03a036d 100644 --- a/lib/src/view/engine/engine_depth.dart +++ b/lib/src/view/engine/engine_depth.dart @@ -18,62 +18,60 @@ class EngineDepth extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final depth = ref.watch( - engineEvaluationProvider.select((value) => value.eval?.depth), - ) ?? + final depth = + ref.watch(engineEvaluationProvider.select((value) => value.eval?.depth)) ?? defaultEval?.depth; return depth != null ? AppBarTextButton( - onPressed: () { - showPopover( - context: context, - bodyBuilder: (context) { - return _StockfishInfo(defaultEval); - }, - direction: PopoverDirection.top, - width: 240, - backgroundColor: + onPressed: () { + showPopover( + context: context, + bodyBuilder: (context) { + return _StockfishInfo(defaultEval); + }, + direction: PopoverDirection.top, + width: 240, + backgroundColor: + Theme.of(context).platform == TargetPlatform.android + ? DialogTheme.of(context).backgroundColor ?? + Theme.of(context).colorScheme.surfaceContainerHigh + : CupertinoDynamicColor.resolve( + CupertinoColors.tertiarySystemBackground, + context, + ), + transitionDuration: Duration.zero, + popoverTransitionBuilder: (_, child) => child, + ); + }, + child: RepaintBoundary( + child: Container( + width: 20.0, + height: 20.0, + padding: const EdgeInsets.all(2.0), + decoration: BoxDecoration( + color: Theme.of(context).platform == TargetPlatform.android - ? DialogTheme.of(context).backgroundColor ?? - Theme.of(context).colorScheme.surfaceContainerHigh - : CupertinoDynamicColor.resolve( - CupertinoColors.tertiarySystemBackground, - context, - ), - transitionDuration: Duration.zero, - popoverTransitionBuilder: (_, child) => child, - ); - }, - child: RepaintBoundary( - child: Container( - width: 20.0, - height: 20.0, - padding: const EdgeInsets.all(2.0), - decoration: BoxDecoration( - color: Theme.of(context).platform == TargetPlatform.android - ? Theme.of(context).colorScheme.secondary - : CupertinoTheme.of(context).primaryColor, - borderRadius: BorderRadius.circular(4.0), - ), - child: FittedBox( - fit: BoxFit.contain, - child: Text( - '${math.min(99, depth)}', - style: TextStyle( - color: Theme.of(context).platform == - TargetPlatform.android - ? Theme.of(context).colorScheme.onSecondary - : CupertinoTheme.of(context).primaryContrastingColor, - fontFeatures: const [ - FontFeature.tabularFigures(), - ], - ), + ? Theme.of(context).colorScheme.secondary + : CupertinoTheme.of(context).primaryColor, + borderRadius: BorderRadius.circular(4.0), + ), + child: FittedBox( + fit: BoxFit.contain, + child: Text( + '${math.min(99, depth)}', + style: TextStyle( + color: + Theme.of(context).platform == TargetPlatform.android + ? Theme.of(context).colorScheme.onSecondary + : CupertinoTheme.of(context).primaryContrastingColor, + fontFeatures: const [FontFeature.tabularFigures()], ), ), ), ), - ) + ), + ) : const SizedBox.shrink(); } } @@ -85,29 +83,22 @@ class _StockfishInfo extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final (engineName: engineName, eval: eval, state: engineState) = - ref.watch(engineEvaluationProvider); + final (engineName: engineName, eval: eval, state: engineState) = ref.watch( + engineEvaluationProvider, + ); final currentEval = eval ?? defaultEval; - final knps = engineState == EngineState.computing - ? ', ${eval?.knps.round()}kn/s' - : ''; + final knps = engineState == EngineState.computing ? ', ${eval?.knps.round()}kn/s' : ''; final depth = currentEval?.depth ?? 0; return Column( mainAxisSize: MainAxisSize.min, children: [ PlatformListTile( - leading: Image.asset( - 'assets/images/stockfish/icon.png', - width: 44, - height: 44, - ), + leading: Image.asset('assets/images/stockfish/icon.png', width: 44, height: 44), title: Text(engineName), - subtitle: Text( - context.l10n.depthX('$depth$knps'), - ), + subtitle: Text(context.l10n.depthX('$depth$knps')), ), ], ); diff --git a/lib/src/view/engine/engine_gauge.dart b/lib/src/view/engine/engine_gauge.dart index 0da958228c..438cd880d2 100644 --- a/lib/src/view/engine/engine_gauge.dart +++ b/lib/src/view/engine/engine_gauge.dart @@ -14,29 +14,24 @@ const Color _kEvalGaugeBackgroundColor = Color(0xFF444444); const Color _kEvalGaugeValueColorDarkBg = Color(0xEEEEEEEE); const Color _kEvalGaugeValueColorLightBg = Color(0xFFFFFFFF); -enum EngineGaugeDisplayMode { - vertical, - horizontal, -} +enum EngineGaugeDisplayMode { vertical, horizontal } -typedef EngineGaugeParams = ({ - bool isLocalEngineAvailable, +typedef EngineGaugeParams = + ({ + bool isLocalEngineAvailable, - /// Only used for vertical display mode. - Side orientation, + /// Only used for vertical display mode. + Side orientation, - /// Position to evaluate. - Position position, + /// Position to evaluate. + Position position, - /// Saved evaluation to display when the current evaluation is not available. - Eval? savedEval, -}); + /// Saved evaluation to display when the current evaluation is not available. + Eval? savedEval, + }); class EngineGauge extends ConsumerWidget { - const EngineGauge({ - required this.displayMode, - required this.params, - }); + const EngineGauge({required this.displayMode, required this.params}); final EngineGaugeDisplayMode displayMode; @@ -46,8 +41,8 @@ class EngineGauge extends ConsumerWidget { Theme.of(context).platform == TargetPlatform.iOS ? _kEvalGaugeBackgroundColor : brightness == Brightness.dark - ? lighten(Theme.of(context).colorScheme.surface, .07) - : lighten(Theme.of(context).colorScheme.onSurface, .17); + ? lighten(Theme.of(context).colorScheme.surface, .07) + : lighten(Theme.of(context).colorScheme.onSurface, .17); static Color valueColor(BuildContext context, Brightness brightness) => Theme.of(context).platform == TargetPlatform.iOS @@ -55,34 +50,33 @@ class EngineGauge extends ConsumerWidget { ? _kEvalGaugeValueColorDarkBg : _kEvalGaugeValueColorLightBg : brightness == Brightness.dark - ? darken(Theme.of(context).colorScheme.onSurface, .03) - : darken(Theme.of(context).colorScheme.surface, .01); + ? darken(Theme.of(context).colorScheme.onSurface, .03) + : darken(Theme.of(context).colorScheme.surface, .01); @override Widget build(BuildContext context, WidgetRef ref) { - final localEval = params.isLocalEngineAvailable - ? ref.watch(engineEvaluationProvider).eval - : null; + final localEval = + params.isLocalEngineAvailable ? ref.watch(engineEvaluationProvider).eval : null; return localEval != null ? _EvalGauge( - displayMode: displayMode, - position: params.position, - orientation: params.orientation, - eval: localEval, - ) + displayMode: displayMode, + position: params.position, + orientation: params.orientation, + eval: localEval, + ) : params.savedEval != null - ? _EvalGauge( - displayMode: displayMode, - position: params.position, - orientation: params.orientation, - eval: params.savedEval, - ) - : _EvalGauge( - displayMode: displayMode, - position: params.position, - orientation: params.orientation, - ); + ? _EvalGauge( + displayMode: displayMode, + position: params.position, + orientation: params.orientation, + eval: params.savedEval, + ) + : _EvalGauge( + displayMode: displayMode, + position: params.position, + orientation: params.orientation, + ); } } @@ -121,23 +115,25 @@ class _EvalGaugeState extends ConsumerState<_EvalGauge> { final brightness = ref.watch(currentBrightnessProvider); final TextDirection textDirection = Directionality.of(context); - final evalDisplay = widget.position.outcome != null - ? widget.position.outcome!.winner == null - ? widget.position.isStalemate - ? context.l10n.stalemate - : context.l10n.insufficientMaterial - : widget.position.isCheckmate + final evalDisplay = + widget.position.outcome != null + ? widget.position.outcome!.winner == null + ? widget.position.isStalemate + ? context.l10n.stalemate + : context.l10n.insufficientMaterial + : widget.position.isCheckmate ? context.l10n.checkmate : context.l10n.variantEnding - : widget.eval?.evalString; + : widget.eval?.evalString; - final toValue = widget.position.outcome != null - ? widget.position.outcome!.winner == null - ? 0.5 - : widget.position.outcome!.winner == Side.white + final toValue = + widget.position.outcome != null + ? widget.position.outcome!.winner == null + ? 0.5 + : widget.position.outcome!.winner == Side.white ? 1.0 : 0.0 - : widget.animationValue; + : widget.animationValue; return TweenAnimationBuilder( tween: Tween(begin: fromValue, end: toValue), @@ -149,56 +145,44 @@ class _EvalGaugeState extends ConsumerState<_EvalGauge> { value: evalDisplay ?? context.l10n.loadingEngine, child: RepaintBoundary( child: Container( - constraints: widget.displayMode == EngineGaugeDisplayMode.vertical - ? const BoxConstraints( - minWidth: kEvalGaugeSize, - minHeight: double.infinity, - ) - : const BoxConstraints( - minWidth: double.infinity, - minHeight: kEvalGaugeSize, - ), - width: widget.displayMode == EngineGaugeDisplayMode.vertical - ? kEvalGaugeSize - : null, - height: widget.displayMode == EngineGaugeDisplayMode.vertical - ? null - : kEvalGaugeSize, + constraints: + widget.displayMode == EngineGaugeDisplayMode.vertical + ? const BoxConstraints(minWidth: kEvalGaugeSize, minHeight: double.infinity) + : const BoxConstraints(minWidth: double.infinity, minHeight: kEvalGaugeSize), + width: widget.displayMode == EngineGaugeDisplayMode.vertical ? kEvalGaugeSize : null, + height: widget.displayMode == EngineGaugeDisplayMode.vertical ? null : kEvalGaugeSize, child: CustomPaint( - painter: widget.displayMode == EngineGaugeDisplayMode.vertical - ? _EvalGaugeVerticalPainter( - orientation: widget.orientation, - backgroundColor: - EngineGauge.backgroundColor(context, brightness), - valueColor: EngineGauge.valueColor(context, brightness), - value: value, - ) - : _EvalGaugeHorizontalPainter( - backgroundColor: - EngineGauge.backgroundColor(context, brightness), - valueColor: EngineGauge.valueColor(context, brightness), - value: value, - textDirection: textDirection, - ), - child: widget.displayMode == EngineGaugeDisplayMode.vertical - ? const SizedBox.shrink() - : Align( - alignment: toValue >= 0.5 - ? Alignment.centerLeft - : Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: Text( - evalDisplay ?? '', - style: TextStyle( - color: - toValue >= 0.5 ? Colors.black : Colors.white, - fontSize: kEvalGaugeFontSize, - fontWeight: FontWeight.bold, + painter: + widget.displayMode == EngineGaugeDisplayMode.vertical + ? _EvalGaugeVerticalPainter( + orientation: widget.orientation, + backgroundColor: EngineGauge.backgroundColor(context, brightness), + valueColor: EngineGauge.valueColor(context, brightness), + value: value, + ) + : _EvalGaugeHorizontalPainter( + backgroundColor: EngineGauge.backgroundColor(context, brightness), + valueColor: EngineGauge.valueColor(context, brightness), + value: value, + textDirection: textDirection, + ), + child: + widget.displayMode == EngineGaugeDisplayMode.vertical + ? const SizedBox.shrink() + : Align( + alignment: toValue >= 0.5 ? Alignment.centerLeft : Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: Text( + evalDisplay ?? '', + style: TextStyle( + color: toValue >= 0.5 ? Colors.black : Colors.white, + fontSize: kEvalGaugeFontSize, + fontWeight: FontWeight.bold, + ), ), ), ), - ), ), ), ), @@ -223,9 +207,10 @@ class _EvalGaugeHorizontalPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { - final Paint paint = Paint() - ..color = backgroundColor - ..style = PaintingStyle.fill; + final Paint paint = + Paint() + ..color = backgroundColor + ..style = PaintingStyle.fill; canvas.drawRect(Offset.zero & size, paint); paint.color = valueColor; @@ -272,9 +257,10 @@ class _EvalGaugeVerticalPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { - final Paint paint = Paint() - ..color = backgroundColor - ..style = PaintingStyle.fill; + final Paint paint = + Paint() + ..color = backgroundColor + ..style = PaintingStyle.fill; canvas.drawRect(Offset.zero & size, paint); paint.color = valueColor; diff --git a/lib/src/view/engine/engine_lines.dart b/lib/src/view/engine/engine_lines.dart index 96ffd7fcc1..1e31077c34 100644 --- a/lib/src/view/engine/engine_lines.dart +++ b/lib/src/view/engine/engine_lines.dart @@ -11,46 +11,31 @@ import 'package:lichess_mobile/src/view/engine/engine_gauge.dart'; import 'package:lichess_mobile/src/widgets/buttons.dart'; class EngineLines extends ConsumerWidget { - const EngineLines({ - required this.onTapMove, - required this.clientEval, - required this.isGameOver, - }); + const EngineLines({required this.onTapMove, required this.clientEval, required this.isGameOver}); final void Function(NormalMove move) onTapMove; final ClientEval? clientEval; final bool isGameOver; @override Widget build(BuildContext context, WidgetRef ref) { - final numEvalLines = ref.watch( - analysisPreferencesProvider.select( - (p) => p.numEvalLines, - ), - ); + final numEvalLines = ref.watch(analysisPreferencesProvider.select((p) => p.numEvalLines)); final engineEval = ref.watch(engineEvaluationProvider).eval; final eval = engineEval ?? clientEval; - final emptyLines = List.filled( - numEvalLines, - const Engineline.empty(), - ); + final emptyLines = List.filled(numEvalLines, const Engineline.empty()); - final content = isGameOver - ? emptyLines - : (eval != null - ? eval.pvs - .take(numEvalLines) - .map( - (pv) => Engineline(onTapMove, eval.position, pv), - ) - .toList() - : emptyLines); + final content = + isGameOver + ? emptyLines + : (eval != null + ? eval.pvs + .take(numEvalLines) + .map((pv) => Engineline(onTapMove, eval.position, pv)) + .toList() + : emptyLines); if (content.length < numEvalLines) { - final padding = List.filled( - numEvalLines - content.length, - const Engineline.empty(), - ); + final padding = List.filled(numEvalLines - content.length, const Engineline.empty()); content.addAll(padding); } @@ -64,16 +49,12 @@ class EngineLines extends ConsumerWidget { } class Engineline extends ConsumerWidget { - const Engineline( - this.onTapMove, - this.fromPosition, - this.pvData, - ); + const Engineline(this.onTapMove, this.fromPosition, this.pvData); const Engineline.empty() - : onTapMove = null, - pvData = const PvData(moves: IListConst([])), - fromPosition = Chess.initial; + : onTapMove = null, + pvData = const PvData(moves: IListConst([])), + fromPosition = Chess.initial; final void Function(NormalMove move)? onTapMove; final Position fromPosition; @@ -82,16 +63,12 @@ class Engineline extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { if (pvData.moves.isEmpty) { - return const SizedBox( - height: kEvalGaugeSize, - child: SizedBox.shrink(), - ); + return const SizedBox(height: kEvalGaugeSize, child: SizedBox.shrink()); } - final pieceNotation = ref.watch(pieceNotationProvider).maybeWhen( - data: (value) => value, - orElse: () => defaultAccountPreferences.pieceNotation, - ); + final pieceNotation = ref + .watch(pieceNotationProvider) + .maybeWhen(data: (value) => value, orElse: () => defaultAccountPreferences.pieceNotation); final lineBuffer = StringBuffer(); int ply = fromPosition.ply + 1; @@ -100,8 +77,8 @@ class Engineline extends ConsumerWidget { ply.isOdd ? '${(ply / 2).ceil()}. $s ' : i == 0 - ? '${(ply / 2).ceil()}... $s ' - : '$s ', + ? '${(ply / 2).ceil()}... $s ' + : '$s ', ); ply += 1; }); @@ -122,21 +99,17 @@ class Engineline extends ConsumerWidget { children: [ Container( decoration: BoxDecoration( - color: pvData.winningSide == Side.black - ? EngineGauge.backgroundColor(context, brightness) - : EngineGauge.valueColor(context, brightness), + color: + pvData.winningSide == Side.black + ? EngineGauge.backgroundColor(context, brightness) + : EngineGauge.valueColor(context, brightness), borderRadius: BorderRadius.circular(4.0), ), - padding: const EdgeInsets.symmetric( - horizontal: 4.0, - vertical: 2.0, - ), + padding: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0), child: Text( evalString, style: TextStyle( - color: pvData.winningSide == Side.black - ? Colors.white - : Colors.black, + color: pvData.winningSide == Side.black ? Colors.white : Colors.black, fontSize: kEvalGaugeFontSize, fontWeight: FontWeight.w600, ), @@ -149,9 +122,7 @@ class Engineline extends ConsumerWidget { maxLines: 1, softWrap: false, style: TextStyle( - fontFamily: pieceNotation == PieceNotation.symbol - ? 'ChessFont' - : null, + fontFamily: pieceNotation == PieceNotation.symbol ? 'ChessFont' : null, ), overflow: TextOverflow.ellipsis, ), diff --git a/lib/src/view/game/archived_game_screen.dart b/lib/src/view/game/archived_game_screen.dart index 88b720a6f1..230e346c66 100644 --- a/lib/src/view/game/archived_game_screen.dart +++ b/lib/src/view/game/archived_game_screen.dart @@ -45,27 +45,15 @@ class ArchivedGameScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { if (gameData != null) { - return _Body( - gameData: gameData, - orientation: orientation, - initialCursor: initialCursor, - ); + return _Body(gameData: gameData, orientation: orientation, initialCursor: initialCursor); } else { - return _LoadGame( - gameId: gameId!, - orientation: orientation, - initialCursor: initialCursor, - ); + return _LoadGame(gameId: gameId!, orientation: orientation, initialCursor: initialCursor); } } } class _LoadGame extends ConsumerWidget { - const _LoadGame({ - required this.gameId, - required this.orientation, - required this.initialCursor, - }); + const _LoadGame({required this.gameId, required this.orientation, required this.initialCursor}); final GameId gameId; final Side orientation; @@ -76,21 +64,11 @@ class _LoadGame extends ConsumerWidget { final game = ref.watch(archivedGameProvider(id: gameId)); return game.when( data: (game) { - return _Body( - gameData: game.data, - orientation: orientation, - initialCursor: initialCursor, - ); + return _Body(gameData: game.data, orientation: orientation, initialCursor: initialCursor); }, - loading: () => _Body( - gameData: null, - orientation: orientation, - initialCursor: initialCursor, - ), + loading: () => _Body(gameData: null, orientation: orientation, initialCursor: initialCursor), error: (error, stackTrace) { - debugPrint( - 'SEVERE: [ArchivedGameScreen] could not load game; $error\n$stackTrace', - ); + debugPrint('SEVERE: [ArchivedGameScreen] could not load game; $error\n$stackTrace'); switch (error) { case ServerException _ when error.statusCode == 404: return _Body( @@ -113,12 +91,7 @@ class _LoadGame extends ConsumerWidget { } class _Body extends StatelessWidget { - const _Body({ - required this.gameData, - required this.orientation, - this.initialCursor, - this.error, - }); + const _Body({required this.gameData, required this.orientation, this.initialCursor, this.error}); final LightArchivedGame? gameData; final Object? error; @@ -129,12 +102,9 @@ class _Body extends StatelessWidget { Widget build(BuildContext context) { return PlatformScaffold( appBar: PlatformAppBar( - title: gameData != null - ? _GameTitle(gameData: gameData!) - : const SizedBox.shrink(), + title: gameData != null ? _GameTitle(gameData: gameData!) : const SizedBox.shrink(), actions: [ - if (gameData == null && error == null) - const PlatformAppBarLoadingIndicator(), + if (gameData == null && error == null) const PlatformAppBarLoadingIndicator(), const ToggleSoundButton(), ], ), @@ -159,9 +129,7 @@ class _Body extends StatelessWidget { } class _GameTitle extends StatelessWidget { - const _GameTitle({ - required this.gameData, - }); + const _GameTitle({required this.gameData}); final LightArchivedGame gameData; @@ -173,22 +141,14 @@ class _GameTitle extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ if (gameData.source == GameSource.import) - Icon( - Icons.cloud_upload, - color: DefaultTextStyle.of(context).style.color, - ) + Icon(Icons.cloud_upload, color: DefaultTextStyle.of(context).style.color) else - Icon( - gameData.perf.icon, - color: DefaultTextStyle.of(context).style.color, - ), + Icon(gameData.perf.icon, color: DefaultTextStyle.of(context).style.color), const SizedBox(width: 4.0), if (gameData.source == GameSource.import) Text('Import • ${_dateFormat.format(gameData.createdAt)}') else - Text( - '${gameData.clockDisplay} • ${_dateFormat.format(gameData.lastMoveAt)}', - ), + Text('${gameData.clockDisplay} • ${_dateFormat.format(gameData.lastMoveAt)}'), ], ); } @@ -212,39 +172,26 @@ class _BoardBody extends ConsumerWidget { final gameData = archivedGameData; if (gameData == null) { - return BoardTable.empty( - showMoveListPlaceholder: true, - errorMessage: error?.toString(), - ); + return BoardTable.empty(showMoveListPlaceholder: true, errorMessage: error?.toString()); } if (initialCursor != null) { ref.listen(gameCursorProvider(gameData.id), (prev, cursor) { if (prev?.isLoading == true && cursor.hasValue) { - ref - .read(gameCursorProvider(gameData.id).notifier) - .cursorAt(initialCursor!); + ref.read(gameCursorProvider(gameData.id).notifier).cursorAt(initialCursor!); } }); } final isBoardTurned = ref.watch(isBoardTurnedProvider); final gameCursor = ref.watch(gameCursorProvider(gameData.id)); - final black = GamePlayer( - key: const ValueKey('black-player'), - player: gameData.black, - ); - final white = GamePlayer( - key: const ValueKey('white-player'), - player: gameData.white, - ); + final black = GamePlayer(key: const ValueKey('black-player'), player: gameData.black); + final white = GamePlayer(key: const ValueKey('white-player'), player: gameData.white); final topPlayer = orientation == Side.white ? black : white; final bottomPlayer = orientation == Side.white ? white : black; final loadingBoard = BoardTable( orientation: (isBoardTurned ? orientation.opposite : orientation), - fen: initialCursor == null - ? gameData.lastFen ?? kEmptyBoardFEN - : kEmptyBoardFEN, + fen: initialCursor == null ? gameData.lastFen ?? kEmptyBoardFEN : kEmptyBoardFEN, topTable: topPlayer, bottomTable: bottomPlayer, showMoveListPlaceholder: true, @@ -268,7 +215,8 @@ class _BoardBody extends ConsumerWidget { materialDiff: game.materialDiffAt(cursor, Side.white), ); - final topPlayerIsBlack = orientation == Side.white && !isBoardTurned || + final topPlayerIsBlack = + orientation == Side.white && !isBoardTurned || orientation == Side.black && isBoardTurned; final topPlayer = topPlayerIsBlack ? black : white; final bottomPlayer = topPlayerIsBlack ? white : black; @@ -281,23 +229,16 @@ class _BoardBody extends ConsumerWidget { lastMove: game.moveAt(cursor) as NormalMove?, topTable: topPlayer, bottomTable: bottomPlayer, - moves: game.steps - .skip(1) - .map((e) => e.sanMove!.san) - .toList(growable: false), + moves: game.steps.skip(1).map((e) => e.sanMove!.san).toList(growable: false), currentMoveIndex: cursor, onSelectMove: (moveIndex) { - ref - .read(gameCursorProvider(gameData.id).notifier) - .cursorAt(moveIndex); + ref.read(gameCursorProvider(gameData.id).notifier).cursorAt(moveIndex); }, ); }, loading: () => loadingBoard, error: (error, stackTrace) { - debugPrint( - 'SEVERE: [ArchivedGameScreen] could not load game; $error\n$stackTrace', - ); + debugPrint('SEVERE: [ArchivedGameScreen] could not load game; $error\n$stackTrace'); return loadingBoard; }, ); @@ -349,11 +290,7 @@ class _BottomBar extends ConsumerWidget { return BottomBar( children: [ - BottomBarButton( - label: context.l10n.menu, - onTap: showGameMenu, - icon: Icons.menu, - ), + BottomBarButton(label: context.l10n.menu, onTap: showGameMenu, icon: Icons.menu), if (gameCursor.hasValue) BottomBarButton( label: context.l10n.mobileShowResult, @@ -361,29 +298,30 @@ class _BottomBar extends ConsumerWidget { onTap: () { showAdaptiveDialog( context: context, - builder: (context) => - ArchivedGameResultDialog(game: gameCursor.requireValue.$1), + builder: (context) => ArchivedGameResultDialog(game: gameCursor.requireValue.$1), barrierDismissible: true, ); }, ), BottomBarButton( label: context.l10n.gameAnalysis, - onTap: gameCursor.hasValue - ? () { - final cursor = gameCursor.requireValue.$2; - pushPlatformRoute( - context, - builder: (context) => AnalysisScreen( - options: AnalysisOptions( - orientation: orientation, - gameId: gameData.id, - initialMoveCursor: cursor, - ), - ), - ); - } - : null, + onTap: + gameCursor.hasValue + ? () { + final cursor = gameCursor.requireValue.$2; + pushPlatformRoute( + context, + builder: + (context) => AnalysisScreen( + options: AnalysisOptions( + orientation: orientation, + gameId: gameData.id, + initialMoveCursor: cursor, + ), + ), + ); + } + : null, icon: Icons.biotech, ), RepeatButton( @@ -419,8 +357,6 @@ class _BottomBar extends ConsumerWidget { void _cursorBackward(WidgetRef ref) { if (archivedGameData == null) return; - ref - .read(gameCursorProvider(archivedGameData!.id).notifier) - .cursorBackward(); + ref.read(gameCursorProvider(archivedGameData!.id).notifier).cursorBackward(); } } diff --git a/lib/src/view/game/correspondence_clock_widget.dart b/lib/src/view/game/correspondence_clock_widget.dart index 73f4e2a076..8743daf3c8 100644 --- a/lib/src/view/game/correspondence_clock_widget.dart +++ b/lib/src/view/game/correspondence_clock_widget.dart @@ -18,16 +18,10 @@ class CorrespondenceClock extends ConsumerStatefulWidget { /// Callback when the clock reaches zero. final VoidCallback? onFlag; - const CorrespondenceClock({ - required this.duration, - required this.active, - this.onFlag, - super.key, - }); + const CorrespondenceClock({required this.duration, required this.active, this.onFlag, super.key}); @override - ConsumerState createState() => - _CorrespondenceClockState(); + ConsumerState createState() => _CorrespondenceClockState(); } const _period = Duration(seconds: 1); @@ -95,27 +89,24 @@ class _CorrespondenceClockState extends ConsumerState { final mins = timeLeft.inMinutes.remainder(60); final secs = timeLeft.inSeconds.remainder(60).toString().padLeft(2, '0'); final brightness = ref.watch(currentBrightnessProvider); - final clockStyle = brightness == Brightness.dark - ? ClockStyle.darkThemeStyle - : ClockStyle.lightThemeStyle; + final clockStyle = + brightness == Brightness.dark ? ClockStyle.darkThemeStyle : ClockStyle.lightThemeStyle; final remainingHeight = estimateRemainingHeightLeftBoard(context); - final daysStr = days > 1 - ? context.l10n.nbDays(days) - : days == 1 + final daysStr = + days > 1 + ? context.l10n.nbDays(days) + : days == 1 ? context.l10n.oneDay : ''; - final hoursStr = - days > 0 && hours > 0 ? ' ${context.l10n.nbHours(hours)}' : ''; + final hoursStr = days > 0 && hours > 0 ? ' ${context.l10n.nbHours(hours)}' : ''; return RepaintBoundary( child: Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(5.0)), - color: widget.active - ? clockStyle.activeBackgroundColor - : clockStyle.backgroundColor, + color: widget.active ? clockStyle.activeBackgroundColor : clockStyle.backgroundColor, ), child: Padding( padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 5.0), @@ -127,19 +118,10 @@ class _CorrespondenceClockState extends ConsumerState { text: TextSpan( text: '$daysStr$hoursStr', style: TextStyle( - color: widget.active - ? clockStyle.activeTextColor - : clockStyle.textColor, + color: widget.active ? clockStyle.activeTextColor : clockStyle.textColor, fontSize: 18, - height: - remainingHeight < kSmallRemainingHeightLeftBoardThreshold - ? 1.0 - : null, - fontFeatures: days == 0 - ? const [ - FontFeature.tabularFigures(), - ] - : null, + height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 1.0 : null, + fontFeatures: days == 0 ? const [FontFeature.tabularFigures()] : null, ), children: [ if (days == 0) ...[ @@ -147,17 +129,14 @@ class _CorrespondenceClockState extends ConsumerState { TextSpan( text: ':', style: TextStyle( - color: widget.active && - timeLeft.inSeconds.remainder(2) == 0 - ? clockStyle.activeTextColor.withValues(alpha: 0.5) - : null, + color: + widget.active && timeLeft.inSeconds.remainder(2) == 0 + ? clockStyle.activeTextColor.withValues(alpha: 0.5) + : null, ), ), TextSpan(text: mins.toString().padLeft(2, '0')), - if (hours == 0) ...[ - const TextSpan(text: ':'), - TextSpan(text: secs), - ], + if (hours == 0) ...[const TextSpan(text: ':'), TextSpan(text: secs)], ], ], ), diff --git a/lib/src/view/game/game_body.dart b/lib/src/view/game/game_body.dart index 955f629a39..187006c047 100644 --- a/lib/src/view/game/game_body.dart +++ b/lib/src/view/game/game_body.dart @@ -94,20 +94,11 @@ class GameBody extends ConsumerWidget { ref.listen( ctrlProvider, - (prev, state) => _stateListener( - prev, - state, - context: context, - ref: ref, - ), + (prev, state) => _stateListener(prev, state, context: context, ref: ref), ); final boardPreferences = ref.watch(boardPreferencesProvider); - final blindfoldMode = ref.watch( - gamePreferencesProvider.select( - (prefs) => prefs.blindfoldMode, - ), - ); + final blindfoldMode = ref.watch(gamePreferencesProvider.select((prefs) => prefs.blindfoldMode)); final gameStateAsync = ref.watch(ctrlProvider); @@ -115,122 +106,110 @@ class GameBody extends ConsumerWidget { data: (gameState) { final (position, legalMoves) = gameState.currentPosition; final youAre = gameState.game.youAre ?? Side.white; - final archivedBlackClock = - gameState.game.archivedBlackClockAt(gameState.stepCursor); - final archivedWhiteClock = - gameState.game.archivedWhiteClockAt(gameState.stepCursor); + final archivedBlackClock = gameState.game.archivedBlackClockAt(gameState.stepCursor); + final archivedWhiteClock = gameState.game.archivedWhiteClockAt(gameState.stepCursor); final black = GamePlayer( player: gameState.game.black, - materialDiff: boardPreferences.materialDifferenceFormat.visible - ? gameState.game.materialDiffAt(gameState.stepCursor, Side.black) - : null, + materialDiff: + boardPreferences.materialDifferenceFormat.visible + ? gameState.game.materialDiffAt(gameState.stepCursor, Side.black) + : null, materialDifferenceFormat: boardPreferences.materialDifferenceFormat, - timeToMove: gameState.game.sideToMove == Side.black - ? gameState.timeToMove - : null, + timeToMove: gameState.game.sideToMove == Side.black ? gameState.timeToMove : null, mePlaying: youAre == Side.black, zenMode: gameState.isZenModeActive, clockPosition: boardPreferences.clockPosition, confirmMoveCallbacks: youAre == Side.black && gameState.moveToConfirm != null ? ( - confirm: () { - ref.read(ctrlProvider.notifier).confirmMove(); - }, - cancel: () { - ref.read(ctrlProvider.notifier).cancelMove(); - }, - ) + confirm: () { + ref.read(ctrlProvider.notifier).confirmMove(); + }, + cancel: () { + ref.read(ctrlProvider.notifier).cancelMove(); + }, + ) : null, - clock: archivedBlackClock != null - ? Clock( - timeLeft: archivedBlackClock, - active: false, - ) - : gameState.liveClock != null + clock: + archivedBlackClock != null + ? Clock(timeLeft: archivedBlackClock, active: false) + : gameState.liveClock != null ? RepaintBoundary( - child: ValueListenableBuilder( - key: blackClockKey, - valueListenable: gameState.liveClock!.black, - builder: (context, value, _) { - return Clock( - timeLeft: value, - active: gameState.activeClockSide == Side.black, - emergencyThreshold: youAre == Side.black - ? gameState.game.meta.clock?.emergency - : null, - ); - }, - ), - ) - : gameState.game.correspondenceClock != null - ? CorrespondenceClock( - duration: gameState.game.correspondenceClock!.black, + child: ValueListenableBuilder( + key: blackClockKey, + valueListenable: gameState.liveClock!.black, + builder: (context, value, _) { + return Clock( + timeLeft: value, active: gameState.activeClockSide == Side.black, - onFlag: () => - ref.read(ctrlProvider.notifier).onFlag(), - ) - : null, + emergencyThreshold: + youAre == Side.black ? gameState.game.meta.clock?.emergency : null, + ); + }, + ), + ) + : gameState.game.correspondenceClock != null + ? CorrespondenceClock( + duration: gameState.game.correspondenceClock!.black, + active: gameState.activeClockSide == Side.black, + onFlag: () => ref.read(ctrlProvider.notifier).onFlag(), + ) + : null, ); final white = GamePlayer( player: gameState.game.white, - materialDiff: boardPreferences.materialDifferenceFormat.visible - ? gameState.game.materialDiffAt(gameState.stepCursor, Side.white) - : null, + materialDiff: + boardPreferences.materialDifferenceFormat.visible + ? gameState.game.materialDiffAt(gameState.stepCursor, Side.white) + : null, materialDifferenceFormat: boardPreferences.materialDifferenceFormat, - timeToMove: gameState.game.sideToMove == Side.white - ? gameState.timeToMove - : null, + timeToMove: gameState.game.sideToMove == Side.white ? gameState.timeToMove : null, mePlaying: youAre == Side.white, zenMode: gameState.isZenModeActive, clockPosition: boardPreferences.clockPosition, confirmMoveCallbacks: youAre == Side.white && gameState.moveToConfirm != null ? ( - confirm: () { - ref.read(ctrlProvider.notifier).confirmMove(); - }, - cancel: () { - ref.read(ctrlProvider.notifier).cancelMove(); - }, - ) + confirm: () { + ref.read(ctrlProvider.notifier).confirmMove(); + }, + cancel: () { + ref.read(ctrlProvider.notifier).cancelMove(); + }, + ) : null, - clock: archivedWhiteClock != null - ? Clock( - timeLeft: archivedWhiteClock, - active: false, - ) - : gameState.liveClock != null + clock: + archivedWhiteClock != null + ? Clock(timeLeft: archivedWhiteClock, active: false) + : gameState.liveClock != null ? RepaintBoundary( - child: ValueListenableBuilder( - key: whiteClockKey, - valueListenable: gameState.liveClock!.white, - builder: (context, value, _) { - return Clock( - timeLeft: value, - active: gameState.activeClockSide == Side.white, - emergencyThreshold: youAre == Side.white - ? gameState.game.meta.clock?.emergency - : null, - ); - }, - ), - ) - : gameState.game.correspondenceClock != null - ? CorrespondenceClock( - duration: gameState.game.correspondenceClock!.white, + child: ValueListenableBuilder( + key: whiteClockKey, + valueListenable: gameState.liveClock!.white, + builder: (context, value, _) { + return Clock( + timeLeft: value, active: gameState.activeClockSide == Side.white, - onFlag: () => - ref.read(ctrlProvider.notifier).onFlag(), - ) - : null, + emergencyThreshold: + youAre == Side.white ? gameState.game.meta.clock?.emergency : null, + ); + }, + ), + ) + : gameState.game.correspondenceClock != null + ? CorrespondenceClock( + duration: gameState.game.correspondenceClock!.white, + active: gameState.activeClockSide == Side.white, + onFlag: () => ref.read(ctrlProvider.notifier).onFlag(), + ) + : null, ); final isBoardTurned = ref.watch(isBoardTurnedProvider); - final topPlayerIsBlack = youAre == Side.white && !isBoardTurned || - youAre == Side.black && isBoardTurned; + final topPlayerIsBlack = + youAre == Side.white && !isBoardTurned || youAre == Side.black && isBoardTurned; final topPlayer = topPlayerIsBlack ? black : white; final bottomPlayer = topPlayerIsBlack ? white : black; @@ -243,8 +222,7 @@ class GameBody extends ConsumerWidget { final content = WakelockWidget( shouldEnableOnFocusGained: () => gameState.game.playable, child: PopScope( - canPop: gameState.game.meta.speed == Speed.correspondence || - !gameState.game.playable, + canPop: gameState.game.meta.speed == Speed.correspondence || !gameState.game.playable, child: Column( children: [ Expanded( @@ -255,14 +233,12 @@ class GameBody extends ConsumerWidget { boardSettingsOverrides: BoardSettingsOverrides( animationDuration: animationDuration, autoQueenPromotion: gameState.canAutoQueen, - autoQueenPromotionOnPremove: - gameState.canAutoQueenOnPremove, + autoQueenPromotionOnPremove: gameState.canAutoQueenOnPremove, blindfoldMode: blindfoldMode, ), orientation: isBoardTurned ? youAre.opposite : youAre, fen: position.fen, - lastMove: gameState.game.moveAt(gameState.stepCursor) - as NormalMove?, + lastMove: gameState.game.moveAt(gameState.stepCursor) as NormalMove?, gameData: GameData( playerSide: gameState.game.playable && !gameState.isReplaying @@ -270,40 +246,32 @@ class GameBody extends ConsumerWidget { ? PlayerSide.white : PlayerSide.black : PlayerSide.none, - isCheck: boardPreferences.boardHighlights && - position.isCheck, + isCheck: boardPreferences.boardHighlights && position.isCheck, sideToMove: position.turn, validMoves: legalMoves, promotionMove: gameState.promotionMove, onMove: (move, {isDrop}) { - ref.read(ctrlProvider.notifier).userMove( - move, - isDrop: isDrop, - ); + ref.read(ctrlProvider.notifier).userMove(move, isDrop: isDrop); }, onPromotionSelection: (role) { - ref - .read(ctrlProvider.notifier) - .onPromotionSelection(role); + ref.read(ctrlProvider.notifier).onPromotionSelection(role); }, - premovable: gameState.canPremove - ? ( - onSetPremove: (move) { - ref - .read(ctrlProvider.notifier) - .setPremove(move); - }, - premove: gameState.premove, - ) - : null, + premovable: + gameState.canPremove + ? ( + onSetPremove: (move) { + ref.read(ctrlProvider.notifier).setPremove(move); + }, + premove: gameState.premove, + ) + : null, ), topTable: topPlayer, - bottomTable: gameState.canShowClaimWinCountdown && - gameState.opponentLeftCountdown != null - ? _ClaimWinCountdown( - duration: gameState.opponentLeftCountdown!, - ) - : bottomPlayer, + bottomTable: + gameState.canShowClaimWinCountdown && + gameState.opponentLeftCountdown != null + ? _ClaimWinCountdown(duration: gameState.opponentLeftCountdown!) + : bottomPlayer, moves: gameState.game.steps .skip(1) .map((e) => e.sanMove!.san) @@ -328,42 +296,33 @@ class GameBody extends ConsumerWidget { return Theme.of(context).platform == TargetPlatform.android ? AndroidGesturesExclusionWidget( - boardKey: boardKey, - shouldExcludeGesturesOnFocusGained: () => - gameState.game.meta.speed != Speed.correspondence && - gameState.game.playable, - shouldSetImmersiveMode: - boardPreferences.immersiveModeWhilePlaying ?? false, - child: content, - ) + boardKey: boardKey, + shouldExcludeGesturesOnFocusGained: + () => + gameState.game.meta.speed != Speed.correspondence && gameState.game.playable, + shouldSetImmersiveMode: boardPreferences.immersiveModeWhilePlaying ?? false, + child: content, + ) : content; }, - loading: () => PopScope( - canPop: true, - child: Column( - children: [ - Expanded( - child: SafeArea( - bottom: false, - child: loadingBoardWidget, - ), - ), - _GameBottomBar( - id: id, - onLoadGameCallback: onLoadGameCallback, - onNewOpponentCallback: onNewOpponentCallback, + loading: + () => PopScope( + canPop: true, + child: Column( + children: [ + Expanded(child: SafeArea(bottom: false, child: loadingBoardWidget)), + _GameBottomBar( + id: id, + onLoadGameCallback: onLoadGameCallback, + onNewOpponentCallback: onNewOpponentCallback, + ), + ], ), - ], - ), - ), + ), error: (e, s) { - debugPrint( - 'SEVERE: [GameBody] could not load game data; $e\n$s', - ); + debugPrint('SEVERE: [GameBody] could not load game data; $e\n$s'); return const PopScope( - child: LoadGameError( - 'Sorry, we could not load the game. Please try again later.', - ), + child: LoadGameError('Sorry, we could not load the game. Please try again later.'), ); }, ); @@ -379,17 +338,15 @@ class GameBody extends ConsumerWidget { // If the game is no longer playable, show the game end dialog. // We want to show it only once, whether the game is already finished on // first load or not. - if ((prev?.hasValue != true || - prev!.requireValue.game.playable == true) && + if ((prev?.hasValue != true || prev!.requireValue.game.playable == true) && state.requireValue.game.playable == false) { Timer(const Duration(milliseconds: 500), () { if (context.mounted) { showAdaptiveDialog( context: context, - builder: (context) => GameResultDialog( - id: id, - onNewOpponentCallback: onNewOpponentCallback, - ), + builder: + (context) => + GameResultDialog(id: id, onNewOpponentCallback: onNewOpponentCallback), barrierDismissible: true, ); } @@ -397,8 +354,7 @@ class GameBody extends ConsumerWidget { } // true when the game was loaded, playable, and just finished - if (prev?.valueOrNull?.game.playable == true && - state.requireValue.game.playable == false) { + if (prev?.valueOrNull?.game.playable == true && state.requireValue.game.playable == false) { clearAndroidBoardGesturesExclusion(); } // true when the game was not loaded: handles rematches @@ -408,8 +364,7 @@ class GameBody extends ConsumerWidget { setAndroidBoardGesturesExclusion( boardKey, withImmersiveMode: - ref.read(boardPreferencesProvider).immersiveModeWhilePlaying ?? - false, + ref.read(boardPreferencesProvider).immersiveModeWhilePlaying ?? false, ); } } @@ -417,8 +372,7 @@ class GameBody extends ConsumerWidget { if (prev?.hasValue == true && state.hasValue) { // Opponent is gone long enough to show the claim win dialog. - if (!prev!.requireValue.game.canClaimWin && - state.requireValue.game.canClaimWin) { + if (!prev!.requireValue.game.canClaimWin && state.requireValue.game.canClaimWin) { if (context.mounted) { showAdaptiveDialog( context: context, @@ -453,26 +407,27 @@ class _GameBottomBar extends ConsumerWidget { final ongoingGames = ref.watch(ongoingGamesProvider); final gamePrefs = ref.watch(gamePreferencesProvider); final gameStateAsync = ref.watch(gameControllerProvider(id)); - final chatStateAsync = gamePrefs.enableChat == true - ? ref.watch(chatControllerProvider(id)) - : null; + final chatStateAsync = + gamePrefs.enableChat == true ? ref.watch(chatControllerProvider(id)) : null; return BottomBar( children: gameStateAsync.when( data: (gameState) { - final isChatEnabled = - chatStateAsync != null && !gameState.isZenModeActive; - - final chatUnreadLabel = isChatEnabled - ? chatStateAsync.maybeWhen( - data: (s) => s.unreadMessages > 0 - ? (s.unreadMessages < 10) - ? s.unreadMessages.toString() - : '9+' - : null, - orElse: () => null, - ) - : null; + final isChatEnabled = chatStateAsync != null && !gameState.isZenModeActive; + + final chatUnreadLabel = + isChatEnabled + ? chatStateAsync.maybeWhen( + data: + (s) => + s.unreadMessages > 0 + ? (s.unreadMessages < 10) + ? s.unreadMessages.toString() + : '9+' + : null, + orElse: () => null, + ) + : null; return [ BottomBarButton( @@ -488,43 +443,37 @@ class _GameBottomBar extends ConsumerWidget { onTap: () { showAdaptiveDialog( context: context, - builder: (context) => GameResultDialog( - id: id, - onNewOpponentCallback: onNewOpponentCallback, - ), + builder: + (context) => + GameResultDialog(id: id, onNewOpponentCallback: onNewOpponentCallback), barrierDismissible: true, ); }, icon: Icons.info_outline, ), - if (gameState.game.playable && - gameState.game.opponent?.offeringDraw == true) + if (gameState.game.playable && gameState.game.opponent?.offeringDraw == true) BottomBarButton( label: context.l10n.yourOpponentOffersADraw, highlighted: true, onTap: () { showAdaptiveDialog( context: context, - builder: (context) => _GameNegotiationDialog( - title: Text(context.l10n.yourOpponentOffersADraw), - onAccept: () { - ref - .read(gameControllerProvider(id).notifier) - .offerOrAcceptDraw(); - }, - onDecline: () { - ref - .read(gameControllerProvider(id).notifier) - .cancelOrDeclineDraw(); - }, - ), + builder: + (context) => _GameNegotiationDialog( + title: Text(context.l10n.yourOpponentOffersADraw), + onAccept: () { + ref.read(gameControllerProvider(id).notifier).offerOrAcceptDraw(); + }, + onDecline: () { + ref.read(gameControllerProvider(id).notifier).cancelOrDeclineDraw(); + }, + ), barrierDismissible: true, ); }, icon: Icons.handshake_outlined, ) - else if (gameState.game.playable && - gameState.game.isThreefoldRepetition == true) + else if (gameState.game.playable && gameState.game.isThreefoldRepetition == true) BottomBarButton( label: context.l10n.threefoldRepetition, highlighted: true, @@ -537,27 +486,23 @@ class _GameBottomBar extends ConsumerWidget { }, icon: Icons.handshake_outlined, ) - else if (gameState.game.playable && - gameState.game.opponent?.proposingTakeback == true) + else if (gameState.game.playable && gameState.game.opponent?.proposingTakeback == true) BottomBarButton( label: context.l10n.yourOpponentProposesATakeback, highlighted: true, onTap: () { showAdaptiveDialog( context: context, - builder: (context) => _GameNegotiationDialog( - title: Text(context.l10n.yourOpponentProposesATakeback), - onAccept: () { - ref - .read(gameControllerProvider(id).notifier) - .acceptTakeback(); - }, - onDecline: () { - ref - .read(gameControllerProvider(id).notifier) - .cancelOrDeclineTakeback(); - }, - ), + builder: + (context) => _GameNegotiationDialog( + title: Text(context.l10n.yourOpponentProposesATakeback), + onAccept: () { + ref.read(gameControllerProvider(id).notifier).acceptTakeback(); + }, + onDecline: () { + ref.read(gameControllerProvider(id).notifier).cancelOrDeclineTakeback(); + }, + ), barrierDismissible: true, ); }, @@ -570,8 +515,7 @@ class _GameBottomBar extends ConsumerWidget { onTap: () { pushPlatformRoute( context, - builder: (_) => - AnalysisScreen(options: gameState.analysisOptions), + builder: (_) => AnalysisScreen(options: gameState.analysisOptions), ); }, ) @@ -579,23 +523,20 @@ class _GameBottomBar extends ConsumerWidget { gameState.game.meta.speed == Speed.ultraBullet) BottomBarButton( label: context.l10n.resign, - onTap: gameState.game.resignable - ? gameState.shouldConfirmResignAndDrawOffer - ? () => _showConfirmDialog( + onTap: + gameState.game.resignable + ? gameState.shouldConfirmResignAndDrawOffer + ? () => _showConfirmDialog( context, description: Text(context.l10n.resignTheGame), onConfirm: () { - ref - .read(gameControllerProvider(id).notifier) - .resignGame(); + ref.read(gameControllerProvider(id).notifier).resignGame(); }, ) - : () { - ref - .read(gameControllerProvider(id).notifier) - .resignGame(); - } - : null, + : () { + ref.read(gameControllerProvider(id).notifier).resignGame(); + } + : null, icon: Icons.flag, ) else @@ -606,8 +547,7 @@ class _GameBottomBar extends ConsumerWidget { }, icon: CupertinoIcons.arrow_2_squarepath, ), - if (gameState.game.meta.speed == Speed.correspondence && - !gameState.game.finished) + if (gameState.game.meta.speed == Speed.correspondence && !gameState.game.finished) BottomBarButton( label: 'Go to the next game', icon: Icons.skip_next, @@ -616,50 +556,45 @@ class _GameBottomBar extends ConsumerWidget { final nextTurn = games .whereNot((g) => g.fullId == id) .firstWhereOrNull((g) => g.isMyTurn); - return nextTurn != null - ? () => onLoadGameCallback(nextTurn.fullId) - : null; + return nextTurn != null ? () => onLoadGameCallback(nextTurn.fullId) : null; }, orElse: () => null, ), ), BottomBarButton( label: context.l10n.chat, - onTap: isChatEnabled - ? () { - pushPlatformRoute( - context, - builder: (BuildContext context) { - return MessageScreen( - title: UserFullNameWidget( - user: gameState.game.opponent?.user, - ), - me: gameState.game.me?.user, - id: id, - ); - }, - ); - } - : null, - icon: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoIcons.chat_bubble - : Icons.chat_bubble_outline, + onTap: + isChatEnabled + ? () { + pushPlatformRoute( + context, + builder: (BuildContext context) { + return MessageScreen( + title: UserFullNameWidget(user: gameState.game.opponent?.user), + me: gameState.game.me?.user, + id: id, + ); + }, + ); + } + : null, + icon: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoIcons.chat_bubble + : Icons.chat_bubble_outline, badgeLabel: chatUnreadLabel, ), RepeatButton( - onLongPress: - gameState.canGoBackward ? () => _moveBackward(ref) : null, + onLongPress: gameState.canGoBackward ? () => _moveBackward(ref) : null, child: BottomBarButton( - onTap: - gameState.canGoBackward ? () => _moveBackward(ref) : null, + onTap: gameState.canGoBackward ? () => _moveBackward(ref) : null, label: 'Previous', icon: CupertinoIcons.chevron_back, showTooltip: false, ), ), RepeatButton( - onLongPress: - gameState.canGoForward ? () => _moveForward(ref) : null, + onLongPress: gameState.canGoForward ? () => _moveForward(ref) : null, child: BottomBarButton( onTap: gameState.canGoForward ? () => _moveForward(ref) : null, label: context.l10n.next, @@ -694,15 +629,13 @@ class _GameBottomBar extends ConsumerWidget { ref.read(isBoardTurnedProvider.notifier).toggle(); }, ), - if (gameState.game.playable && - gameState.game.meta.speed == Speed.correspondence) + if (gameState.game.playable && gameState.game.meta.speed == Speed.correspondence) BottomSheetAction( makeLabel: (context) => Text(context.l10n.analysis), onPressed: (context) { pushPlatformRoute( context, - builder: (_) => - AnalysisScreen(options: gameState.analysisOptions), + builder: (_) => AnalysisScreen(options: gameState.analysisOptions), ); }, ), @@ -715,11 +648,10 @@ class _GameBottomBar extends ConsumerWidget { ), if (gameState.game.meta.clock != null && gameState.game.canGiveTime) BottomSheetAction( - makeLabel: (context) => Text( - context.l10n.giveNbSeconds( - gameState.game.meta.clock!.moreTime?.inSeconds ?? 15, - ), - ), + makeLabel: + (context) => Text( + context.l10n.giveNbSeconds(gameState.game.meta.clock!.moreTime?.inSeconds ?? 15), + ), onPressed: (context) { ref.read(gameControllerProvider(id).notifier).moreTime(); }, @@ -733,50 +665,43 @@ class _GameBottomBar extends ConsumerWidget { ), if (gameState.game.me?.proposingTakeback == true) BottomSheetAction( - makeLabel: (context) => - Text(context.l10n.mobileCancelTakebackOffer), + makeLabel: (context) => Text(context.l10n.mobileCancelTakebackOffer), isDestructiveAction: true, onPressed: (context) { - ref - .read(gameControllerProvider(id).notifier) - .cancelOrDeclineTakeback(); + ref.read(gameControllerProvider(id).notifier).cancelOrDeclineTakeback(); }, ), if (gameState.canOfferDraw) BottomSheetAction( makeLabel: (context) => Text(context.l10n.offerDraw), - onPressed: gameState.shouldConfirmResignAndDrawOffer - ? (context) => _showConfirmDialog( + onPressed: + gameState.shouldConfirmResignAndDrawOffer + ? (context) => _showConfirmDialog( context, description: Text(context.l10n.offerDraw), onConfirm: () { - ref - .read(gameControllerProvider(id).notifier) - .offerOrAcceptDraw(); + ref.read(gameControllerProvider(id).notifier).offerOrAcceptDraw(); }, ) - : (context) { - ref - .read(gameControllerProvider(id).notifier) - .offerOrAcceptDraw(); - }, + : (context) { + ref.read(gameControllerProvider(id).notifier).offerOrAcceptDraw(); + }, ), if (gameState.game.resignable) BottomSheetAction( makeLabel: (context) => Text(context.l10n.resign), - onPressed: gameState.shouldConfirmResignAndDrawOffer - ? (context) => _showConfirmDialog( + onPressed: + gameState.shouldConfirmResignAndDrawOffer + ? (context) => _showConfirmDialog( context, description: Text(context.l10n.resignTheGame), onConfirm: () { - ref - .read(gameControllerProvider(id).notifier) - .resignGame(); + ref.read(gameControllerProvider(id).notifier).resignGame(); }, ) - : (context) { - ref.read(gameControllerProvider(id).notifier).resignGame(); - }, + : (context) { + ref.read(gameControllerProvider(id).notifier).resignGame(); + }, ), if (gameState.game.canClaimWin) ...[ BottomSheetAction( @@ -803,15 +728,12 @@ class _GameBottomBar extends ConsumerWidget { ref.read(gameControllerProvider(id).notifier).declineRematch(); }, ) - else if (gameState.canOfferRematch && - gameState.game.opponent?.onGame == true) + else if (gameState.canOfferRematch && gameState.game.opponent?.onGame == true) BottomSheetAction( makeLabel: (context) => Text(context.l10n.rematch), dismissOnPress: true, onPressed: (context) { - ref - .read(gameControllerProvider(id).notifier) - .proposeOrAcceptRematch(); + ref.read(gameControllerProvider(id).notifier).proposeOrAcceptRematch(); }, ), if (gameState.canGetNewOpponent) @@ -822,8 +744,7 @@ class _GameBottomBar extends ConsumerWidget { if (gameState.game.finished) ...makeFinishedGameShareActions( gameState.game, - currentGamePosition: - gameState.game.positionAt(gameState.stepCursor), + currentGamePosition: gameState.game.positionAt(gameState.stepCursor), lastMove: gameState.game.moveAt(gameState.stepCursor), orientation: gameState.game.youAre ?? Side.white, context: context, @@ -840,14 +761,15 @@ class _GameBottomBar extends ConsumerWidget { }) async { final result = await showAdaptiveDialog( context: context, - builder: (context) => YesNoDialog( - title: Text(context.l10n.mobileAreYouSure), - content: description, - onYes: () { - return Navigator.of(context).pop(true); - }, - onNo: () => Navigator.of(context).pop(false), - ), + builder: + (context) => YesNoDialog( + title: Text(context.l10n.mobileAreYouSure), + content: description, + onYes: () { + return Navigator.of(context).pop(true); + }, + onNo: () => Navigator.of(context).pop(false), + ), ); if (result == true) { onConfirm(); @@ -881,23 +803,15 @@ class _GameNegotiationDialog extends StatelessWidget { return PlatformAlertDialog( content: title, actions: [ - PlatformDialogAction( - onPressed: accept, - child: Text(context.l10n.accept), - ), - PlatformDialogAction( - onPressed: decline, - child: Text(context.l10n.decline), - ), + PlatformDialogAction(onPressed: accept, child: Text(context.l10n.accept)), + PlatformDialogAction(onPressed: decline, child: Text(context.l10n.decline)), ], ); } } class _ThreefoldDialog extends ConsumerWidget { - const _ThreefoldDialog({ - required this.id, - }); + const _ThreefoldDialog({required this.id}); final GameFullId id; @@ -917,23 +831,15 @@ class _ThreefoldDialog extends ConsumerWidget { return PlatformAlertDialog( content: content, actions: [ - PlatformDialogAction( - onPressed: accept, - child: Text(context.l10n.claimADraw), - ), - PlatformDialogAction( - onPressed: decline, - child: Text(context.l10n.cancel), - ), + PlatformDialogAction(onPressed: accept, child: Text(context.l10n.claimADraw)), + PlatformDialogAction(onPressed: decline, child: Text(context.l10n.cancel)), ], ); } } class _ClaimWinDialog extends ConsumerWidget { - const _ClaimWinDialog({ - required this.id, - }); + const _ClaimWinDialog({required this.id}); final GameFullId id; @@ -972,9 +878,7 @@ class _ClaimWinDialog extends ConsumerWidget { } class _ClaimWinCountdown extends StatelessWidget { - const _ClaimWinCountdown({ - required this.duration, - }); + const _ClaimWinCountdown({required this.duration}); final Duration duration; diff --git a/lib/src/view/game/game_common_widgets.dart b/lib/src/view/game/game_common_widgets.dart index b5aff24c69..81cfed3c1c 100644 --- a/lib/src/view/game/game_common_widgets.dart +++ b/lib/src/view/game/game_common_widgets.dart @@ -27,13 +27,7 @@ import 'ping_rating.dart'; final _gameTitledateFormat = DateFormat.yMMMd(); class GameAppBar extends ConsumerWidget { - const GameAppBar({ - this.id, - this.seek, - this.challenge, - this.lastMoveAt, - super.key, - }); + const GameAppBar({this.id, this.seek, this.challenge, this.lastMoveAt, super.key}); final GameSeek? seek; final ChallengeRequest? challenge; @@ -49,36 +43,35 @@ class GameAppBar extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final shouldPreventGoingBackAsync = id != null - ? ref.watch(shouldPreventGoingBackProvider(id!)) - : const AsyncValue.data(true); + final shouldPreventGoingBackAsync = + id != null ? ref.watch(shouldPreventGoingBackProvider(id!)) : const AsyncValue.data(true); return PlatformAppBar( leading: shouldPreventGoingBackAsync.maybeWhen( data: (prevent) => prevent ? pingRating : null, orElse: () => pingRating, ), - title: id != null - ? _StandaloneGameTitle(id: id!, lastMoveAt: lastMoveAt) - : seek != null + title: + id != null + ? _StandaloneGameTitle(id: id!, lastMoveAt: lastMoveAt) + : seek != null ? _LobbyGameTitle(seek: seek!) : challenge != null - ? _ChallengeGameTitle(challenge: challenge!) - : const SizedBox.shrink(), + ? _ChallengeGameTitle(challenge: challenge!) + : const SizedBox.shrink(), actions: [ const ToggleSoundButton(), if (id != null) AppBarIconButton( - onPressed: () => showAdaptiveBottomSheet( - context: context, - isDismissible: true, - isScrollControlled: true, - showDragHandle: true, - constraints: BoxConstraints( - minHeight: MediaQuery.sizeOf(context).height * 0.5, - ), - builder: (_) => GameSettings(id: id!), - ), + onPressed: + () => showAdaptiveBottomSheet( + context: context, + isDismissible: true, + isScrollControlled: true, + showDragHandle: true, + constraints: BoxConstraints(minHeight: MediaQuery.sizeOf(context).height * 0.5), + builder: (_) => GameSettings(id: id!), + ), semanticsLabel: context.l10n.settingsSettings, icon: const Icon(Icons.settings), ), @@ -106,12 +99,13 @@ List makeFinishedGameShareActions( isDismissible: true, isScrollControlled: true, showDragHandle: true, - builder: (context) => GameShareBottomSheet( - game: game, - currentGamePosition: currentGamePosition, - orientation: orientation, - lastMove: lastMove, - ), + builder: + (context) => GameShareBottomSheet( + game: game, + currentGamePosition: currentGamePosition, + orientation: orientation, + lastMove: lastMove, + ), ); }, ), @@ -140,10 +134,7 @@ class GameShareBottomSheet extends ConsumerWidget { icon: CupertinoIcons.link, closeOnPressed: false, onPressed: () { - launchShareDialog( - context, - uri: lichessUri('/${game.id}'), - ); + launchShareDialog(context, uri: lichessUri('/${game.id}')); }, child: Text(context.l10n.mobileShareGameURL), ), @@ -166,20 +157,14 @@ class GameShareBottomSheet extends ConsumerWidget { launchShareDialog( context, files: [gif], - subject: '${game.meta.perf.title} • ${context.l10n.resVsX( - game.white.fullName(context), - game.black.fullName(context), - )}', + subject: + '${game.meta.perf.title} • ${context.l10n.resVsX(game.white.fullName(context), game.black.fullName(context))}', ); } } catch (e) { debugPrint(e.toString()); if (context.mounted) { - showPlatformSnackbar( - context, - 'Failed to get GIF', - type: SnackBarType.error, - ); + showPlatformSnackbar(context, 'Failed to get GIF', type: SnackBarType.error); } } }, @@ -201,11 +186,7 @@ class GameShareBottomSheet extends ConsumerWidget { try { final image = await ref .read(gameShareServiceProvider) - .screenshotPosition( - orientation, - currentGamePosition.fen, - lastMove, - ); + .screenshotPosition(orientation, currentGamePosition.fen, lastMove); if (context.mounted) { launchShareDialog( context, @@ -217,11 +198,7 @@ class GameShareBottomSheet extends ConsumerWidget { } } catch (e) { if (context.mounted) { - showPlatformSnackbar( - context, - 'Failed to get GIF', - type: SnackBarType.error, - ); + showPlatformSnackbar(context, 'Failed to get GIF', type: SnackBarType.error); } } }, @@ -240,22 +217,13 @@ class GameShareBottomSheet extends ConsumerWidget { child: Text('PGN: ${context.l10n.downloadAnnotated}'), onPressed: () async { try { - final pgn = await ref - .read(gameShareServiceProvider) - .annotatedPgn(game.id); + final pgn = await ref.read(gameShareServiceProvider).annotatedPgn(game.id); if (context.mounted) { - launchShareDialog( - context, - text: pgn, - ); + launchShareDialog(context, text: pgn); } } catch (e) { if (context.mounted) { - showPlatformSnackbar( - context, - 'Failed to get PGN', - type: SnackBarType.error, - ); + showPlatformSnackbar(context, 'Failed to get PGN', type: SnackBarType.error); } } }, @@ -275,21 +243,13 @@ class GameShareBottomSheet extends ConsumerWidget { child: Text('PGN: ${context.l10n.downloadRaw}'), onPressed: () async { try { - final pgn = - await ref.read(gameShareServiceProvider).rawPgn(game.id); + final pgn = await ref.read(gameShareServiceProvider).rawPgn(game.id); if (context.mounted) { - launchShareDialog( - context, - text: pgn, - ); + launchShareDialog(context, text: pgn); } } catch (e) { if (context.mounted) { - showPlatformSnackbar( - context, - 'Failed to get PGN', - type: SnackBarType.error, - ); + showPlatformSnackbar(context, 'Failed to get PGN', type: SnackBarType.error); } } }, @@ -308,15 +268,11 @@ class _LobbyGameTitle extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final mode = - seek.rated ? ' • ${context.l10n.rated}' : ' • ${context.l10n.casual}'; + final mode = seek.rated ? ' • ${context.l10n.rated}' : ' • ${context.l10n.casual}'; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - seek.perf.icon, - color: DefaultTextStyle.of(context).style.color, - ), + Icon(seek.perf.icon, color: DefaultTextStyle.of(context).style.color), const SizedBox(width: 4.0), Text('${seek.timeIncrement?.display}$mode'), ], @@ -325,41 +281,29 @@ class _LobbyGameTitle extends ConsumerWidget { } class _ChallengeGameTitle extends ConsumerWidget { - const _ChallengeGameTitle({ - required this.challenge, - }); + const _ChallengeGameTitle({required this.challenge}); final ChallengeRequest challenge; @override Widget build(BuildContext context, WidgetRef ref) { - final mode = challenge.rated - ? ' • ${context.l10n.rated}' - : ' • ${context.l10n.casual}'; + final mode = challenge.rated ? ' • ${context.l10n.rated}' : ' • ${context.l10n.casual}'; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - challenge.perf.icon, - color: DefaultTextStyle.of(context).style.color, - ), + Icon(challenge.perf.icon, color: DefaultTextStyle.of(context).style.color), const SizedBox(width: 4.0), if (challenge.timeIncrement != null) Text('${challenge.timeIncrement?.display}$mode') else if (challenge.days != null) - Text( - '${context.l10n.nbDays(challenge.days!)}$mode', - ), + Text('${context.l10n.nbDays(challenge.days!)}$mode'), ], ); } } class _StandaloneGameTitle extends ConsumerWidget { - const _StandaloneGameTitle({ - required this.id, - this.lastMoveAt, - }); + const _StandaloneGameTitle({required this.id, this.lastMoveAt}); final GameFullId id; @@ -370,21 +314,14 @@ class _StandaloneGameTitle extends ConsumerWidget { final metaAsync = ref.watch(gameMetaProvider(id)); return metaAsync.maybeWhen( data: (meta) { - final mode = meta.rated - ? ' • ${context.l10n.rated}' - : ' • ${context.l10n.casual}'; + final mode = meta.rated ? ' • ${context.l10n.rated}' : ' • ${context.l10n.casual}'; - final info = lastMoveAt != null - ? ' • ${_gameTitledateFormat.format(lastMoveAt!)}' - : mode; + final info = lastMoveAt != null ? ' • ${_gameTitledateFormat.format(lastMoveAt!)}' : mode; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - meta.perf.icon, - color: DefaultTextStyle.of(context).style.color, - ), + Icon(meta.perf.icon, color: DefaultTextStyle.of(context).style.color), const SizedBox(width: 4.0), if (meta.clock != null) Expanded( @@ -404,11 +341,7 @@ class _StandaloneGameTitle extends ConsumerWidget { ) else Expanded( - child: AutoSizeText( - '${meta.perf.title}$info', - maxLines: 1, - minFontSize: 14.0, - ), + child: AutoSizeText('${meta.perf.title}$info', maxLines: 1, minFontSize: 14.0), ), ], ); diff --git a/lib/src/view/game/game_list_tile.dart b/lib/src/view/game/game_list_tile.dart index d0f9d2de20..9aa7014cf9 100644 --- a/lib/src/view/game/game_list_tile.dart +++ b/lib/src/view/game/game_list_tile.dart @@ -61,26 +61,26 @@ class GameListTile extends StatelessWidget { isDismissible: true, isScrollControlled: true, showDragHandle: true, - builder: (context) => _ContextMenu( - game: game, - mySide: mySide, - oppponentTitle: opponentTitle, - icon: icon, - subtitle: subtitle, - trailing: trailing, - ), + builder: + (context) => _ContextMenu( + game: game, + mySide: mySide, + oppponentTitle: opponentTitle, + icon: icon, + subtitle: subtitle, + trailing: trailing, + ), ); }, leading: icon != null ? Icon(icon) : null, title: opponentTitle, - subtitle: subtitle != null - ? DefaultTextStyle.merge( - child: subtitle!, - style: TextStyle( - color: textShade(context, Styles.subtitleOpacity), - ), - ) - : null, + subtitle: + subtitle != null + ? DefaultTextStyle.merge( + child: subtitle!, + style: TextStyle(color: textShade(context, Styles.subtitleOpacity)), + ) + : null, trailing: trailing, padding: padding, ); @@ -114,25 +114,18 @@ class _ContextMenu extends ConsumerWidget { return BottomSheetScrollableContainer( children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0).add( - const EdgeInsets.only(bottom: 8.0), - ), + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + ).add(const EdgeInsets.only(bottom: 8.0)), child: Text( - context.l10n.resVsX( - game.white.fullName(context), - game.black.fullName(context), - ), - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - letterSpacing: -0.5, - ), + context.l10n.resVsX(game.white.fullName(context), game.black.fullName(context)), + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, letterSpacing: -0.5), ), ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0).add( - const EdgeInsets.only(bottom: 8.0), - ), + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + ).add(const EdgeInsets.only(bottom: 8.0)), child: LayoutBuilder( builder: (context, constraints) { return IntrinsicHeight( @@ -141,8 +134,7 @@ class _ContextMenu extends ConsumerWidget { children: [ if (game.lastFen != null) BoardThumbnail( - size: constraints.maxWidth - - (constraints.maxWidth / 1.618), + size: constraints.maxWidth - (constraints.maxWidth / 1.618), fen: game.lastFen!, orientation: mySide, lastMove: game.lastMove, @@ -160,17 +152,12 @@ class _ContextMenu extends ConsumerWidget { children: [ Text( '${game.clockDisplay} • ${game.rated ? context.l10n.rated : context.l10n.casual}', - style: const TextStyle( - fontWeight: FontWeight.w500, - ), + style: const TextStyle(fontWeight: FontWeight.w500), ), Text( _dateFormatter.format(game.lastMoveAt), style: TextStyle( - color: textShade( - context, - Styles.subtitleOpacity, - ), + color: textShade(context, Styles.subtitleOpacity), fontSize: 12, ), ), @@ -189,9 +176,10 @@ class _ContextMenu extends ConsumerWidget { winner: game.winner, ), style: TextStyle( - color: game.winner == null - ? customColors?.brag - : game.winner == mySide + color: + game.winner == null + ? customColors?.brag + : game.winner == mySide ? customColors?.good : customColors?.error, ), @@ -201,10 +189,7 @@ class _ContextMenu extends ConsumerWidget { game.opening!.name, maxLines: 2, style: TextStyle( - color: textShade( - context, - Styles.subtitleOpacity, - ), + color: textShade(context, Styles.subtitleOpacity), fontSize: 12, ), overflow: TextOverflow.ellipsis, @@ -221,33 +206,29 @@ class _ContextMenu extends ConsumerWidget { ), BottomSheetContextMenuAction( icon: Icons.biotech, - onPressed: game.variant.isReadSupported - ? () { - pushPlatformRoute( - context, - builder: (context) => AnalysisScreen( - options: AnalysisOptions( - orientation: orientation, - gameId: game.id, - ), - ), - ); - } - : () { - showPlatformSnackbar( - context, - 'This variant is not supported yet.', - type: SnackBarType.info, - ); - }, + onPressed: + game.variant.isReadSupported + ? () { + pushPlatformRoute( + context, + builder: + (context) => AnalysisScreen( + options: AnalysisOptions(orientation: orientation, gameId: game.id), + ), + ); + } + : () { + showPlatformSnackbar( + context, + 'This variant is not supported yet.', + type: SnackBarType.info, + ); + }, child: Text(context.l10n.gameAnalysis), ), BottomSheetContextMenuAction( onPressed: () { - launchShareDialog( - context, - uri: lichessUri('/${game.id}'), - ); + launchShareDialog(context, uri: lichessUri('/${game.id}')); }, icon: CupertinoIcons.link, closeOnPressed: false, @@ -272,20 +253,14 @@ class _ContextMenu extends ConsumerWidget { launchShareDialog( context, files: [gif], - subject: '${game.perf.title} • ${context.l10n.resVsX( - game.white.fullName(context), - game.black.fullName(context), - )}', + subject: + '${game.perf.title} • ${context.l10n.resVsX(game.white.fullName(context), game.black.fullName(context))}', ); } } catch (e) { debugPrint(e.toString()); if (context.mounted) { - showPlatformSnackbar( - context, - 'Failed to get GIF', - type: SnackBarType.error, - ); + showPlatformSnackbar(context, 'Failed to get GIF', type: SnackBarType.error); } } }, @@ -307,11 +282,7 @@ class _ContextMenu extends ConsumerWidget { try { final image = await ref .read(gameShareServiceProvider) - .screenshotPosition( - orientation, - game.lastFen!, - game.lastMove, - ); + .screenshotPosition(orientation, game.lastFen!, game.lastMove); if (context.mounted) { launchShareDialog( context, @@ -323,11 +294,7 @@ class _ContextMenu extends ConsumerWidget { } } catch (e) { if (context.mounted) { - showPlatformSnackbar( - context, - 'Failed to get GIF', - type: SnackBarType.error, - ); + showPlatformSnackbar(context, 'Failed to get GIF', type: SnackBarType.error); } } }, @@ -346,22 +313,13 @@ class _ContextMenu extends ConsumerWidget { child: Text('PGN: ${context.l10n.downloadAnnotated}'), onPressed: () async { try { - final pgn = await ref - .read(gameShareServiceProvider) - .annotatedPgn(game.id); + final pgn = await ref.read(gameShareServiceProvider).annotatedPgn(game.id); if (context.mounted) { - launchShareDialog( - context, - text: pgn, - ); + launchShareDialog(context, text: pgn); } } catch (e) { if (context.mounted) { - showPlatformSnackbar( - context, - 'Failed to get PGN', - type: SnackBarType.error, - ); + showPlatformSnackbar(context, 'Failed to get PGN', type: SnackBarType.error); } } }, @@ -381,21 +339,13 @@ class _ContextMenu extends ConsumerWidget { child: Text('PGN: ${context.l10n.downloadRaw}'), onPressed: () async { try { - final pgn = - await ref.read(gameShareServiceProvider).rawPgn(game.id); + final pgn = await ref.read(gameShareServiceProvider).rawPgn(game.id); if (context.mounted) { - launchShareDialog( - context, - text: pgn, - ); + launchShareDialog(context, text: pgn); } } catch (e) { if (context.mounted) { - showPlatformSnackbar( - context, - 'Failed to get PGN', - type: SnackBarType.error, - ); + showPlatformSnackbar(context, 'Failed to get PGN', type: SnackBarType.error); } } }, @@ -409,11 +359,7 @@ class _ContextMenu extends ConsumerWidget { /// A list tile that shows extended game info including a result icon and analysis icon. class ExtendedGameListTile extends StatelessWidget { - const ExtendedGameListTile({ - required this.item, - this.userId, - this.padding, - }); + const ExtendedGameListTile({required this.item, this.userId, this.padding}); final LightArchivedGameWithPov item; final UserId? userId; @@ -427,27 +373,14 @@ class ExtendedGameListTile extends StatelessWidget { final opponent = youAre == Side.white ? game.black : game.white; Widget getResultIcon(LightArchivedGame game, Side mySide) { - if (game.status == GameStatus.aborted || - game.status == GameStatus.noStart) { - return const Icon( - CupertinoIcons.xmark_square_fill, - color: LichessColors.grey, - ); + if (game.status == GameStatus.aborted || game.status == GameStatus.noStart) { + return const Icon(CupertinoIcons.xmark_square_fill, color: LichessColors.grey); } else { return game.winner == null - ? Icon( - CupertinoIcons.equal_square_fill, - color: context.lichessColors.brag, - ) + ? Icon(CupertinoIcons.equal_square_fill, color: context.lichessColors.brag) : game.winner == mySide - ? Icon( - CupertinoIcons.plus_square_fill, - color: context.lichessColors.good, - ) - : Icon( - CupertinoIcons.minus_square_fill, - color: context.lichessColors.error, - ); + ? Icon(CupertinoIcons.plus_square_fill, color: context.lichessColors.good) + : Icon(CupertinoIcons.minus_square_fill, color: context.lichessColors.error); } } @@ -455,32 +388,32 @@ class ExtendedGameListTile extends StatelessWidget { game: game, mySide: youAre, padding: padding, - onTap: game.variant.isReadSupported - ? () { - pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => game.fullId != null - ? GameScreen( - initialGameId: game.fullId, - loadingFen: game.lastFen, - loadingLastMove: game.lastMove, - loadingOrientation: youAre, - lastMoveAt: game.lastMoveAt, - ) - : ArchivedGameScreen( - gameData: game, - orientation: youAre, - ), - ); - } - : () { - showPlatformSnackbar( - context, - 'This variant is not supported yet.', - type: SnackBarType.info, - ); - }, + onTap: + game.variant.isReadSupported + ? () { + pushPlatformRoute( + context, + rootNavigator: true, + builder: + (context) => + game.fullId != null + ? GameScreen( + initialGameId: game.fullId, + loadingFen: game.lastFen, + loadingLastMove: game.lastMove, + loadingOrientation: youAre, + lastMoveAt: game.lastMoveAt, + ) + : ArchivedGameScreen(gameData: game, orientation: youAre), + ); + } + : () { + showPlatformSnackbar( + context, + 'This variant is not supported yet.', + type: SnackBarType.info, + ); + }, icon: game.perf.icon, opponentTitle: UserFullNameWidget.player( user: opponent.user, @@ -492,10 +425,7 @@ class ExtendedGameListTile extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ if (me.analysis != null) ...[ - Icon( - CupertinoIcons.chart_bar_alt_fill, - color: textShade(context, 0.5), - ), + Icon(CupertinoIcons.chart_bar_alt_fill, color: textShade(context, 0.5)), const SizedBox(width: 5), ], getResultIcon(game, youAre), diff --git a/lib/src/view/game/game_loading_board.dart b/lib/src/view/game/game_loading_board.dart index bf0a216b2b..0f7a3252c1 100644 --- a/lib/src/view/game/game_loading_board.dart +++ b/lib/src/view/game/game_loading_board.dart @@ -46,10 +46,7 @@ class LobbyScreenLoadingContent extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - Icon( - seek.perf.icon, - color: DefaultTextStyle.of(context).style.color, - ), + Icon(seek.perf.icon, color: DefaultTextStyle.of(context).style.color), const SizedBox(width: 8.0), Text( seek.timeIncrement?.display ?? @@ -171,12 +168,7 @@ class ChallengeLoadingContent extends StatelessWidget { } class StandaloneGameLoadingBoard extends StatelessWidget { - const StandaloneGameLoadingBoard({ - this.fen, - this.lastMove, - this.orientation, - super.key, - }); + const StandaloneGameLoadingBoard({this.fen, this.lastMove, this.orientation, super.key}); final String? fen; final Side? orientation; @@ -234,10 +226,7 @@ class LoadGameError extends StatelessWidget { /// A board that shows a message that a challenge has been declined. class ChallengeDeclinedBoard extends StatelessWidget { - const ChallengeDeclinedBoard({ - required this.declineReason, - required this.challenge, - }); + const ChallengeDeclinedBoard({required this.declineReason, required this.challenge}); final String declineReason; final Challenge challenge; @@ -272,10 +261,7 @@ class ChallengeDeclinedBoard extends StatelessWidget { ), const SizedBox(height: 8.0), Divider(height: 26.0, thickness: 0.0, color: textColor), - Text( - declineReason, - style: const TextStyle(fontStyle: FontStyle.italic), - ), + Text(declineReason, style: const TextStyle(fontStyle: FontStyle.italic)), Divider(height: 26.0, thickness: 0.0, color: textColor), if (challenge.destUser != null) Align( @@ -284,9 +270,7 @@ class ChallengeDeclinedBoard extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ const Text(' — '), - UserFullNameWidget( - user: challenge.destUser?.user, - ), + UserFullNameWidget(user: challenge.destUser?.user), if (challenge.destUser?.lagRating != null) ...[ const SizedBox(width: 6.0), LagIndicator( @@ -329,13 +313,9 @@ class _LobbyNumbers extends ConsumerWidget { if (lobbyNumbers == null) { return Column( children: [ - Text( - context.l10n.nbPlayers(0).replaceAll('0', '...'), - ), + Text(context.l10n.nbPlayers(0).replaceAll('0', '...')), const SizedBox(height: 8.0), - Text( - context.l10n.nbGamesInPlay(0).replaceAll('0', '...'), - ), + Text(context.l10n.nbGamesInPlay(0).replaceAll('0', '...')), ], ); } else { diff --git a/lib/src/view/game/game_player.dart b/lib/src/view/game/game_player.dart index b46198dea2..e73dc5b354 100644 --- a/lib/src/view/game/game_player.dart +++ b/lib/src/view/game/game_player.dart @@ -58,8 +58,7 @@ class GamePlayer extends StatelessWidget { @override Widget build(BuildContext context) { final remaingHeight = estimateRemainingHeightLeftBoard(context); - final playerFontSize = - remaingHeight <= kSmallRemainingHeightLeftBoardThreshold ? 14.0 : 16.0; + final playerFontSize = remaingHeight <= kSmallRemainingHeightLeftBoardThreshold ? 14.0 : 16.0; final playerWidget = Column( mainAxisAlignment: MainAxisAlignment.center, @@ -67,9 +66,10 @@ class GamePlayer extends StatelessWidget { children: [ if (!zenMode) Row( - mainAxisAlignment: clockPosition == ClockPosition.right - ? MainAxisAlignment.start - : MainAxisAlignment.end, + mainAxisAlignment: + clockPosition == ClockPosition.right + ? MainAxisAlignment.start + : MainAxisAlignment.end, children: [ if (player.user != null) ...[ Icon( @@ -92,11 +92,11 @@ class GamePlayer extends StatelessWidget { player.user!.title!, style: TextStyle( fontSize: playerFontSize, - fontWeight: - player.user?.title == 'BOT' ? null : FontWeight.bold, - color: player.user?.title == 'BOT' - ? context.lichessColors.fancy - : context.lichessColors.brag, + fontWeight: player.user?.title == 'BOT' ? null : FontWeight.bold, + color: + player.user?.title == 'BOT' + ? context.lichessColors.fancy + : context.lichessColors.brag, ), ), const SizedBox(width: 5), @@ -105,10 +105,7 @@ class GamePlayer extends StatelessWidget { child: Text( player.displayName(context), overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: playerFontSize, - fontWeight: FontWeight.w600, - ), + style: TextStyle(fontSize: playerFontSize, fontWeight: FontWeight.w600), ), ), if (player.user?.flair != null) ...[ @@ -124,26 +121,22 @@ class GamePlayer extends StatelessWidget { RatingPrefAware( child: Text.rich( TextSpan( - text: - ' ${player.rating}${player.provisional == true ? '?' : ''}', + text: ' ${player.rating}${player.provisional == true ? '?' : ''}', children: [ if (player.ratingDiff != null) TextSpan( - text: - ' ${player.ratingDiff! > 0 ? '+' : ''}${player.ratingDiff}', + text: ' ${player.ratingDiff! > 0 ? '+' : ''}${player.ratingDiff}', style: TextStyle( - color: player.ratingDiff! > 0 - ? context.lichessColors.good - : context.lichessColors.error, + color: + player.ratingDiff! > 0 + ? context.lichessColors.good + : context.lichessColors.error, ), ), ], ), overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 14, - color: textShade(context, 0.7), - ), + style: TextStyle(fontSize: 14, color: textShade(context, 0.7)), ), ), ], @@ -164,8 +157,7 @@ class GamePlayer extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (clock != null && clockPosition == ClockPosition.left) - Flexible(flex: 3, child: clock!), + if (clock != null && clockPosition == ClockPosition.left) Flexible(flex: 3, child: clock!), if (mePlaying && confirmMoveCallbacks != null) Expanded( flex: 7, @@ -182,38 +174,35 @@ class GamePlayer extends StatelessWidget { flex: 7, child: Padding( padding: const EdgeInsets.only(right: 16.0), - child: shouldLinkToUserProfile - ? GestureDetector( - onTap: player.user != null - ? () { - pushPlatformRoute( - context, - builder: (context) => mePlaying - ? const ProfileScreen() - : UserScreen( - user: player.user!, - ), - ); - } - : null, - child: playerWidget, - ) - : playerWidget, + child: + shouldLinkToUserProfile + ? GestureDetector( + onTap: + player.user != null + ? () { + pushPlatformRoute( + context, + builder: + (context) => + mePlaying + ? const ProfileScreen() + : UserScreen(user: player.user!), + ); + } + : null, + child: playerWidget, + ) + : playerWidget, ), ), - if (clock != null && clockPosition == ClockPosition.right) - Flexible(flex: 3, child: clock!), + if (clock != null && clockPosition == ClockPosition.right) Flexible(flex: 3, child: clock!), ], ); } } class ConfirmMove extends StatelessWidget { - const ConfirmMove({ - required this.onConfirm, - required this.onCancel, - super.key, - }); + const ConfirmMove({required this.onConfirm, required this.onCancel, super.key}); final VoidCallback onConfirm; final VoidCallback onCancel; @@ -253,11 +242,7 @@ class ConfirmMove extends StatelessWidget { } class MoveExpiration extends ConsumerStatefulWidget { - const MoveExpiration({ - required this.timeToMove, - required this.mePlaying, - super.key, - }); + const MoveExpiration({required this.timeToMove, required this.mePlaying, super.key}); final Duration timeToMove; final bool mePlaying; @@ -318,13 +303,9 @@ class _MoveExpirationState extends ConsumerState { return secs <= 20 ? Text( - context.l10n.nbSecondsToPlayTheFirstMove(secs), - style: TextStyle( - color: widget.mePlaying && emerg - ? context.lichessColors.error - : null, - ), - ) + context.l10n.nbSecondsToPlayTheFirstMove(secs), + style: TextStyle(color: widget.mePlaying && emerg ? context.lichessColors.error : null), + ) : const Text(''); } } @@ -347,24 +328,17 @@ class MaterialDifferenceDisplay extends StatelessWidget { return materialDifferenceFormat?.visible ?? true ? Row( - children: [ - for (final role in Role.values) - for (int i = 0; i < piecesToRender[role]!; i++) - Icon( - _iconByRole[role], - size: 13, - color: Colors.grey, - ), - const SizedBox(width: 3), - Text( - style: const TextStyle( - fontSize: 13, - color: Colors.grey, - ), - materialDiff.score > 0 ? '+${materialDiff.score}' : '', - ), - ], - ) + children: [ + for (final role in Role.values) + for (int i = 0; i < piecesToRender[role]!; i++) + Icon(_iconByRole[role], size: 13, color: Colors.grey), + const SizedBox(width: 3), + Text( + style: const TextStyle(fontSize: 13, color: Colors.grey), + materialDiff.score > 0 ? '+${materialDiff.score}' : '', + ), + ], + ) : const SizedBox.shrink(); } } diff --git a/lib/src/view/game/game_result_dialog.dart b/lib/src/view/game/game_result_dialog.dart index a4e6522d53..6c456921ba 100644 --- a/lib/src/view/game/game_result_dialog.dart +++ b/lib/src/view/game/game_result_dialog.dart @@ -23,11 +23,7 @@ import 'package:lichess_mobile/src/widgets/pgn.dart'; import 'status_l10n.dart'; class GameResultDialog extends ConsumerStatefulWidget { - const GameResultDialog({ - required this.id, - required this.onNewOpponentCallback, - super.key, - }); + const GameResultDialog({required this.id, required this.onNewOpponentCallback, super.key}); final GameFullId id; @@ -46,19 +42,18 @@ Widget _adaptiveDialog(BuildContext context, Widget content) { ); final screenWidth = MediaQuery.of(context).size.width; - final paddedContent = Padding( - padding: const EdgeInsets.all(16.0), - child: content, - ); + final paddedContent = Padding(padding: const EdgeInsets.all(16.0), child: content); return Dialog( - backgroundColor: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoDynamicColor.resolve(dialogColor, context) - : null, + backgroundColor: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoDynamicColor.resolve(dialogColor, context) + : null, child: SizedBox( width: min(screenWidth, kMaterialPopupMenuMaxWidth), - child: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoPopupSurface(child: paddedContent) - : paddedContent, + child: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoPopupSurface(child: paddedContent) + : paddedContent, ), ); } @@ -108,10 +103,7 @@ class _GameEndDialogState extends ConsumerState { children: [ const Padding( padding: EdgeInsets.only(bottom: 15.0), - child: Text( - 'Your opponent has offered a rematch', - textAlign: TextAlign.center, - ), + child: Text('Your opponent has offered a rematch', textAlign: TextAlign.center), ), Padding( padding: const EdgeInsets.only(bottom: 15.0), @@ -122,9 +114,7 @@ class _GameEndDialogState extends ConsumerState { semanticsLabel: context.l10n.rematch, child: const Text('Accept rematch'), onPressed: () { - ref - .read(ctrlProvider.notifier) - .proposeOrAcceptRematch(); + ref.read(ctrlProvider.notifier).proposeOrAcceptRematch(); }, ), SecondaryButton( @@ -139,9 +129,10 @@ class _GameEndDialogState extends ConsumerState { ), ], ), - crossFadeState: gameState.game.opponent?.offeringRematch ?? false - ? CrossFadeState.showSecond - : CrossFadeState.showFirst, + crossFadeState: + gameState.game.opponent?.offeringRematch ?? false + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, ), if (gameState.game.me?.offeringRematch == true) SecondaryButton( @@ -149,40 +140,32 @@ class _GameEndDialogState extends ConsumerState { onPressed: () { ref.read(ctrlProvider.notifier).declineRematch(); }, - child: Text( - context.l10n.cancelRematchOffer, - textAlign: TextAlign.center, - ), + child: Text(context.l10n.cancelRematchOffer, textAlign: TextAlign.center), ) else if (gameState.canOfferRematch) SecondaryButton( semanticsLabel: context.l10n.rematch, - onPressed: _activateButtons && - gameState.game.opponent?.onGame == true && - gameState.game.opponent?.offeringRematch != true - ? () { - ref.read(ctrlProvider.notifier).proposeOrAcceptRematch(); - } - : null, - child: Text( - context.l10n.rematch, - textAlign: TextAlign.center, - ), + onPressed: + _activateButtons && + gameState.game.opponent?.onGame == true && + gameState.game.opponent?.offeringRematch != true + ? () { + ref.read(ctrlProvider.notifier).proposeOrAcceptRematch(); + } + : null, + child: Text(context.l10n.rematch, textAlign: TextAlign.center), ), if (gameState.canGetNewOpponent) SecondaryButton( semanticsLabel: context.l10n.newOpponent, - onPressed: _activateButtons - ? () { - Navigator.of(context) - .popUntil((route) => route is! PopupRoute); - widget.onNewOpponentCallback(gameState.game); - } - : null, - child: Text( - context.l10n.newOpponent, - textAlign: TextAlign.center, - ), + onPressed: + _activateButtons + ? () { + Navigator.of(context).popUntil((route) => route is! PopupRoute); + widget.onNewOpponentCallback(gameState.game); + } + : null, + child: Text(context.l10n.newOpponent, textAlign: TextAlign.center), ), if (gameState.game.userAnalysable) SecondaryButton( @@ -190,15 +173,10 @@ class _GameEndDialogState extends ConsumerState { onPressed: () { pushPlatformRoute( context, - builder: (_) => AnalysisScreen( - options: gameState.analysisOptions, - ), + builder: (_) => AnalysisScreen(options: gameState.analysisOptions), ); }, - child: Text( - context.l10n.analysis, - textAlign: TextAlign.center, - ), + child: Text(context.l10n.analysis, textAlign: TextAlign.center), ), ], ); @@ -217,11 +195,7 @@ class ArchivedGameResultDialog extends StatelessWidget { final content = Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - GameResult(game: game), - const SizedBox(height: 16.0), - PlayerSummary(game: game), - ], + children: [GameResult(game: game), const SizedBox(height: 16.0), PlayerSummary(game: game)], ); return _adaptiveDialog(context, content); @@ -229,11 +203,7 @@ class ArchivedGameResultDialog extends StatelessWidget { } class OverTheBoardGameResultDialog extends StatelessWidget { - const OverTheBoardGameResultDialog({ - super.key, - required this.game, - required this.onRematch, - }); + const OverTheBoardGameResultDialog({super.key, required this.game, required this.onRematch}); final OverTheBoardGame game; @@ -249,32 +219,27 @@ class OverTheBoardGameResultDialog extends StatelessWidget { SecondaryButton( semanticsLabel: context.l10n.rematch, onPressed: onRematch, - child: Text( - context.l10n.rematch, - textAlign: TextAlign.center, - ), + child: Text(context.l10n.rematch, textAlign: TextAlign.center), ), SecondaryButton( semanticsLabel: context.l10n.analysis, onPressed: () { pushPlatformRoute( context, - builder: (_) => AnalysisScreen( - options: AnalysisOptions( - orientation: Side.white, - standalone: ( - pgn: game.makePgn(), - isComputerAnalysisAllowed: true, - variant: game.meta.variant, + builder: + (_) => AnalysisScreen( + options: AnalysisOptions( + orientation: Side.white, + standalone: ( + pgn: game.makePgn(), + isComputerAnalysisAllowed: true, + variant: game.meta.variant, + ), + ), ), - ), - ), ); }, - child: Text( - context.l10n.analysis, - textAlign: TextAlign.center, - ), + child: Text(context.l10n.analysis, textAlign: TextAlign.center), ), ], ); @@ -298,22 +263,14 @@ class PlayerSummary extends ConsumerWidget { return const SizedBox.shrink(); } - Widget makeStatCol( - int value, - String Function(int count) labelFn, - Color? color, - ) { + Widget makeStatCol(int value, String Function(int count) labelFn, Color? color) { return Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( value.toString(), - style: TextStyle( - fontSize: 18.0, - color: color, - fontWeight: FontWeight.bold, - ), + style: TextStyle(fontSize: 18.0, color: color, fontWeight: FontWeight.bold), ), const SizedBox(height: 4.0), FittedBox( @@ -358,9 +315,10 @@ class GameResult extends StatelessWidget { @override Widget build(BuildContext context) { - final showWinner = game.winner != null - ? ' • ${game.winner == Side.white ? context.l10n.whiteIsVictorious : context.l10n.blackIsVictorious}' - : ''; + final showWinner = + game.winner != null + ? ' • ${game.winner == Side.white ? context.l10n.whiteIsVictorious : context.l10n.blackIsVictorious}' + : ''; return Column( mainAxisSize: MainAxisSize.min, @@ -370,27 +328,15 @@ class GameResult extends StatelessWidget { game.winner == null ? '½-½' : game.winner == Side.white - ? '1-0' - : '0-1', - style: const TextStyle( - fontSize: 18.0, - fontWeight: FontWeight.bold, - ), + ? '1-0' + : '0-1', + style: const TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), const SizedBox(height: 6.0), Text( - '${gameStatusL10n( - context, - variant: game.meta.variant, - status: game.status, - lastPosition: game.lastPosition, - winner: game.winner, - isThreefoldRepetition: game.isThreefoldRepetition, - )}$showWinner', - style: const TextStyle( - fontStyle: FontStyle.italic, - ), + '${gameStatusL10n(context, variant: game.meta.variant, status: game.status, lastPosition: game.lastPosition, winner: game.winner, isThreefoldRepetition: game.isThreefoldRepetition)}$showWinner', + style: const TextStyle(fontStyle: FontStyle.italic), textAlign: TextAlign.center, ), ], diff --git a/lib/src/view/game/game_screen.dart b/lib/src/view/game/game_screen.dart index 7e394c3587..7b806549f5 100644 --- a/lib/src/view/game/game_screen.dart +++ b/lib/src/view/game/game_screen.dart @@ -39,9 +39,9 @@ class GameScreen extends ConsumerStatefulWidget { this.lastMoveAt, super.key, }) : assert( - initialGameId != null || seek != null || challenge != null, - 'Either a seek, a challenge or an initial game id must be provided.', - ); + initialGameId != null || seek != null || challenge != null, + 'Either a seek, a challenge or an initial game id must be provided.', + ); // tweak final GameSeek? seek; @@ -95,9 +95,7 @@ class _GameScreenState extends ConsumerState with RouteAware { @override void didPop() { - if (mounted && - (widget.source == _GameSource.lobby || - widget.source == _GameSource.challenge)) { + if (mounted && (widget.source == _GameSource.lobby || widget.source == _GameSource.challenge)) { ref.invalidate(myRecentGamesProvider); ref.invalidate(accountProvider); } @@ -106,105 +104,99 @@ class _GameScreenState extends ConsumerState with RouteAware { @override Widget build(BuildContext context) { - final provider = currentGameProvider( - widget.seek, - widget.challenge, - widget.initialGameId, - ); - - return ref.watch(provider).when( - data: (data) { - final ( - gameFullId: gameId, - challenge: challenge, - declineReason: declineReason - ) = data; - final body = gameId != null - ? GameBody( - id: gameId, - loadingBoardWidget: StandaloneGameLoadingBoard( - fen: widget.loadingFen, - lastMove: widget.loadingLastMove, - orientation: widget.loadingOrientation, - ), - whiteClockKey: _whiteClockKey, - blackClockKey: _blackClockKey, - boardKey: _boardKey, - onLoadGameCallback: (id) { - ref.read(provider.notifier).loadGame(id); - }, - onNewOpponentCallback: (game) { - if (widget.source == _GameSource.lobby) { - ref.read(provider.notifier).newOpponent(); - } else { - final savedSetup = ref.read(gameSetupPreferencesProvider); - pushReplacementPlatformRoute( - context, - rootNavigator: true, - builder: (_) => GameScreen( - seek: GameSeek.newOpponentFromGame(game, savedSetup), + final provider = currentGameProvider(widget.seek, widget.challenge, widget.initialGameId); + + return ref + .watch(provider) + .when( + data: (data) { + final (gameFullId: gameId, challenge: challenge, declineReason: declineReason) = data; + final body = + gameId != null + ? GameBody( + id: gameId, + loadingBoardWidget: StandaloneGameLoadingBoard( + fen: widget.loadingFen, + lastMove: widget.loadingLastMove, + orientation: widget.loadingOrientation, ), + whiteClockKey: _whiteClockKey, + blackClockKey: _blackClockKey, + boardKey: _boardKey, + onLoadGameCallback: (id) { + ref.read(provider.notifier).loadGame(id); + }, + onNewOpponentCallback: (game) { + if (widget.source == _GameSource.lobby) { + ref.read(provider.notifier).newOpponent(); + } else { + final savedSetup = ref.read(gameSetupPreferencesProvider); + pushReplacementPlatformRoute( + context, + rootNavigator: true, + builder: + (_) => GameScreen( + seek: GameSeek.newOpponentFromGame(game, savedSetup), + ), + ); + } + }, + ) + : widget.challenge != null && challenge != null + ? ChallengeDeclinedBoard( + challenge: challenge, + declineReason: + declineReason != null + ? declineReason.label(context.l10n) + : ChallengeDeclineReason.generic.label(context.l10n), + ) + : const LoadGameError('Could not create the game.'); + return PlatformScaffold( + resizeToAvoidBottomInset: false, + appBar: GameAppBar(id: gameId, lastMoveAt: widget.lastMoveAt), + body: body, + ); + }, + loading: () { + final loadingBoard = + widget.seek != null + ? LobbyScreenLoadingContent( + widget.seek!, + () => ref.read(createGameServiceProvider).cancelSeek(), + ) + : widget.challenge != null + ? ChallengeLoadingContent( + widget.challenge!, + () => ref.read(createGameServiceProvider).cancelChallenge(), + ) + : const StandaloneGameLoadingBoard(); + + return PlatformScaffold( + resizeToAvoidBottomInset: false, + appBar: GameAppBar(seek: widget.seek, lastMoveAt: widget.lastMoveAt), + body: PopScope(canPop: false, child: loadingBoard), + ); + }, + error: (e, s) { + debugPrint('SEVERE: [GameScreen] could not create game; $e\n$s'); + + // lichess sends a 400 response if user has disallowed challenges + final message = + e is ServerException && e.statusCode == 400 + ? LoadGameError( + 'Could not create the game: ${e.jsonError?['error'] as String?}', + ) + : const LoadGameError( + 'Sorry, we could not create the game. Please try again later.', ); - } - }, - ) - : widget.challenge != null && challenge != null - ? ChallengeDeclinedBoard( - challenge: challenge, - declineReason: declineReason != null - ? declineReason.label(context.l10n) - : ChallengeDeclineReason.generic.label(context.l10n), - ) - : const LoadGameError('Could not create the game.'); - return PlatformScaffold( - resizeToAvoidBottomInset: false, - appBar: GameAppBar(id: gameId, lastMoveAt: widget.lastMoveAt), - body: body, - ); - }, - loading: () { - final loadingBoard = widget.seek != null - ? LobbyScreenLoadingContent( - widget.seek!, - () => ref.read(createGameServiceProvider).cancelSeek(), - ) - : widget.challenge != null - ? ChallengeLoadingContent( - widget.challenge!, - () => ref.read(createGameServiceProvider).cancelChallenge(), - ) - : const StandaloneGameLoadingBoard(); - - return PlatformScaffold( - resizeToAvoidBottomInset: false, - appBar: GameAppBar(seek: widget.seek, lastMoveAt: widget.lastMoveAt), - body: PopScope( - canPop: false, - child: loadingBoard, - ), - ); - }, - error: (e, s) { - debugPrint( - 'SEVERE: [GameScreen] could not create game; $e\n$s', - ); - - // lichess sends a 400 response if user has disallowed challenges - final message = e is ServerException && e.statusCode == 400 - ? LoadGameError( - 'Could not create the game: ${e.jsonError?['error'] as String?}', - ) - : const LoadGameError( - 'Sorry, we could not create the game. Please try again later.', - ); - final body = PopScope(child: message); + final body = PopScope(child: message); - return PlatformScaffold( - appBar: GameAppBar(seek: widget.seek, lastMoveAt: widget.lastMoveAt), - body: body, + return PlatformScaffold( + appBar: GameAppBar(seek: widget.seek, lastMoveAt: widget.lastMoveAt), + body: body, + ); + }, ); - }, - ); } } diff --git a/lib/src/view/game/game_screen_providers.dart b/lib/src/view/game/game_screen_providers.dart index 285750cd9f..8f138ae38c 100644 --- a/lib/src/view/game/game_screen_providers.dart +++ b/lib/src/view/game/game_screen_providers.dart @@ -17,11 +17,7 @@ part 'game_screen_providers.g.dart'; @riverpod class CurrentGame extends _$CurrentGame { @override - Future build( - GameSeek? seek, - ChallengeRequest? challenge, - GameFullId? gameId, - ) { + Future build(GameSeek? seek, ChallengeRequest? challenge, GameFullId? gameId) { assert( gameId != null || seek != null || challenge != null, 'Either a seek, challenge or a game id must be provided.', @@ -37,9 +33,7 @@ class CurrentGame extends _$CurrentGame { return service.newRealTimeChallenge(challenge); } - return Future.value( - (gameFullId: gameId!, challenge: null, declineReason: null), - ); + return Future.value((gameFullId: gameId!, challenge: null, declineReason: null)); } /// Search for a new opponent (lobby only). @@ -48,17 +42,16 @@ class CurrentGame extends _$CurrentGame { final service = ref.read(createGameServiceProvider); state = const AsyncValue.loading(); state = AsyncValue.data( - await service.newLobbyGame(seek!).then( - (id) => (gameFullId: id, challenge: null, declineReason: null), - ), + await service + .newLobbyGame(seek!) + .then((id) => (gameFullId: id, challenge: null, declineReason: null)), ); } } /// Load a game from its id. void loadGame(GameFullId id) { - state = - AsyncValue.data((gameFullId: id, challenge: null, declineReason: null)); + state = AsyncValue.data((gameFullId: id, challenge: null, declineReason: null)); } } @@ -77,45 +70,33 @@ class IsBoardTurned extends _$IsBoardTurned { @riverpod Future shouldPreventGoingBack(Ref ref, GameFullId gameId) { return ref.watch( - gameControllerProvider(gameId).selectAsync( - (state) => - state.game.meta.speed != Speed.correspondence && state.game.playable, - ), + gameControllerProvider( + gameId, + ).selectAsync((state) => state.game.meta.speed != Speed.correspondence && state.game.playable), ); } /// User game preferences, defined server-side. @riverpod -Future< - ({ - ServerGamePrefs? prefs, - bool shouldConfirmMove, - bool isZenModeEnabled, - bool canAutoQueen - })> userGamePrefs(Ref ref, GameFullId gameId) async { +Future<({ServerGamePrefs? prefs, bool shouldConfirmMove, bool isZenModeEnabled, bool canAutoQueen})> +userGamePrefs(Ref ref, GameFullId gameId) async { final prefs = await ref.watch( gameControllerProvider(gameId).selectAsync((state) => state.game.prefs), ); final shouldConfirmMove = await ref.watch( - gameControllerProvider(gameId).selectAsync( - (state) => state.shouldConfirmMove, - ), + gameControllerProvider(gameId).selectAsync((state) => state.shouldConfirmMove), ); final isZenModeEnabled = await ref.watch( - gameControllerProvider(gameId).selectAsync( - (state) => state.isZenModeEnabled, - ), + gameControllerProvider(gameId).selectAsync((state) => state.isZenModeEnabled), ); final canAutoQueen = await ref.watch( - gameControllerProvider(gameId).selectAsync( - (state) => state.canAutoQueen, - ), + gameControllerProvider(gameId).selectAsync((state) => state.canAutoQueen), ); return ( prefs: prefs, shouldConfirmMove: shouldConfirmMove, isZenModeEnabled: isZenModeEnabled, - canAutoQueen: canAutoQueen + canAutoQueen: canAutoQueen, ); } @@ -124,7 +105,5 @@ Future< /// This is data that won't change during the game. @riverpod Future gameMeta(Ref ref, GameFullId gameId) async { - return await ref.watch( - gameControllerProvider(gameId).selectAsync((state) => state.game.meta), - ); + return await ref.watch(gameControllerProvider(gameId).selectAsync((state) => state.game.meta)); } diff --git a/lib/src/view/game/game_settings.dart b/lib/src/view/game/game_settings.dart index b797510639..592709eb63 100644 --- a/lib/src/view/game/game_settings.dart +++ b/lib/src/view/game/game_settings.dart @@ -29,32 +29,22 @@ class GameSettings extends ConsumerWidget { return [ if (data.prefs?.submitMove == true) SwitchSettingTile( - title: Text( - context.l10n.preferencesMoveConfirmation, - ), + title: Text(context.l10n.preferencesMoveConfirmation), value: data.shouldConfirmMove, onChanged: (value) { - ref - .read(gameControllerProvider(id).notifier) - .toggleMoveConfirmation(); + ref.read(gameControllerProvider(id).notifier).toggleMoveConfirmation(); }, ), if (data.prefs?.autoQueen == AutoQueen.always) SwitchSettingTile( - title: Text( - context.l10n.preferencesPromoteToQueenAutomatically, - ), + title: Text(context.l10n.preferencesPromoteToQueenAutomatically), value: data.canAutoQueen, onChanged: (value) { - ref - .read(gameControllerProvider(id).notifier) - .toggleAutoQueen(); + ref.read(gameControllerProvider(id).notifier).toggleAutoQueen(); }, ), SwitchSettingTile( - title: Text( - context.l10n.preferencesZenMode, - ), + title: Text(context.l10n.preferencesZenMode), value: data.isZenModeEnabled, onChanged: (value) { ref.read(gameControllerProvider(id).notifier).toggleZenMode(); @@ -69,17 +59,11 @@ class GameSettings extends ConsumerWidget { title: const Text('Board settings'), trailing: const Icon(CupertinoIcons.chevron_right), onTap: () { - pushPlatformRoute( - context, - fullscreenDialog: true, - screen: const BoardSettingsScreen(), - ); + pushPlatformRoute(context, fullscreenDialog: true, screen: const BoardSettingsScreen()); }, ), SwitchSettingTile( - title: Text( - context.l10n.toggleTheChat, - ), + title: Text(context.l10n.toggleTheChat), value: gamePrefs.enableChat ?? false, onChanged: (value) { ref.read(gamePreferencesProvider.notifier).toggleChat(); diff --git a/lib/src/view/game/message_screen.dart b/lib/src/view/game/message_screen.dart index 00187cc040..2e21301209 100644 --- a/lib/src/view/game/message_screen.dart +++ b/lib/src/view/game/message_screen.dart @@ -19,11 +19,7 @@ class MessageScreen extends ConsumerStatefulWidget { final Widget title; final LightUser? me; - const MessageScreen({ - required this.id, - required this.title, - this.me, - }); + const MessageScreen({required this.id, required this.title, this.me}); @override ConsumerState createState() => _MessageScreenState(); @@ -54,10 +50,7 @@ class _MessageScreenState extends ConsumerState with RouteAware { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: widget.title, - centerTitle: true, - ), + appBar: PlatformAppBar(title: widget.title, centerTitle: true), body: _Body(me: widget.me, id: widget.id), ); } @@ -67,10 +60,7 @@ class _Body extends ConsumerWidget { final GameFullId id; final LightUser? me; - const _Body({ - required this.id, - required this.me, - }); + const _Body({required this.id, required this.me}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -84,9 +74,8 @@ class _Body extends ConsumerWidget { onTap: () => FocusScope.of(context).unfocus(), child: chatStateAsync.when( data: (chatState) { - final selectedMessages = chatState.messages - .where((m) => !m.troll && !m.deleted && !isSpam(m)) - .toList(); + final selectedMessages = + chatState.messages.where((m) => !m.troll && !m.deleted && !isSpam(m)).toList(); final messagesCount = selectedMessages.length; return ListView.builder( // remove the automatic bottom padding of the ListView, which on iOS @@ -100,23 +89,13 @@ class _Body extends ConsumerWidget { return (message.username == 'lichess') ? _MessageAction(message: message.message) : (message.username == me?.name) - ? _MessageBubble( - you: true, - message: message.message, - ) - : _MessageBubble( - you: false, - message: message.message, - ); + ? _MessageBubble(you: true, message: message.message) + : _MessageBubble(you: false, message: message.message); }, ); }, - loading: () => const Center( - child: CircularProgressIndicator(), - ), - error: (error, _) => Center( - child: Text(error.toString()), - ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, _) => Center(child: Text(error.toString())), ), ), ), @@ -138,10 +117,10 @@ class _MessageBubble extends ConsumerWidget { ? Theme.of(context).colorScheme.primaryContainer : CupertinoColors.systemGrey4.resolveFrom(context) : you - ? Theme.of(context).colorScheme.primaryContainer - : brightness == Brightness.light - ? lighten(LichessColors.grey) - : darken(LichessColors.grey, 0.5); + ? Theme.of(context).colorScheme.primaryContainer + : brightness == Brightness.light + ? lighten(LichessColors.grey) + : darken(LichessColors.grey, 0.5); Color _textColor(BuildContext context, Brightness brightness) => Theme.of(context).platform == TargetPlatform.iOS @@ -149,10 +128,10 @@ class _MessageBubble extends ConsumerWidget { ? Theme.of(context).colorScheme.onPrimaryContainer : CupertinoColors.label.resolveFrom(context) : you - ? Theme.of(context).colorScheme.onPrimaryContainer - : brightness == Brightness.light - ? Colors.black - : Colors.white; + ? Theme.of(context).colorScheme.onPrimaryContainer + : brightness == Brightness.light + ? Colors.black + : Colors.white; @override Widget build(BuildContext context, WidgetRef ref) { @@ -170,12 +149,7 @@ class _MessageBubble extends ConsumerWidget { borderRadius: BorderRadius.circular(16.0), color: _bubbleColor(context, brightness), ), - child: Text( - message, - style: TextStyle( - color: _textColor(context, brightness), - ), - ), + child: Text(message, style: TextStyle(color: _textColor(context, brightness))), ), ), ); @@ -227,40 +201,36 @@ class _ChatBottomBarState extends ConsumerState<_ChatBottomBar> { final session = ref.watch(authSessionProvider); final sendButton = ValueListenableBuilder( valueListenable: _textController, - builder: (context, value, child) => PlatformIconButton( - onTap: session != null && value.text.isNotEmpty - ? () { - ref - .read(chatControllerProvider(widget.id).notifier) - .sendMessage(_textController.text); - _textController.clear(); - } - : null, - icon: Icons.send, - padding: EdgeInsets.zero, - semanticsLabel: context.l10n.send, - ), + builder: + (context, value, child) => PlatformIconButton( + onTap: + session != null && value.text.isNotEmpty + ? () { + ref + .read(chatControllerProvider(widget.id).notifier) + .sendMessage(_textController.text); + _textController.clear(); + } + : null, + icon: Icons.send, + padding: EdgeInsets.zero, + semanticsLabel: context.l10n.send, + ), ); - final placeholder = - session != null ? context.l10n.talkInChat : context.l10n.loginToChat; + final placeholder = session != null ? context.l10n.talkInChat : context.l10n.loginToChat; return SafeArea( top: false, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: AdaptiveTextField( materialDecoration: InputDecoration( - contentPadding: - const EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), + contentPadding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), suffixIcon: sendButton, - border: const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(20.0)), - ), + border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(20.0))), hintText: placeholder, ), cupertinoDecoration: BoxDecoration( - border: Border.all( - color: CupertinoColors.separator.resolveFrom(context), - ), + border: Border.all(color: CupertinoColors.separator.resolveFrom(context)), borderRadius: const BorderRadius.all(Radius.circular(30.0)), ), placeholder: placeholder, diff --git a/lib/src/view/game/offline_correspondence_games_screen.dart b/lib/src/view/game/offline_correspondence_games_screen.dart index e4f8dcc356..d5e51b49a6 100644 --- a/lib/src/view/game/offline_correspondence_games_screen.dart +++ b/lib/src/view/game/offline_correspondence_games_screen.dart @@ -35,17 +35,15 @@ class _Body extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final offlineGames = ref.watch(offlineOngoingCorrespondenceGamesProvider); return offlineGames.maybeWhen( - data: (data) => ListView( - children: [ - const SizedBox(height: 8.0), - ...data.map( - (game) => OfflineCorrespondenceGamePreview( - game: game.$2, - lastModified: game.$1, - ), + data: + (data) => ListView( + children: [ + const SizedBox(height: 8.0), + ...data.map( + (game) => OfflineCorrespondenceGamePreview(game: game.$2, lastModified: game.$1), + ), + ], ), - ], - ), orElse: () => const SizedBox.shrink(), ); } @@ -69,10 +67,7 @@ class OfflineCorrespondenceGamePreview extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - UserFullNameWidget( - user: game.opponent.user, - style: Styles.boardPreviewTitle, - ), + UserFullNameWidget(user: game.opponent.user, style: Styles.boardPreviewTitle), if (game.myTimeLeft(lastModified) != null) Text( timeago.format( @@ -80,20 +75,14 @@ class OfflineCorrespondenceGamePreview extends ConsumerWidget { allowFromNow: true, ), ), - Icon( - game.perf.icon, - size: 40, - color: DefaultTextStyle.of(context).style.color, - ), + Icon(game.perf.icon, size: 40, color: DefaultTextStyle.of(context).style.color), ], ), onTap: () { pushPlatformRoute( context, rootNavigator: true, - builder: (_) => OfflineCorrespondenceGameScreen( - initialGame: (lastModified, game), - ), + builder: (_) => OfflineCorrespondenceGameScreen(initialGame: (lastModified, game)), ); }, ); diff --git a/lib/src/view/game/ping_rating.dart b/lib/src/view/game/ping_rating.dart index 4ee011d17f..f38a98645b 100644 --- a/lib/src/view/game/ping_rating.dart +++ b/lib/src/view/game/ping_rating.dart @@ -12,19 +12,16 @@ final pingRatingProvider = Provider.autoDispose((ref) { return ping == 0 ? 0 : ping < 150 - ? 4 - : ping < 300 - ? 3 - : ping < 500 - ? 2 - : 1; + ? 4 + : ping < 300 + ? 3 + : ping < 500 + ? 2 + : 1; }); class SocketPingRating extends ConsumerWidget { - const SocketPingRating({ - required this.size, - super.key, - }); + const SocketPingRating({required this.size, super.key}); final double size; @@ -32,10 +29,6 @@ class SocketPingRating extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final pingRating = ref.watch(pingRatingProvider); - return LagIndicator( - lagRating: pingRating, - size: size, - showLoadingIndicator: true, - ); + return LagIndicator(lagRating: pingRating, size: size, showLoadingIndicator: true); } } diff --git a/lib/src/view/game/status_l10n.dart b/lib/src/view/game/status_l10n.dart index 2b74280e86..a08a3d003c 100644 --- a/lib/src/view/game/status_l10n.dart +++ b/lib/src/view/game/status_l10n.dart @@ -20,9 +20,7 @@ String gameStatusL10n( case GameStatus.mate: return context.l10n.checkmate; case GameStatus.resign: - return winner == Side.black - ? context.l10n.whiteResigned - : context.l10n.blackResigned; + return winner == Side.black ? context.l10n.whiteResigned : context.l10n.blackResigned; case GameStatus.stalemate: return context.l10n.stalemate; case GameStatus.timeout: @@ -31,8 +29,8 @@ String gameStatusL10n( ? '${context.l10n.whiteLeftTheGame} • ${context.l10n.draw}' : '${context.l10n.blackLeftTheGame} • ${context.l10n.draw}' : winner == Side.black - ? context.l10n.whiteLeftTheGame - : context.l10n.blackLeftTheGame; + ? context.l10n.whiteLeftTheGame + : context.l10n.blackLeftTheGame; case GameStatus.draw: if (lastPosition.isInsufficientMaterial) { return '${context.l10n.insufficientMaterial} • ${context.l10n.draw}'; @@ -47,12 +45,10 @@ String gameStatusL10n( ? '${context.l10n.whiteTimeOut} • ${context.l10n.draw}' : '${context.l10n.blackTimeOut} • ${context.l10n.draw}' : winner == Side.black - ? context.l10n.whiteTimeOut - : context.l10n.blackTimeOut; + ? context.l10n.whiteTimeOut + : context.l10n.blackTimeOut; case GameStatus.noStart: - return winner == Side.black - ? context.l10n.whiteDidntMove - : context.l10n.blackDidntMove; + return winner == Side.black ? context.l10n.whiteDidntMove : context.l10n.blackDidntMove; case GameStatus.unknownFinish: return context.l10n.finished; case GameStatus.cheat: diff --git a/lib/src/view/home/home_tab_screen.dart b/lib/src/view/home/home_tab_screen.dart index 2754be744d..31179d5972 100644 --- a/lib/src/view/home/home_tab_screen.dart +++ b/lib/src/view/home/home_tab_screen.dart @@ -78,31 +78,19 @@ class _HomeScreenState extends ConsumerState with RouteAware { data: (status) { final session = ref.watch(authSessionProvider); final ongoingGames = ref.watch(ongoingGamesProvider); - final emptyRecent = ref.watch(myRecentGamesProvider).maybeWhen( - data: (data) => data.isEmpty, - orElse: () => false, - ); + final emptyRecent = ref + .watch(myRecentGamesProvider) + .maybeWhen(data: (data) => data.isEmpty, orElse: () => false); final isTablet = isTabletOrLarger(context); // Show the welcome screen if there are no recent games and no stored games // (i.e. first installation, or the user has never played a game) - final widgets = emptyRecent - ? _welcomeScreenWidgets( - session: session, - status: status, - isTablet: isTablet, - ) - : isTablet - ? _tabletWidgets( - session: session, - status: status, - ongoingGames: ongoingGames, - ) - : _handsetWidgets( - session: session, - status: status, - ongoingGames: ongoingGames, - ); + final widgets = + emptyRecent + ? _welcomeScreenWidgets(session: session, status: status, isTablet: isTablet) + : isTablet + ? _tabletWidgets(session: session, status: status, ongoingGames: ongoingGames) + : _handsetWidgets(session: session, status: status, ongoingGames: ongoingGames); if (Theme.of(context).platform == TargetPlatform.iOS) { return CupertinoPageScaffold( @@ -113,26 +101,19 @@ class _HomeScreenState extends ConsumerState with RouteAware { controller: homeScrollController, slivers: [ CupertinoSliverNavigationBar( - padding: const EdgeInsetsDirectional.only( - start: 16.0, - end: 8.0, - ), + padding: const EdgeInsetsDirectional.only(start: 16.0, end: 8.0), largeTitle: Text(context.l10n.mobileHomeTab), leading: CupertinoButton( alignment: Alignment.centerLeft, padding: EdgeInsets.zero, onPressed: () { - ref.read(editModeProvider.notifier).state = - !isEditing; + ref.read(editModeProvider.notifier).state = !isEditing; }, child: Text(isEditing ? 'Done' : 'Edit'), ), trailing: const Row( mainAxisSize: MainAxisSize.min, - children: [ - _ChallengeScreenButton(), - _PlayerScreenButton(), - ], + children: [_ChallengeScreenButton(), _PlayerScreenButton()], ), ), CupertinoSliverRefreshControl( @@ -141,9 +122,7 @@ class _HomeScreenState extends ConsumerState with RouteAware { const SliverToBoxAdapter(child: ConnectivityBanner()), SliverSafeArea( top: false, - sliver: SliverList( - delegate: SliverChildListDelegate(widgets), - ), + sliver: SliverList(delegate: SliverChildListDelegate(widgets)), ), ], ), @@ -153,8 +132,7 @@ class _HomeScreenState extends ConsumerState with RouteAware { right: 8.0, child: FloatingActionButton.extended( backgroundColor: CupertinoTheme.of(context).primaryColor, - foregroundColor: - CupertinoTheme.of(context).primaryContrastingColor, + foregroundColor: CupertinoTheme.of(context).primaryContrastingColor, onPressed: () { pushPlatformRoute( context, @@ -178,9 +156,7 @@ class _HomeScreenState extends ConsumerState with RouteAware { onPressed: () { ref.read(editModeProvider.notifier).state = !isEditing; }, - icon: Icon( - isEditing ? Icons.save_outlined : Icons.app_registration, - ), + icon: Icon(isEditing ? Icons.save_outlined : Icons.app_registration), tooltip: isEditing ? 'Save' : 'Edit', ), const _ChallengeScreenButton(), @@ -193,27 +169,20 @@ class _HomeScreenState extends ConsumerState with RouteAware { child: Column( children: [ const ConnectivityBanner(), - Expanded( - child: ListView( - controller: homeScrollController, - children: widgets, - ), - ), + Expanded(child: ListView(controller: homeScrollController, children: widgets)), ], ), ), - floatingActionButton: isTablet - ? null - : FloatingActionButton.extended( - onPressed: () { - pushPlatformRoute( - context, - builder: (_) => const PlayScreen(), - ); - }, - icon: const Icon(Icons.add), - label: Text(context.l10n.play), - ), + floatingActionButton: + isTablet + ? null + : FloatingActionButton.extended( + onPressed: () { + pushPlatformRoute(context, builder: (_) => const PlayScreen()); + }, + icon: const Icon(Icons.add), + label: Text(context.l10n.play), + ), ); } }, @@ -228,26 +197,17 @@ class _HomeScreenState extends ConsumerState with RouteAware { required AsyncValue> ongoingGames, }) { return [ - const _EditableWidget( - widget: EnabledWidget.hello, - shouldShow: true, - child: _HelloWidget(), - ), + const _EditableWidget(widget: EnabledWidget.hello, shouldShow: true, child: _HelloWidget()), if (status.isOnline) _EditableWidget( widget: EnabledWidget.perfCards, shouldShow: session != null, - child: const AccountPerfCards( - padding: Styles.horizontalBodyPadding, - ), + child: const AccountPerfCards(padding: Styles.horizontalBodyPadding), ), _EditableWidget( widget: EnabledWidget.quickPairing, shouldShow: status.isOnline, - child: const Padding( - padding: Styles.bodySectionPadding, - child: QuickGameMatrix(), - ), + child: const Padding(padding: Styles.bodySectionPadding, child: QuickGameMatrix()), ), if (status.isOnline) _OngoingGamesCarousel(ongoingGames, maxGamesToShow: 20) @@ -270,17 +230,15 @@ class _HomeScreenState extends ConsumerState with RouteAware { Padding( padding: Styles.horizontalBodyPadding, child: LichessMessage( - style: Theme.of(context).platform == TargetPlatform.iOS - ? const TextStyle(fontSize: 18) - : Theme.of(context).textTheme.bodyLarge, + style: + Theme.of(context).platform == TargetPlatform.iOS + ? const TextStyle(fontSize: 18) + : Theme.of(context).textTheme.bodyLarge, textAlign: TextAlign.center, ), ), const SizedBox(height: 24.0), - if (session == null) ...[ - const Center(child: _SignInWidget()), - const SizedBox(height: 16.0), - ], + if (session == null) ...[const Center(child: _SignInWidget()), const SizedBox(height: 16.0)], if (Theme.of(context).platform != TargetPlatform.iOS && (session == null || session.user.isPatron != true)) ...[ Center( @@ -309,14 +267,8 @@ class _HomeScreenState extends ConsumerState with RouteAware { if (isTablet) Row( children: [ - const Flexible( - child: _TabletCreateAGameSection(), - ), - Flexible( - child: Column( - children: welcomeWidgets, - ), - ), + const Flexible(child: _TabletCreateAGameSection()), + Flexible(child: Column(children: welcomeWidgets)), ], ) else ...[ @@ -324,10 +276,7 @@ class _HomeScreenState extends ConsumerState with RouteAware { const _EditableWidget( widget: EnabledWidget.quickPairing, shouldShow: true, - child: Padding( - padding: Styles.bodySectionPadding, - child: QuickGameMatrix(), - ), + child: Padding(padding: Styles.bodySectionPadding, child: QuickGameMatrix()), ), ...welcomeWidgets, ], @@ -340,18 +289,12 @@ class _HomeScreenState extends ConsumerState with RouteAware { required AsyncValue> ongoingGames, }) { return [ - const _EditableWidget( - widget: EnabledWidget.hello, - shouldShow: true, - child: _HelloWidget(), - ), + const _EditableWidget(widget: EnabledWidget.hello, shouldShow: true, child: _HelloWidget()), if (status.isOnline) _EditableWidget( widget: EnabledWidget.perfCards, shouldShow: session != null, - child: const AccountPerfCards( - padding: Styles.bodySectionPadding, - ), + child: const AccountPerfCards(padding: Styles.bodySectionPadding), ), Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -362,14 +305,9 @@ class _HomeScreenState extends ConsumerState with RouteAware { const SizedBox(height: 8.0), const _TabletCreateAGameSection(), if (status.isOnline) - _OngoingGamesPreview( - ongoingGames, - maxGamesToShow: 5, - ) + _OngoingGamesPreview(ongoingGames, maxGamesToShow: 5) else - const _OfflineCorrespondencePreview( - maxGamesToShow: 5, - ), + const _OfflineCorrespondencePreview(maxGamesToShow: 5), ], ), ), @@ -377,10 +315,7 @@ class _HomeScreenState extends ConsumerState with RouteAware { child: Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, - children: [ - SizedBox(height: 8.0), - RecentGamesWidget(), - ], + children: [SizedBox(height: 8.0), RecentGamesWidget()], ), ), ], @@ -406,9 +341,10 @@ class _SignInWidget extends ConsumerWidget { return SecondaryButton( semanticsLabel: context.l10n.signIn, - onPressed: authController.isLoading - ? null - : () => ref.read(authControllerProvider.notifier).signIn(), + onPressed: + authController.isLoading + ? null + : () => ref.read(authControllerProvider.notifier).signIn(), child: Text(context.l10n.signIn), ); } @@ -428,11 +364,7 @@ class _SignInWidget extends ConsumerWidget { /// This parameter is only active when the user is not in edit mode, as we /// always want to display the widget in edit mode. class _EditableWidget extends ConsumerWidget { - const _EditableWidget({ - required this.child, - required this.widget, - required this.shouldShow, - }); + const _EditableWidget({required this.child, required this.widget, required this.shouldShow}); final Widget child; final EnabledWidget widget; @@ -450,30 +382,23 @@ class _EditableWidget extends ConsumerWidget { return isEditing ? Row( - mainAxisSize: MainAxisSize.max, - children: [ - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Checkbox.adaptive( - value: isEnabled, - onChanged: (_) { - ref - .read(homePreferencesProvider.notifier) - .toggleWidget(widget); - }, - ), - ), - Expanded( - child: IgnorePointer( - ignoring: isEditing, - child: child, - ), + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Checkbox.adaptive( + value: isEnabled, + onChanged: (_) { + ref.read(homePreferencesProvider.notifier).toggleWidget(widget); + }, ), - ], - ) + ), + Expanded(child: IgnorePointer(ignoring: isEditing, child: child)), + ], + ) : isEnabled - ? child - : const SizedBox.shrink(); + ? child + : const SizedBox.shrink(); } } @@ -483,42 +408,33 @@ class _HelloWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final session = ref.watch(authSessionProvider); - final style = Theme.of(context).platform == TargetPlatform.iOS - ? const TextStyle(fontSize: 20) - : Theme.of(context).textTheme.bodyLarge; + final style = + Theme.of(context).platform == TargetPlatform.iOS + ? const TextStyle(fontSize: 20) + : Theme.of(context).textTheme.bodyLarge; - final iconSize = - Theme.of(context).platform == TargetPlatform.iOS ? 26.0 : 24.0; + final iconSize = Theme.of(context).platform == TargetPlatform.iOS ? 26.0 : 24.0; // fetch the account user to be sure we have the latest data (flair, etc.) - final accountUser = ref.watch(accountProvider).maybeWhen( - data: (data) => data?.lightUser, - orElse: () => null, - ); + final accountUser = ref + .watch(accountProvider) + .maybeWhen(data: (data) => data?.lightUser, orElse: () => null); final user = accountUser ?? session?.user; return Padding( - padding: - Styles.horizontalBodyPadding.add(Styles.sectionBottomPadding).add( - const EdgeInsets.only(top: 8.0), - ), + padding: Styles.horizontalBodyPadding + .add(Styles.sectionBottomPadding) + .add(const EdgeInsets.only(top: 8.0)), child: GestureDetector( onTap: () { ref.invalidate(accountActivityProvider); - pushPlatformRoute( - context, - builder: (context) => const ProfileScreen(), - ); + pushPlatformRoute(context, builder: (context) => const ProfileScreen()); }, child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon( - Icons.wb_sunny, - size: iconSize, - color: context.lichessColors.brag, - ), + Icon(Icons.wb_sunny, size: iconSize, color: context.lichessColors.brag), const SizedBox(width: 5.0), if (user != null) l10nWithWidget( @@ -546,15 +462,9 @@ class _TabletCreateAGameSection extends StatelessWidget { _EditableWidget( widget: EnabledWidget.quickPairing, shouldShow: true, - child: Padding( - padding: Styles.bodySectionPadding, - child: QuickGameMatrix(), - ), - ), - Padding( - padding: Styles.bodySectionPadding, - child: QuickGameButton(), + child: Padding(padding: Styles.bodySectionPadding, child: QuickGameMatrix()), ), + Padding(padding: Styles.bodySectionPadding, child: QuickGameButton()), CreateGameOptions(), ], ); @@ -577,21 +487,23 @@ class _OngoingGamesCarousel extends ConsumerWidget { } return _GamesCarousel( list: data, - builder: (game) => _GamePreviewCarouselItem( - game: game, - onTap: () { - pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => GameScreen( - initialGameId: game.fullId, - loadingFen: game.fen, - loadingOrientation: game.orientation, - loadingLastMove: game.lastMove, - ), - ); - }, - ), + builder: + (game) => _GamePreviewCarouselItem( + game: game, + onTap: () { + pushPlatformRoute( + context, + rootNavigator: true, + builder: + (context) => GameScreen( + initialGameId: game.fullId, + loadingFen: game.fen, + loadingOrientation: game.orientation, + loadingLastMove: game.lastMove, + ), + ); + }, + ), moreScreenBuilder: (_) => const OngoingGamesScreen(), maxGamesToShow: maxGamesToShow, ); @@ -608,8 +520,7 @@ class _OfflineCorrespondenceCarousel extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final offlineCorresGames = - ref.watch(offlineOngoingCorrespondenceGamesProvider); + final offlineCorresGames = ref.watch(offlineOngoingCorrespondenceGamesProvider); return offlineCorresGames.maybeWhen( data: (data) { if (data.isEmpty) { @@ -617,32 +528,31 @@ class _OfflineCorrespondenceCarousel extends ConsumerWidget { } return _GamesCarousel( list: data, - builder: (el) => _GamePreviewCarouselItem( - game: OngoingGame( - id: el.$2.id, - fullId: el.$2.fullId, - orientation: el.$2.orientation, - fen: el.$2.lastPosition.fen, - perf: el.$2.perf, - speed: el.$2.speed, - variant: el.$2.variant, - opponent: el.$2.opponent.user, - isMyTurn: el.$2.isMyTurn, - opponentRating: el.$2.opponent.rating, - opponentAiLevel: el.$2.opponent.aiLevel, - lastMove: el.$2.lastMove, - secondsLeft: el.$2.myTimeLeft(el.$1)?.inSeconds, - ), - onTap: () { - pushPlatformRoute( - context, - rootNavigator: true, - builder: (_) => OfflineCorrespondenceGameScreen( - initialGame: (el.$1, el.$2), + builder: + (el) => _GamePreviewCarouselItem( + game: OngoingGame( + id: el.$2.id, + fullId: el.$2.fullId, + orientation: el.$2.orientation, + fen: el.$2.lastPosition.fen, + perf: el.$2.perf, + speed: el.$2.speed, + variant: el.$2.variant, + opponent: el.$2.opponent.user, + isMyTurn: el.$2.isMyTurn, + opponentRating: el.$2.opponent.rating, + opponentAiLevel: el.$2.opponent.aiLevel, + lastMove: el.$2.lastMove, + secondsLeft: el.$2.myTimeLeft(el.$1)?.inSeconds, ), - ); - }, - ), + onTap: () { + pushPlatformRoute( + context, + rootNavigator: true, + builder: (_) => OfflineCorrespondenceGameScreen(initialGame: (el.$1, el.$2)), + ); + }, + ), moreScreenBuilder: (_) => const OfflineCorrespondenceGamesScreen(), maxGamesToShow: maxGamesToShow, ); @@ -748,36 +658,25 @@ class _GamePreviewCarouselItem extends StatelessWidget { Row( children: [ if (game.isMyTurn) ...const [ - Icon( - Icons.timer, - size: 16.0, - color: Colors.white, - ), + Icon(Icons.timer, size: 16.0, color: Colors.white), SizedBox(width: 4.0), ], Text( game.secondsLeft != null && game.isMyTurn ? timeago.format( - DateTime.now().add( - Duration(seconds: game.secondsLeft!), - ), - allowFromNow: true, - ) + DateTime.now().add(Duration(seconds: game.secondsLeft!)), + allowFromNow: true, + ) : game.isMyTurn - ? context.l10n.yourTurn - : context.l10n.waitingForOpponent, - style: Theme.of(context).platform == TargetPlatform.iOS - ? const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - ) - : TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium - ?.fontSize, - fontWeight: FontWeight.w500, - ), + ? context.l10n.yourTurn + : context.l10n.waitingForOpponent, + style: + Theme.of(context).platform == TargetPlatform.iOS + ? const TextStyle(fontSize: 12, fontWeight: FontWeight.w500) + : TextStyle( + fontSize: Theme.of(context).textTheme.labelMedium?.fontSize, + fontWeight: FontWeight.w500, + ), ), ], ), @@ -827,17 +726,13 @@ class _OfflineCorrespondencePreview extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final offlineCorresGames = - ref.watch(offlineOngoingCorrespondenceGamesProvider); + final offlineCorresGames = ref.watch(offlineOngoingCorrespondenceGamesProvider); return offlineCorresGames.maybeWhen( data: (data) { return PreviewGameList( list: data, maxGamesToShow: maxGamesToShow, - builder: (el) => OfflineCorrespondenceGamePreview( - game: el.$2, - lastModified: el.$1, - ), + builder: (el) => OfflineCorrespondenceGamePreview(game: el.$2, lastModified: el.$1), moreScreenBuilder: (_) => const OfflineCorrespondenceGamesScreen(), ); }, @@ -868,9 +763,7 @@ class PreviewGameList extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: Styles.horizontalBodyPadding.add( - const EdgeInsets.only(top: 16.0), - ), + padding: Styles.horizontalBodyPadding.add(const EdgeInsets.only(top: 16.0)), child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -912,24 +805,27 @@ class _PlayerScreenButton extends ConsumerWidget { final connectivity = ref.watch(connectivityChangesProvider); return connectivity.maybeWhen( - data: (connectivity) => AppBarIconButton( - icon: const Icon(Icons.group_outlined), - semanticsLabel: context.l10n.players, - onPressed: !connectivity.isOnline - ? null - : () { - pushPlatformRoute( - context, - title: context.l10n.players, - builder: (_) => const PlayerScreen(), - ); - }, - ), - orElse: () => AppBarIconButton( - icon: const Icon(Icons.group_outlined), - semanticsLabel: context.l10n.players, - onPressed: null, - ), + data: + (connectivity) => AppBarIconButton( + icon: const Icon(Icons.group_outlined), + semanticsLabel: context.l10n.players, + onPressed: + !connectivity.isOnline + ? null + : () { + pushPlatformRoute( + context, + title: context.l10n.players, + builder: (_) => const PlayerScreen(), + ); + }, + ), + orElse: + () => AppBarIconButton( + icon: const Icon(Icons.group_outlined), + semanticsLabel: context.l10n.players, + onPressed: null, + ), ); } } @@ -950,26 +846,29 @@ class _ChallengeScreenButton extends ConsumerWidget { final count = challenges.valueOrNull?.inward.length; return connectivity.maybeWhen( - data: (connectivity) => AppBarNotificationIconButton( - icon: const Icon(LichessIcons.crossed_swords, size: 18.0), - semanticsLabel: context.l10n.preferencesNotifyChallenge, - onPressed: !connectivity.isOnline - ? null - : () { - ref.invalidate(challengesProvider); - pushPlatformRoute( - context, - title: context.l10n.preferencesNotifyChallenge, - builder: (_) => const ChallengeRequestsScreen(), - ); - }, - count: count ?? 0, - ), - orElse: () => AppBarIconButton( - icon: const Icon(LichessIcons.crossed_swords, size: 18.0), - semanticsLabel: context.l10n.preferencesNotifyChallenge, - onPressed: null, - ), + data: + (connectivity) => AppBarNotificationIconButton( + icon: const Icon(LichessIcons.crossed_swords, size: 18.0), + semanticsLabel: context.l10n.preferencesNotifyChallenge, + onPressed: + !connectivity.isOnline + ? null + : () { + ref.invalidate(challengesProvider); + pushPlatformRoute( + context, + title: context.l10n.preferencesNotifyChallenge, + builder: (_) => const ChallengeRequestsScreen(), + ); + }, + count: count ?? 0, + ), + orElse: + () => AppBarIconButton( + icon: const Icon(LichessIcons.crossed_swords, size: 18.0), + semanticsLabel: context.l10n.preferencesNotifyChallenge, + onPressed: null, + ), ); } } diff --git a/lib/src/view/opening_explorer/opening_explorer_screen.dart b/lib/src/view/opening_explorer/opening_explorer_screen.dart index 2c9722ba5a..6aa6c1d9a9 100644 --- a/lib/src/view/opening_explorer/opening_explorer_screen.dart +++ b/lib/src/view/opening_explorer/opening_explorer_screen.dart @@ -36,39 +36,35 @@ class OpeningExplorerScreen extends ConsumerWidget { final body = switch (ref.watch(ctrlProvider)) { AsyncData(value: final state) => _Body(options: options, state: state), AsyncError(:final error) => Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Text(error.toString()), - ), - ), + child: Padding(padding: const EdgeInsets.all(16.0), child: Text(error.toString())), + ), _ => const CenterLoadingIndicator(), }; return PlatformWidget( - androidBuilder: (_) => Scaffold( - body: body, - appBar: AppBar( - title: Text(context.l10n.openingExplorer), - bottom: _MoveList(options: options), - ), - ), - iosBuilder: (_) => CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: Text(context.l10n.openingExplorer), - automaticBackgroundVisibility: false, - border: null, - ), - child: body, - ), + androidBuilder: + (_) => Scaffold( + body: body, + appBar: AppBar( + title: Text(context.l10n.openingExplorer), + bottom: _MoveList(options: options), + ), + ), + iosBuilder: + (_) => CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(context.l10n.openingExplorer), + automaticBackgroundVisibility: false, + border: null, + ), + child: body, + ), ); } } class _Body extends ConsumerWidget { - const _Body({ - required this.options, - required this.state, - }); + const _Body({required this.options, required this.state}); final AnalysisOptions options; final AnalysisState state; @@ -83,28 +79,29 @@ class _Body extends ConsumerWidget { children: [ if (Theme.of(context).platform == TargetPlatform.iOS) Padding( - padding: isTablet - ? const EdgeInsets.symmetric( - horizontal: kTabletBoardTableSidePadding, - ) - : EdgeInsets.zero, + padding: + isTablet + ? const EdgeInsets.symmetric(horizontal: kTabletBoardTableSidePadding) + : EdgeInsets.zero, child: _MoveList(options: options), ), Expanded( child: LayoutBuilder( builder: (context, constraints) { - final orientation = constraints.maxWidth > constraints.maxHeight - ? Orientation.landscape - : Orientation.portrait; + final orientation = + constraints.maxWidth > constraints.maxHeight + ? Orientation.landscape + : Orientation.portrait; if (orientation == Orientation.landscape) { - final sideWidth = constraints.biggest.longestSide - - constraints.biggest.shortestSide; - final defaultBoardSize = constraints.biggest.shortestSide - - (kTabletBoardTableSidePadding * 2); - final boardSize = sideWidth >= 250 - ? defaultBoardSize - : constraints.biggest.longestSide / kGoldenRatio - - (kTabletBoardTableSidePadding * 2); + final sideWidth = + constraints.biggest.longestSide - constraints.biggest.shortestSide; + final defaultBoardSize = + constraints.biggest.shortestSide - (kTabletBoardTableSidePadding * 2); + final boardSize = + sideWidth >= 250 + ? defaultBoardSize + : constraints.biggest.longestSide / kGoldenRatio - + (kTabletBoardTableSidePadding * 2); return Row( mainAxisSize: MainAxisSize.max, children: [ @@ -129,21 +126,14 @@ class _Body extends ConsumerWidget { Expanded( child: PlatformCard( clipBehavior: Clip.hardEdge, - borderRadius: const BorderRadius.all( - Radius.circular(4.0), - ), - margin: const EdgeInsets.all( - kTabletBoardTableSidePadding, - ), + borderRadius: const BorderRadius.all(Radius.circular(4.0)), + margin: const EdgeInsets.all(kTabletBoardTableSidePadding), semanticContainer: false, child: OpeningExplorerView( position: state.position, onMoveSelected: (move) { ref - .read( - analysisControllerProvider(options) - .notifier, - ) + .read(analysisControllerProvider(options).notifier) .onUserMove(move); }, ), @@ -156,20 +146,18 @@ class _Body extends ConsumerWidget { ); } else { final defaultBoardSize = constraints.biggest.shortestSide; - final remainingHeight = - constraints.maxHeight - defaultBoardSize; - final isSmallScreen = - remainingHeight < kSmallRemainingHeightLeftBoardThreshold; - final boardSize = isTablet || isSmallScreen - ? defaultBoardSize - kTabletBoardTableSidePadding * 2 - : defaultBoardSize; + final remainingHeight = constraints.maxHeight - defaultBoardSize; + final isSmallScreen = remainingHeight < kSmallRemainingHeightLeftBoardThreshold; + final boardSize = + isTablet || isSmallScreen + ? defaultBoardSize - kTabletBoardTableSidePadding * 2 + : defaultBoardSize; return ListView( - padding: isTablet - ? const EdgeInsets.symmetric( - horizontal: kTabletBoardTableSidePadding, - ) - : EdgeInsets.zero, + padding: + isTablet + ? const EdgeInsets.symmetric(horizontal: kTabletBoardTableSidePadding) + : EdgeInsets.zero, children: [ GestureDetector( // disable scrolling when dragging the board @@ -182,19 +170,12 @@ class _Body extends ConsumerWidget { ), OpeningExplorerView( position: state.position, - opening: state.currentNode.isRoot - ? LightOpening( - eco: '', - name: context.l10n.startPosition, - ) - : state.currentNode.opening ?? - state.currentBranchOpening, + opening: + state.currentNode.isRoot + ? LightOpening(eco: '', name: context.l10n.startPosition) + : state.currentNode.opening ?? state.currentBranchOpening, onMoveSelected: (move) { - ref - .read( - analysisControllerProvider(options).notifier, - ) - .onUserMove(move); + ref.read(analysisControllerProvider(options).notifier).onUserMove(move); }, scrollable: false, ), @@ -234,17 +215,13 @@ class _MoveList extends ConsumerWidget implements PreferredSizeWidget { final currentMoveIndex = state.currentNode.position.ply; return MoveList( - inlineDecoration: Theme.of(context).platform == TargetPlatform.iOS - ? BoxDecoration( - color: Styles.cupertinoAppBarColor.resolveFrom(context), - border: const Border( - bottom: BorderSide( - color: Color(0x4D000000), - width: 0.0, - ), - ), - ) - : null, + inlineDecoration: + Theme.of(context).platform == TargetPlatform.iOS + ? BoxDecoration( + color: Styles.cupertinoAppBarColor.resolveFrom(context), + border: const Border(bottom: BorderSide(color: Color(0x4D000000), width: 0.0)), + ) + : null, type: MoveListType.inline, slicedMoves: slicedMoves, currentMoveIndex: currentMoveIndex, @@ -265,13 +242,10 @@ class _BottomBar extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final db = ref - .watch(openingExplorerPreferencesProvider.select((value) => value.db)); + final db = ref.watch(openingExplorerPreferencesProvider.select((value) => value.db)); final ctrlProvider = analysisControllerProvider(options); - final canGoBack = - ref.watch(ctrlProvider.select((value) => value.requireValue.canGoBack)); - final canGoNext = - ref.watch(ctrlProvider.select((value) => value.requireValue.canGoNext)); + final canGoBack = ref.watch(ctrlProvider.select((value) => value.requireValue.canGoBack)); + final canGoNext = ref.watch(ctrlProvider.select((value) => value.requireValue.canGoNext)); final dbLabel = switch (db) { OpeningDatabase.master => 'Masters', @@ -284,13 +258,14 @@ class _BottomBar extends ConsumerWidget { BottomBarButton( label: dbLabel, showLabel: true, - onTap: () => showAdaptiveBottomSheet( - context: context, - isScrollControlled: true, - showDragHandle: true, - isDismissible: true, - builder: (_) => const OpeningExplorerSettings(), - ), + onTap: + () => showAdaptiveBottomSheet( + context: context, + isScrollControlled: true, + showDragHandle: true, + isDismissible: true, + builder: (_) => const OpeningExplorerSettings(), + ), icon: Icons.tune, ), BottomBarButton( diff --git a/lib/src/view/opening_explorer/opening_explorer_settings.dart b/lib/src/view/opening_explorer/opening_explorer_settings.dart index 15c11c63da..3a31eb28c2 100644 --- a/lib/src/view/opening_explorer/opening_explorer_settings.dart +++ b/lib/src/view/opening_explorer/opening_explorer_settings.dart @@ -29,9 +29,10 @@ class OpeningExplorerSettings extends ConsumerWidget { (key) => ChoiceChip( label: Text(key), selected: prefs.masterDb.sinceYear == MasterDb.datesMap[key], - onSelected: (_) => ref - .read(openingExplorerPreferencesProvider.notifier) - .setMasterDbSince(MasterDb.datesMap[key]!), + onSelected: + (_) => ref + .read(openingExplorerPreferencesProvider.notifier) + .setMasterDbSince(MasterDb.datesMap[key]!), ), ) .toList(growable: false), @@ -49,19 +50,14 @@ class OpeningExplorerSettings extends ConsumerWidget { (speed) => FilterChip( label: Text( String.fromCharCode(speed.icon.codePoint), - style: TextStyle( - fontFamily: speed.icon.fontFamily, - fontSize: 18.0, - ), + style: TextStyle(fontFamily: speed.icon.fontFamily, fontSize: 18.0), ), - tooltip: Perf.fromVariantAndSpeed( - Variant.standard, - speed, - ).title, + tooltip: Perf.fromVariantAndSpeed(Variant.standard, speed).title, selected: prefs.lichessDb.speeds.contains(speed), - onSelected: (_) => ref - .read(openingExplorerPreferencesProvider.notifier) - .toggleLichessDbSpeed(speed), + onSelected: + (_) => ref + .read(openingExplorerPreferencesProvider.notifier) + .toggleLichessDbSpeed(speed), ), ) .toList(growable: false), @@ -75,15 +71,17 @@ class OpeningExplorerSettings extends ConsumerWidget { .map( (rating) => FilterChip( label: Text(rating.toString()), - tooltip: rating == 400 - ? '400-1000' - : rating == 2500 + tooltip: + rating == 400 + ? '400-1000' + : rating == 2500 ? '2500+' : '$rating-${rating + 200}', selected: prefs.lichessDb.ratings.contains(rating), - onSelected: (_) => ref - .read(openingExplorerPreferencesProvider.notifier) - .toggleLichessDbRating(rating), + onSelected: + (_) => ref + .read(openingExplorerPreferencesProvider.notifier) + .toggleLichessDbRating(rating), ), ) .toList(growable: false), @@ -98,9 +96,10 @@ class OpeningExplorerSettings extends ConsumerWidget { (key) => ChoiceChip( label: Text(key), selected: prefs.lichessDb.since == LichessDb.datesMap[key], - onSelected: (_) => ref - .read(openingExplorerPreferencesProvider.notifier) - .setLichessDbSince(LichessDb.datesMap[key]!), + onSelected: + (_) => ref + .read(openingExplorerPreferencesProvider.notifier) + .setLichessDbSince(LichessDb.datesMap[key]!), ), ) .toList(growable: false), @@ -112,26 +111,26 @@ class OpeningExplorerSettings extends ConsumerWidget { title: Text.rich( TextSpan( text: '${context.l10n.player}: ', - style: const TextStyle( - fontWeight: FontWeight.normal, - ), + style: const TextStyle(fontWeight: FontWeight.normal), children: [ TextSpan( - recognizer: TapGestureRecognizer() - ..onTap = () => pushPlatformRoute( - context, - fullscreenDialog: true, - builder: (_) => SearchScreen( - onUserTap: (user) => { - ref - .read( - openingExplorerPreferencesProvider.notifier, - ) - .setPlayerDbUsernameOrId(user.name), - Navigator.of(context).pop(), - }, - ), - ), + recognizer: + TapGestureRecognizer() + ..onTap = + () => pushPlatformRoute( + context, + fullscreenDialog: true, + builder: + (_) => SearchScreen( + onUserTap: + (user) => { + ref + .read(openingExplorerPreferencesProvider.notifier) + .setPlayerDbUsernameOrId(user.name), + Navigator.of(context).pop(), + }, + ), + ), style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18, @@ -155,9 +154,10 @@ class OpeningExplorerSettings extends ConsumerWidget { Side.black => const Text('Black'), }, selected: prefs.playerDb.side == side, - onSelected: (_) => ref - .read(openingExplorerPreferencesProvider.notifier) - .setPlayerDbSide(side), + onSelected: + (_) => ref + .read(openingExplorerPreferencesProvider.notifier) + .setPlayerDbSide(side), ), ) .toList(growable: false), @@ -172,19 +172,14 @@ class OpeningExplorerSettings extends ConsumerWidget { (speed) => FilterChip( label: Text( String.fromCharCode(speed.icon.codePoint), - style: TextStyle( - fontFamily: speed.icon.fontFamily, - fontSize: 18.0, - ), + style: TextStyle(fontFamily: speed.icon.fontFamily, fontSize: 18.0), ), - tooltip: Perf.fromVariantAndSpeed( - Variant.standard, - speed, - ).title, + tooltip: Perf.fromVariantAndSpeed(Variant.standard, speed).title, selected: prefs.playerDb.speeds.contains(speed), - onSelected: (_) => ref - .read(openingExplorerPreferencesProvider.notifier) - .togglePlayerDbSpeed(speed), + onSelected: + (_) => ref + .read(openingExplorerPreferencesProvider.notifier) + .togglePlayerDbSpeed(speed), ), ) .toList(growable: false), @@ -197,16 +192,15 @@ class OpeningExplorerSettings extends ConsumerWidget { children: GameMode.values .map( (gameMode) => FilterChip( - label: Text( - switch (gameMode) { - GameMode.casual => 'Casual', - GameMode.rated => 'Rated', - }, - ), + label: Text(switch (gameMode) { + GameMode.casual => 'Casual', + GameMode.rated => 'Rated', + }), selected: prefs.playerDb.gameModes.contains(gameMode), - onSelected: (_) => ref - .read(openingExplorerPreferencesProvider.notifier) - .togglePlayerDbGameMode(gameMode), + onSelected: + (_) => ref + .read(openingExplorerPreferencesProvider.notifier) + .togglePlayerDbGameMode(gameMode), ), ) .toList(growable: false), @@ -221,9 +215,10 @@ class OpeningExplorerSettings extends ConsumerWidget { (key) => ChoiceChip( label: Text(key), selected: prefs.playerDb.since == PlayerDb.datesMap[key], - onSelected: (_) => ref - .read(openingExplorerPreferencesProvider.notifier) - .setPlayerDbSince(PlayerDb.datesMap[key]!), + onSelected: + (_) => ref + .read(openingExplorerPreferencesProvider.notifier) + .setPlayerDbSince(PlayerDb.datesMap[key]!), ), ) .toList(growable: false), @@ -241,23 +236,26 @@ class OpeningExplorerSettings extends ConsumerWidget { ChoiceChip( label: const Text('Masters'), selected: prefs.db == OpeningDatabase.master, - onSelected: (_) => ref - .read(openingExplorerPreferencesProvider.notifier) - .setDatabase(OpeningDatabase.master), + onSelected: + (_) => ref + .read(openingExplorerPreferencesProvider.notifier) + .setDatabase(OpeningDatabase.master), ), ChoiceChip( label: const Text('Lichess'), selected: prefs.db == OpeningDatabase.lichess, - onSelected: (_) => ref - .read(openingExplorerPreferencesProvider.notifier) - .setDatabase(OpeningDatabase.lichess), + onSelected: + (_) => ref + .read(openingExplorerPreferencesProvider.notifier) + .setDatabase(OpeningDatabase.lichess), ), ChoiceChip( label: Text(context.l10n.player), selected: prefs.db == OpeningDatabase.player, - onSelected: (_) => ref - .read(openingExplorerPreferencesProvider.notifier) - .setDatabase(OpeningDatabase.player), + onSelected: + (_) => ref + .read(openingExplorerPreferencesProvider.notifier) + .setDatabase(OpeningDatabase.player), ), ], ), diff --git a/lib/src/view/opening_explorer/opening_explorer_view.dart b/lib/src/view/opening_explorer/opening_explorer_view.dart index 00c358ff46..c577a252c8 100644 --- a/lib/src/view/opening_explorer/opening_explorer_view.dart +++ b/lib/src/view/opening_explorer/opening_explorer_view.dart @@ -45,9 +45,7 @@ class _OpeningExplorerState extends ConsumerState { @override Widget build(BuildContext context) { if (widget.position.ply >= 50) { - return Center( - child: Text(context.l10n.maxDepthReached), - ); + return Center(child: Text(context.l10n.maxDepthReached)); } final prefs = ref.watch(openingExplorerPreferencesProvider); @@ -59,22 +57,15 @@ class _OpeningExplorerState extends ConsumerState { ); } - final cacheKey = OpeningExplorerCacheKey( - fen: widget.position.fen, - prefs: prefs, - ); + final cacheKey = OpeningExplorerCacheKey(fen: widget.position.fen, prefs: prefs); final cacheOpeningExplorer = cache[cacheKey]; - final openingExplorerAsync = cacheOpeningExplorer != null - ? AsyncValue.data( - (entry: cacheOpeningExplorer, isIndexing: false), - ) - : ref.watch( - openingExplorerProvider(fen: widget.position.fen), - ); + final openingExplorerAsync = + cacheOpeningExplorer != null + ? AsyncValue.data((entry: cacheOpeningExplorer, isIndexing: false)) + : ref.watch(openingExplorerProvider(fen: widget.position.fen)); if (cacheOpeningExplorer == null) { - ref.listen(openingExplorerProvider(fen: widget.position.fen), - (_, curAsync) { + ref.listen(openingExplorerProvider(fen: widget.position.fen), (_, curAsync) { curAsync.whenData((cur) { if (cur != null && !cur.isIndexing) { cache[cacheKey] = cur.entry; @@ -89,7 +80,8 @@ class _OpeningExplorerState extends ConsumerState { return _ExplorerListView( scrollable: widget.scrollable, isLoading: true, - children: lastExplorerWidgets ?? + children: + lastExplorerWidgets ?? [ const Shimmer( child: ShimmerLoading( @@ -107,8 +99,7 @@ class _OpeningExplorerState extends ConsumerState { final ply = widget.position.ply; final children = [ - if (widget.opening != null) - OpeningNameHeader(opening: widget.opening!), + if (widget.opening != null) OpeningNameHeader(opening: widget.opening!), OpeningExplorerMoveTable( moves: value.entry.moves, whiteWins: value.entry.white, @@ -122,40 +113,34 @@ class _OpeningExplorerState extends ConsumerState { key: const Key('topGamesHeader'), child: Text(context.l10n.topGames), ), - ...List.generate( - topGames.length, - (int index) { - return OpeningExplorerGameTile( - key: Key('top-game-${topGames.get(index).id}'), - game: topGames.get(index), - color: index.isEven - ? Theme.of(context).colorScheme.surfaceContainerLow - : Theme.of(context).colorScheme.surfaceContainerHigh, - ply: ply, - ); - }, - growable: false, - ), + ...List.generate(topGames.length, (int index) { + return OpeningExplorerGameTile( + key: Key('top-game-${topGames.get(index).id}'), + game: topGames.get(index), + color: + index.isEven + ? Theme.of(context).colorScheme.surfaceContainerLow + : Theme.of(context).colorScheme.surfaceContainerHigh, + ply: ply, + ); + }, growable: false), ], if (recentGames != null && recentGames.isNotEmpty) ...[ OpeningExplorerHeaderTile( key: const Key('recentGamesHeader'), child: Text(context.l10n.recentGames), ), - ...List.generate( - recentGames.length, - (int index) { - return OpeningExplorerGameTile( - key: Key('recent-game-${recentGames.get(index).id}'), - game: recentGames.get(index), - color: index.isEven - ? Theme.of(context).colorScheme.surfaceContainerLow - : Theme.of(context).colorScheme.surfaceContainerHigh, - ply: ply, - ); - }, - growable: false, - ), + ...List.generate(recentGames.length, (int index) { + return OpeningExplorerGameTile( + key: Key('recent-game-${recentGames.get(index).id}'), + game: recentGames.get(index), + color: + index.isEven + ? Theme.of(context).colorScheme.surfaceContainerLow + : Theme.of(context).colorScheme.surfaceContainerHigh, + ply: ply, + ); + }, growable: false), ], ]; @@ -167,32 +152,23 @@ class _OpeningExplorerState extends ConsumerState { children: children, ); case AsyncError(:final error): - debugPrint( - 'SEVERE: [OpeningExplorerView] could not load opening explorer data; $error', - ); + debugPrint('SEVERE: [OpeningExplorerView] could not load opening explorer data; $error'); final connectivity = ref.watch(connectivityChangesProvider); // TODO l10n final message = connectivity.whenIs( online: () => 'Could not load opening explorer data.', offline: () => 'Opening Explorer is not available offline.', ); - return Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Text(message), - ), - ); + return Center(child: Padding(padding: const EdgeInsets.all(16.0), child: Text(message))); case _: return _ExplorerListView( scrollable: widget.scrollable, isLoading: true, - children: lastExplorerWidgets ?? + children: + lastExplorerWidgets ?? [ const Shimmer( - child: ShimmerLoading( - isLoading: true, - child: OpeningExplorerMoveTable.loading(), - ), + child: ShimmerLoading(isLoading: true, child: OpeningExplorerMoveTable.loading()), ), ], ); @@ -221,9 +197,7 @@ class _ExplorerListView extends StatelessWidget { duration: const Duration(milliseconds: 400), curve: Curves.fastOutSlowIn, opacity: isLoading ? 0.20 : 0.0, - child: ColoredBox( - color: brightness == Brightness.dark ? Colors.black : Colors.white, - ), + child: ColoredBox(color: brightness == Brightness.dark ? Colors.black : Colors.white), ), ), ); diff --git a/lib/src/view/opening_explorer/opening_explorer_widgets.dart b/lib/src/view/opening_explorer/opening_explorer_widgets.dart index cad14f93fb..c953f2d83c 100644 --- a/lib/src/view/opening_explorer/opening_explorer_widgets.dart +++ b/lib/src/view/opening_explorer/opening_explorer_widgets.dart @@ -38,17 +38,12 @@ class OpeningNameHeader extends StatelessWidget { Widget build(BuildContext context) { return Container( padding: _kTableRowPadding, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.secondaryContainer, - ), + decoration: BoxDecoration(color: Theme.of(context).colorScheme.secondaryContainer), child: GestureDetector( - onTap: opening.name == context.l10n.startPosition - ? null - : () => launchUrl( - Uri.parse( - 'https://lichess.org/opening/${opening.name}', - ), - ), + onTap: + opening.name == context.l10n.startPosition + ? null + : () => launchUrl(Uri.parse('https://lichess.org/opening/${opening.name}')), child: Row( children: [ if (opening.name != context.l10n.startPosition) ...[ @@ -87,13 +82,13 @@ class OpeningExplorerMoveTable extends ConsumerWidget { }) : _isLoading = false; const OpeningExplorerMoveTable.loading() - : _isLoading = true, - moves = const IListConst([]), - whiteWins = 0, - draws = 0, - blackWins = 0, - isIndexing = false, - onMoveSelected = null; + : _isLoading = true, + moves = const IListConst([]), + whiteWins = 0, + draws = 0, + blackWins = 0, + isIndexing = false, + onMoveSelected = null; final IList moves; final int whiteWins; @@ -124,9 +119,7 @@ class OpeningExplorerMoveTable extends ConsumerWidget { columnWidths: columnWidths, children: [ TableRow( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.secondaryContainer, - ), + decoration: BoxDecoration(color: Theme.of(context).colorScheme.secondaryContainer), children: [ Padding( padding: _kTableRowPadding, @@ -155,56 +148,49 @@ class OpeningExplorerMoveTable extends ConsumerWidget { ), ], ), - ...List.generate( - moves.length, - (int index) { - final move = moves.get(index); - final percentGames = ((move.games / games) * 100).round(); - return TableRow( - decoration: BoxDecoration( - color: index.isEven - ? Theme.of(context).colorScheme.surfaceContainerLow - : Theme.of(context).colorScheme.surfaceContainerHigh, + ...List.generate(moves.length, (int index) { + final move = moves.get(index); + final percentGames = ((move.games / games) * 100).round(); + return TableRow( + decoration: BoxDecoration( + color: + index.isEven + ? Theme.of(context).colorScheme.surfaceContainerLow + : Theme.of(context).colorScheme.surfaceContainerHigh, + ), + children: [ + TableRowInkWell( + onTap: () => onMoveSelected?.call(NormalMove.fromUci(move.uci)), + child: Padding(padding: _kTableRowPadding, child: Text(move.san)), ), - children: [ - TableRowInkWell( - onTap: () => - onMoveSelected?.call(NormalMove.fromUci(move.uci)), - child: Padding( - padding: _kTableRowPadding, - child: Text(move.san), - ), + TableRowInkWell( + onTap: () => onMoveSelected?.call(NormalMove.fromUci(move.uci)), + child: Padding( + padding: _kTableRowPadding, + child: Text('${formatNum(move.games)} ($percentGames%)'), ), - TableRowInkWell( - onTap: () => - onMoveSelected?.call(NormalMove.fromUci(move.uci)), - child: Padding( - padding: _kTableRowPadding, - child: Text('${formatNum(move.games)} ($percentGames%)'), - ), - ), - TableRowInkWell( - onTap: () => - onMoveSelected?.call(NormalMove.fromUci(move.uci)), - child: Padding( - padding: _kTableRowPadding, - child: _WinPercentageChart( - whiteWins: move.white, - draws: move.draws, - blackWins: move.black, - ), + ), + TableRowInkWell( + onTap: () => onMoveSelected?.call(NormalMove.fromUci(move.uci)), + child: Padding( + padding: _kTableRowPadding, + child: _WinPercentageChart( + whiteWins: move.white, + draws: move.draws, + blackWins: move.black, ), ), - ], - ); - }, - ), + ), + ], + ); + }), if (moves.isNotEmpty) TableRow( decoration: BoxDecoration( - color: moves.length.isEven - ? Theme.of(context).colorScheme.surfaceContainerLow - : Theme.of(context).colorScheme.surfaceContainerHigh, + color: + moves.length.isEven + ? Theme.of(context).colorScheme.surfaceContainerLow + : Theme.of(context).colorScheme.surfaceContainerHigh, ), children: [ Container( @@ -212,10 +198,7 @@ class OpeningExplorerMoveTable extends ConsumerWidget { alignment: Alignment.centerLeft, child: const Icon(Icons.functions), ), - Padding( - padding: _kTableRowPadding, - child: Text('${formatNum(games)} (100%)'), - ), + Padding(padding: _kTableRowPadding, child: Text('${formatNum(games)} (100%)')), Padding( padding: _kTableRowPadding, child: _WinPercentageChart( @@ -228,27 +211,17 @@ class OpeningExplorerMoveTable extends ConsumerWidget { ) else TableRow( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerLow, - ), + decoration: BoxDecoration(color: Theme.of(context).colorScheme.surfaceContainerLow), children: [ Padding( padding: _kTableRowPadding, child: Text( String.fromCharCode(Icons.not_interested_outlined.codePoint), - style: TextStyle( - fontFamily: Icons.not_interested_outlined.fontFamily, - ), + style: TextStyle(fontFamily: Icons.not_interested_outlined.fontFamily), ), ), - Padding( - padding: _kTableRowPadding, - child: Text(context.l10n.noGameFound), - ), - const Padding( - padding: _kTableRowPadding, - child: SizedBox.shrink(), - ), + Padding(padding: _kTableRowPadding, child: Text(context.l10n.noGameFound)), + const Padding(padding: _kTableRowPadding, child: SizedBox.shrink()), ], ), ], @@ -307,16 +280,13 @@ class IndexingIndicator extends StatefulWidget { State createState() => _IndexingIndicatorState(); } -class _IndexingIndicatorState extends State - with TickerProviderStateMixin { +class _IndexingIndicatorState extends State with TickerProviderStateMixin { late AnimationController controller; @override void initState() { - controller = AnimationController( - vsync: this, - duration: const Duration(seconds: 3), - )..addListener(() { + controller = AnimationController(vsync: this, duration: const Duration(seconds: 3)) + ..addListener(() { setState(() {}); }); controller.repeat(); @@ -354,9 +324,7 @@ class OpeningExplorerHeaderTile extends StatelessWidget { return Container( width: double.infinity, padding: _kTableRowPadding, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.secondaryContainer, - ), + decoration: BoxDecoration(color: Theme.of(context).colorScheme.secondaryContainer), child: child, ); } @@ -376,12 +344,10 @@ class OpeningExplorerGameTile extends ConsumerStatefulWidget { final int ply; @override - ConsumerState createState() => - _OpeningExplorerGameTileState(); + ConsumerState createState() => _OpeningExplorerGameTileState(); } -class _OpeningExplorerGameTileState - extends ConsumerState { +class _OpeningExplorerGameTileState extends ConsumerState { @override Widget build(BuildContext context) { const widthResultBox = 50.0; @@ -394,11 +360,12 @@ class _OpeningExplorerGameTileState onTap: () { pushPlatformRoute( context, - builder: (_) => ArchivedGameScreen( - gameId: widget.game.id, - orientation: Side.white, - initialCursor: widget.ply, - ), + builder: + (_) => ArchivedGameScreen( + gameId: widget.game.id, + orientation: Side.white, + initialCursor: widget.ply, + ), ); }, child: Row( @@ -416,14 +383,8 @@ class _OpeningExplorerGameTileState child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - widget.game.white.name, - overflow: TextOverflow.ellipsis, - ), - Text( - widget.game.black.name, - overflow: TextOverflow.ellipsis, - ), + Text(widget.game.white.name, overflow: TextOverflow.ellipsis), + Text(widget.game.black.name, overflow: TextOverflow.ellipsis), ], ), ), @@ -440,9 +401,7 @@ class _OpeningExplorerGameTileState child: const Text( '1-0', textAlign: TextAlign.center, - style: TextStyle( - color: Colors.black, - ), + style: TextStyle(color: Colors.black), ), ) else if (widget.game.winner == 'black') @@ -456,9 +415,7 @@ class _OpeningExplorerGameTileState child: const Text( '0-1', textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - ), + style: TextStyle(color: Colors.white), ), ) else @@ -472,18 +429,14 @@ class _OpeningExplorerGameTileState child: const Text( '½-½', textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - ), + style: TextStyle(color: Colors.white), ), ), if (widget.game.month != null) ...[ const SizedBox(width: 10.0), Text( widget.game.month!, - style: const TextStyle( - fontFeatures: [FontFeature.tabularFigures()], - ), + style: const TextStyle(fontFeatures: [FontFeature.tabularFigures()]), ), ], if (widget.game.speed != null) ...[ @@ -510,8 +463,7 @@ class _WinPercentageChart extends StatelessWidget { final int draws; final int blackWins; - int percentGames(int games) => - ((games / (whiteWins + draws + blackWins)) * 100).round(); + int percentGames(int games) => ((games / (whiteWins + draws + blackWins)) * 100).round(); String label(int percent) => percent < 20 ? '' : '$percent%'; @override diff --git a/lib/src/view/over_the_board/configure_over_the_board_game.dart b/lib/src/view/over_the_board/configure_over_the_board_game.dart index 1cee182f10..7836038b61 100644 --- a/lib/src/view/over_the_board/configure_over_the_board_game.dart +++ b/lib/src/view/over_the_board/configure_over_the_board_game.dart @@ -15,19 +15,14 @@ import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/non_linear_slider.dart'; import 'package:lichess_mobile/src/widgets/settings.dart'; -void showConfigureGameSheet( - BuildContext context, { - required bool isDismissible, -}) { +void showConfigureGameSheet(BuildContext context, {required bool isDismissible}) { final double screenHeight = MediaQuery.sizeOf(context).height; showAdaptiveBottomSheet( context: context, isScrollControlled: true, showDragHandle: true, isDismissible: isDismissible, - constraints: BoxConstraints( - maxHeight: screenHeight - (screenHeight / 10), - ), + constraints: BoxConstraints(maxHeight: screenHeight - (screenHeight / 10)), builder: (BuildContext context) { return const _ConfigureOverTheBoardGameSheet(); }, @@ -42,8 +37,7 @@ class _ConfigureOverTheBoardGameSheet extends ConsumerStatefulWidget { _ConfigureOverTheBoardGameSheetState(); } -class _ConfigureOverTheBoardGameSheetState - extends ConsumerState<_ConfigureOverTheBoardGameSheet> { +class _ConfigureOverTheBoardGameSheetState extends ConsumerState<_ConfigureOverTheBoardGameSheet> { late Variant chosenVariant; late TimeIncrement timeIncrement; @@ -59,19 +53,13 @@ class _ConfigureOverTheBoardGameSheetState void _setTotalTime(num seconds) { setState(() { - timeIncrement = TimeIncrement( - seconds.toInt(), - timeIncrement.increment, - ); + timeIncrement = TimeIncrement(seconds.toInt(), timeIncrement.increment); }); } void _setIncrement(num seconds) { setState(() { - timeIncrement = TimeIncrement( - timeIncrement.time, - seconds.toInt(), - ); + timeIncrement = TimeIncrement(timeIncrement.time, seconds.toInt()); }); } @@ -86,16 +74,16 @@ class _ConfigureOverTheBoardGameSheetState onTap: () { showChoicePicker( context, - choices: playSupportedVariants - .where( - (variant) => variant != Variant.fromPosition, - ) - .toList(), + choices: + playSupportedVariants + .where((variant) => variant != Variant.fromPosition) + .toList(), selectedItem: chosenVariant, labelBuilder: (Variant variant) => Text(variant.label), - onSelectedItemChanged: (Variant variant) => setState(() { - chosenVariant = variant; - }), + onSelectedItemChanged: + (Variant variant) => setState(() { + chosenVariant = variant; + }), ); }, ), @@ -105,10 +93,7 @@ class _ConfigureOverTheBoardGameSheetState text: '${context.l10n.minutesPerSide}: ', children: [ TextSpan( - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), text: clockLabelInMinutes(timeIncrement.time), ), ], @@ -118,9 +103,7 @@ class _ConfigureOverTheBoardGameSheetState value: timeIncrement.time, values: kAvailableTimesInSeconds, labelBuilder: clockLabelInMinutes, - onChange: Theme.of(context).platform == TargetPlatform.iOS - ? _setTotalTime - : null, + onChange: Theme.of(context).platform == TargetPlatform.iOS ? _setTotalTime : null, onChangeEnd: _setTotalTime, ), ), @@ -130,10 +113,7 @@ class _ConfigureOverTheBoardGameSheetState text: '${context.l10n.incrementInSeconds}: ', children: [ TextSpan( - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), text: timeIncrement.increment.toString(), ), ], @@ -142,28 +122,20 @@ class _ConfigureOverTheBoardGameSheetState subtitle: NonLinearSlider( value: timeIncrement.increment, values: kAvailableIncrementsInSeconds, - onChange: Theme.of(context).platform == TargetPlatform.iOS - ? _setIncrement - : null, + onChange: Theme.of(context).platform == TargetPlatform.iOS ? _setIncrement : null, onChangeEnd: _setIncrement, ), ), SecondaryButton( onPressed: () { + ref.read(overTheBoardClockProvider.notifier).setupClock(timeIncrement); ref - .read(overTheBoardClockProvider.notifier) - .setupClock(timeIncrement); - ref.read(overTheBoardGameControllerProvider.notifier).startNewGame( - chosenVariant, - timeIncrement, - ); + .read(overTheBoardGameControllerProvider.notifier) + .startNewGame(chosenVariant, timeIncrement); Navigator.pop(context); }, semanticsLabel: context.l10n.play, - child: Text( - context.l10n.play, - style: Styles.bold, - ), + child: Text(context.l10n.play, style: Styles.bold), ), ], ); @@ -193,16 +165,14 @@ class OverTheBoardDisplaySettings extends ConsumerWidget { SwitchSettingTile( title: const Text('Use symmetric pieces'), value: prefs.symmetricPieces, - onChanged: (_) => ref - .read(overTheBoardPreferencesProvider.notifier) - .toggleSymmetricPieces(), + onChanged: + (_) => ref.read(overTheBoardPreferencesProvider.notifier).toggleSymmetricPieces(), ), SwitchSettingTile( title: const Text('Flip pieces and oponent info after move'), value: prefs.flipPiecesAfterMove, - onChanged: (_) => ref - .read(overTheBoardPreferencesProvider.notifier) - .toggleFlipPiecesAfterMove(), + onChanged: + (_) => ref.read(overTheBoardPreferencesProvider.notifier).toggleFlipPiecesAfterMove(), ), ], ); diff --git a/lib/src/view/over_the_board/over_the_board_screen.dart b/lib/src/view/over_the_board/over_the_board_screen.dart index 7470cac66d..c4e5f82985 100644 --- a/lib/src/view/over_the_board/over_the_board_screen.dart +++ b/lib/src/view/over_the_board/over_the_board_screen.dart @@ -76,8 +76,7 @@ class _BodyState extends ConsumerState<_Body> { final overTheBoardPrefs = ref.watch(overTheBoardPreferencesProvider); - ref.listen(overTheBoardClockProvider.select((value) => value.flagSide), - (previous, flagSide) { + ref.listen(overTheBoardClockProvider.select((value) => value.flagSide), (previous, flagSide) { if (previous == null && flagSide != null) { ref.read(overTheBoardGameControllerProvider.notifier).onFlag(flagSide); } @@ -90,19 +89,18 @@ class _BodyState extends ConsumerState<_Body> { if (context.mounted) { showAdaptiveDialog( context: context, - builder: (context) => OverTheBoardGameResultDialog( - game: newGameState.game, - onRematch: () { - setState(() { - orientation = orientation.opposite; - ref - .read(overTheBoardGameControllerProvider.notifier) - .rematch(); - ref.read(overTheBoardClockProvider.notifier).restart(); - Navigator.pop(context); - }); - }, - ), + builder: + (context) => OverTheBoardGameResultDialog( + game: newGameState.game, + onRematch: () { + setState(() { + orientation = orientation.opposite; + ref.read(overTheBoardGameControllerProvider.notifier).rematch(); + ref.read(overTheBoardClockProvider.notifier).restart(); + Navigator.pop(context); + }); + }, + ), barrierDismissible: true, ); } @@ -121,40 +119,37 @@ class _BodyState extends ConsumerState<_Body> { key: _boardKey, topTable: _Player( side: orientation.opposite, - upsideDown: !overTheBoardPrefs.flipPiecesAfterMove || - orientation != gameState.turn, + upsideDown: + !overTheBoardPrefs.flipPiecesAfterMove || orientation != gameState.turn, clockKey: const ValueKey('topClock'), ), bottomTable: _Player( side: orientation, - upsideDown: overTheBoardPrefs.flipPiecesAfterMove && - orientation != gameState.turn, + upsideDown: + overTheBoardPrefs.flipPiecesAfterMove && orientation != gameState.turn, clockKey: const ValueKey('bottomClock'), ), orientation: orientation, fen: gameState.currentPosition.fen, lastMove: gameState.lastMove, gameData: GameData( - isCheck: boardPreferences.boardHighlights && - gameState.currentPosition.isCheck, - playerSide: gameState.game.finished - ? PlayerSide.none - : gameState.turn == Side.white + isCheck: boardPreferences.boardHighlights && gameState.currentPosition.isCheck, + playerSide: + gameState.game.finished + ? PlayerSide.none + : gameState.turn == Side.white ? PlayerSide.white : PlayerSide.black, sideToMove: gameState.turn, validMoves: gameState.legalMoves, - onPromotionSelection: ref - .read(overTheBoardGameControllerProvider.notifier) - .onPromotionSelection, + onPromotionSelection: + ref.read(overTheBoardGameControllerProvider.notifier).onPromotionSelection, promotionMove: gameState.promotionMove, onMove: (move, {isDrop}) { - ref.read(overTheBoardClockProvider.notifier).onMove( - newSideToMove: gameState.turn.opposite, - ); ref - .read(overTheBoardGameControllerProvider.notifier) - .makeMove(move); + .read(overTheBoardClockProvider.notifier) + .onMove(newSideToMove: gameState.turn.opposite); + ref.read(overTheBoardGameControllerProvider.notifier).makeMove(move); }, ), moves: gameState.moves, @@ -165,9 +160,8 @@ class _BodyState extends ConsumerState<_Body> { overTheBoardPrefs.flipPiecesAfterMove ? PieceOrientationBehavior.sideToPlay : PieceOrientationBehavior.opponentUpsideDown, - pieceAssets: overTheBoardPrefs.symmetricPieces - ? PieceSet.symmetric.assets - : null, + pieceAssets: + overTheBoardPrefs.symmetricPieces ? PieceSet.symmetric.assets : null, ), ), ), @@ -187,9 +181,7 @@ class _BodyState extends ConsumerState<_Body> { } class _BottomBar extends ConsumerWidget { - const _BottomBar({ - required this.onFlipBoard, - }); + const _BottomBar({required this.onFlipBoard}); final VoidCallback onFlipBoard; @@ -215,51 +207,46 @@ class _BottomBar extends ConsumerWidget { if (!clock.timeIncrement.isInfinite) BottomBarButton( label: clock.active ? 'Pause' : 'Resume', - onTap: gameState.finished - ? null - : () { - if (clock.active) { - ref.read(overTheBoardClockProvider.notifier).pause(); - } else { - ref - .read(overTheBoardClockProvider.notifier) - .resume(gameState.turn); - } - }, + onTap: + gameState.finished + ? null + : () { + if (clock.active) { + ref.read(overTheBoardClockProvider.notifier).pause(); + } else { + ref.read(overTheBoardClockProvider.notifier).resume(gameState.turn); + } + }, icon: clock.active ? CupertinoIcons.pause : CupertinoIcons.play, ), BottomBarButton( label: 'Previous', - onTap: gameState.canGoBack - ? () { - ref - .read(overTheBoardGameControllerProvider.notifier) - .goBack(); - if (clock.active) { - ref.read(overTheBoardClockProvider.notifier).switchSide( - newSideToMove: gameState.turn.opposite, - addIncrement: false, - ); + onTap: + gameState.canGoBack + ? () { + ref.read(overTheBoardGameControllerProvider.notifier).goBack(); + if (clock.active) { + ref + .read(overTheBoardClockProvider.notifier) + .switchSide(newSideToMove: gameState.turn.opposite, addIncrement: false); + } } - } - : null, + : null, icon: CupertinoIcons.chevron_back, ), BottomBarButton( label: 'Next', - onTap: gameState.canGoForward - ? () { - ref - .read(overTheBoardGameControllerProvider.notifier) - .goForward(); - if (clock.active) { - ref.read(overTheBoardClockProvider.notifier).switchSide( - newSideToMove: gameState.turn.opposite, - addIncrement: false, - ); + onTap: + gameState.canGoForward + ? () { + ref.read(overTheBoardGameControllerProvider.notifier).goForward(); + if (clock.active) { + ref + .read(overTheBoardClockProvider.notifier) + .switchSide(newSideToMove: gameState.turn.opposite, addIncrement: false); + } } - } - : null, + : null, icon: CupertinoIcons.chevron_forward, ), ], @@ -268,11 +255,7 @@ class _BottomBar extends ConsumerWidget { } class _Player extends ConsumerWidget { - const _Player({ - required this.clockKey, - required this.side, - required this.upsideDown, - }); + const _Player({required this.clockKey, required this.side, required this.upsideDown}); final Side side; @@ -287,40 +270,35 @@ class _Player extends ConsumerWidget { final clock = ref.watch(overTheBoardClockProvider); final brightness = ref.watch(currentBrightnessProvider); - final clockStyle = brightness == Brightness.dark - ? ClockStyle.darkThemeStyle - : ClockStyle.lightThemeStyle; + final clockStyle = + brightness == Brightness.dark ? ClockStyle.darkThemeStyle : ClockStyle.lightThemeStyle; return RotatedBox( quarterTurns: upsideDown ? 2 : 0, child: GamePlayer( player: Player( onGame: true, - user: LightUser( - id: UserId(side.name), - name: side.name.capitalize(), - ), + user: LightUser(id: UserId(side.name), name: side.name.capitalize()), ), - materialDiff: boardPreferences.materialDifferenceFormat.visible - ? gameState.currentMaterialDiff(side) - : null, + materialDiff: + boardPreferences.materialDifferenceFormat.visible + ? gameState.currentMaterialDiff(side) + : null, materialDifferenceFormat: boardPreferences.materialDifferenceFormat, shouldLinkToUserProfile: false, - clock: clock.timeIncrement.isInfinite - ? null - : Clock( - timeLeft: Duration( - milliseconds: max(0, clock.timeLeft(side)!.inMilliseconds), - ), - key: clockKey, - active: clock.activeClock == side, - clockStyle: clockStyle, - // https://github.com/lichess-org/mobile/issues/785#issuecomment-2183903498 - emergencyThreshold: Duration( - seconds: - (clock.timeIncrement.time * 0.125).clamp(10, 60).toInt(), + clock: + clock.timeIncrement.isInfinite + ? null + : Clock( + timeLeft: Duration(milliseconds: max(0, clock.timeLeft(side)!.inMilliseconds)), + key: clockKey, + active: clock.activeClock == side, + clockStyle: clockStyle, + // https://github.com/lichess-org/mobile/issues/785#issuecomment-2183903498 + emergencyThreshold: Duration( + seconds: (clock.timeIncrement.time * 0.125).clamp(10, 60).toInt(), + ), ), - ), ), ); } diff --git a/lib/src/view/play/challenge_list_item.dart b/lib/src/view/play/challenge_list_item.dart index cc17fb53b6..c8d55a696a 100644 --- a/lib/src/view/play/challenge_list_item.dart +++ b/lib/src/view/play/challenge_list_item.dart @@ -43,29 +43,24 @@ class ChallengeListItem extends ConsumerWidget { final me = ref.watch(authSessionProvider)?.user; final isMyChallenge = me != null && me.id == challengerUser.id; - final color = isMyChallenge - ? context.lichessColors.good.withValues(alpha: 0.2) - : null; + final color = isMyChallenge ? context.lichessColors.good.withValues(alpha: 0.2) : null; final isFromPosition = challenge.variant == Variant.fromPosition; final leading = Icon(challenge.perf.icon, size: 36); - final trailing = challenge.challenger?.lagRating != null - ? LagIndicator(lagRating: challenge.challenger!.lagRating!) - : null; - final title = isMyChallenge - // shows destUser if it exists, otherwise shows the challenger (me) - // if no destUser, it's an open challenge I sent - ? UserFullNameWidget( - user: challenge.destUser != null - ? challenge.destUser!.user - : challengerUser, - rating: challenge.destUser?.rating ?? challenge.challenger?.rating, - ) - : UserFullNameWidget( - user: challengerUser, - rating: challenge.challenger?.rating, - ); + final trailing = + challenge.challenger?.lagRating != null + ? LagIndicator(lagRating: challenge.challenger!.lagRating!) + : null; + final title = + isMyChallenge + // shows destUser if it exists, otherwise shows the challenger (me) + // if no destUser, it's an open challenge I sent + ? UserFullNameWidget( + user: challenge.destUser != null ? challenge.destUser!.user : challengerUser, + rating: challenge.destUser?.rating ?? challenge.challenger?.rating, + ) + : UserFullNameWidget(user: challengerUser, rating: challenge.challenger?.rating); final subtitle = Text(challenge.description(context.l10n)); final screenWidth = MediaQuery.sizeOf(context).width; @@ -73,9 +68,7 @@ class ChallengeListItem extends ConsumerWidget { return Container( color: color, child: Slidable( - enabled: onAccept != null || - onDecline != null || - (isMyChallenge && onCancel != null), + enabled: onAccept != null || onDecline != null || (isMyChallenge && onCancel != null), dragStartBehavior: DragStartBehavior.start, endActionPane: ActionPane( motion: const StretchMotion(), @@ -93,50 +86,45 @@ class ChallengeListItem extends ConsumerWidget { if (onDecline != null || (isMyChallenge && onCancel != null)) SlidableAction( icon: Icons.close, - onPressed: isMyChallenge - ? (_) => onCancel!() - : onDecline != null + onPressed: + isMyChallenge + ? (_) => onCancel!() + : onDecline != null ? (_) => onDecline!(null) : null, spacing: 8.0, backgroundColor: context.lichessColors.error, foregroundColor: Colors.white, - label: - isMyChallenge ? context.l10n.cancel : context.l10n.decline, + label: isMyChallenge ? context.l10n.cancel : context.l10n.decline, ), ], ), - child: isFromPosition - ? ExpansionTile( - childrenPadding: Styles.bodyPadding - .subtract(const EdgeInsets.only(top: 8.0)), - leading: leading, - title: title, - subtitle: subtitle, - children: [ - if (challenge.variant == Variant.fromPosition && - challenge.initialFen != null) - BoardThumbnail( - size: min( - 400, - screenWidth - 2 * Styles.bodyPadding.horizontal, + child: + isFromPosition + ? ExpansionTile( + childrenPadding: Styles.bodyPadding.subtract(const EdgeInsets.only(top: 8.0)), + leading: leading, + title: title, + subtitle: subtitle, + children: [ + if (challenge.variant == Variant.fromPosition && challenge.initialFen != null) + BoardThumbnail( + size: min(400, screenWidth - 2 * Styles.bodyPadding.horizontal), + orientation: + challenge.sideChoice == SideChoice.white ? Side.white : Side.black, + fen: challenge.initialFen!, + onTap: onPressed, ), - orientation: challenge.sideChoice == SideChoice.white - ? Side.white - : Side.black, - fen: challenge.initialFen!, - onTap: onPressed, - ), - ], - // onTap: onPressed, - ) - : AdaptiveListTile( - leading: leading, - title: title, - subtitle: subtitle, - trailing: trailing, - onTap: onPressed, - ), + ], + // onTap: onPressed, + ) + : AdaptiveListTile( + leading: leading, + title: title, + subtitle: subtitle, + trailing: trailing, + onTap: onPressed, + ), ), ); } @@ -164,13 +152,15 @@ class CorrespondenceChallengeListItem extends StatelessWidget { status: ChallengeStatus.created, variant: challenge.variant, speed: Speed.correspondence, - timeControl: challenge.days != null - ? ChallengeTimeControlType.correspondence - : ChallengeTimeControlType.unlimited, + timeControl: + challenge.days != null + ? ChallengeTimeControlType.correspondence + : ChallengeTimeControlType.unlimited, rated: challenge.rated, - sideChoice: challenge.side == null - ? SideChoice.random - : challenge.side == Side.white + sideChoice: + challenge.side == null + ? SideChoice.random + : challenge.side == Side.white ? SideChoice.white : SideChoice.black, days: challenge.days, diff --git a/lib/src/view/play/common_play_widgets.dart b/lib/src/view/play/common_play_widgets.dart index 846c7da11b..abe9d2b5e9 100644 --- a/lib/src/view/play/common_play_widgets.dart +++ b/lib/src/view/play/common_play_widgets.dart @@ -49,9 +49,7 @@ class _PlayRatingRangeState extends State { opacity: isRatingRangeAvailable ? 1 : 0.5, child: PlatformListTile( harmonizeCupertinoTitleStyle: true, - title: Text( - context.l10n.ratingRange, - ), + title: Text(context.l10n.ratingRange), subtitle: Row( mainAxisSize: MainAxisSize.max, children: [ @@ -61,27 +59,25 @@ class _PlayRatingRangeState extends State { NonLinearSlider( value: _subtract, values: kSubtractingRatingRange, - onChange: Theme.of(context).platform == TargetPlatform.iOS - ? (num value) { - setState(() { - _subtract = value.toInt(); - }); - } - : null, - onChangeEnd: isRatingRangeAvailable - ? (num value) { - widget.onRatingDeltaChange( - value.toInt(), - value == 0 && _add == 0 - ? kAddingRatingRange[1] - : _add, - ); - } - : null, - ), - Center( - child: Text('${_subtract == 0 ? '-' : ''}$_subtract'), + onChange: + Theme.of(context).platform == TargetPlatform.iOS + ? (num value) { + setState(() { + _subtract = value.toInt(); + }); + } + : null, + onChangeEnd: + isRatingRangeAvailable + ? (num value) { + widget.onRatingDeltaChange( + value.toInt(), + value == 0 && _add == 0 ? kAddingRatingRange[1] : _add, + ); + } + : null, ), + Center(child: Text('${_subtract == 0 ? '-' : ''}$_subtract')), ], ), ), @@ -98,28 +94,27 @@ class _PlayRatingRangeState extends State { NonLinearSlider( value: _add, values: kAddingRatingRange, - onChange: Theme.of(context).platform == TargetPlatform.iOS - ? (num value) { - setState(() { - _add = value.toInt(); - }); - } - : null, - onChangeEnd: isRatingRangeAvailable - ? (num value) { - widget.onRatingDeltaChange( - value == 0 && _subtract == 0 - ? kSubtractingRatingRange[ - kSubtractingRatingRange.length - 2] - : _subtract, - value.toInt(), - ); - } - : null, - ), - Center( - child: Text('+$_add'), + onChange: + Theme.of(context).platform == TargetPlatform.iOS + ? (num value) { + setState(() { + _add = value.toInt(); + }); + } + : null, + onChangeEnd: + isRatingRangeAvailable + ? (num value) { + widget.onRatingDeltaChange( + value == 0 && _subtract == 0 + ? kSubtractingRatingRange[kSubtractingRatingRange.length - 2] + : _subtract, + value.toInt(), + ); + } + : null, ), + Center(child: Text('+$_add')), ], ), ), diff --git a/lib/src/view/play/create_challenge_screen.dart b/lib/src/view/play/create_challenge_screen.dart index 4e1e0ad70f..62319a0df5 100644 --- a/lib/src/view/play/create_challenge_screen.dart +++ b/lib/src/view/play/create_challenge_screen.dart @@ -38,9 +38,7 @@ class CreateChallengeScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.challengeChallengesX(user.name)), - ), + appBar: PlatformAppBar(title: Text(context.l10n.challengeChallengesX(user.name))), body: _ChallengeBody(user), ); } @@ -84,12 +82,12 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { final isValidTimeControl = preferences.timeControl != ChallengeTimeControlType.clock || - preferences.clock.time > Duration.zero || - preferences.clock.increment > Duration.zero; + preferences.clock.time > Duration.zero || + preferences.clock.increment > Duration.zero; final isValidPosition = (fromPositionFenInput != null && fromPositionFenInput!.isNotEmpty) || - preferences.variant != Variant.fromPosition; + preferences.variant != Variant.fromPosition; return accountAsync.when( data: (account) { @@ -98,9 +96,10 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { return Center( child: ListView( shrinkWrap: true, - padding: Theme.of(context).platform == TargetPlatform.iOS - ? Styles.sectionBottomPadding - : Styles.verticalBodyPadding, + padding: + Theme.of(context).platform == TargetPlatform.iOS + ? Styles.sectionBottomPadding + : Styles.verticalBodyPadding, children: [ PlatformListTile( harmonizeCupertinoTitleStyle: true, @@ -114,21 +113,14 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { ChallengeTimeControlType.correspondence, ], selectedItem: preferences.timeControl, - labelBuilder: (ChallengeTimeControlType timeControl) => - Text( - switch (timeControl) { - ChallengeTimeControlType.clock => - context.l10n.realTime, - ChallengeTimeControlType.correspondence => - context.l10n.correspondence, - ChallengeTimeControlType.unlimited => - context.l10n.unlimited, - }, - ), + labelBuilder: + (ChallengeTimeControlType timeControl) => Text(switch (timeControl) { + ChallengeTimeControlType.clock => context.l10n.realTime, + ChallengeTimeControlType.correspondence => context.l10n.correspondence, + ChallengeTimeControlType.unlimited => context.l10n.unlimited, + }), onSelectedItemChanged: (ChallengeTimeControlType value) { - ref - .read(challengePreferencesProvider.notifier) - .setTimeControl(value); + ref.read(challengePreferencesProvider.notifier).setTimeControl(value); }, ); }, @@ -152,10 +144,7 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { text: '${context.l10n.minutesPerSide}: ', children: [ TextSpan( - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), text: clockLabelInMinutes(seconds), ), ], @@ -168,10 +157,10 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { onChange: Theme.of(context).platform == TargetPlatform.iOS ? (num value) { - setState(() { - seconds = value.toInt(); - }); - } + setState(() { + seconds = value.toInt(); + }); + } : null, onChangeEnd: (num value) { setState(() { @@ -192,8 +181,7 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { ), Builder( builder: (context) { - int incrementSeconds = - preferences.clock.increment.inSeconds; + int incrementSeconds = preferences.clock.increment.inSeconds; return StatefulBuilder( builder: (context, setState) { return PlatformListTile( @@ -203,10 +191,7 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { text: '${context.l10n.incrementInSeconds}: ', children: [ TextSpan( - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), text: incrementSeconds.toString(), ), ], @@ -218,10 +203,10 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { onChange: Theme.of(context).platform == TargetPlatform.iOS ? (num value) { - setState(() { - incrementSeconds = value.toInt(); - }); - } + setState(() { + incrementSeconds = value.toInt(); + }); + } : null, onChangeEnd: (num value) { setState(() { @@ -253,10 +238,7 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { text: '${context.l10n.daysPerTurn}: ', children: [ TextSpan( - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), text: _daysLabel(daysPerTurn), ), ], @@ -269,10 +251,10 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { onChange: Theme.of(context).platform == TargetPlatform.iOS ? (num value) { - setState(() { - daysPerTurn = value.toInt(); - }); - } + setState(() { + daysPerTurn = value.toInt(); + }); + } : null, onChangeEnd: (num value) { setState(() { @@ -296,17 +278,11 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { onPressed: () { showChoicePicker( context, - choices: [ - Variant.standard, - Variant.chess960, - Variant.fromPosition, - ], + choices: [Variant.standard, Variant.chess960, Variant.fromPosition], selectedItem: preferences.variant, labelBuilder: (Variant variant) => Text(variant.label), onSelectedItemChanged: (Variant variant) { - ref - .read(challengePreferencesProvider.notifier) - .setVariant(variant); + ref.read(challengePreferencesProvider.notifier).setVariant(variant); }, ); }, @@ -316,9 +292,7 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { ExpandedSection( expand: preferences.variant == Variant.fromPosition, child: SmallBoardPreview( - orientation: preferences.sideChoice == SideChoice.black - ? Side.black - : Side.white, + orientation: preferences.sideChoice == SideChoice.black ? Side.black : Side.white, fen: fromPositionFenInput ?? kEmptyFen, description: AdaptiveTextField( maxLines: 5, @@ -330,8 +304,7 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { ), ), ExpandedSection( - expand: preferences.rated == false || - preferences.variant == Variant.fromPosition, + expand: preferences.rated == false || preferences.variant == Variant.fromPosition, child: PlatformListTile( harmonizeCupertinoTitleStyle: true, title: Text(context.l10n.side), @@ -341,18 +314,13 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { context, choices: SideChoice.values, selectedItem: preferences.sideChoice, - labelBuilder: (SideChoice side) => - Text(side.label(context.l10n)), + labelBuilder: (SideChoice side) => Text(side.label(context.l10n)), onSelectedItemChanged: (SideChoice side) { - ref - .read(challengePreferencesProvider.notifier) - .setSideChoice(side); + ref.read(challengePreferencesProvider.notifier).setSideChoice(side); }, ); }, - child: Text( - preferences.sideChoice.label(context.l10n), - ), + child: Text(preferences.sideChoice.label(context.l10n)), ), ), ), @@ -366,9 +334,7 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { applyCupertinoTheme: true, value: preferences.rated, onChanged: (bool value) { - ref - .read(challengePreferencesProvider.notifier) - .setRated(value); + ref.read(challengePreferencesProvider.notifier).setRated(value); }, ), ), @@ -381,69 +347,56 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { padding: const EdgeInsets.symmetric(horizontal: 20.0), child: FatButton( semanticsLabel: context.l10n.challengeChallengeToPlay, - onPressed: timeControl == ChallengeTimeControlType.clock - ? isValidTimeControl && isValidPosition - ? () { - pushPlatformRoute( - context, - rootNavigator: true, - builder: (BuildContext context) { - return GameScreen( - challenge: preferences.makeRequest( - widget.user, - preferences.variant != - Variant.fromPosition - ? null - : fromPositionFenInput, - ), - ); - }, - ); - } - : null - : timeControl == - ChallengeTimeControlType.correspondence && - snapshot.connectionState != - ConnectionState.waiting + onPressed: + timeControl == ChallengeTimeControlType.clock + ? isValidTimeControl && isValidPosition + ? () { + pushPlatformRoute( + context, + rootNavigator: true, + builder: (BuildContext context) { + return GameScreen( + challenge: preferences.makeRequest( + widget.user, + preferences.variant != Variant.fromPosition + ? null + : fromPositionFenInput, + ), + ); + }, + ); + } + : null + : timeControl == ChallengeTimeControlType.correspondence && + snapshot.connectionState != ConnectionState.waiting ? () async { - final createGameService = - ref.read(createGameServiceProvider); - _pendingCorrespondenceChallenge = - createGameService - .newCorrespondenceChallenge( - preferences.makeRequest( - widget.user, - preferences.variant != - Variant.fromPosition - ? null - : fromPositionFenInput, - ), - ); + final createGameService = ref.read(createGameServiceProvider); + _pendingCorrespondenceChallenge = createGameService + .newCorrespondenceChallenge( + preferences.makeRequest( + widget.user, + preferences.variant != Variant.fromPosition + ? null + : fromPositionFenInput, + ), + ); - await _pendingCorrespondenceChallenge!; + await _pendingCorrespondenceChallenge!; - if (!context.mounted) return; + if (!context.mounted) return; - Navigator.of(context).pop(); + Navigator.of(context).pop(); - // Switch to the home tab - ref - .read(currentBottomTabProvider.notifier) - .state = BottomTab.home; + // Switch to the home tab + ref.read(currentBottomTabProvider.notifier).state = BottomTab.home; - // Navigate to the challenges screen where - // the new correspondence challenge will be - // displayed - pushPlatformRoute( - context, - screen: const ChallengeRequestsScreen(), - ); - } + // Navigate to the challenges screen where + // the new correspondence challenge will be + // displayed + pushPlatformRoute(context, screen: const ChallengeRequestsScreen()); + } : null, - child: Text( - context.l10n.challengeChallengeToPlay, - style: Styles.bold, - ), + child: Text(context.l10n.challengeChallengeToPlay, style: Styles.bold), ), ); }, @@ -453,9 +406,7 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { ); }, loading: () => const Center(child: CircularProgressIndicator.adaptive()), - error: (error, stackTrace) => const Center( - child: Text('Could not load account data'), - ), + error: (error, stackTrace) => const Center(child: Text('Could not load account data')), ); } @@ -465,13 +416,9 @@ class _ChallengeBodyState extends ConsumerState<_ChallengeBody> { try { Chess.fromSetup(Setup.parseFen(data.text!.trim())); _controller.text = data.text!; - } catch (_, __) { + } catch (_) { if (mounted) { - showPlatformSnackbar( - context, - context.l10n.invalidFen, - type: SnackBarType.error, - ); + showPlatformSnackbar(context, context.l10n.invalidFen, type: SnackBarType.error); } } } diff --git a/lib/src/view/play/create_custom_game_screen.dart b/lib/src/view/play/create_custom_game_screen.dart index a1fa193a8f..e6fc127d3e 100644 --- a/lib/src/view/play/create_custom_game_screen.dart +++ b/lib/src/view/play/create_custom_game_screen.dart @@ -49,9 +49,7 @@ class CreateCustomGameScreen extends StatelessWidget { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( automaticBackgroundVisibility: false, - backgroundColor: Styles.cupertinoAppBarColor - .resolveFrom(context) - .withValues(alpha: 0.0), + backgroundColor: Styles.cupertinoAppBarColor.resolveFrom(context).withValues(alpha: 0.0), border: null, ), child: const _CupertinoBody(), @@ -70,8 +68,7 @@ class _AndroidBody extends StatefulWidget { State<_AndroidBody> createState() => _AndroidBodyState(); } -class _AndroidBodyState extends State<_AndroidBody> - with TickerProviderStateMixin { +class _AndroidBodyState extends State<_AndroidBody> with TickerProviderStateMixin { late final TabController _tabController; @override @@ -179,17 +176,18 @@ class _CupertinoBodyState extends State<_CupertinoBody> { ); return NotificationListener( onNotification: handleScrollNotification, - child: _selectedSegment == _ViewMode.create - ? _TabView( - cupertinoTabSwitcher: tabSwitcher, - cupertinoHeaderOpacity: headerOpacity, - sliver: _CreateGameBody(setViewMode: setViewMode), - ) - : _TabView( - cupertinoTabSwitcher: tabSwitcher, - cupertinoHeaderOpacity: headerOpacity, - sliver: _ChallengesBody(setViewMode: setViewMode), - ), + child: + _selectedSegment == _ViewMode.create + ? _TabView( + cupertinoTabSwitcher: tabSwitcher, + cupertinoHeaderOpacity: headerOpacity, + sliver: _CreateGameBody(setViewMode: setViewMode), + ) + : _TabView( + cupertinoTabSwitcher: tabSwitcher, + cupertinoHeaderOpacity: headerOpacity, + sliver: _ChallengesBody(setViewMode: setViewMode), + ), ); } } @@ -207,7 +205,8 @@ class _TabView extends StatelessWidget { @override Widget build(BuildContext context) { - final edgeInsets = MediaQuery.paddingOf(context) - + final edgeInsets = + MediaQuery.paddingOf(context) - (cupertinoTabSwitcher != null ? EdgeInsets.only(top: MediaQuery.paddingOf(context).top) : EdgeInsets.zero) + @@ -224,29 +223,28 @@ class _TabView extends StatelessWidget { child: AnimatedContainer( duration: const Duration(milliseconds: 200), decoration: ShapeDecoration( - color: cupertinoHeaderOpacity == 1.0 - ? backgroundColor - : backgroundColor.withAlpha(0), + color: + cupertinoHeaderOpacity == 1.0 + ? backgroundColor + : backgroundColor.withAlpha(0), shape: LinearBorder.bottom( side: BorderSide( - color: cupertinoHeaderOpacity == 1.0 - ? const Color(0x4D000000) - : Colors.transparent, + color: + cupertinoHeaderOpacity == 1.0 + ? const Color(0x4D000000) + : Colors.transparent, width: 0.0, ), ), ), - padding: Styles.bodyPadding + - EdgeInsets.only(top: MediaQuery.paddingOf(context).top), + padding: + Styles.bodyPadding + EdgeInsets.only(top: MediaQuery.paddingOf(context).top), child: cupertinoTabSwitcher, ), ), ), ), - SliverPadding( - padding: edgeInsets, - sliver: sliver, - ), + SliverPadding(padding: edgeInsets, sliver: sliver), ], ); } @@ -270,8 +268,7 @@ class _ChallengesBodyState extends ConsumerState<_ChallengesBody> { void initState() { super.initState(); - socketClient = - ref.read(socketPoolProvider).open(Uri(path: '/lobby/socket/v5')); + socketClient = ref.read(socketPoolProvider).open(Uri(path: '/lobby/socket/v5')); _socketSubscription = socketClient.stream.listen((event) { switch (event.topic) { @@ -311,17 +308,15 @@ class _ChallengesBodyState extends ConsumerState<_ChallengesBody> { return challengesAsync.when( data: (challenges) { - final supportedChallenges = challenges - .where((challenge) => challenge.variant.isPlaySupported) - .toList(); + final supportedChallenges = + challenges.where((challenge) => challenge.variant.isPlaySupported).toList(); return SliverList.separated( itemCount: supportedChallenges.length, - separatorBuilder: (context, index) => - const PlatformDivider(height: 1, cupertinoHasLeading: true), + separatorBuilder: + (context, index) => const PlatformDivider(height: 1, cupertinoHasLeading: true), itemBuilder: (context, index) { final challenge = supportedChallenges[index]; - final isMySeek = - UserId.fromUserName(challenge.username) == session?.user.id; + final isMySeek = UserId.fromUserName(challenge.username) == session?.user.id; return CorrespondenceChallengeListItem( challenge: challenge, @@ -330,36 +325,29 @@ class _ChallengesBodyState extends ConsumerState<_ChallengesBody> { name: challenge.username, title: challenge.title, ), - onPressed: isMySeek - ? null - : session == null + onPressed: + isMySeek + ? null + : session == null ? () { - showPlatformSnackbar( - context, - context.l10n.youNeedAnAccountToDoThat, - ); - } + showPlatformSnackbar(context, context.l10n.youNeedAnAccountToDoThat); + } : () { - showConfirmDialog( - context, - title: Text(context.l10n.accept), - isDestructiveAction: true, - onConfirm: (_) { - socketClient.send( - 'joinSeek', - challenge.id.toString(), - ); - }, - ); - }, - onCancel: isMySeek - ? () { - socketClient.send( - 'cancelSeek', - challenge.id.toString(), - ); - } - : null, + showConfirmDialog( + context, + title: Text(context.l10n.accept), + isDestructiveAction: true, + onConfirm: (_) { + socketClient.send('joinSeek', challenge.id.toString()); + }, + ); + }, + onCancel: + isMySeek + ? () { + socketClient.send('cancelSeek', challenge.id.toString()); + } + : null, ); }, ); @@ -369,9 +357,10 @@ class _ChallengesBodyState extends ConsumerState<_ChallengesBody> { child: Center(child: CircularProgressIndicator.adaptive()), ); }, - error: (error, stack) => SliverFillRemaining( - child: Center(child: Text(context.l10n.mobileCustomGameJoinAGame)), - ), + error: + (error, stack) => SliverFillRemaining( + child: Center(child: Text(context.l10n.mobileCustomGameJoinAGame)), + ), ); } } @@ -392,8 +381,8 @@ class _CreateGameBodyState extends ConsumerState<_CreateGameBody> { Widget build(BuildContext context) { final accountAsync = ref.watch(accountProvider); final preferences = ref.watch(gameSetupPreferencesProvider); - final isValidTimeControl = preferences.customTimeSeconds > 0 || - preferences.customIncrementSeconds > 0; + final isValidTimeControl = + preferences.customTimeSeconds > 0 || preferences.customIncrementSeconds > 0; final realTimeSelector = [ Builder( @@ -408,10 +397,7 @@ class _CreateGameBodyState extends ConsumerState<_CreateGameBody> { text: '${context.l10n.minutesPerSide}: ', children: [ TextSpan( - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), text: clockLabelInMinutes(customTimeSeconds), ), ], @@ -421,13 +407,14 @@ class _CreateGameBodyState extends ConsumerState<_CreateGameBody> { value: customTimeSeconds, values: kAvailableTimesInSeconds, labelBuilder: clockLabelInMinutes, - onChange: Theme.of(context).platform == TargetPlatform.iOS - ? (num value) { - setState(() { - customTimeSeconds = value.toInt(); - }); - } - : null, + onChange: + Theme.of(context).platform == TargetPlatform.iOS + ? (num value) { + setState(() { + customTimeSeconds = value.toInt(); + }); + } + : null, onChangeEnd: (num value) { setState(() { customTimeSeconds = value.toInt(); @@ -454,10 +441,7 @@ class _CreateGameBodyState extends ConsumerState<_CreateGameBody> { text: '${context.l10n.incrementInSeconds}: ', children: [ TextSpan( - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), text: customIncrementSeconds.toString(), ), ], @@ -466,13 +450,14 @@ class _CreateGameBodyState extends ConsumerState<_CreateGameBody> { subtitle: NonLinearSlider( value: customIncrementSeconds, values: kAvailableIncrementsInSeconds, - onChange: Theme.of(context).platform == TargetPlatform.iOS - ? (num value) { - setState(() { - customIncrementSeconds = value.toInt(); - }); - } - : null, + onChange: + Theme.of(context).platform == TargetPlatform.iOS + ? (num value) { + setState(() { + customIncrementSeconds = value.toInt(); + }); + } + : null, onChangeEnd: (num value) { setState(() { customIncrementSeconds = value.toInt(); @@ -502,10 +487,7 @@ class _CreateGameBodyState extends ConsumerState<_CreateGameBody> { text: '${context.l10n.daysPerTurn}: ', children: [ TextSpan( - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), text: _daysLabel(daysPerTurn), ), ], @@ -515,13 +497,14 @@ class _CreateGameBodyState extends ConsumerState<_CreateGameBody> { value: daysPerTurn, values: kAvailableDaysPerTurn, labelBuilder: _daysLabel, - onChange: Theme.of(context).platform == TargetPlatform.iOS - ? (num value) { - setState(() { - daysPerTurn = value.toInt(); - }); - } - : null, + onChange: + Theme.of(context).platform == TargetPlatform.iOS + ? (num value) { + setState(() { + daysPerTurn = value.toInt(); + }); + } + : null, onChangeEnd: (num value) { setState(() { daysPerTurn = value.toInt(); @@ -540,169 +523,151 @@ class _CreateGameBodyState extends ConsumerState<_CreateGameBody> { return accountAsync.when( data: (account) { - final timeControl = account == null - ? TimeControl.realTime - : preferences.customTimeControl; + final timeControl = account == null ? TimeControl.realTime : preferences.customTimeControl; - final userPerf = account?.perfs[timeControl == TimeControl.realTime - ? preferences.perfFromCustom - : Perf.correspondence]; + final userPerf = + account?.perfs[timeControl == TimeControl.realTime + ? preferences.perfFromCustom + : Perf.correspondence]; return SliverPadding( padding: Styles.sectionBottomPadding, sliver: SliverList( - delegate: SliverChildListDelegate( - [ - if (account != null) - PlatformListTile( - harmonizeCupertinoTitleStyle: true, - title: Text(context.l10n.timeControl), - trailing: AdaptiveTextButton( - onPressed: () { - showChoicePicker( - context, - choices: [ - TimeControl.realTime, - TimeControl.correspondence, - ], - selectedItem: preferences.customTimeControl, - labelBuilder: (TimeControl timeControl) => Text( - timeControl == TimeControl.realTime - ? context.l10n.realTime - : context.l10n.correspondence, - ), - onSelectedItemChanged: (TimeControl value) { - ref - .read(gameSetupPreferencesProvider.notifier) - .setCustomTimeControl(value); - }, - ); - }, - child: Text( - preferences.customTimeControl == TimeControl.realTime - ? context.l10n.realTime - : context.l10n.correspondence, - ), - ), - ), - if (timeControl == TimeControl.realTime) - ...realTimeSelector - else - ...correspondenceSelector, + delegate: SliverChildListDelegate([ + if (account != null) PlatformListTile( harmonizeCupertinoTitleStyle: true, - title: Text(context.l10n.variant), + title: Text(context.l10n.timeControl), trailing: AdaptiveTextButton( onPressed: () { showChoicePicker( context, - choices: [Variant.standard, Variant.chess960], - selectedItem: preferences.customVariant, - labelBuilder: (Variant variant) => Text(variant.label), - onSelectedItemChanged: (Variant variant) { + choices: [TimeControl.realTime, TimeControl.correspondence], + selectedItem: preferences.customTimeControl, + labelBuilder: + (TimeControl timeControl) => Text( + timeControl == TimeControl.realTime + ? context.l10n.realTime + : context.l10n.correspondence, + ), + onSelectedItemChanged: (TimeControl value) { ref .read(gameSetupPreferencesProvider.notifier) - .setCustomVariant(variant); + .setCustomTimeControl(value); }, ); }, - child: Text(preferences.customVariant.label), - ), - ), - ExpandedSection( - expand: preferences.customRated == false, - child: PlatformListTile( - harmonizeCupertinoTitleStyle: true, - title: Text(context.l10n.side), - trailing: AdaptiveTextButton( - onPressed: null, - child: Text(SideChoice.random.label(context.l10n)), + child: Text( + preferences.customTimeControl == TimeControl.realTime + ? context.l10n.realTime + : context.l10n.correspondence, ), ), ), - if (account != null) - PlatformListTile( - harmonizeCupertinoTitleStyle: true, - title: Text(context.l10n.rated), - trailing: Switch.adaptive( - applyCupertinoTheme: true, - value: preferences.customRated, - onChanged: (bool value) { - ref - .read(gameSetupPreferencesProvider.notifier) - .setCustomRated(value); + if (timeControl == TimeControl.realTime) + ...realTimeSelector + else + ...correspondenceSelector, + PlatformListTile( + harmonizeCupertinoTitleStyle: true, + title: Text(context.l10n.variant), + trailing: AdaptiveTextButton( + onPressed: () { + showChoicePicker( + context, + choices: [Variant.standard, Variant.chess960], + selectedItem: preferences.customVariant, + labelBuilder: (Variant variant) => Text(variant.label), + onSelectedItemChanged: (Variant variant) { + ref.read(gameSetupPreferencesProvider.notifier).setCustomVariant(variant); }, - ), + ); + }, + child: Text(preferences.customVariant.label), + ), + ), + ExpandedSection( + expand: preferences.customRated == false, + child: PlatformListTile( + harmonizeCupertinoTitleStyle: true, + title: Text(context.l10n.side), + trailing: AdaptiveTextButton( + onPressed: null, + child: Text(SideChoice.random.label(context.l10n)), ), - if (userPerf != null) - PlayRatingRange( - perf: userPerf, - ratingDelta: preferences.customRatingDelta, - onRatingDeltaChange: (int subtract, int add) { - ref - .read(gameSetupPreferencesProvider.notifier) - .setCustomRatingRange(subtract, add); + ), + ), + if (account != null) + PlatformListTile( + harmonizeCupertinoTitleStyle: true, + title: Text(context.l10n.rated), + trailing: Switch.adaptive( + applyCupertinoTheme: true, + value: preferences.customRated, + onChanged: (bool value) { + ref.read(gameSetupPreferencesProvider.notifier).setCustomRated(value); }, ), - const SizedBox(height: 20), - FutureBuilder( - future: _pendingCreateGame, - builder: (context, snapshot) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: FatButton( - semanticsLabel: context.l10n.createAGame, - onPressed: timeControl == TimeControl.realTime - ? isValidTimeControl - ? () { + ), + if (userPerf != null) + PlayRatingRange( + perf: userPerf, + ratingDelta: preferences.customRatingDelta, + onRatingDeltaChange: (int subtract, int add) { + ref + .read(gameSetupPreferencesProvider.notifier) + .setCustomRatingRange(subtract, add); + }, + ), + const SizedBox(height: 20), + FutureBuilder( + future: _pendingCreateGame, + builder: (context, snapshot) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: FatButton( + semanticsLabel: context.l10n.createAGame, + onPressed: + timeControl == TimeControl.realTime + ? isValidTimeControl + ? () { pushPlatformRoute( context, rootNavigator: true, builder: (BuildContext context) { return GameScreen( - seek: GameSeek.custom( - preferences, - account, - ), + seek: GameSeek.custom(preferences, account), ); }, ); } - : null - : snapshot.connectionState == - ConnectionState.waiting - ? null - : () async { - _pendingCreateGame = ref - .read(createGameServiceProvider) - .newCorrespondenceGame( - GameSeek.correspondence( - preferences, - account, - ), - ); + : null + : snapshot.connectionState == ConnectionState.waiting + ? null + : () async { + _pendingCreateGame = ref + .read(createGameServiceProvider) + .newCorrespondenceGame( + GameSeek.correspondence(preferences, account), + ); - await _pendingCreateGame; - widget.setViewMode(_ViewMode.challenges); - }, - child: - Text(context.l10n.createAGame, style: Styles.bold), - ), - ); - }, - ), - ], - ), + await _pendingCreateGame; + widget.setViewMode(_ViewMode.challenges); + }, + child: Text(context.l10n.createAGame, style: Styles.bold), + ), + ); + }, + ), + ]), ), ); }, - loading: () => const SliverFillRemaining( - child: Center(child: CircularProgressIndicator.adaptive()), - ), - error: (error, stackTrace) => const SliverFillRemaining( - child: Center( - child: Text('Could not load account data'), - ), - ), + loading: + () => + const SliverFillRemaining(child: Center(child: CircularProgressIndicator.adaptive())), + error: + (error, stackTrace) => + const SliverFillRemaining(child: Center(child: Text('Could not load account data'))), ); } } diff --git a/lib/src/view/play/create_game_options.dart b/lib/src/view/play/create_game_options.dart index c00a745c86..c5c1bdb528 100644 --- a/lib/src/view/play/create_game_options.dart +++ b/lib/src/view/play/create_game_options.dart @@ -18,37 +18,38 @@ class CreateGameOptions extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isOnline = - ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? false; + final isOnline = ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? false; return Column( children: [ _Section( children: [ _CreateGamePlatformButton( - onTap: isOnline - ? () { - ref.invalidate(accountProvider); - pushPlatformRoute( - context, - title: context.l10n.custom, - builder: (_) => const CreateCustomGameScreen(), - ); - } - : null, + onTap: + isOnline + ? () { + ref.invalidate(accountProvider); + pushPlatformRoute( + context, + title: context.l10n.custom, + builder: (_) => const CreateCustomGameScreen(), + ); + } + : null, icon: Icons.tune, label: context.l10n.custom, ), _CreateGamePlatformButton( - onTap: isOnline - ? () { - pushPlatformRoute( - context, - title: context.l10n.onlineBots, - builder: (_) => const OnlineBotsScreen(), - ); - } - : null, + onTap: + isOnline + ? () { + pushPlatformRoute( + context, + title: context.l10n.onlineBots, + builder: (_) => const OnlineBotsScreen(), + ); + } + : null, icon: Icons.computer, label: context.l10n.onlineBots, ), @@ -76,35 +77,23 @@ class CreateGameOptions extends ConsumerWidget { } class _Section extends StatelessWidget { - const _Section({ - required this.children, - }); + const _Section({required this.children}); final List children; @override Widget build(BuildContext context) { return Theme.of(context).platform == TargetPlatform.iOS - ? ListSection( - hasLeading: true, - children: children, - ) + ? ListSection(hasLeading: true, children: children) : Padding( - padding: Styles.horizontalBodyPadding.add(Styles.sectionTopPadding), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: children, - ), - ); + padding: Styles.horizontalBodyPadding.add(Styles.sectionTopPadding), + child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: children), + ); } } class _CreateGamePlatformButton extends StatelessWidget { - const _CreateGamePlatformButton({ - required this.icon, - required this.label, - required this.onTap, - }); + const _CreateGamePlatformButton({required this.icon, required this.label, required this.onTap}); final IconData icon; @@ -116,18 +105,11 @@ class _CreateGamePlatformButton extends StatelessWidget { Widget build(BuildContext context) { return Theme.of(context).platform == TargetPlatform.iOS ? PlatformListTile( - leading: Icon( - icon, - size: 28, - ), - trailing: const CupertinoListTileChevron(), - title: Text(label, style: Styles.mainListTileTitle), - onTap: onTap, - ) - : ElevatedButton.icon( - onPressed: onTap, - icon: Icon(icon), - label: Text(label), - ); + leading: Icon(icon, size: 28), + trailing: const CupertinoListTileChevron(), + title: Text(label, style: Styles.mainListTileTitle), + onTap: onTap, + ) + : ElevatedButton.icon(onPressed: onTap, icon: Icon(icon), label: Text(label)); } } diff --git a/lib/src/view/play/ongoing_games_screen.dart b/lib/src/view/play/ongoing_games_screen.dart index 7856985951..d9843a22a1 100644 --- a/lib/src/view/play/ongoing_games_screen.dart +++ b/lib/src/view/play/ongoing_games_screen.dart @@ -17,26 +17,18 @@ class OngoingGamesScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return ConsumerPlatformWidget( - ref: ref, - androidBuilder: _buildAndroid, - iosBuilder: _buildIos, - ); + return ConsumerPlatformWidget(ref: ref, androidBuilder: _buildAndroid, iosBuilder: _buildIos); } Widget _buildIos(BuildContext context, WidgetRef ref) { - return CupertinoPageScaffold( - navigationBar: const CupertinoNavigationBar(), - child: _Body(), - ); + return CupertinoPageScaffold(navigationBar: const CupertinoNavigationBar(), child: _Body()); } Widget _buildAndroid(BuildContext context, WidgetRef ref) { final ongoingGames = ref.watch(ongoingGamesProvider); return Scaffold( appBar: ongoingGames.maybeWhen( - data: (data) => - AppBar(title: Text(context.l10n.nbGamesInPlay(data.length))), + data: (data) => AppBar(title: Text(context.l10n.nbGamesInPlay(data.length))), orElse: () => AppBar(title: const SizedBox.shrink()), ), body: _Body(), @@ -49,12 +41,13 @@ class _Body extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final ongoingGames = ref.watch(ongoingGamesProvider); return ongoingGames.maybeWhen( - data: (data) => ListView( - children: [ - const SizedBox(height: 8.0), - ...data.map((game) => OngoingGamePreview(game: game)), - ], - ), + data: + (data) => ListView( + children: [ + const SizedBox(height: 8.0), + ...data.map((game) => OngoingGamePreview(game: game)), + ], + ), orElse: () => const SizedBox.shrink(), ); } @@ -84,17 +77,10 @@ class OngoingGamePreview extends ConsumerWidget { Icon( game.perf.icon, size: 34, - color: DefaultTextStyle.of(context) - .style - .color - ?.withValues(alpha: 0.6), + color: DefaultTextStyle.of(context).style.color?.withValues(alpha: 0.6), ), if (game.secondsLeft != null && game.secondsLeft! > 0) - Text( - game.isMyTurn - ? context.l10n.yourTurn - : context.l10n.waitingForOpponent, - ), + Text(game.isMyTurn ? context.l10n.yourTurn : context.l10n.waitingForOpponent), if (game.isMyTurn && game.secondsLeft != null) Text( timeago.format( @@ -108,12 +94,13 @@ class OngoingGamePreview extends ConsumerWidget { pushPlatformRoute( context, rootNavigator: true, - builder: (context) => GameScreen( - initialGameId: game.fullId, - loadingFen: game.fen, - loadingOrientation: game.orientation, - loadingLastMove: game.lastMove, - ), + builder: + (context) => GameScreen( + initialGameId: game.fullId, + loadingFen: game.fen, + loadingOrientation: game.orientation, + loadingLastMove: game.lastMove, + ), ).then((_) { if (context.mounted) { ref.invalidate(ongoingGamesProvider); diff --git a/lib/src/view/play/online_bots_screen.dart b/lib/src/view/play/online_bots_screen.dart index 356b555380..3961253ee8 100644 --- a/lib/src/view/play/online_bots_screen.dart +++ b/lib/src/view/play/online_bots_screen.dart @@ -24,23 +24,14 @@ import 'package:url_launcher/url_launcher.dart'; // TODO(#796): remove when Leela featured bots special challenges are ready // https://github.com/lichess-org/mobile/issues/796 -const _disabledBots = { - 'leelaknightodds', - 'leelaqueenodds', - 'leelaqueenforknight', - 'leelarookodds', -}; +const _disabledBots = {'leelaknightodds', 'leelaqueenodds', 'leelaqueenforknight', 'leelarookodds'}; -final _onlineBotsProvider = - FutureProvider.autoDispose>((ref) async { +final _onlineBotsProvider = FutureProvider.autoDispose>((ref) async { return ref.withClientCacheFor( (client) => UserRepository(client).getOnlineBots().then( - (bots) => bots - .whereNot( - (bot) => _disabledBots.contains(bot.id.value.toLowerCase()), - ) - .toIList(), - ), + (bots) => + bots.whereNot((bot) => _disabledBots.contains(bot.id.value.toLowerCase())).toIList(), + ), const Duration(hours: 5), ); }); @@ -51,9 +42,7 @@ class OnlineBotsScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.onlineBots), - ), + appBar: PlatformAppBar(title: Text(context.l10n.onlineBots)), body: _Body(), ); } @@ -65,128 +54,111 @@ class _Body extends ConsumerWidget { final onlineBots = ref.watch(_onlineBotsProvider); return onlineBots.when( - data: (data) => ListView.separated( - itemCount: data.length, - separatorBuilder: (context, index) => - Theme.of(context).platform == TargetPlatform.iOS - ? Divider( - height: 0, - thickness: 0, - // equals to _kNotchedPaddingWithoutLeading constant - // See: https://github.com/flutter/flutter/blob/89ea49204b37523a16daec53b5e6fae70995929d/packages/flutter/lib/src/cupertino/list_tile.dart#L24 - indent: 28, - color: CupertinoDynamicColor.resolve( - CupertinoColors.separator, - context, - ), - ) - : const SizedBox.shrink(), - itemBuilder: (context, index) { - final bot = data[index]; - return PlatformListTile( - isThreeLine: true, - trailing: Theme.of(context).platform == TargetPlatform.iOS - ? Row( - children: [ - if (bot.verified == true) ...[ - const Icon(Icons.verified_outlined), - const SizedBox(width: 5), - ], - const CupertinoListTileChevron(), - ], - ) - : bot.verified == true - ? const Icon(Icons.verified_outlined) - : null, - title: Padding( - padding: const EdgeInsets.only(right: 5.0), - child: UserFullNameWidget( - user: bot.lightUser, - style: const TextStyle(fontWeight: FontWeight.w600), - ), - ), - subtitle: Column( - children: [ - Row( - children: - [Perf.blitz, Perf.rapid, Perf.classical].map((perf) { - final rating = bot.perfs[perf]?.rating; - final nbGames = bot.perfs[perf]?.games ?? 0; - return Padding( - padding: const EdgeInsets.only( - right: 16.0, - top: 4.0, - bottom: 4.0, - ), - child: Row( - children: [ - Icon(perf.icon, size: 16), - const SizedBox(width: 4.0), - if (rating != null && nbGames > 0) - Text( - '$rating', - style: const TextStyle( - color: Colors.grey, - ), - ) - else - const Text(' - '), - ], - ), - ); - }).toList(), + data: + (data) => ListView.separated( + itemCount: data.length, + separatorBuilder: + (context, index) => + Theme.of(context).platform == TargetPlatform.iOS + ? Divider( + height: 0, + thickness: 0, + // equals to _kNotchedPaddingWithoutLeading constant + // See: https://github.com/flutter/flutter/blob/89ea49204b37523a16daec53b5e6fae70995929d/packages/flutter/lib/src/cupertino/list_tile.dart#L24 + indent: 28, + color: CupertinoDynamicColor.resolve(CupertinoColors.separator, context), + ) + : const SizedBox.shrink(), + itemBuilder: (context, index) { + final bot = data[index]; + return PlatformListTile( + isThreeLine: true, + trailing: + Theme.of(context).platform == TargetPlatform.iOS + ? Row( + children: [ + if (bot.verified == true) ...[ + const Icon(Icons.verified_outlined), + const SizedBox(width: 5), + ], + const CupertinoListTileChevron(), + ], + ) + : bot.verified == true + ? const Icon(Icons.verified_outlined) + : null, + title: Padding( + padding: const EdgeInsets.only(right: 5.0), + child: UserFullNameWidget( + user: bot.lightUser, + style: const TextStyle(fontWeight: FontWeight.w600), + ), ), - Text( - bot.profile?.bio ?? '', - maxLines: 2, - overflow: TextOverflow.ellipsis, + subtitle: Column( + children: [ + Row( + children: + [Perf.blitz, Perf.rapid, Perf.classical].map((perf) { + final rating = bot.perfs[perf]?.rating; + final nbGames = bot.perfs[perf]?.games ?? 0; + return Padding( + padding: const EdgeInsets.only(right: 16.0, top: 4.0, bottom: 4.0), + child: Row( + children: [ + Icon(perf.icon, size: 16), + const SizedBox(width: 4.0), + if (rating != null && nbGames > 0) + Text('$rating', style: const TextStyle(color: Colors.grey)) + else + const Text(' - '), + ], + ), + ); + }).toList(), + ), + Text(bot.profile?.bio ?? '', maxLines: 2, overflow: TextOverflow.ellipsis), + ], ), - ], - ), - onTap: () { - final session = ref.read(authSessionProvider); - if (session == null) { - showPlatformSnackbar( - context, - context.l10n.challengeRegisterToSendChallenges, - type: SnackBarType.error, - ); - return; - } - pushPlatformRoute( - context, - title: context.l10n.challengeChallengesX(bot.lightUser.name), - builder: (context) => CreateChallengeScreen(bot.lightUser), - ); - }, - onLongPress: () { - showAdaptiveBottomSheet( - context: context, - useRootNavigator: true, - isDismissible: true, - isScrollControlled: true, - showDragHandle: true, - builder: (context) => _ContextMenu(bot: bot), + onTap: () { + final session = ref.read(authSessionProvider); + if (session == null) { + showPlatformSnackbar( + context, + context.l10n.challengeRegisterToSendChallenges, + type: SnackBarType.error, + ); + return; + } + pushPlatformRoute( + context, + title: context.l10n.challengeChallengesX(bot.lightUser.name), + builder: (context) => CreateChallengeScreen(bot.lightUser), + ); + }, + onLongPress: () { + showAdaptiveBottomSheet( + context: context, + useRootNavigator: true, + isDismissible: true, + isScrollControlled: true, + showDragHandle: true, + builder: (context) => _ContextMenu(bot: bot), + ); + }, ); }, - ); - }, - ), + ), loading: () => const Center(child: CircularProgressIndicator.adaptive()), error: (e, s) { debugPrint('Could not load bots: $e'); - return FullScreenRetryRequest( - onRetry: () => ref.refresh(_onlineBotsProvider), - ); + return FullScreenRetryRequest(onRetry: () => ref.refresh(_onlineBotsProvider)); }, ); } } class _ContextMenu extends ConsumerWidget { - const _ContextMenu({ - required this.bot, - }); + const _ContextMenu({required this.bot}); final User bot; @@ -195,16 +167,11 @@ class _ContextMenu extends ConsumerWidget { return BottomSheetScrollableContainer( children: [ Padding( - padding: Styles.horizontalBodyPadding.add( - const EdgeInsets.only(bottom: 16.0), - ), + padding: Styles.horizontalBodyPadding.add(const EdgeInsets.only(bottom: 16.0)), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - UserFullNameWidget( - user: bot.lightUser, - style: Styles.title, - ), + UserFullNameWidget(user: bot.lightUser, style: Styles.title), const SizedBox(height: 8.0), if (bot.profile?.bio != null) Linkify( @@ -213,22 +180,16 @@ class _ContextMenu extends ConsumerWidget { final username = link.originText.substring(1); pushPlatformRoute( context, - builder: (ctx) => UserScreen( - user: LightUser( - id: UserId.fromUserName(username), - name: username, - ), - ), + builder: + (ctx) => UserScreen( + user: LightUser(id: UserId.fromUserName(username), name: username), + ), ); } else { launchUrl(Uri.parse(link.url)); } }, - linkifiers: const [ - UrlLinkifier(), - EmailLinkifier(), - UserTagLinkifier(), - ], + linkifiers: const [UrlLinkifier(), EmailLinkifier(), UserTagLinkifier()], text: bot.profile!.bio!, maxLines: 20, overflow: TextOverflow.ellipsis, @@ -243,12 +204,7 @@ class _ContextMenu extends ConsumerWidget { const PlatformDivider(), BottomSheetContextMenuAction( onPressed: () { - pushPlatformRoute( - context, - builder: (context) => UserScreen( - user: bot.lightUser, - ), - ); + pushPlatformRoute(context, builder: (context) => UserScreen(user: bot.lightUser)); }, icon: Icons.person, child: Text(context.l10n.profile), diff --git a/lib/src/view/play/play_screen.dart b/lib/src/view/play/play_screen.dart index 5db3f41f9c..d206c3052c 100644 --- a/lib/src/view/play/play_screen.dart +++ b/lib/src/view/play/play_screen.dart @@ -13,17 +13,12 @@ class PlayScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.play), - ), + appBar: PlatformAppBar(title: Text(context.l10n.play)), body: const SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Padding( - padding: Styles.horizontalBodyPadding, - child: QuickGameButton(), - ), + Padding(padding: Styles.horizontalBodyPadding, child: QuickGameButton()), CreateGameOptions(), ], ), diff --git a/lib/src/view/play/quick_game_button.dart b/lib/src/view/play/quick_game_button.dart index c587b32aee..7c20db1ecf 100644 --- a/lib/src/view/play/quick_game_button.dart +++ b/lib/src/view/play/quick_game_button.dart @@ -21,8 +21,7 @@ class QuickGameButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final playPrefs = ref.watch(gameSetupPreferencesProvider); final session = ref.watch(authSessionProvider); - final isOnline = - ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? false; + final isOnline = ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? false; return Row( children: [ @@ -51,9 +50,7 @@ class QuickGameButton extends ConsumerWidget { context: context, isScrollControlled: true, showDragHandle: true, - constraints: BoxConstraints( - maxHeight: screenHeight - (screenHeight / 10), - ), + constraints: BoxConstraints(maxHeight: screenHeight - (screenHeight / 10)), builder: (BuildContext context) { return TimeControlModal( value: playPrefs.quickPairingTimeIncrement, @@ -68,52 +65,53 @@ class QuickGameButton extends ConsumerWidget { }, ), ), - if (Theme.of(context).platform == TargetPlatform.android) - const SizedBox(width: 8.0), + if (Theme.of(context).platform == TargetPlatform.android) const SizedBox(width: 8.0), Expanded( flex: kFlexGoldenRatio, - child: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoButton.tinted( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 16.0, - ), - onPressed: isOnline - ? () { - pushPlatformRoute( - context, - rootNavigator: true, - builder: (_) => GameScreen( - seek: GameSeek.fastPairing( - playPrefs.quickPairingTimeIncrement, - session, - ), - ), - ); - } - : null, - child: Text(context.l10n.play, style: Styles.bold), - ) - : FilledButton( - onPressed: isOnline - ? () { - pushPlatformRoute( - context, - rootNavigator: true, - builder: (_) => GameScreen( - seek: GameSeek.fastPairing( - playPrefs.quickPairingTimeIncrement, - session, - ), - ), - ); - } - : null, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), + child: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoButton.tinted( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), + onPressed: + isOnline + ? () { + pushPlatformRoute( + context, + rootNavigator: true, + builder: + (_) => GameScreen( + seek: GameSeek.fastPairing( + playPrefs.quickPairingTimeIncrement, + session, + ), + ), + ); + } + : null, child: Text(context.l10n.play, style: Styles.bold), + ) + : FilledButton( + onPressed: + isOnline + ? () { + pushPlatformRoute( + context, + rootNavigator: true, + builder: + (_) => GameScreen( + seek: GameSeek.fastPairing( + playPrefs.quickPairingTimeIncrement, + session, + ), + ), + ); + } + : null, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text(context.l10n.play, style: Styles.bold), + ), ), - ), ), ], ); diff --git a/lib/src/view/play/quick_game_matrix.dart b/lib/src/view/play/quick_game_matrix.dart index f8753b27d0..d7aaff84ab 100644 --- a/lib/src/view/play/quick_game_matrix.dart +++ b/lib/src/view/play/quick_game_matrix.dart @@ -21,9 +21,8 @@ class QuickGameMatrix extends StatelessWidget { @override Widget build(BuildContext context) { final brightness = Theme.of(context).brightness; - final logoColor = brightness == Brightness.light - ? const Color(0x0F000000) - : const Color(0x80FFFFFF); + final logoColor = + brightness == Brightness.light ? const Color(0x0F000000) : const Color(0x80FFFFFF); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -41,35 +40,18 @@ class QuickGameMatrix extends StatelessWidget { child: const Column( children: [ _SectionChoices( - choices: [ - TimeIncrement(60, 0), - TimeIncrement(120, 1), - TimeIncrement(180, 0), - ], + choices: [TimeIncrement(60, 0), TimeIncrement(120, 1), TimeIncrement(180, 0)], ), SizedBox(height: _kMatrixSpacing), _SectionChoices( - choices: [ - TimeIncrement(180, 2), - TimeIncrement(300, 0), - TimeIncrement(300, 3), - ], + choices: [TimeIncrement(180, 2), TimeIncrement(300, 0), TimeIncrement(300, 3)], ), SizedBox(height: _kMatrixSpacing), _SectionChoices( - choices: [ - TimeIncrement(600, 0), - TimeIncrement(600, 5), - TimeIncrement(900, 10), - ], + choices: [TimeIncrement(600, 0), TimeIncrement(600, 5), TimeIncrement(900, 10)], ), SizedBox(height: _kMatrixSpacing), - _SectionChoices( - choices: [ - TimeIncrement(1800, 0), - TimeIncrement(1800, 20), - ], - ), + _SectionChoices(choices: [TimeIncrement(1800, 0), TimeIncrement(1800, 20)]), ], ), ), @@ -86,41 +68,37 @@ class _SectionChoices extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final session = ref.watch(authSessionProvider); - final isOnline = - ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? false; - final choiceWidgets = choices - .mapIndexed((index, choice) { - return [ - Expanded( - child: _ChoiceChip( - key: ValueKey(choice), - label: Text( - choice.display, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 20.0, + final isOnline = ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? false; + final choiceWidgets = + choices + .mapIndexed((index, choice) { + return [ + Expanded( + child: _ChoiceChip( + key: ValueKey(choice), + label: Text( + choice.display, + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0), + ), + speed: choice.speed, + onTap: + isOnline + ? () { + pushPlatformRoute( + context, + rootNavigator: true, + builder: + (_) => GameScreen(seek: GameSeek.fastPairing(choice, session)), + ); + } + : null, ), ), - speed: choice.speed, - onTap: isOnline - ? () { - pushPlatformRoute( - context, - rootNavigator: true, - builder: (_) => GameScreen( - seek: GameSeek.fastPairing(choice, session), - ), - ); - } - : null, - ), - ), - if (index < choices.length - 1) - const SizedBox(width: _kMatrixSpacing), - ]; - }) - .flattened - .toList(); + if (index < choices.length - 1) const SizedBox(width: _kMatrixSpacing), + ]; + }) + .flattened + .toList(); return IntrinsicHeight( child: Row( @@ -132,14 +110,15 @@ class _SectionChoices extends ConsumerWidget { Expanded( child: _ChoiceChip( label: Text(context.l10n.custom), - onTap: isOnline - ? () { - pushPlatformRoute( - context, - builder: (_) => const CreateCustomGameScreen(), - ); - } - : null, + onTap: + isOnline + ? () { + pushPlatformRoute( + context, + builder: (_) => const CreateCustomGameScreen(), + ); + } + : null, ), ), ], @@ -150,12 +129,7 @@ class _SectionChoices extends ConsumerWidget { } class _ChoiceChip extends StatefulWidget { - const _ChoiceChip({ - required this.label, - this.speed, - required this.onTap, - super.key, - }); + const _ChoiceChip({required this.label, this.speed, required this.onTap, super.key}); final Widget label; final Speed? speed; @@ -168,9 +142,10 @@ class _ChoiceChip extends StatefulWidget { class _ChoiceChipState extends State<_ChoiceChip> { @override Widget build(BuildContext context) { - final cardColor = Theme.of(context).platform == TargetPlatform.iOS - ? Styles.cupertinoCardColor.resolveFrom(context).withValues(alpha: 0.7) - : Theme.of(context).colorScheme.surfaceContainer.withValues(alpha: 0.7); + final cardColor = + Theme.of(context).platform == TargetPlatform.iOS + ? Styles.cupertinoCardColor.resolveFrom(context).withValues(alpha: 0.7) + : Theme.of(context).colorScheme.surfaceContainer.withValues(alpha: 0.7); return Opacity( opacity: widget.onTap != null ? 1.0 : 0.5, diff --git a/lib/src/view/play/time_control_modal.dart b/lib/src/view/play/time_control_modal.dart index 4abe8ea2a9..5ff39f8fa2 100644 --- a/lib/src/view/play/time_control_modal.dart +++ b/lib/src/view/play/time_control_modal.dart @@ -34,10 +34,7 @@ class TimeControlModal extends ConsumerWidget { return BottomSheetScrollableContainer( padding: Styles.bodyPadding, children: [ - Text( - context.l10n.timeControl, - style: Styles.title, - ), + Text(context.l10n.timeControl, style: Styles.title), const SizedBox(height: 26.0), _SectionChoices( value, @@ -47,10 +44,7 @@ class TimeControlModal extends ConsumerWidget { const TimeIncrement(60, 1), const TimeIncrement(120, 1), ], - title: const _SectionTitle( - title: 'Bullet', - icon: LichessIcons.bullet, - ), + title: const _SectionTitle(title: 'Bullet', icon: LichessIcons.bullet), onSelected: onSelected, ), const SizedBox(height: 20.0), @@ -62,10 +56,7 @@ class TimeControlModal extends ConsumerWidget { TimeIncrement(300, 0), TimeIncrement(300, 3), ], - title: const _SectionTitle( - title: 'Blitz', - icon: LichessIcons.blitz, - ), + title: const _SectionTitle(title: 'Blitz', icon: LichessIcons.blitz), onSelected: onSelected, ), const SizedBox(height: 20.0), @@ -77,10 +68,7 @@ class TimeControlModal extends ConsumerWidget { TimeIncrement(900, 0), TimeIncrement(900, 10), ], - title: const _SectionTitle( - title: 'Rapid', - icon: LichessIcons.rapid, - ), + title: const _SectionTitle(title: 'Rapid', icon: LichessIcons.rapid), onSelected: onSelected, ), const SizedBox(height: 20.0), @@ -92,20 +80,14 @@ class TimeControlModal extends ConsumerWidget { TimeIncrement(1800, 20), TimeIncrement(3600, 0), ], - title: const _SectionTitle( - title: 'Classical', - icon: LichessIcons.classical, - ), + title: const _SectionTitle(title: 'Classical', icon: LichessIcons.classical), onSelected: onSelected, ), const SizedBox(height: 20.0), Theme( data: Theme.of(context).copyWith(dividerColor: Colors.transparent), child: ExpansionTile( - title: _SectionTitle( - title: context.l10n.custom, - icon: Icons.tune, - ), + title: _SectionTitle(title: context.l10n.custom, icon: Icons.tune), tilePadding: EdgeInsets.zero, minTileHeight: 0, children: [ @@ -124,23 +106,20 @@ class TimeControlModal extends ConsumerWidget { value: custom.time, values: kAvailableTimesInSeconds, labelBuilder: clockLabelInMinutes, - onChange: Theme.of(context).platform == - TargetPlatform.iOS - ? (num value) { - setState(() { - custom = TimeIncrement( - value.toInt(), - custom.increment, - ); - }); - } - : null, + onChange: + Theme.of(context).platform == TargetPlatform.iOS + ? (num value) { + setState(() { + custom = TimeIncrement( + value.toInt(), + custom.increment, + ); + }); + } + : null, onChangeEnd: (num value) { setState(() { - custom = TimeIncrement( - value.toInt(), - custom.increment, - ); + custom = TimeIncrement(value.toInt(), custom.increment); }); }, ), @@ -150,8 +129,7 @@ class TimeControlModal extends ConsumerWidget { child: Center( child: Text( custom.display, - style: - Styles.timeControl.merge(Styles.bold), + style: Styles.timeControl.merge(Styles.bold), ), ), ), @@ -159,23 +137,17 @@ class TimeControlModal extends ConsumerWidget { child: NonLinearSlider( value: custom.increment, values: kAvailableIncrementsInSeconds, - onChange: Theme.of(context).platform == - TargetPlatform.iOS - ? (num value) { - setState(() { - custom = TimeIncrement( - custom.time, - value.toInt(), - ); - }); - } - : null, + onChange: + Theme.of(context).platform == TargetPlatform.iOS + ? (num value) { + setState(() { + custom = TimeIncrement(custom.time, value.toInt()); + }); + } + : null, onChangeEnd: (num value) { setState(() { - custom = TimeIncrement( - custom.time, - value.toInt(), - ); + custom = TimeIncrement(custom.time, value.toInt()); }); }, ), @@ -183,14 +155,9 @@ class TimeControlModal extends ConsumerWidget { ], ), SecondaryButton( - onPressed: custom.isInfinite - ? null - : () => onSelected(custom), + onPressed: custom.isInfinite ? null : () => onSelected(custom), semanticsLabel: 'OK', - child: Text( - context.l10n.mobileOkButton, - style: Styles.bold, - ), + child: Text(context.l10n.mobileOkButton, style: Styles.bold), ), ], ); @@ -221,46 +188,38 @@ class _SectionChoices extends StatelessWidget { @override Widget build(BuildContext context) { - final choiceWidgets = choices - .mapIndexed((index, choice) { - return [ - Expanded( - child: _ChoiceChip( - key: ValueKey(choice), - label: Text(choice.display, style: Styles.bold), - selected: selected == choice, - onSelected: (bool selected) { - if (selected) onSelected(choice); - }, - ), - ), - if (index < choices.length - 1) const SizedBox(width: 10), - ]; - }) - .flattened - .toList(); + final choiceWidgets = + choices + .mapIndexed((index, choice) { + return [ + Expanded( + child: _ChoiceChip( + key: ValueKey(choice), + label: Text(choice.display, style: Styles.bold), + selected: selected == choice, + onSelected: (bool selected) { + if (selected) onSelected(choice); + }, + ), + ), + if (index < choices.length - 1) const SizedBox(width: 10), + ]; + }) + .flattened + .toList(); if (choices.length < 4) { final placeHolders = [ const [SizedBox(width: 10)], for (int i = choices.length; i < 4; i++) - [ - const Expanded(child: SizedBox(width: 10)), - if (i < 3) const SizedBox(width: 10), - ], + [const Expanded(child: SizedBox(width: 10)), if (i < 3) const SizedBox(width: 10)], ]; choiceWidgets.addAll(placeHolders.flattened); } return Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [ - title, - const SizedBox(height: 10), - Row( - children: choiceWidgets, - ), - ], + children: [title, const SizedBox(height: 10), Row(children: choiceWidgets)], ); } } @@ -281,36 +240,24 @@ class _ChoiceChip extends StatelessWidget { Widget build(BuildContext context) { return Container( decoration: BoxDecoration( - color: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoColors.secondarySystemGroupedBackground - .resolveFrom(context) - : Theme.of(context).colorScheme.surfaceContainerHighest, + color: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoColors.secondarySystemGroupedBackground.resolveFrom(context) + : Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: const BorderRadius.all(Radius.circular(5.0)), - border: selected - ? Border.fromBorderSide( - BorderSide( - color: Theme.of(context).colorScheme.primary, - width: 2.0, - ), - ) - : const Border.fromBorderSide( - BorderSide( - color: Colors.transparent, - width: 2.0, - ), - ), + border: + selected + ? Border.fromBorderSide( + BorderSide(color: Theme.of(context).colorScheme.primary, width: 2.0), + ) + : const Border.fromBorderSide(BorderSide(color: Colors.transparent, width: 2.0)), ), child: AdaptiveInkWell( borderRadius: const BorderRadius.all(Radius.circular(5.0)), onTap: () => onSelected(true), child: Padding( padding: const EdgeInsets.symmetric(vertical: 10.0), - child: Center( - child: DefaultTextStyle.merge( - style: Styles.timeControl, - child: label, - ), - ), + child: Center(child: DefaultTextStyle.merge(style: Styles.timeControl, child: label)), ), ), ); @@ -326,17 +273,12 @@ class _SectionTitle extends StatelessWidget { @override Widget build(BuildContext context) { return IconTheme( - data: CupertinoIconThemeData( - color: CupertinoColors.systemGrey.resolveFrom(context), - ), + data: CupertinoIconThemeData(color: CupertinoColors.systemGrey.resolveFrom(context)), child: Row( children: [ Icon(icon, size: 20.0), const SizedBox(width: 10), - Text( - title, - style: _titleStyle, - ), + Text(title, style: _titleStyle), ], ), ); diff --git a/lib/src/view/puzzle/dashboard_screen.dart b/lib/src/view/puzzle/dashboard_screen.dart index 7cb172be93..49a09e9067 100644 --- a/lib/src/view/puzzle/dashboard_screen.dart +++ b/lib/src/view/puzzle/dashboard_screen.dart @@ -26,10 +26,7 @@ class PuzzleDashboardScreen extends StatelessWidget { Widget build(BuildContext context) { return const PlatformScaffold( body: _Body(), - appBar: PlatformAppBar( - title: SizedBox.shrink(), - actions: [DaysSelector()], - ), + appBar: PlatformAppBar(title: SizedBox.shrink(), actions: [DaysSelector()]), ); } } @@ -39,27 +36,21 @@ class _Body extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return ListView( - children: [ - PuzzleDashboardWidget(), - ], - ); + return ListView(children: [PuzzleDashboardWidget()]); } } class PuzzleDashboardWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final puzzleDashboard = - ref.watch(puzzleDashboardProvider(ref.watch(daysProvider).days)); + final puzzleDashboard = ref.watch(puzzleDashboardProvider(ref.watch(daysProvider).days)); return puzzleDashboard.when( data: (dashboard) { if (dashboard == null) { return const SizedBox.shrink(); } - final chartData = - dashboard.themes.take(9).sortedBy((e) => e.theme.name).toList(); + final chartData = dashboard.themes.take(9).sortedBy((e) => e.theme.name).toList(); return ListSection( header: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -67,9 +58,7 @@ class PuzzleDashboardWidget extends ConsumerWidget { Text(context.l10n.puzzlePuzzleDashboard), Text( context.l10n.puzzlePuzzleDashboardDescription, - style: Styles.subtitle.copyWith( - color: textShade(context, Styles.subtitleOpacity), - ), + style: Styles.subtitle.copyWith(color: textShade(context, Styles.subtitleOpacity)), ), ], ), @@ -77,14 +66,12 @@ class PuzzleDashboardWidget extends ConsumerWidget { cupertinoAdditionalDividerMargin: -14, children: [ Padding( - padding: Theme.of(context).platform == TargetPlatform.iOS - ? EdgeInsets.zero - : Styles.horizontalBodyPadding, + padding: + Theme.of(context).platform == TargetPlatform.iOS + ? EdgeInsets.zero + : Styles.horizontalBodyPadding, child: StatCardRow([ - StatCard( - context.l10n.performance, - value: dashboard.global.performance.toString(), - ), + StatCard(context.l10n.performance, value: dashboard.global.performance.toString()), StatCard( context.l10n .puzzleNbPlayed(dashboard.global.nb) @@ -95,8 +82,7 @@ class PuzzleDashboardWidget extends ConsumerWidget { ), StatCard( context.l10n.puzzleSolved.capitalize(), - value: - '${((dashboard.global.firstWins / dashboard.global.nb) * 100).round()}%', + value: '${((dashboard.global.firstWins / dashboard.global.nb) * 100).round()}%', ), ]), ), @@ -104,10 +90,7 @@ class PuzzleDashboardWidget extends ConsumerWidget { Padding( padding: const EdgeInsets.all(10.0), child: AspectRatio( - aspectRatio: - MediaQuery.sizeOf(context).width > FormFactor.desktop - ? 2.8 - : 1.2, + aspectRatio: MediaQuery.sizeOf(context).width > FormFactor.desktop ? 2.8 : 1.2, child: PuzzleChart(chartData), ), ), @@ -115,18 +98,13 @@ class PuzzleDashboardWidget extends ConsumerWidget { ); }, error: (e, s) { - debugPrint( - 'SEVERE: [PuzzleDashboardWidget] could not load puzzle dashboard; $e\n$s', - ); + debugPrint('SEVERE: [PuzzleDashboardWidget] could not load puzzle dashboard; $e\n$s'); return Padding( padding: Styles.bodySectionPadding, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - context.l10n.puzzlePuzzleDashboard, - style: Styles.sectionTitle, - ), + Text(context.l10n.puzzlePuzzleDashboard, style: Styles.sectionTitle), if (e is ClientException && e.message.contains('404')) Text(context.l10n.puzzleNoPuzzlesToShow) else @@ -185,8 +163,7 @@ class PuzzleChart extends StatelessWidget { @override Widget build(BuildContext context) { - final radarColor = - Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5); + final radarColor = Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5); final chartColor = Theme.of(context).colorScheme.tertiary; return RadarChart( RadarChartData( @@ -198,14 +175,13 @@ class PuzzleChart extends StatelessWidget { RadarDataSet( fillColor: chartColor.withValues(alpha: 0.2), borderColor: chartColor, - dataEntries: puzzleData - .map((theme) => RadarEntry(value: theme.performance.toDouble())) - .toList(), + dataEntries: + puzzleData.map((theme) => RadarEntry(value: theme.performance.toDouble())).toList(), ), ], - getTitle: (index, angle) => RadarChartTitle( - text: puzzleData[index].theme.l10n(context.l10n).name, - ), + getTitle: + (index, angle) => + RadarChartTitle(text: puzzleData[index].theme.l10n(context.l10n).name), titleTextStyle: const TextStyle(fontSize: 10), titlePositionPercentageOffset: 0.09, tickCount: 3, @@ -224,17 +200,18 @@ class DaysSelector extends ConsumerWidget { final day = ref.watch(daysProvider); return session != null ? AppBarTextButton( - onPressed: () => showChoicePicker( - context, - choices: Days.values, - selectedItem: day, - labelBuilder: (t) => Text(_daysL10n(context, t)), - onSelectedItemChanged: (newDay) { - ref.read(daysProvider.notifier).state = newDay; - }, - ), - child: Text(_daysL10n(context, day)), - ) + onPressed: + () => showChoicePicker( + context, + choices: Days.values, + selectedItem: day, + labelBuilder: (t) => Text(_daysL10n(context, t)), + onSelectedItemChanged: (newDay) { + ref.read(daysProvider.notifier).state = newDay; + }, + ), + child: Text(_daysL10n(context, day)), + ) : const SizedBox.shrink(); } } diff --git a/lib/src/view/puzzle/opening_screen.dart b/lib/src/view/puzzle/opening_screen.dart index b2386aab32..725614b11d 100644 --- a/lib/src/view/puzzle/opening_screen.dart +++ b/lib/src/view/puzzle/opening_screen.dart @@ -14,18 +14,18 @@ import 'package:lichess_mobile/src/widgets/platform_scaffold.dart'; import 'puzzle_screen.dart'; -final _openingsProvider = FutureProvider.autoDispose< - (bool, IMap, IList?)>((ref) async { - final connectivity = await ref.watch(connectivityChangesProvider.future); - final savedOpenings = await ref.watch(savedOpeningBatchesProvider.future); - IList? onlineOpenings; - try { - onlineOpenings = await ref.watch(puzzleOpeningsProvider.future); - } catch (e) { - onlineOpenings = null; - } - return (connectivity.isOnline, savedOpenings, onlineOpenings); -}); +final _openingsProvider = + FutureProvider.autoDispose<(bool, IMap, IList?)>((ref) async { + final connectivity = await ref.watch(connectivityChangesProvider.future); + final savedOpenings = await ref.watch(savedOpeningBatchesProvider.future); + IList? onlineOpenings; + try { + onlineOpenings = await ref.watch(puzzleOpeningsProvider.future); + } catch (e) { + onlineOpenings = null; + } + return (connectivity.isOnline, savedOpenings, onlineOpenings); + }); class OpeningThemeScreen extends StatelessWidget { const OpeningThemeScreen({super.key}); @@ -33,9 +33,7 @@ class OpeningThemeScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.puzzlePuzzlesByOpenings), - ), + appBar: PlatformAppBar(title: Text(context.l10n.puzzlePuzzlesByOpenings)), body: const _Body(), ); } @@ -46,11 +44,10 @@ class _Body extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final titleStyle = Theme.of(context).platform == TargetPlatform.iOS - ? TextStyle( - color: CupertinoTheme.of(context).textTheme.textStyle.color, - ) - : null; + final titleStyle = + Theme.of(context).platform == TargetPlatform.iOS + ? TextStyle(color: CupertinoTheme.of(context).textTheme.textStyle.color) + : null; final openings = ref.watch(_openingsProvider); return openings.when( @@ -60,10 +57,7 @@ class _Body extends ConsumerWidget { return ListView( children: [ for (final openingFamily in onlineOpenings) - _OpeningFamily( - openingFamily: openingFamily, - titleStyle: titleStyle, - ), + _OpeningFamily(openingFamily: openingFamily, titleStyle: titleStyle), ], ); } else { @@ -93,10 +87,7 @@ class _Body extends ConsumerWidget { } class _OpeningFamily extends ConsumerWidget { - const _OpeningFamily({ - required this.openingFamily, - required this.titleStyle, - }); + const _OpeningFamily({required this.openingFamily, required this.titleStyle}); final PuzzleOpeningFamily openingFamily; final TextStyle? titleStyle; @@ -105,62 +96,49 @@ class _OpeningFamily extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return Theme( data: Theme.of(context).copyWith(dividerColor: Colors.transparent), - child: openingFamily.openings.isNotEmpty - ? ExpansionTile( - title: Text( - openingFamily.name, - overflow: TextOverflow.ellipsis, - style: titleStyle, - ), - subtitle: Text( - '${openingFamily.count}', - style: TextStyle( - color: textShade(context, Styles.subtitleOpacity), + child: + openingFamily.openings.isNotEmpty + ? ExpansionTile( + title: Text(openingFamily.name, overflow: TextOverflow.ellipsis, style: titleStyle), + subtitle: Text( + '${openingFamily.count}', + style: TextStyle(color: textShade(context, Styles.subtitleOpacity)), ), - ), - children: [ - ListSection( - children: [ - _OpeningTile( - name: openingFamily.name, - openingKey: openingFamily.key, - count: openingFamily.count, - titleStyle: titleStyle, - ), - ...openingFamily.openings.map( - (opening) => _OpeningTile( - name: opening.name, - openingKey: opening.key, - count: opening.count, + children: [ + ListSection( + children: [ + _OpeningTile( + name: openingFamily.name, + openingKey: openingFamily.key, + count: openingFamily.count, titleStyle: titleStyle, ), - ), - ], - ), - ], - ) - : ListTile( - title: Text( - openingFamily.name, - overflow: TextOverflow.ellipsis, - style: titleStyle, - ), - subtitle: Text( - '${openingFamily.count}', - style: TextStyle( - color: textShade(context, 0.5), + ...openingFamily.openings.map( + (opening) => _OpeningTile( + name: opening.name, + openingKey: opening.key, + count: opening.count, + titleStyle: titleStyle, + ), + ), + ], + ), + ], + ) + : ListTile( + title: Text(openingFamily.name, overflow: TextOverflow.ellipsis, style: titleStyle), + subtitle: Text( + '${openingFamily.count}', + style: TextStyle(color: textShade(context, 0.5)), ), + onTap: () { + pushPlatformRoute( + context, + rootNavigator: true, + builder: (context) => PuzzleScreen(angle: PuzzleOpening(openingFamily.key)), + ); + }, ), - onTap: () { - pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => PuzzleScreen( - angle: PuzzleOpening(openingFamily.key), - ), - ); - }, - ), ); } } @@ -181,27 +159,14 @@ class _OpeningTile extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformListTile( - leading: Theme.of(context).platform == TargetPlatform.iOS - ? null - : const SizedBox.shrink(), - title: Text( - name, - overflow: TextOverflow.ellipsis, - style: titleStyle, - ), - trailing: Text( - '$count', - style: TextStyle( - color: textShade(context, Styles.subtitleOpacity), - ), - ), + leading: Theme.of(context).platform == TargetPlatform.iOS ? null : const SizedBox.shrink(), + title: Text(name, overflow: TextOverflow.ellipsis, style: titleStyle), + trailing: Text('$count', style: TextStyle(color: textShade(context, Styles.subtitleOpacity))), onTap: () { pushPlatformRoute( context, rootNavigator: true, - builder: (context) => PuzzleScreen( - angle: PuzzleOpening(openingKey), - ), + builder: (context) => PuzzleScreen(angle: PuzzleOpening(openingKey)), ); }, ); diff --git a/lib/src/view/puzzle/puzzle_feedback_widget.dart b/lib/src/view/puzzle/puzzle_feedback_widget.dart index 0b0cf7b568..02729e5ea0 100644 --- a/lib/src/view/puzzle/puzzle_feedback_widget.dart +++ b/lib/src/view/puzzle/puzzle_feedback_widget.dart @@ -11,11 +11,7 @@ import 'package:lichess_mobile/src/utils/string.dart'; import 'package:lichess_mobile/src/view/account/rating_pref_aware.dart'; class PuzzleFeedbackWidget extends ConsumerWidget { - const PuzzleFeedbackWidget({ - required this.puzzle, - required this.state, - required this.onStreak, - }); + const PuzzleFeedbackWidget({required this.puzzle, required this.state, required this.onStreak}); final Puzzle puzzle; final PuzzleState state; @@ -23,71 +19,54 @@ class PuzzleFeedbackWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final pieceSet = - ref.watch(boardPreferencesProvider.select((value) => value.pieceSet)); - final boardTheme = - ref.watch(boardPreferencesProvider.select((state) => state.boardTheme)); + final pieceSet = ref.watch(boardPreferencesProvider.select((value) => value.pieceSet)); + final boardTheme = ref.watch(boardPreferencesProvider.select((state) => state.boardTheme)); final brightness = ref.watch(currentBrightnessProvider); - final piece = - state.pov == Side.white ? PieceKind.whiteKing : PieceKind.blackKing; + final piece = state.pov == Side.white ? PieceKind.whiteKing : PieceKind.blackKing; final asset = pieceSet.assets[piece]!; switch (state.mode) { case PuzzleMode.view: - final puzzleRating = - context.l10n.puzzleRatingX(puzzle.puzzle.rating.toString()); - final playedXTimes = context.l10n - .puzzlePlayedXTimes(puzzle.puzzle.plays) - .localizeNumbers(); + final puzzleRating = context.l10n.puzzleRatingX(puzzle.puzzle.rating.toString()); + final playedXTimes = context.l10n.puzzlePlayedXTimes(puzzle.puzzle.plays).localizeNumbers(); return _FeedbackTile( - leading: state.result == PuzzleResult.win - ? Icon(Icons.check, size: 36, color: context.lichessColors.good) - : null, - title: onStreak && state.result == PuzzleResult.lose - ? const Text( - 'GAME OVER', - style: TextStyle( - fontSize: 24, - letterSpacing: 2.0, - ), - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - ) - : Text( - state.result == PuzzleResult.win - ? context.l10n.puzzlePuzzleSuccess - : context.l10n.puzzlePuzzleComplete, - overflow: TextOverflow.ellipsis, - ), - subtitle: onStreak && state.result == PuzzleResult.lose - ? null - : RatingPrefAware( - orElse: Text( - '$playedXTimes.', + leading: + state.result == PuzzleResult.win + ? Icon(Icons.check, size: 36, color: context.lichessColors.good) + : null, + title: + onStreak && state.result == PuzzleResult.lose + ? const Text( + 'GAME OVER', + style: TextStyle(fontSize: 24, letterSpacing: 2.0), + textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, - maxLines: 2, - ), - child: Text( - '$puzzleRating. $playedXTimes.', + ) + : Text( + state.result == PuzzleResult.win + ? context.l10n.puzzlePuzzleSuccess + : context.l10n.puzzlePuzzleComplete, overflow: TextOverflow.ellipsis, - maxLines: 2, ), - ), + subtitle: + onStreak && state.result == PuzzleResult.lose + ? null + : RatingPrefAware( + orElse: Text('$playedXTimes.', overflow: TextOverflow.ellipsis, maxLines: 2), + child: Text( + '$puzzleRating. $playedXTimes.', + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), ); case PuzzleMode.load: case PuzzleMode.play: if (state.feedback == PuzzleFeedback.bad) { return _FeedbackTile( - leading: Icon( - Icons.close, - size: 36, - color: context.lichessColors.error, - ), - title: Text( - context.l10n.puzzleNotTheMove, - overflow: TextOverflow.ellipsis, - ), + leading: Icon(Icons.close, size: 36, color: context.lichessColors.error), + title: Text(context.l10n.puzzleNotTheMove, overflow: TextOverflow.ellipsis), subtitle: Text( context.l10n.puzzleTrySomethingElse, overflow: TextOverflow.ellipsis, @@ -96,8 +75,7 @@ class PuzzleFeedbackWidget extends ConsumerWidget { ); } else if (state.feedback == PuzzleFeedback.good) { return _FeedbackTile( - leading: - Icon(Icons.check, size: 36, color: context.lichessColors.good), + leading: Icon(Icons.check, size: 36, color: context.lichessColors.good), title: Text(context.l10n.puzzleBestMove), subtitle: Text(context.l10n.puzzleKeepGoing), ); @@ -106,9 +84,10 @@ class PuzzleFeedbackWidget extends ConsumerWidget { leading: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(4.0), - color: brightness == Brightness.light - ? boardTheme.colors.lightSquare - : boardTheme.colors.darkSquare, + color: + brightness == Brightness.light + ? boardTheme.colors.lightSquare + : boardTheme.colors.darkSquare, ), child: Padding( padding: const EdgeInsets.all(2.0), @@ -121,10 +100,7 @@ class PuzzleFeedbackWidget extends ConsumerWidget { ), ), ), - title: Text( - context.l10n.yourTurn, - overflow: TextOverflow.ellipsis, - ), + title: Text(context.l10n.yourTurn, overflow: TextOverflow.ellipsis), subtitle: Text( state.pov == Side.white ? context.l10n.puzzleFindTheBestMoveForWhite @@ -139,11 +115,7 @@ class PuzzleFeedbackWidget extends ConsumerWidget { } class _FeedbackTile extends StatelessWidget { - const _FeedbackTile({ - this.leading, - required this.title, - this.subtitle, - }); + const _FeedbackTile({this.leading, required this.title, this.subtitle}); final Widget? leading; final Widget title; @@ -155,10 +127,7 @@ class _FeedbackTile extends StatelessWidget { return Row( children: [ - if (leading != null) ...[ - leading!, - const SizedBox(width: 16.0), - ], + if (leading != null) ...[leading!, const SizedBox(width: 16.0)], Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -167,8 +136,7 @@ class _FeedbackTile extends StatelessWidget { children: [ DefaultTextStyle.merge( style: TextStyle( - fontSize: - defaultFontSize != null ? defaultFontSize * 1.2 : null, + fontSize: defaultFontSize != null ? defaultFontSize * 1.2 : null, fontWeight: FontWeight.bold, ), child: title, diff --git a/lib/src/view/puzzle/puzzle_history_screen.dart b/lib/src/view/puzzle/puzzle_history_screen.dart index ba97ba3263..b5561ae58d 100644 --- a/lib/src/view/puzzle/puzzle_history_screen.dart +++ b/lib/src/view/puzzle/puzzle_history_screen.dart @@ -32,8 +32,7 @@ class PuzzleHistoryPreview extends ConsumerWidget { return _PreviewBoardsGrid( rowGap: 16, builder: (crossAxisCount, boardWidth) { - final cappedHistory = - maxRows != null ? history.take(crossAxisCount * maxRows!) : history; + final cappedHistory = maxRows != null ? history.take(crossAxisCount * maxRows!) : history; return cappedHistory.map((e) { final (fen, side, lastMove) = e.preview; @@ -43,10 +42,9 @@ class PuzzleHistoryPreview extends ConsumerWidget { pushPlatformRoute( context, rootNavigator: true, - builder: (_) => PuzzleScreen( - angle: const PuzzleTheme(PuzzleThemeKey.mix), - puzzleId: e.id, - ), + builder: + (_) => + PuzzleScreen(angle: const PuzzleTheme(PuzzleThemeKey.mix), puzzleId: e.id), ); }, orientation: side, @@ -54,11 +52,7 @@ class PuzzleHistoryPreview extends ConsumerWidget { lastMove: lastMove, footer: Padding( padding: const EdgeInsets.only(top: 2.0), - child: Row( - children: [ - _PuzzleResult(e), - ], - ), + child: Row(children: [_PuzzleResult(e)]), ), ); }).toList(); @@ -104,8 +98,7 @@ class _BodyState extends ConsumerState<_Body> { } void _scrollListener() { - if (_scrollController.position.pixels >= - _scrollController.position.maxScrollExtent - 300) { + if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 300) { final currentState = ref.read(puzzleActivityProvider).valueOrNull; if (currentState != null && !currentState.isLoading) { ref.read(puzzleActivityProvider.notifier).getNext(); @@ -120,17 +113,11 @@ class _BodyState extends ConsumerState<_Body> { return historyState.when( data: (state) { if (state.hasError) { - showPlatformSnackbar( - context, - 'Error loading history', - type: SnackBarType.error, - ); + showPlatformSnackbar(context, 'Error loading history', type: SnackBarType.error); } - final crossAxisCount = - MediaQuery.sizeOf(context).width > FormFactor.tablet ? 4 : 2; + final crossAxisCount = MediaQuery.sizeOf(context).width > FormFactor.tablet ? 4 : 2; final columnsGap = _kPuzzlePadding * crossAxisCount + _kPuzzlePadding; - final boardWidth = - (MediaQuery.sizeOf(context).width - columnsGap) / crossAxisCount; + final boardWidth = (MediaQuery.sizeOf(context).width - columnsGap) / crossAxisCount; // List prepared for the ListView.builder. // It includes the date headers, and puzzles are sliced into rows of `crossAxisCount` length. @@ -158,29 +145,22 @@ class _BodyState extends ConsumerState<_Body> { return Padding( padding: const EdgeInsets.only(right: _kPuzzlePadding), child: Row( - children: element - .map( - (e) => PuzzleHistoryBoard( - e as PuzzleHistoryEntry, - boardWidth, - ), - ) - .toList(), + children: + element + .map((e) => PuzzleHistoryBoard(e as PuzzleHistoryEntry, boardWidth)) + .toList(), ), ); } else if (element is DateTime) { - final title = DateTime.now().difference(element).inDays >= 15 - ? _dateFormatter.format(element) - : timeago.format(element); + final title = + DateTime.now().difference(element).inDays >= 15 + ? _dateFormatter.format(element) + : timeago.format(element); return Padding( - padding: const EdgeInsets.only(left: _kPuzzlePadding) - .add(Styles.sectionTopPadding), + padding: const EdgeInsets.only(left: _kPuzzlePadding).add(Styles.sectionTopPadding), child: Text( title, - style: const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 18, - ), + style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18), ), ); } else { @@ -190,9 +170,7 @@ class _BodyState extends ConsumerState<_Body> { ); }, error: (e, s) { - debugPrint( - 'SEVERE: [PuzzleHistoryScreen] could not load puzzle history', - ); + debugPrint('SEVERE: [PuzzleHistoryScreen] could not load puzzle history'); return const Center(child: Text('Could not load Puzzle History')); }, loading: () => const CenterLoadingIndicator(), @@ -221,19 +199,15 @@ class PuzzleHistoryBoard extends ConsumerWidget { pushPlatformRoute( context, rootNavigator: true, - builder: (ctx) => PuzzleScreen( - angle: const PuzzleTheme(PuzzleThemeKey.mix), - puzzleId: puzzle.id, - ), + builder: + (ctx) => + PuzzleScreen(angle: const PuzzleTheme(PuzzleThemeKey.mix), puzzleId: puzzle.id), ); }, orientation: turn, fen: fen, lastMove: lastMove, - footer: Padding( - padding: const EdgeInsets.only(top: 2), - child: _PuzzleResult(puzzle), - ), + footer: Padding(padding: const EdgeInsets.only(top: 2), child: _PuzzleResult(puzzle)), ), ); } @@ -247,14 +221,12 @@ class _PuzzleResult extends StatelessWidget { @override Widget build(BuildContext context) { return ColoredBox( - color: entry.win - ? context.lichessColors.good.withValues(alpha: 0.7) - : context.lichessColors.error.withValues(alpha: 0.7), + color: + entry.win + ? context.lichessColors.good.withValues(alpha: 0.7) + : context.lichessColors.error.withValues(alpha: 0.7), child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 1, - horizontal: 3, - ), + padding: const EdgeInsets.symmetric(vertical: 1, horizontal: 3), child: Row( children: [ Text( @@ -274,24 +246,13 @@ class _PuzzleResult extends StatelessWidget { Text( '${entry.solvingTime!.inSeconds}s', overflow: TextOverflow.fade, - style: const TextStyle( - color: Colors.white, - fontSize: 10, - height: 1.0, - ), + style: const TextStyle(color: Colors.white, fontSize: 10, height: 1.0), ) else Text( - (entry.win - ? context.l10n.puzzleSolved - : context.l10n.puzzleFailed) - .toUpperCase(), + (entry.win ? context.l10n.puzzleSolved : context.l10n.puzzleFailed).toUpperCase(), overflow: TextOverflow.fade, - style: const TextStyle( - fontSize: 10, - color: Colors.white, - height: 1.0, - ), + style: const TextStyle(fontSize: 10, color: Colors.white, height: 1.0), ), ], ), @@ -304,32 +265,26 @@ class _PreviewBoardsGrid extends StatelessWidget { final List Function(int, double) builder; final double rowGap; - const _PreviewBoardsGrid({ - required this.builder, - required this.rowGap, - }); + const _PreviewBoardsGrid({required this.builder, required this.rowGap}); @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { - final crossAxisCount = constraints.maxWidth > 600 - ? 4 - : constraints.maxWidth > 450 + final crossAxisCount = + constraints.maxWidth > 600 + ? 4 + : constraints.maxWidth > 450 ? 3 : 2; const columnGap = 12.0; final boardWidth = - (constraints.maxWidth - (columnGap * crossAxisCount - columnGap)) / - crossAxisCount; + (constraints.maxWidth - (columnGap * crossAxisCount - columnGap)) / crossAxisCount; final boards = builder(crossAxisCount, boardWidth); return LayoutGrid( columnSizes: List.generate(crossAxisCount, (_) => 1.fr), - rowSizes: List.generate( - (boards.length / crossAxisCount).ceil(), - (_) => auto, - ), + rowSizes: List.generate((boards.length / crossAxisCount).ceil(), (_) => auto), rowGap: rowGap, columnGap: columnGap, children: boards, diff --git a/lib/src/view/puzzle/puzzle_screen.dart b/lib/src/view/puzzle/puzzle_screen.dart index a76e2d36e7..25df10f270 100644 --- a/lib/src/view/puzzle/puzzle_screen.dart +++ b/lib/src/view/puzzle/puzzle_screen.dart @@ -49,11 +49,7 @@ class PuzzleScreen extends ConsumerStatefulWidget { /// Creates a new puzzle screen. /// /// If [puzzleId] is provided, the screen will load the puzzle with that id. Otherwise, it will load the next puzzle from the queue. - const PuzzleScreen({ - required this.angle, - this.puzzleId, - super.key, - }); + const PuzzleScreen({required this.angle, this.puzzleId, super.key}); final PuzzleAngle angle; final PuzzleId? puzzleId; @@ -91,37 +87,32 @@ class _PuzzleScreenState extends ConsumerState with RouteAware { return WakelockWidget( child: PlatformScaffold( appBar: PlatformAppBar( - actions: const [ - ToggleSoundButton(), - _PuzzleSettingsButton(), - ], + actions: const [ToggleSoundButton(), _PuzzleSettingsButton()], title: _Title(angle: widget.angle), ), - body: widget.puzzleId != null - ? _LoadPuzzleFromId(angle: widget.angle, id: widget.puzzleId!) - : _LoadNextPuzzle(angle: widget.angle), + body: + widget.puzzleId != null + ? _LoadPuzzleFromId(angle: widget.angle, id: widget.puzzleId!) + : _LoadNextPuzzle(angle: widget.angle), ), ); } } class _Title extends ConsumerWidget { - const _Title({ - required this.angle, - }); + const _Title({required this.angle}); final PuzzleAngle angle; @override Widget build(BuildContext context, WidgetRef ref) { return switch (angle) { - PuzzleTheme(themeKey: final key) => key == PuzzleThemeKey.mix - ? Text(context.l10n.puzzleDesc) - : Text(key.l10n(context.l10n).name), + PuzzleTheme(themeKey: final key) => + key == PuzzleThemeKey.mix + ? Text(context.l10n.puzzleDesc) + : Text(key.l10n(context.l10n).name), PuzzleOpening(key: final key) => ref - .watch( - puzzleOpeningNameProvider(key), - ) + .watch(puzzleOpeningNameProvider(key)) .when( data: (data) => Text(data), loading: () => const SizedBox.shrink(), @@ -151,22 +142,14 @@ class _LoadNextPuzzle extends ConsumerWidget { ), ); } else { - return _Body( - initialPuzzleContext: data, - ); + return _Body(initialPuzzleContext: data); } }, loading: () => const Center(child: CircularProgressIndicator.adaptive()), error: (e, s) { - debugPrint( - 'SEVERE: [PuzzleScreen] could not load next puzzle; $e\n$s', - ); + debugPrint('SEVERE: [PuzzleScreen] could not load next puzzle; $e\n$s'); return Center( - child: BoardTable( - fen: kEmptyFen, - orientation: Side.white, - errorMessage: e.toString(), - ), + child: BoardTable(fen: kEmptyFen, orientation: Side.white, errorMessage: e.toString()), ); }, ); @@ -194,23 +177,20 @@ class _LoadPuzzleFromId extends ConsumerWidget { ), ); }, - loading: () => const Column( - children: [ - Expanded( - child: SafeArea( - bottom: false, - child: BoardTable.empty( - showEngineGaugePlaceholder: true, + loading: + () => const Column( + children: [ + Expanded( + child: SafeArea( + bottom: false, + child: BoardTable.empty(showEngineGaugePlaceholder: true), + ), ), - ), + BottomBar.empty(), + ], ), - BottomBar.empty(), - ], - ), error: (e, s) { - debugPrint( - 'SEVERE: [PuzzleScreen] could not load next puzzle; $e\n$s', - ); + debugPrint('SEVERE: [PuzzleScreen] could not load next puzzle; $e\n$s'); return Column( children: [ Expanded( @@ -232,9 +212,7 @@ class _LoadPuzzleFromId extends ConsumerWidget { } class _Body extends ConsumerWidget { - const _Body({ - required this.initialPuzzleContext, - }); + const _Body({required this.initialPuzzleContext}); final PuzzleContext initialPuzzleContext; @@ -245,11 +223,8 @@ class _Body extends ConsumerWidget { final boardPreferences = ref.watch(boardPreferencesProvider); - final currentEvalBest = ref.watch( - engineEvaluationProvider.select((s) => s.eval?.bestMove), - ); - final evalBestMove = - (currentEvalBest ?? puzzleState.node.eval?.bestMove) as NormalMove?; + final currentEvalBest = ref.watch(engineEvaluationProvider.select((s) => s.eval?.bestMove)); + final evalBestMove = (currentEvalBest ?? puzzleState.node.eval?.bestMove) as NormalMove?; return Column( children: [ @@ -261,16 +236,15 @@ class _Body extends ConsumerWidget { fen: puzzleState.fen, lastMove: puzzleState.lastMove as NormalMove?, gameData: GameData( - playerSide: puzzleState.mode == PuzzleMode.load || - puzzleState.position.isGameOver - ? PlayerSide.none - : puzzleState.mode == PuzzleMode.view + playerSide: + puzzleState.mode == PuzzleMode.load || puzzleState.position.isGameOver + ? PlayerSide.none + : puzzleState.mode == PuzzleMode.view ? PlayerSide.both : puzzleState.pov == Side.white - ? PlayerSide.white - : PlayerSide.black, - isCheck: boardPreferences.boardHighlights && - puzzleState.position.isCheck, + ? PlayerSide.white + : PlayerSide.black, + isCheck: boardPreferences.boardHighlights && puzzleState.position.isCheck, sideToMove: puzzleState.position.turn, validMoves: puzzleState.validMoves, promotionMove: puzzleState.promotionMove, @@ -281,23 +255,25 @@ class _Body extends ConsumerWidget { ref.read(ctrlProvider.notifier).onPromotionSelection(role); }, ), - shapes: puzzleState.isEngineEnabled && evalBestMove != null - ? ISet([ - Arrow( - color: const Color(0x40003088), - orig: evalBestMove.from, - dest: evalBestMove.to, - ), - ]) - : null, - engineGauge: puzzleState.isEngineEnabled - ? ( - orientation: puzzleState.pov, - isLocalEngineAvailable: true, - position: puzzleState.position, - savedEval: puzzleState.node.eval, - ) - : null, + shapes: + puzzleState.isEngineEnabled && evalBestMove != null + ? ISet([ + Arrow( + color: const Color(0x40003088), + orig: evalBestMove.from, + dest: evalBestMove.to, + ), + ]) + : null, + engineGauge: + puzzleState.isEngineEnabled + ? ( + orientation: puzzleState.pov, + isLocalEngineAvailable: true, + position: puzzleState.position, + savedEval: puzzleState.node.eval, + ) + : null, showEngineGaugePlaceholder: true, topTable: Center( child: PuzzleFeedbackWidget( @@ -312,9 +288,7 @@ class _Body extends ConsumerWidget { if (puzzleState.glicko != null) RatingPrefAware( child: Padding( - padding: const EdgeInsets.only( - top: 10.0, - ), + padding: const EdgeInsets.only(top: 10.0), child: Row( children: [ Text(context.l10n.rating), @@ -322,7 +296,8 @@ class _Body extends ConsumerWidget { TweenAnimationBuilder( tween: Tween( begin: puzzleState.glicko!.rating, - end: puzzleState.nextContext?.glicko?.rating ?? + end: + puzzleState.nextContext?.glicko?.rating ?? puzzleState.glicko!.rating, ), duration: const Duration(milliseconds: 500), @@ -349,20 +324,14 @@ class _Body extends ConsumerWidget { ), ), ), - _BottomBar( - initialPuzzleContext: initialPuzzleContext, - ctrlProvider: ctrlProvider, - ), + _BottomBar(initialPuzzleContext: initialPuzzleContext, ctrlProvider: ctrlProvider), ], ); } } class _BottomBar extends ConsumerWidget { - const _BottomBar({ - required this.initialPuzzleContext, - required this.ctrlProvider, - }); + const _BottomBar({required this.initialPuzzleContext, required this.ctrlProvider}); final PuzzleContext initialPuzzleContext; final PuzzleControllerProvider ctrlProvider; @@ -393,9 +362,10 @@ class _BottomBar extends ConsumerWidget { icon: Icons.help, label: context.l10n.viewTheSolution, showLabel: true, - onTap: puzzleState.canViewSolution - ? () => ref.read(ctrlProvider.notifier).viewSolution() - : null, + onTap: + puzzleState.canViewSolution + ? () => ref.read(ctrlProvider.notifier).viewSolution() + : null, ), if (puzzleState.mode == PuzzleMode.view) BottomBarButton( @@ -417,8 +387,7 @@ class _BottomBar extends ConsumerWidget { if (puzzleState.mode == PuzzleMode.view) RepeatButton( triggerDelays: _repeatTriggerDelays, - onLongPress: - puzzleState.canGoBack ? () => _moveBackward(ref) : null, + onLongPress: puzzleState.canGoBack ? () => _moveBackward(ref) : null, child: BottomBarButton( onTap: puzzleState.canGoBack ? () => _moveBackward(ref) : null, label: 'Previous', @@ -440,12 +409,10 @@ class _BottomBar extends ConsumerWidget { ), if (puzzleState.mode == PuzzleMode.view) BottomBarButton( - onTap: puzzleState.mode == PuzzleMode.view && - puzzleState.nextContext != null - ? () => ref - .read(ctrlProvider.notifier) - .loadPuzzle(puzzleState.nextContext!) - : null, + onTap: + puzzleState.mode == PuzzleMode.view && puzzleState.nextContext != null + ? () => ref.read(ctrlProvider.notifier).loadPuzzle(puzzleState.nextContext!) + : null, highlighted: true, label: context.l10n.puzzleContinueTraining, icon: CupertinoIcons.play_arrow_solid, @@ -464,8 +431,7 @@ class _BottomBar extends ConsumerWidget { onPressed: (context) { launchShareDialog( context, - text: lichessUri('/training/${puzzleState.puzzle.puzzle.id}') - .toString(), + text: lichessUri('/training/${puzzleState.puzzle.puzzle.id}').toString(), ); }, ), @@ -474,24 +440,24 @@ class _BottomBar extends ConsumerWidget { onPressed: (context) { pushPlatformRoute( context, - builder: (context) => AnalysisScreen( - options: AnalysisOptions( - orientation: puzzleState.pov, - standalone: ( - pgn: ref.read(ctrlProvider.notifier).makePgn(), - isComputerAnalysisAllowed: true, - variant: Variant.standard, + builder: + (context) => AnalysisScreen( + options: AnalysisOptions( + orientation: puzzleState.pov, + standalone: ( + pgn: ref.read(ctrlProvider.notifier).makePgn(), + isComputerAnalysisAllowed: true, + variant: Variant.standard, + ), + initialMoveCursor: 0, + ), ), - initialMoveCursor: 0, - ), - ), ); }, ), BottomSheetAction( - makeLabel: (context) => Text( - context.l10n.puzzleFromGameLink(puzzleState.puzzle.game.id.value), - ), + makeLabel: + (context) => Text(context.l10n.puzzleFromGameLink(puzzleState.puzzle.game.id.value)), onPressed: (_) async { final game = await ref.read( archivedGameProvider(id: puzzleState.puzzle.game.id).future, @@ -499,11 +465,12 @@ class _BottomBar extends ConsumerWidget { if (context.mounted) { pushPlatformRoute( context, - builder: (context) => ArchivedGameScreen( - gameData: game.data, - orientation: puzzleState.pov, - initialCursor: puzzleState.puzzle.puzzle.initialPly + 1, - ), + builder: + (context) => ArchivedGameScreen( + gameData: game.data, + orientation: puzzleState.pov, + initialCursor: puzzleState.puzzle.puzzle.initialPly + 1, + ), ); } }, @@ -522,65 +489,57 @@ class _BottomBar extends ConsumerWidget { } class _DifficultySelector extends ConsumerWidget { - const _DifficultySelector({ - required this.initialPuzzleContext, - required this.ctrlProvider, - }); + const _DifficultySelector({required this.initialPuzzleContext, required this.ctrlProvider}); final PuzzleContext initialPuzzleContext; final PuzzleControllerProvider ctrlProvider; @override Widget build(BuildContext context, WidgetRef ref) { - final difficulty = ref.watch( - puzzlePreferencesProvider.select((state) => state.difficulty), - ); + final difficulty = ref.watch(puzzlePreferencesProvider.select((state) => state.difficulty)); final state = ref.watch(ctrlProvider); final connectivity = ref.watch(connectivityChangesProvider); return connectivity.when( - data: (data) => StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - PuzzleDifficulty selectedDifficulty = difficulty; - return BottomBarButton( - icon: Icons.tune, - label: puzzleDifficultyL10n(context, difficulty), - tooltip: context.l10n.puzzleDifficultyLevel, - showLabel: true, - onTap: !data.isOnline || state.isChangingDifficulty - ? null - : () { - showChoicePicker( - context, - choices: PuzzleDifficulty.values, - selectedItem: difficulty, - labelBuilder: (t) => - Text(puzzleDifficultyL10n(context, t)), - onSelectedItemChanged: (PuzzleDifficulty? d) { - if (d != null) { - setState(() { - selectedDifficulty = d; + data: + (data) => StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + PuzzleDifficulty selectedDifficulty = difficulty; + return BottomBarButton( + icon: Icons.tune, + label: puzzleDifficultyL10n(context, difficulty), + tooltip: context.l10n.puzzleDifficultyLevel, + showLabel: true, + onTap: + !data.isOnline || state.isChangingDifficulty + ? null + : () { + showChoicePicker( + context, + choices: PuzzleDifficulty.values, + selectedItem: difficulty, + labelBuilder: (t) => Text(puzzleDifficultyL10n(context, t)), + onSelectedItemChanged: (PuzzleDifficulty? d) { + if (d != null) { + setState(() { + selectedDifficulty = d; + }); + } + }, + ).then((_) async { + if (selectedDifficulty == difficulty) { + return; + } + final nextContext = await ref + .read(ctrlProvider.notifier) + .changeDifficulty(selectedDifficulty); + if (context.mounted && nextContext != null) { + ref.read(ctrlProvider.notifier).loadPuzzle(nextContext); + } }); - } - }, - ).then( - (_) async { - if (selectedDifficulty == difficulty) { - return; - } - final nextContext = await ref - .read(ctrlProvider.notifier) - .changeDifficulty(selectedDifficulty); - if (context.mounted && nextContext != null) { - ref - .read(ctrlProvider.notifier) - .loadPuzzle(nextContext); - } - }, - ); - }, - ); - }, - ), + }, + ); + }, + ), loading: () => const ButtonLoadingIndicator(), error: (_, __) => const SizedBox.shrink(), ); @@ -593,16 +552,15 @@ class _PuzzleSettingsButton extends StatelessWidget { @override Widget build(BuildContext context) { return AppBarIconButton( - onPressed: () => showAdaptiveBottomSheet( - context: context, - isDismissible: true, - isScrollControlled: true, - showDragHandle: true, - constraints: BoxConstraints( - minHeight: MediaQuery.sizeOf(context).height * 0.5, - ), - builder: (_) => const PuzzleSettingsScreen(), - ), + onPressed: + () => showAdaptiveBottomSheet( + context: context, + isDismissible: true, + isScrollControlled: true, + showDragHandle: true, + constraints: BoxConstraints(minHeight: MediaQuery.sizeOf(context).height * 0.5), + builder: (_) => const PuzzleSettingsScreen(), + ), semanticsLabel: context.l10n.settingsSettings, icon: const Icon(Icons.settings), ); diff --git a/lib/src/view/puzzle/puzzle_session_widget.dart b/lib/src/view/puzzle/puzzle_session_widget.dart index 37d8511b92..841c622b05 100644 --- a/lib/src/view/puzzle/puzzle_session_widget.dart +++ b/lib/src/view/puzzle/puzzle_session_widget.dart @@ -14,17 +14,13 @@ import 'package:lichess_mobile/src/utils/screen.dart'; import 'package:lichess_mobile/src/view/account/rating_pref_aware.dart'; class PuzzleSessionWidget extends ConsumerStatefulWidget { - const PuzzleSessionWidget({ - required this.initialPuzzleContext, - required this.ctrlProvider, - }); + const PuzzleSessionWidget({required this.initialPuzzleContext, required this.ctrlProvider}); final PuzzleContext initialPuzzleContext; final PuzzleControllerProvider ctrlProvider; @override - ConsumerState createState() => - PuzzleSessionWidgetState(); + ConsumerState createState() => PuzzleSessionWidgetState(); } class PuzzleSessionWidgetState extends ConsumerState { @@ -36,9 +32,7 @@ class PuzzleSessionWidgetState extends ConsumerState { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { if (lastAttemptKey.currentContext != null) { - Scrollable.ensureVisible( - lastAttemptKey.currentContext!, - ); + Scrollable.ensureVisible(lastAttemptKey.currentContext!); } }); } @@ -60,10 +54,7 @@ class PuzzleSessionWidgetState extends ConsumerState { @override Widget build(BuildContext context) { final session = ref.watch( - puzzleSessionProvider( - widget.initialPuzzleContext.userId, - widget.initialPuzzleContext.angle, - ), + puzzleSessionProvider(widget.initialPuzzleContext.userId, widget.initialPuzzleContext.angle), ); final puzzleState = ref.watch(widget.ctrlProvider); final brightness = ref.watch(currentBrightnessProvider); @@ -78,13 +69,13 @@ class PuzzleSessionWidgetState extends ConsumerState { final remainingSpace = estimateRemainingHeightLeftBoard(context); final estimatedTableHeight = remainingSpace / 2; const estimatedRatingWidgetHeight = 33.0; - final estimatedWidgetHeight = - estimatedTableHeight - estimatedRatingWidgetHeight; - final maxHeight = orientation == Orientation.portrait - ? estimatedWidgetHeight >= 60 - ? 60.0 - : 26.0 - : 60.0; + final estimatedWidgetHeight = estimatedTableHeight - estimatedRatingWidgetHeight; + final maxHeight = + orientation == Orientation.portrait + ? estimatedWidgetHeight >= 60 + ? 60.0 + : 26.0 + : 60.0; return ConstrainedBox( constraints: BoxConstraints(minHeight: 26.0, maxHeight: maxHeight), @@ -101,36 +92,33 @@ class PuzzleSessionWidgetState extends ConsumerState { isLoading: loadingPuzzleId == attempt.id, brightness: brightness, attempt: attempt, - onTap: puzzleState.puzzle.puzzle.id != attempt.id && - loadingPuzzleId == null - ? (id) async { - final provider = puzzleProvider(id); - setState(() { - loadingPuzzleId = id; - }); - try { - final puzzle = await ref.read(provider.future); - final nextContext = PuzzleContext( - userId: widget.initialPuzzleContext.userId, - angle: widget.initialPuzzleContext.angle, - puzzle: puzzle, - ); - - ref - .read(widget.ctrlProvider.notifier) - .loadPuzzle(nextContext); - } finally { - if (mounted) { - setState(() { - loadingPuzzleId = null; - }); + onTap: + puzzleState.puzzle.puzzle.id != attempt.id && loadingPuzzleId == null + ? (id) async { + final provider = puzzleProvider(id); + setState(() { + loadingPuzzleId = id; + }); + try { + final puzzle = await ref.read(provider.future); + final nextContext = PuzzleContext( + userId: widget.initialPuzzleContext.userId, + angle: widget.initialPuzzleContext.angle, + puzzle: puzzle, + ); + + ref.read(widget.ctrlProvider.notifier).loadPuzzle(nextContext); + } finally { + if (mounted) { + setState(() { + loadingPuzzleId = null; + }); + } } } - } - : null, + : null, ), - if (puzzleState.mode == PuzzleMode.view || - currentAttempt == null) + if (puzzleState.mode == PuzzleMode.view || currentAttempt == null) _SessionItem( isCurrent: currentAttempt == null, isLoading: false, @@ -163,15 +151,17 @@ class _SessionItem extends StatelessWidget { final Brightness brightness; final void Function(PuzzleId id)? onTap; - Color get good => brightness == Brightness.light - ? LichessColors.good.shade300 - : defaultTargetPlatform == TargetPlatform.iOS + Color get good => + brightness == Brightness.light + ? LichessColors.good.shade300 + : defaultTargetPlatform == TargetPlatform.iOS ? LichessColors.good.shade600 : LichessColors.good.shade400; - Color get error => brightness == Brightness.light - ? LichessColors.error.shade300 - : defaultTargetPlatform == TargetPlatform.iOS + Color get error => + brightness == Brightness.light + ? LichessColors.error.shade300 + : defaultTargetPlatform == TargetPlatform.iOS ? LichessColors.error.shade600 : LichessColors.error.shade400; @@ -188,55 +178,28 @@ class _SessionItem extends StatelessWidget { height: 26, padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), decoration: BoxDecoration( - color: isCurrent - ? Colors.grey - : attempt != null + color: + isCurrent + ? Colors.grey + : attempt != null ? attempt!.win ? good.harmonizeWith(colorScheme.primary) : error.harmonizeWith(colorScheme.primary) : next, borderRadius: const BorderRadius.all(Radius.circular(5)), ), - child: isLoading - ? const Padding( - padding: EdgeInsets.all(2.0), - child: FittedBox( - fit: BoxFit.cover, - child: CircularProgressIndicator.adaptive( - backgroundColor: Colors.white, + child: + isLoading + ? const Padding( + padding: EdgeInsets.all(2.0), + child: FittedBox( + fit: BoxFit.cover, + child: CircularProgressIndicator.adaptive(backgroundColor: Colors.white), ), - ), - ) - : attempt?.ratingDiff != null && attempt!.ratingDiff != 0 + ) + : attempt?.ratingDiff != null && attempt!.ratingDiff != 0 ? RatingPrefAware( - orElse: Icon( - attempt != null - ? attempt!.win - ? Icons.check - : Icons.close - : null, - color: Colors.white, - size: 18, - ), - child: Padding( - padding: const EdgeInsets.all(2.0), - child: FittedBox( - fit: BoxFit.fitHeight, - child: Text( - attempt!.ratingDiffString!, - maxLines: 1, - style: const TextStyle( - color: Colors.white, - height: 1, - fontFeatures: [ - FontFeature.tabularFigures(), - ], - ), - ), - ), - ), - ) - : Icon( + orElse: Icon( attempt != null ? attempt!.win ? Icons.check @@ -245,6 +208,31 @@ class _SessionItem extends StatelessWidget { color: Colors.white, size: 18, ), + child: Padding( + padding: const EdgeInsets.all(2.0), + child: FittedBox( + fit: BoxFit.fitHeight, + child: Text( + attempt!.ratingDiffString!, + maxLines: 1, + style: const TextStyle( + color: Colors.white, + height: 1, + fontFeatures: [FontFeature.tabularFigures()], + ), + ), + ), + ), + ) + : Icon( + attempt != null + ? attempt!.win + ? Icons.check + : Icons.close + : null, + color: Colors.white, + size: 18, + ), ), ); } diff --git a/lib/src/view/puzzle/puzzle_settings_screen.dart b/lib/src/view/puzzle/puzzle_settings_screen.dart index 33e14c9518..14a57c25df 100644 --- a/lib/src/view/puzzle/puzzle_settings_screen.dart +++ b/lib/src/view/puzzle/puzzle_settings_screen.dart @@ -13,9 +13,7 @@ class PuzzleSettingsScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final autoNext = ref.watch( - puzzlePreferencesProvider.select((value) => value.autoNext), - ); + final autoNext = ref.watch(puzzlePreferencesProvider.select((value) => value.autoNext)); return BottomSheetScrollableContainer( children: [ SwitchSettingTile( @@ -29,11 +27,7 @@ class PuzzleSettingsScreen extends ConsumerWidget { title: const Text('Board settings'), trailing: const Icon(CupertinoIcons.chevron_right), onTap: () { - pushPlatformRoute( - context, - fullscreenDialog: true, - screen: const BoardSettingsScreen(), - ); + pushPlatformRoute(context, fullscreenDialog: true, screen: const BoardSettingsScreen()); }, ), ], diff --git a/lib/src/view/puzzle/puzzle_tab_screen.dart b/lib/src/view/puzzle/puzzle_tab_screen.dart index 43645c36b5..a870b218b9 100644 --- a/lib/src/view/puzzle/puzzle_tab_screen.dart +++ b/lib/src/view/puzzle/puzzle_tab_screen.dart @@ -73,10 +73,7 @@ Widget _buildMainListItem( ? Styles.sectionTopPadding : EdgeInsets.zero, ), - child: Text( - context.l10n.puzzleDesc, - style: Styles.sectionTitle, - ), + child: Text(context.l10n.puzzleDesc, style: Styles.sectionTitle), ); case 2: return const DailyPuzzle(); @@ -87,9 +84,7 @@ Widget _buildMainListItem( pushPlatformRoute( context, rootNavigator: true, - builder: (context) => const PuzzleScreen( - angle: PuzzleTheme(PuzzleThemeKey.mix), - ), + builder: (context) => const PuzzleScreen(angle: PuzzleTheme(PuzzleThemeKey.mix)), ); }, ); @@ -113,10 +108,7 @@ Widget _buildMainListRemovedItem( BuildContext context, Animation animation, ) { - return SizeTransition( - sizeFactor: animation, - child: PuzzleAnglePreview(angle: angle), - ); + return SizeTransition(sizeFactor: animation, child: PuzzleAnglePreview(angle: angle)); } // display the main body list for cupertino devices, as a workaround @@ -131,8 +123,7 @@ class _CupertinoTabBody extends ConsumerStatefulWidget { } class _CupertinoTabBodyState extends ConsumerState<_CupertinoTabBody> { - final GlobalKey _listKey = - GlobalKey(); + final GlobalKey _listKey = GlobalKey(); late SliverAnimatedListModel _angles; @override @@ -176,17 +167,8 @@ class _CupertinoTabBodyState extends ConsumerState<_CupertinoTabBody> { Widget build(BuildContext context) { final isTablet = isTabletOrLarger(context); - Widget buildItem( - BuildContext context, - int index, - Animation animation, - ) => - _buildMainListItem( - context, - index, - animation, - (index) => _angles[index], - ); + Widget buildItem(BuildContext context, int index, Animation animation) => + _buildMainListItem(context, index, animation, (index) => _angles[index]); if (isTablet) { return Row( @@ -197,16 +179,11 @@ class _CupertinoTabBodyState extends ConsumerState<_CupertinoTabBody> { controller: puzzlesScrollController, slivers: [ CupertinoSliverNavigationBar( - padding: const EdgeInsetsDirectional.only( - start: 16.0, - end: 8.0, - ), + padding: const EdgeInsetsDirectional.only(start: 16.0, end: 8.0), largeTitle: Text(context.l10n.puzzles), trailing: const Row( mainAxisSize: MainAxisSize.min, - children: [ - _DashboardButton(), - ], + children: [_DashboardButton()], ), ), const SliverToBoxAdapter(child: ConnectivityBanner()), @@ -229,18 +206,13 @@ class _CupertinoTabBodyState extends ConsumerState<_CupertinoTabBody> { ), Expanded( child: CupertinoPageScaffold( - backgroundColor: - CupertinoColors.systemBackground.resolveFrom(context), + backgroundColor: CupertinoColors.systemBackground.resolveFrom(context), navigationBar: CupertinoNavigationBar( transitionBetweenRoutes: false, middle: Text(context.l10n.puzzleHistory), trailing: const _HistoryButton(), ), - child: ListView( - children: const [ - PuzzleHistoryWidget(showHeader: false), - ], - ), + child: ListView(children: const [PuzzleHistoryWidget(showHeader: false)]), ), ), ], @@ -252,18 +224,11 @@ class _CupertinoTabBodyState extends ConsumerState<_CupertinoTabBody> { controller: puzzlesScrollController, slivers: [ CupertinoSliverNavigationBar( - padding: const EdgeInsetsDirectional.only( - start: 16.0, - end: 8.0, - ), + padding: const EdgeInsetsDirectional.only(start: 16.0, end: 8.0), largeTitle: Text(context.l10n.puzzles), trailing: const Row( mainAxisSize: MainAxisSize.min, - children: [ - _DashboardButton(), - SizedBox(width: 6.0), - _HistoryButton(), - ], + children: [_DashboardButton(), SizedBox(width: 6.0), _HistoryButton()], ), ), const SliverToBoxAdapter(child: ConnectivityBanner()), @@ -335,17 +300,8 @@ class _MaterialTabBodyState extends ConsumerState<_MaterialTabBody> { Widget build(BuildContext context) { final isTablet = isTabletOrLarger(context); - Widget buildItem( - BuildContext context, - int index, - Animation animation, - ) => - _buildMainListItem( - context, - index, - animation, - (index) => _angles[index], - ); + Widget buildItem(BuildContext context, int index, Animation animation) => + _buildMainListItem(context, index, animation, (index) => _angles[index]); return PopScope( canPop: false, @@ -357,45 +313,37 @@ class _MaterialTabBodyState extends ConsumerState<_MaterialTabBody> { child: Scaffold( appBar: AppBar( title: Text(context.l10n.puzzles), - actions: const [ - _DashboardButton(), - _HistoryButton(), - ], + actions: const [_DashboardButton(), _HistoryButton()], ), - body: isTablet - ? Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: AnimatedList( - key: _listKey, - initialItemCount: _angles.length, - controller: puzzlesScrollController, - itemBuilder: buildItem, - ), - ), - Expanded( - child: ListView( - children: const [ - PuzzleHistoryWidget(), - ], + body: + isTablet + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: AnimatedList( + key: _listKey, + initialItemCount: _angles.length, + controller: puzzlesScrollController, + itemBuilder: buildItem, + ), ), - ), - ], - ) - : Column( - children: [ - const ConnectivityBanner(), - Expanded( - child: AnimatedList( - key: _listKey, - controller: puzzlesScrollController, - initialItemCount: _angles.length, - itemBuilder: buildItem, + Expanded(child: ListView(children: const [PuzzleHistoryWidget()])), + ], + ) + : Column( + children: [ + const ConnectivityBanner(), + Expanded( + child: AnimatedList( + key: _listKey, + controller: puzzlesScrollController, + initialItemCount: _angles.length, + itemBuilder: buildItem, + ), ), - ), - ], - ), + ], + ), ), ); } @@ -417,21 +365,24 @@ class _PuzzleMenuListTile extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformListTile( - padding: Theme.of(context).platform == TargetPlatform.iOS - ? const EdgeInsets.symmetric(vertical: 10.0, horizontal: 14.0) - : null, + padding: + Theme.of(context).platform == TargetPlatform.iOS + ? const EdgeInsets.symmetric(vertical: 10.0, horizontal: 14.0) + : null, leading: Icon( icon, size: Styles.mainListTileIconSize, - color: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoTheme.of(context).primaryColor - : Theme.of(context).colorScheme.primary, + color: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoTheme.of(context).primaryColor + : Theme.of(context).colorScheme.primary, ), title: Text(title, style: Styles.mainListTileTitle), subtitle: Text(subtitle, maxLines: 3), - trailing: Theme.of(context).platform == TargetPlatform.iOS - ? const CupertinoListTileChevron() - : null, + trailing: + Theme.of(context).platform == TargetPlatform.iOS + ? const CupertinoListTileChevron() + : null, onTap: onTap, ); } @@ -465,19 +416,21 @@ class _PuzzleMenu extends ConsumerWidget { child: _PuzzleMenuListTile( icon: LichessIcons.streak, title: 'Puzzle Streak', - subtitle: context.l10n.puzzleStreakDescription.characters + subtitle: + context.l10n.puzzleStreakDescription.characters .takeWhile((c) => c != '.') .toString() + (context.l10n.puzzleStreakDescription.contains('.') ? '.' : ''), - onTap: isOnline - ? () { - pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => const StreakScreen(), - ); - } - : null, + onTap: + isOnline + ? () { + pushPlatformRoute( + context, + rootNavigator: true, + builder: (context) => const StreakScreen(), + ); + } + : null, ), ), Opacity( @@ -486,15 +439,16 @@ class _PuzzleMenu extends ConsumerWidget { icon: LichessIcons.storm, title: 'Puzzle Storm', subtitle: context.l10n.mobilePuzzleStormSubtitle, - onTap: isOnline - ? () { - pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => const StormScreen(), - ); - } - : null, + onTap: + isOnline + ? () { + pushPlatformRoute( + context, + rootNavigator: true, + builder: (context) => const StormScreen(), + ); + } + : null, ), ), ], @@ -522,10 +476,7 @@ class PuzzleHistoryWidget extends ConsumerWidget { children: [ Center( child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 16.0, - horizontal: 8.0, - ), + padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0), child: Text(context.l10n.puzzleNoPuzzlesToShow), ), ), @@ -533,57 +484,49 @@ class PuzzleHistoryWidget extends ConsumerWidget { ); } - final maxItems = isTablet - ? _kNumberOfHistoryItemsOnTablet - : _kNumberOfHistoryItemsOnHandset; + final maxItems = + isTablet ? _kNumberOfHistoryItemsOnTablet : _kNumberOfHistoryItemsOnHandset; return ListSection( - cupertinoBackgroundColor: - CupertinoPageScaffoldBackgroundColor.maybeOf(context), + cupertinoBackgroundColor: CupertinoPageScaffoldBackgroundColor.maybeOf(context), cupertinoClipBehavior: Clip.none, header: showHeader ? Text(context.l10n.puzzleHistory) : null, - headerTrailing: showHeader - ? NoPaddingTextButton( - onPressed: () => pushPlatformRoute( - context, - builder: (context) => const PuzzleHistoryScreen(), - ), - child: Text( - context.l10n.more, - ), - ) - : null, + headerTrailing: + showHeader + ? NoPaddingTextButton( + onPressed: + () => pushPlatformRoute( + context, + builder: (context) => const PuzzleHistoryScreen(), + ), + child: Text(context.l10n.more), + ) + : null, children: [ Padding( - padding: Theme.of(context).platform == TargetPlatform.iOS - ? EdgeInsets.zero - : Styles.horizontalBodyPadding, - child: PuzzleHistoryPreview( - recentActivity.take(maxItems).toIList(), - maxRows: 5, - ), + padding: + Theme.of(context).platform == TargetPlatform.iOS + ? EdgeInsets.zero + : Styles.horizontalBodyPadding, + child: PuzzleHistoryPreview(recentActivity.take(maxItems).toIList(), maxRows: 5), ), ], ); }, error: (e, s) { - debugPrint( - 'SEVERE: [PuzzleHistoryWidget] could not load puzzle history', - ); + debugPrint('SEVERE: [PuzzleHistoryWidget] could not load puzzle history'); return const Padding( padding: Styles.bodySectionPadding, child: Text('Could not load Puzzle history.'), ); }, - loading: () => Shimmer( - child: ShimmerLoading( - isLoading: true, - child: ListSection.loading( - itemsNumber: 5, - header: true, + loading: + () => Shimmer( + child: ShimmerLoading( + isLoading: true, + child: ListSection.loading(itemsNumber: 5, header: true), + ), ), - ), - ), ); } } @@ -597,14 +540,17 @@ class _DashboardButton extends ConsumerWidget { if (session == null) { return const SizedBox.shrink(); } - final onPressed = ref.watch(connectivityChangesProvider).whenIs( - online: () => () { - pushPlatformRoute( - context, - title: context.l10n.puzzlePuzzleDashboard, - builder: (_) => const PuzzleDashboardScreen(), - ); - }, + final onPressed = ref + .watch(connectivityChangesProvider) + .whenIs( + online: + () => () { + pushPlatformRoute( + context, + title: context.l10n.puzzlePuzzleDashboard, + builder: (_) => const PuzzleDashboardScreen(), + ); + }, offline: () => null, ); @@ -625,14 +571,17 @@ class _HistoryButton extends ConsumerWidget { if (session == null) { return const SizedBox.shrink(); } - final onPressed = ref.watch(connectivityChangesProvider).whenIs( - online: () => () { - pushPlatformRoute( - context, - title: context.l10n.puzzleHistory, - builder: (_) => const PuzzleHistoryScreen(), - ); - }, + final onPressed = ref + .watch(connectivityChangesProvider) + .whenIs( + online: + () => () { + pushPlatformRoute( + context, + title: context.l10n.puzzleHistory, + builder: (_) => const PuzzleHistoryScreen(), + ); + }, offline: () => null, ); return AppBarIconButton( @@ -656,8 +605,7 @@ class DailyPuzzle extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isOnline = - ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? false; + final isOnline = ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? false; final puzzle = ref.watch(dailyPuzzleProvider); return puzzle.when( @@ -674,14 +622,9 @@ class DailyPuzzle extends ConsumerWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(context.l10n.puzzlePuzzleOfTheDay, style: Styles.boardPreviewTitle), Text( - context.l10n.puzzlePuzzleOfTheDay, - style: Styles.boardPreviewTitle, - ), - Text( - context.l10n - .puzzlePlayedXTimes(data.puzzle.plays) - .localizeNumbers(), + context.l10n.puzzlePlayedXTimes(data.puzzle.plays).localizeNumbers(), style: _puzzlePreviewSubtitleStyle(context), ), ], @@ -689,10 +632,7 @@ class DailyPuzzle extends ConsumerWidget { Icon( Icons.today, size: 34, - color: DefaultTextStyle.of(context) - .style - .color - ?.withValues(alpha: 0.6), + color: DefaultTextStyle.of(context).style.color?.withValues(alpha: 0.6), ), Text( data.puzzle.sideToMove == Side.white @@ -706,28 +646,28 @@ class DailyPuzzle extends ConsumerWidget { pushPlatformRoute( context, rootNavigator: true, - builder: (context) => PuzzleScreen( - angle: const PuzzleTheme(PuzzleThemeKey.mix), - puzzleId: data.puzzle.id, - ), + builder: + (context) => PuzzleScreen( + angle: const PuzzleTheme(PuzzleThemeKey.mix), + puzzleId: data.puzzle.id, + ), ); }, ); }, - loading: () => isOnline - ? const Shimmer( - child: ShimmerLoading( - isLoading: true, - child: SmallBoardPreview.loading(), - ), - ) - : const SizedBox.shrink(), + loading: + () => + isOnline + ? const Shimmer( + child: ShimmerLoading(isLoading: true, child: SmallBoardPreview.loading()), + ) + : const SizedBox.shrink(), error: (error, _) { return isOnline ? const Padding( - padding: Styles.bodySectionPadding, - child: Text('Could not load the daily puzzle.'), - ) + padding: Styles.bodySectionPadding, + child: Text('Could not load the daily puzzle.'), + ) : const SizedBox.shrink(); }, ); @@ -751,118 +691,104 @@ class PuzzleAnglePreview extends ConsumerWidget { return loading ? const Shimmer( - child: ShimmerLoading( - isLoading: true, - child: SmallBoardPreview.loading(), - ), - ) + child: ShimmerLoading(isLoading: true, child: SmallBoardPreview.loading()), + ) : Slidable( - dragStartBehavior: DragStartBehavior.start, - enabled: angle != const PuzzleTheme(PuzzleThemeKey.mix), - endActionPane: ActionPane( - motion: const StretchMotion(), + dragStartBehavior: DragStartBehavior.start, + enabled: angle != const PuzzleTheme(PuzzleThemeKey.mix), + endActionPane: ActionPane( + motion: const StretchMotion(), + children: [ + SlidableAction( + icon: Icons.delete, + onPressed: (context) async { + final service = await ref.read(puzzleServiceProvider.future); + if (context.mounted) { + service.deleteBatch( + userId: ref.read(authSessionProvider)?.user.id, + angle: angle, + ); + } + }, + spacing: 8.0, + backgroundColor: context.lichessColors.error, + foregroundColor: Colors.white, + label: context.l10n.delete, + ), + ], + ), + child: SmallBoardPreview( + orientation: preview?.orientation ?? Side.white, + fen: preview?.initialFen ?? kEmptyFen, + lastMove: preview?.initialMove, + description: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SlidableAction( - icon: Icons.delete, - onPressed: (context) async { - final service = - await ref.read(puzzleServiceProvider.future); - if (context.mounted) { - service.deleteBatch( - userId: ref.read(authSessionProvider)?.user.id, - angle: angle, - ); - } + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: switch (angle) { + PuzzleTheme(themeKey: final themeKey) => [ + Text( + themeKey.l10n(context.l10n).name, + style: Styles.boardPreviewTitle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + Text( + themeKey.l10n(context.l10n).description, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: TextStyle( + height: 1.2, + fontSize: 12.0, + color: DefaultTextStyle.of(context).style.color?.withValues(alpha: 0.6), + ), + ), + ], + PuzzleOpening(key: final openingKey) => [ + Text( + flatOpenings.valueOrNull + ?.firstWhere( + (o) => o.key == openingKey, + orElse: + () => ( + key: openingKey, + name: openingKey.replaceAll('_', ''), + count: 0, + ), + ) + .name ?? + openingKey.replaceAll('_', ' '), + style: Styles.boardPreviewTitle, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ], + }, + ), + Icon( + switch (angle) { + PuzzleTheme(themeKey: final themeKey) => themeKey.icon, + PuzzleOpening() => PuzzleIcons.opening, }, - spacing: 8.0, - backgroundColor: context.lichessColors.error, - foregroundColor: Colors.white, - label: context.l10n.delete, + size: 34, + color: DefaultTextStyle.of(context).style.color?.withValues(alpha: 0.6), ), + if (puzzle != null) + Text( + puzzle.puzzle.sideToMove == Side.white + ? context.l10n.whitePlays + : context.l10n.blackPlays, + ) + else + const Text('No puzzles available, please go online to fetch them.'), ], ), - child: SmallBoardPreview( - orientation: preview?.orientation ?? Side.white, - fen: preview?.initialFen ?? kEmptyFen, - lastMove: preview?.initialMove, - description: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: switch (angle) { - PuzzleTheme(themeKey: final themeKey) => [ - Text( - themeKey.l10n(context.l10n).name, - style: Styles.boardPreviewTitle, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - Text( - themeKey.l10n(context.l10n).description, - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: TextStyle( - height: 1.2, - fontSize: 12.0, - color: DefaultTextStyle.of(context) - .style - .color - ?.withValues(alpha: 0.6), - ), - ), - ], - PuzzleOpening(key: final openingKey) => [ - Text( - flatOpenings.valueOrNull - ?.firstWhere( - (o) => o.key == openingKey, - orElse: () => ( - key: openingKey, - name: openingKey.replaceAll( - '_', - '', - ), - count: 0 - ), - ) - .name ?? - openingKey.replaceAll('_', ' '), - style: Styles.boardPreviewTitle, - maxLines: 3, - overflow: TextOverflow.ellipsis, - ), - ], - }, - ), - Icon( - switch (angle) { - PuzzleTheme(themeKey: final themeKey) => themeKey.icon, - PuzzleOpening() => PuzzleIcons.opening, - }, - size: 34, - color: DefaultTextStyle.of(context) - .style - .color - ?.withValues(alpha: 0.6), - ), - if (puzzle != null) - Text( - puzzle.puzzle.sideToMove == Side.white - ? context.l10n.whitePlays - : context.l10n.blackPlays, - ) - else - const Text( - 'No puzzles available, please go online to fetch them.', - ), - ], - ), - onTap: puzzle != null ? onTap : null, - ), - ); + onTap: puzzle != null ? onTap : null, + ), + ); } return puzzle.maybeWhen( diff --git a/lib/src/view/puzzle/puzzle_themes_screen.dart b/lib/src/view/puzzle/puzzle_themes_screen.dart index 5f080eae17..9d90db4ab6 100644 --- a/lib/src/view/puzzle/puzzle_themes_screen.dart +++ b/lib/src/view/puzzle/puzzle_themes_screen.dart @@ -18,12 +18,8 @@ import 'puzzle_screen.dart'; @riverpod final _themesProvider = FutureProvider.autoDispose< - ( - bool, - IMap, - IMap?, - bool, - )>((ref) async { + (bool, IMap, IMap?, bool) +>((ref) async { final connectivity = await ref.watch(connectivityChangesProvider.future); final savedThemes = await ref.watch(savedThemeBatchesProvider.future); IMap? onlineThemes; @@ -33,12 +29,7 @@ final _themesProvider = FutureProvider.autoDispose< onlineThemes = null; } final savedOpenings = await ref.watch(savedOpeningBatchesProvider.future); - return ( - connectivity.isOnline, - savedThemes, - onlineThemes, - savedOpenings.isNotEmpty - ); + return (connectivity.isOnline, savedThemes, onlineThemes, savedOpenings.isNotEmpty); }); class PuzzleThemesScreen extends StatelessWidget { @@ -47,9 +38,7 @@ class PuzzleThemesScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.puzzlePuzzleThemes), - ), + appBar: PlatformAppBar(title: Text(context.l10n.puzzlePuzzleThemes)), body: const _Body(), ); } @@ -63,21 +52,20 @@ class _Body extends ConsumerWidget { // skip recommended category since we display it on the puzzle tab screen final list = ref.watch(puzzleThemeCategoriesProvider).skip(1).toList(); final themes = ref.watch(_themesProvider); - final expansionTileColor = Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoColors.secondaryLabel.resolveFrom(context) - : null; + final expansionTileColor = + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoColors.secondaryLabel.resolveFrom(context) + : null; return themes.when( data: (data) { - final (hasConnectivity, savedThemes, onlineThemes, hasSavedOpenings) = - data; + final (hasConnectivity, savedThemes, onlineThemes, hasSavedOpenings) = data; final openingsAvailable = hasConnectivity || hasSavedOpenings; return ListView( children: [ Theme( - data: - Theme.of(context).copyWith(dividerColor: Colors.transparent), + data: Theme.of(context).copyWith(dividerColor: Colors.transparent), child: Opacity( opacity: openingsAvailable ? 1 : 0.5, child: ExpansionTile( @@ -85,14 +73,15 @@ class _Body extends ConsumerWidget { collapsedIconColor: expansionTileColor, title: Text(context.l10n.puzzleByOpenings), trailing: const Icon(Icons.keyboard_arrow_right), - onExpansionChanged: openingsAvailable - ? (expanded) { - pushPlatformRoute( - context, - builder: (ctx) => const OpeningThemeScreen(), - ); - } - : null, + onExpansionChanged: + openingsAvailable + ? (expanded) { + pushPlatformRoute( + context, + builder: (ctx) => const OpeningThemeScreen(), + ); + } + : null, ), ), ), @@ -107,8 +96,7 @@ class _Body extends ConsumerWidget { ); }, loading: () => const Center(child: CircularProgressIndicator.adaptive()), - error: (error, stack) => - const Center(child: Text('Could not load themes.')), + error: (error, stack) => const Center(child: Text('Could not load themes.')), ); } } @@ -130,10 +118,7 @@ class _Category extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final themeCountStyle = TextStyle( fontSize: 12, - color: textShade( - context, - Styles.subtitleOpacity, - ), + color: textShade(context, Styles.subtitleOpacity), ); final (categoryName, themes) = category; @@ -145,79 +130,67 @@ class _Category extends ConsumerWidget { children: [ ListSection( hasLeading: true, - children: themes.map( - (theme) { - final isThemeAvailable = - hasConnectivity || savedThemes.containsKey(theme); + children: + themes.map((theme) { + final isThemeAvailable = hasConnectivity || savedThemes.containsKey(theme); - return Opacity( - opacity: isThemeAvailable ? 1 : 0.5, - child: PlatformListTile( - leading: Icon(theme.icon), - trailing: hasConnectivity && - onlineThemes?.containsKey(theme) == true - ? Padding( - padding: const EdgeInsets.only(left: 6.0), - child: Text( - '${onlineThemes![theme]!.count}', - style: themeCountStyle, - ), - ) - : savedThemes.containsKey(theme) - ? Padding( + return Opacity( + opacity: isThemeAvailable ? 1 : 0.5, + child: PlatformListTile( + leading: Icon(theme.icon), + trailing: + hasConnectivity && onlineThemes?.containsKey(theme) == true + ? Padding( padding: const EdgeInsets.only(left: 6.0), child: Text( - '${savedThemes[theme]!}', + '${onlineThemes![theme]!.count}', style: themeCountStyle, ), ) - : null, - title: Padding( - padding: Theme.of(context).platform == TargetPlatform.iOS - ? const EdgeInsets.only(top: 6.0) - : EdgeInsets.zero, - child: Text( - theme.l10n(context.l10n).name, - style: Theme.of(context).platform == TargetPlatform.iOS - ? TextStyle( - color: CupertinoTheme.of(context) - .textTheme - .textStyle - .color, + : savedThemes.containsKey(theme) + ? Padding( + padding: const EdgeInsets.only(left: 6.0), + child: Text('${savedThemes[theme]!}', style: themeCountStyle), ) - : null, + : null, + title: Padding( + padding: + Theme.of(context).platform == TargetPlatform.iOS + ? const EdgeInsets.only(top: 6.0) + : EdgeInsets.zero, + child: Text( + theme.l10n(context.l10n).name, + style: + Theme.of(context).platform == TargetPlatform.iOS + ? TextStyle( + color: CupertinoTheme.of(context).textTheme.textStyle.color, + ) + : null, + ), ), - ), - subtitle: Padding( - padding: const EdgeInsets.only(bottom: 6.0), - child: Text( - theme.l10n(context.l10n).description, - maxLines: 10, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: textShade( - context, - Styles.subtitleOpacity, - ), + subtitle: Padding( + padding: const EdgeInsets.only(bottom: 6.0), + child: Text( + theme.l10n(context.l10n).description, + maxLines: 10, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: textShade(context, Styles.subtitleOpacity)), ), ), + isThreeLine: true, + onTap: + isThemeAvailable + ? () { + pushPlatformRoute( + context, + rootNavigator: true, + builder: (context) => PuzzleScreen(angle: PuzzleTheme(theme)), + ); + } + : null, ), - isThreeLine: true, - onTap: isThemeAvailable - ? () { - pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => PuzzleScreen( - angle: PuzzleTheme(theme), - ), - ); - } - : null, - ), - ); - }, - ).toList(), + ); + }).toList(), ), ], ), diff --git a/lib/src/view/puzzle/storm_clock.dart b/lib/src/view/puzzle/storm_clock.dart index c02d586663..be4dbd0e7f 100644 --- a/lib/src/view/puzzle/storm_clock.dart +++ b/lib/src/view/puzzle/storm_clock.dart @@ -16,23 +16,20 @@ class StormClockWidget extends StatefulWidget { _ClockState createState() => _ClockState(); } -class _ClockState extends State - with SingleTickerProviderStateMixin { +class _ClockState extends State with SingleTickerProviderStateMixin { // ignore: avoid-late-keyword late AnimationController _controller; // ignore: avoid-late-keyword - late final Animation _bonusFadeAnimation = - Tween(begin: 1.0, end: 0.0).animate( - CurvedAnimation(parent: _controller, curve: Curves.easeOut), - ); + late final Animation _bonusFadeAnimation = Tween( + begin: 1.0, + end: 0.0, + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut)); // ignore: avoid-late-keyword late final Animation _bonusSlideAnimation = Tween( begin: const Offset(0.7, 0.0), end: const Offset(0.7, -1.0), - ).animate( - CurvedAnimation(parent: _controller, curve: Curves.easeOut), - ); + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut)); StreamSubscription<(Duration, int?)>? streamSubscription; @@ -46,10 +43,8 @@ class _ClockState extends State // declaring as late final causes an error because the widget is being disposed // after the clock start - _controller = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 1500), - )..addStatusListener((status) { + _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 1500)) + ..addStatusListener((status) { if (status == AnimationStatus.completed) { setState(() { currentBonusSeconds = null; @@ -98,9 +93,8 @@ class _ClockState extends State @override Widget build(BuildContext build) { final brightness = Theme.of(context).brightness; - final clockStyle = brightness == Brightness.dark - ? _ClockStyle.darkThemeStyle - : _ClockStyle.lightThemeStyle; + final clockStyle = + brightness == Brightness.dark ? _ClockStyle.darkThemeStyle : _ClockStyle.lightThemeStyle; final minutes = time.inMinutes.remainder(60).toString().padLeft(2, '0'); final seconds = time.inSeconds.remainder(60).toString().padLeft(2, '0'); @@ -120,9 +114,10 @@ class _ClockState extends State child: Text( '${currentBonusSeconds! > 0 ? '+' : ''}$currentBonusSeconds', style: TextStyle( - color: currentBonusSeconds! < 0 - ? context.lichessColors.error - : context.lichessColors.good, + color: + currentBonusSeconds! < 0 + ? context.lichessColors.error + : context.lichessColors.good, fontWeight: FontWeight.bold, fontSize: 20, fontFeatures: const [FontFeature.tabularFigures()], @@ -139,20 +134,17 @@ class _ClockState extends State ), duration: const Duration(milliseconds: 500), builder: (context, Duration value, _) { - final minutes = - value.inMinutes.remainder(60).toString().padLeft(2, '0'); - final seconds = - value.inSeconds.remainder(60).toString().padLeft(2, '0'); + final minutes = value.inMinutes.remainder(60).toString().padLeft(2, '0'); + final seconds = value.inSeconds.remainder(60).toString().padLeft(2, '0'); return Text( '$minutes:$seconds', style: TextStyle( - color: currentBonusSeconds! < 0 - ? context.lichessColors.error - : context.lichessColors.good, + color: + currentBonusSeconds! < 0 + ? context.lichessColors.error + : context.lichessColors.good, fontSize: _kClockFontSize, - fontFeatures: const [ - FontFeature.tabularFigures(), - ], + fontFeatures: const [FontFeature.tabularFigures()], ), ); }, @@ -161,13 +153,9 @@ class _ClockState extends State Text( '$minutes:$seconds', style: TextStyle( - color: isActive - ? clockStyle.activeTextColor - : clockStyle.textColor, + color: isActive ? clockStyle.activeTextColor : clockStyle.textColor, fontSize: _kClockFontSize, - fontFeatures: const [ - FontFeature.tabularFigures(), - ], + fontFeatures: const [FontFeature.tabularFigures()], ), ), ], @@ -178,19 +166,10 @@ class _ClockState extends State } enum _ClockStyle { - darkThemeStyle( - textColor: Colors.grey, - activeTextColor: Colors.white, - ), - lightThemeStyle( - textColor: Colors.grey, - activeTextColor: Colors.black, - ); - - const _ClockStyle({ - required this.textColor, - required this.activeTextColor, - }); + darkThemeStyle(textColor: Colors.grey, activeTextColor: Colors.white), + lightThemeStyle(textColor: Colors.grey, activeTextColor: Colors.black); + + const _ClockStyle({required this.textColor, required this.activeTextColor}); final Color textColor; final Color activeTextColor; diff --git a/lib/src/view/puzzle/storm_dashboard.dart b/lib/src/view/puzzle/storm_dashboard.dart index 1d7e7ee5d0..25ebd5cfa1 100644 --- a/lib/src/view/puzzle/storm_dashboard.dart +++ b/lib/src/view/puzzle/storm_dashboard.dart @@ -54,44 +54,23 @@ class _Body extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: - Styles.sectionTopPadding.add(Styles.horizontalBodyPadding), - child: StatCardRow( - [ - StatCard( - context.l10n.stormAllTime, - value: data.highScore.allTime.toString(), - ), - StatCard( - context.l10n.stormThisMonth, - value: data.highScore.month.toString(), - ), - ], - ), + padding: Styles.sectionTopPadding.add(Styles.horizontalBodyPadding), + child: StatCardRow([ + StatCard(context.l10n.stormAllTime, value: data.highScore.allTime.toString()), + StatCard(context.l10n.stormThisMonth, value: data.highScore.month.toString()), + ]), ), Padding( - padding: - Styles.sectionTopPadding.add(Styles.horizontalBodyPadding), - child: StatCardRow( - [ - StatCard( - context.l10n.stormThisWeek, - value: data.highScore.week.toString(), - ), - StatCard( - context.l10n.today, - value: data.highScore.day.toString(), - ), - ], - ), + padding: Styles.sectionTopPadding.add(Styles.horizontalBodyPadding), + child: StatCardRow([ + StatCard(context.l10n.stormThisWeek, value: data.highScore.week.toString()), + StatCard(context.l10n.today, value: data.highScore.day.toString()), + ]), ), if (data.dayHighscores.isNotEmpty) ...[ Padding( padding: Styles.bodySectionPadding, - child: Text( - context.l10n.stormBestRunOfDay, - style: Styles.sectionTitle, - ), + child: Text(context.l10n.stormBestRunOfDay, style: Styles.sectionTitle), ), Padding( padding: Styles.horizontalBodyPadding, @@ -100,22 +79,10 @@ class _Body extends ConsumerWidget { children: [ TableRow( children: [ - Text( - textAlign: TextAlign.center, - context.l10n.stormScore, - ), - Text( - textAlign: TextAlign.center, - context.l10n.stormTime, - ), - Text( - textAlign: TextAlign.center, - context.l10n.stormHighestSolved, - ), - Text( - textAlign: TextAlign.center, - context.l10n.stormRuns, - ), + Text(textAlign: TextAlign.center, context.l10n.stormScore), + Text(textAlign: TextAlign.center, context.l10n.stormTime), + Text(textAlign: TextAlign.center, context.l10n.stormHighestSolved), + Text(textAlign: TextAlign.center, context.l10n.stormRuns), ], ), ], @@ -133,10 +100,8 @@ class _Body extends ConsumerWidget { child: Padding( padding: Styles.horizontalBodyPadding, child: Text( - dateFormat - .format(data.dayHighscores[entryIndex].day), - style: - const TextStyle(fontWeight: FontWeight.w600), + dateFormat.format(data.dayHighscores[entryIndex].day), + style: const TextStyle(fontWeight: FontWeight.w600), ), ), ); @@ -144,20 +109,15 @@ class _Body extends ConsumerWidget { // Data row final entryIndex = (index - 1) ~/ 2; return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 15, - vertical: 10, - ), + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), child: Table( - defaultVerticalAlignment: - TableCellVerticalAlignment.middle, + defaultVerticalAlignment: TableCellVerticalAlignment.middle, children: [ TableRow( children: [ Text( textAlign: TextAlign.center, - data.dayHighscores[entryIndex].score - .toString(), + data.dayHighscores[entryIndex].score.toString(), style: TextStyle( color: context.lichessColors.brag, fontWeight: FontWeight.bold, @@ -169,13 +129,11 @@ class _Body extends ConsumerWidget { ), Text( textAlign: TextAlign.center, - data.dayHighscores[entryIndex].highest - .toString(), + data.dayHighscores[entryIndex].highest.toString(), ), Text( textAlign: TextAlign.center, - data.dayHighscores[entryIndex].runs - .toString(), + data.dayHighscores[entryIndex].runs.toString(), ), ], ), @@ -187,17 +145,13 @@ class _Body extends ConsumerWidget { ), ), ] else - Center( - child: Text(context.l10n.mobilePuzzleStormNothingToShow), - ), + Center(child: Text(context.l10n.mobilePuzzleStormNothingToShow)), ], ), ); }, error: (e, s) { - debugPrint( - 'SEVERE: [StormDashboardModel] could not load storm dashboard; $e\n$s', - ); + debugPrint('SEVERE: [StormDashboardModel] could not load storm dashboard; $e\n$s'); return const SafeArea(child: Text('Could not load dashboard')); }, loading: () => _Loading(), diff --git a/lib/src/view/puzzle/storm_screen.dart b/lib/src/view/puzzle/storm_screen.dart index 63e263fc89..2f4b7867e1 100644 --- a/lib/src/view/puzzle/storm_screen.dart +++ b/lib/src/view/puzzle/storm_screen.dart @@ -71,9 +71,7 @@ class _Load extends ConsumerWidget { }, loading: () => const CenterLoadingIndicator(), error: (e, s) { - debugPrint( - 'SEVERE: [PuzzleStormScreen] could not load streak; $e\n$s', - ); + debugPrint('SEVERE: [PuzzleStormScreen] could not load streak; $e\n$s'); return Center( child: BoardTable( topTable: kEmptyWidget, @@ -124,14 +122,15 @@ class _Body extends ConsumerWidget { final NavigatorState navigator = Navigator.of(context); final shouldPop = await showAdaptiveDialog( context: context, - builder: (context) => YesNoDialog( - title: Text(context.l10n.mobileAreYouSure), - content: Text(context.l10n.mobilePuzzleStormConfirmEndRun), - onYes: () { - return Navigator.of(context).pop(true); - }, - onNo: () => Navigator.of(context).pop(false), - ), + builder: + (context) => YesNoDialog( + title: Text(context.l10n.mobileAreYouSure), + content: Text(context.l10n.mobilePuzzleStormConfirmEndRun), + onYes: () { + return Navigator.of(context).pop(true); + }, + onNo: () => Navigator.of(context).pop(false), + ), ); if (shouldPop ?? false) { navigator.pop(); @@ -149,23 +148,23 @@ class _Body extends ConsumerWidget { lastMove: stormState.lastMove as NormalMove?, fen: stormState.position.fen, gameData: GameData( - playerSide: !stormState.firstMovePlayed || - stormState.mode == StormMode.ended || - stormState.position.isGameOver - ? PlayerSide.none - : stormState.pov == Side.white + playerSide: + !stormState.firstMovePlayed || + stormState.mode == StormMode.ended || + stormState.position.isGameOver + ? PlayerSide.none + : stormState.pov == Side.white ? PlayerSide.white : PlayerSide.black, - isCheck: boardPreferences.boardHighlights && - stormState.position.isCheck, + isCheck: boardPreferences.boardHighlights && stormState.position.isCheck, sideToMove: stormState.position.turn, validMoves: stormState.validMoves, promotionMove: stormState.promotionMove, - onMove: (move, {isDrop, captured}) => - ref.read(ctrlProvider.notifier).onUserMove(move), - onPromotionSelection: (role) => ref - .read(ctrlProvider.notifier) - .onPromotionSelection(role), + onMove: + (move, {isDrop, captured}) => + ref.read(ctrlProvider.notifier).onUserMove(move), + onPromotionSelection: + (role) => ref.read(ctrlProvider.notifier).onPromotionSelection(role), ), topTable: _TopTable(data), bottomTable: _Combo(stormState.combo), @@ -180,14 +179,12 @@ class _Body extends ConsumerWidget { return Theme.of(context).platform == TargetPlatform.android ? AndroidGesturesExclusionWidget( - boardKey: boardKey, - shouldExcludeGesturesOnFocusGained: () => - stormState.mode == StormMode.initial || - stormState.mode == StormMode.running, - shouldSetImmersiveMode: - boardPreferences.immersiveModeWhilePlaying ?? false, - child: content, - ) + boardKey: boardKey, + shouldExcludeGesturesOnFocusGained: + () => stormState.mode == StormMode.initial || stormState.mode == StormMode.running, + shouldSetImmersiveMode: boardPreferences.immersiveModeWhilePlaying ?? false, + child: content, + ) : content; } } @@ -201,57 +198,31 @@ Future _stormInfoDialogBuilder(BuildContext context) { text: TextSpan( style: DefaultTextStyle.of(context).style, children: const [ - TextSpan( - text: '\n', - ), + TextSpan(text: '\n'), TextSpan( text: 'Each puzzle grants one point. The goal is to get as many points as you can before the time runs out.', ), - TextSpan( - text: '\n\n', - ), - TextSpan( - text: 'Combo bar\n', - style: TextStyle(fontSize: 18), - ), + TextSpan(text: '\n\n'), + TextSpan(text: 'Combo bar\n', style: TextStyle(fontSize: 18)), TextSpan( text: 'Each correct ', children: [ - TextSpan( - text: 'move', - style: TextStyle(fontWeight: FontWeight.bold), - ), + TextSpan(text: 'move', style: TextStyle(fontWeight: FontWeight.bold)), TextSpan( text: ' fills the combo bar. When the bar is full, you get a time bonus, and you increase the value of the next bonus.', ), ], ), - TextSpan( - text: '\n\n', - ), - TextSpan( - text: 'Bonus values:\n', - ), - TextSpan( - text: '• 5 moves: +3s\n', - ), - TextSpan( - text: '• 12 moves: +5s\n', - ), - TextSpan( - text: '• 20 moves: +7s\n', - ), - TextSpan( - text: '• 30 moves: +10s\n', - ), - TextSpan( - text: '• Then +10s every 10 other moves.\n', - ), - TextSpan( - text: '\n', - ), + TextSpan(text: '\n\n'), + TextSpan(text: 'Bonus values:\n'), + TextSpan(text: '• 5 moves: +3s\n'), + TextSpan(text: '• 12 moves: +5s\n'), + TextSpan(text: '• 20 moves: +7s\n'), + TextSpan(text: '• 30 moves: +10s\n'), + TextSpan(text: '• Then +10s every 10 other moves.\n'), + TextSpan(text: '\n'), TextSpan( text: 'When you play a wrong move, the combo bar is depleted, and you lose 10 seconds.', @@ -291,8 +262,7 @@ class _TopTable extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final stormState = - ref.watch(stormControllerProvider(data.puzzles, data.timestamp)); + final stormState = ref.watch(stormControllerProvider(data.puzzles, data.timestamp)); return Padding( padding: const EdgeInsets.symmetric(horizontal: 10), child: Row( @@ -311,10 +281,7 @@ class _TopTable extends ConsumerWidget { context.l10n.stormMoveToStart, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), Text( stormState.pov == Side.white @@ -322,20 +289,14 @@ class _TopTable extends ConsumerWidget { : context.l10n.stormYouPlayTheBlackPiecesInAllPuzzles, maxLines: 2, overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 12, - ), + style: const TextStyle(fontSize: 12), ), ], ), ), ) else ...[ - Icon( - LichessIcons.storm, - size: 50.0, - color: context.lichessColors.brag, - ), + Icon(LichessIcons.storm, size: 50.0, color: context.lichessColors.brag), const SizedBox(width: 8), Text( stormState.numSolved.toString(), @@ -363,8 +324,7 @@ class _Combo extends ConsumerStatefulWidget { ConsumerState<_Combo> createState() => _ComboState(); } -class _ComboState extends ConsumerState<_Combo> - with SingleTickerProviderStateMixin { +class _ComboState extends ConsumerState<_Combo> with SingleTickerProviderStateMixin { late AnimationController _controller; @override @@ -388,14 +348,12 @@ class _ComboState extends ConsumerState<_Combo> if (ref.read(boardPreferencesProvider).hapticFeedback) { HapticFeedback.heavyImpact(); } - _controller.animateTo(1.0, curve: Curves.easeInOut).then( - (_) async { - await Future.delayed(const Duration(milliseconds: 300)); - if (mounted) { - _controller.value = 0; - } - }, - ); + _controller.animateTo(1.0, curve: Curves.easeInOut).then((_) async { + await Future.delayed(const Duration(milliseconds: 300)); + if (mounted) { + _controller.value = 0; + } + }); return; } _controller.animateTo(newVal, curve: Curves.easeIn); @@ -419,120 +377,113 @@ class _ComboState extends ConsumerState<_Combo> ); return AnimatedBuilder( animation: _controller, - builder: (context, child) => LayoutBuilder( - builder: (context, constraints) { - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - widget.combo.current.toString(), - style: TextStyle( - fontSize: 26, - height: 1.0, - fontWeight: FontWeight.bold, - color: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoTheme.of(context) - .textTheme - .textStyle - .color - : null, - ), - ), - Text( - context.l10n.stormCombo, - style: TextStyle( - color: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoTheme.of(context) - .textTheme - .textStyle - .color - : null, - ), - ), - ], - ), - ), - SizedBox( - width: constraints.maxWidth * 0.65, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - height: 25, - child: Container( - decoration: BoxDecoration( - boxShadow: _controller.value == 1.0 - ? [ - BoxShadow( - color: - indicatorColor.withValues(alpha: 0.3), - blurRadius: 10.0, - spreadRadius: 2.0, - ), - ] - : [], + builder: + (context, child) => LayoutBuilder( + builder: (context, constraints) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + widget.combo.current.toString(), + style: TextStyle( + fontSize: 26, + height: 1.0, + fontWeight: FontWeight.bold, + color: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoTheme.of(context).textTheme.textStyle.color + : null, + ), ), - child: ClipRRect( - borderRadius: - const BorderRadius.all(Radius.circular(3.0)), - child: LinearProgressIndicator( - value: _controller.value, - valueColor: - AlwaysStoppedAnimation(indicatorColor), + Text( + context.l10n.stormCombo, + style: TextStyle( + color: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoTheme.of(context).textTheme.textStyle.color + : null, ), ), - ), + ], ), - const SizedBox(height: 4), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: - StormCombo.levelBonus.mapIndexed((index, level) { - final isCurrentLevel = index < lvl; - return AnimatedContainer( - alignment: Alignment.center, - curve: Curves.easeIn, - duration: const Duration(milliseconds: 1000), - width: 28 * - MediaQuery.textScalerOf(context).scale(14) / - 14, - height: 24 * - MediaQuery.textScalerOf(context).scale(14) / - 14, - decoration: isCurrentLevel - ? BoxDecoration( - color: comboShades[index], - borderRadius: const BorderRadius.all( - Radius.circular(3.0), - ), - ) - : null, - child: Text( - '${level}s', - style: TextStyle( - color: isCurrentLevel - ? Theme.of(context).colorScheme.onSecondary - : null, + ), + SizedBox( + width: constraints.maxWidth * 0.65, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + height: 25, + child: Container( + decoration: BoxDecoration( + boxShadow: + _controller.value == 1.0 + ? [ + BoxShadow( + color: indicatorColor.withValues(alpha: 0.3), + blurRadius: 10.0, + spreadRadius: 2.0, + ), + ] + : [], + ), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(3.0)), + child: LinearProgressIndicator( + value: _controller.value, + valueColor: AlwaysStoppedAnimation(indicatorColor), + ), ), ), - ); - }).toList(), + ), + const SizedBox(height: 4), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: + StormCombo.levelBonus.mapIndexed((index, level) { + final isCurrentLevel = index < lvl; + return AnimatedContainer( + alignment: Alignment.center, + curve: Curves.easeIn, + duration: const Duration(milliseconds: 1000), + width: 28 * MediaQuery.textScalerOf(context).scale(14) / 14, + height: 24 * MediaQuery.textScalerOf(context).scale(14) / 14, + decoration: + isCurrentLevel + ? BoxDecoration( + color: comboShades[index], + borderRadius: const BorderRadius.all( + Radius.circular(3.0), + ), + ) + : null, + child: Text( + '${level}s', + style: TextStyle( + color: + isCurrentLevel + ? Theme.of(context).colorScheme.onSecondary + : null, + ), + ), + ); + }).toList(), + ), + ], ), - ], - ), - ), - const SizedBox(width: 10.0), - ], - ); - }, - ), + ), + const SizedBox(width: 10.0), + ], + ); + }, + ), ); } @@ -550,9 +501,7 @@ class _ComboState extends ConsumerState<_Combo> final double newR = (r - i * step).clamp(0, 255); final double newG = (g - i * step).clamp(0, 255); final double newB = (b - i * step).clamp(0, 255); - shades.add( - Color.from(alpha: baseColor.a, red: newR, green: newG, blue: newB), - ); + shades.add(Color.from(alpha: baseColor.a, red: newR, green: newG, blue: newB)); } // Generate lighter shades @@ -560,9 +509,7 @@ class _ComboState extends ConsumerState<_Combo> final double newR = (r + i * step).clamp(0, 255); final double newG = (g + i * step).clamp(0, 255); final double newB = (b + i * step).clamp(0, 255); - shades.add( - Color.from(alpha: baseColor.a, red: newR, green: newG, blue: newB), - ); + shades.add(Color.from(alpha: baseColor.a, red: newR, green: newG, blue: newB)); } if (light) { @@ -604,13 +551,14 @@ class _BottomBar extends ConsumerWidget { icon: LichessIcons.flag, label: context.l10n.stormEndRun.split('(').first.trimRight(), showLabel: true, - onTap: stormState.puzzleIndex >= 1 - ? () { - if (stormState.clock.startAt != null) { - stormState.clock.sendEnd(); + onTap: + stormState.puzzleIndex >= 1 + ? () { + if (stormState.clock.startAt != null) { + stormState.clock.sendEnd(); + } } - } - : null, + : null, ), if (stormState.mode == StormMode.ended && stormState.stats != null) BottomBarButton( @@ -657,33 +605,24 @@ class _RunStatsPopupState extends ConsumerState<_RunStatsPopup> { @override Widget build(BuildContext context) { final puzzleList = widget.stats.historyFilter(filter); - final highScoreWidgets = widget.stats.newHigh != null - ? [ - const SizedBox(height: 16), - ListTile( - leading: Icon( - LichessIcons.storm, - size: 46, - color: context.lichessColors.brag, - ), - title: Text( - newHighTitle(context, widget.stats.newHigh!), - style: Styles.sectionTitle.copyWith( - color: context.lichessColors.brag, + final highScoreWidgets = + widget.stats.newHigh != null + ? [ + const SizedBox(height: 16), + ListTile( + leading: Icon(LichessIcons.storm, size: 46, color: context.lichessColors.brag), + title: Text( + newHighTitle(context, widget.stats.newHigh!), + style: Styles.sectionTitle.copyWith(color: context.lichessColors.brag), ), - ), - subtitle: Text( - context.l10n.stormPreviousHighscoreWasX( - widget.stats.newHigh!.prev.toString(), - ), - style: TextStyle( - color: context.lichessColors.brag, + subtitle: Text( + context.l10n.stormPreviousHighscoreWasX(widget.stats.newHigh!.prev.toString()), + style: TextStyle(color: context.lichessColors.brag), ), ), - ), - const SizedBox(height: 10), - ] - : null; + const SizedBox(height: 10), + ] + : null; return SafeArea( child: ListView( @@ -691,34 +630,20 @@ class _RunStatsPopupState extends ConsumerState<_RunStatsPopup> { if (highScoreWidgets != null) ...highScoreWidgets, ListSection( cupertinoAdditionalDividerMargin: 6, - header: Text( - '${widget.stats.score} ${context.l10n.stormPuzzlesSolved}', - ), + header: Text('${widget.stats.score} ${context.l10n.stormPuzzlesSolved}'), children: [ - _StatsRow( - context.l10n.stormMoves, - widget.stats.moves.toString(), - ), + _StatsRow(context.l10n.stormMoves, widget.stats.moves.toString()), _StatsRow( context.l10n.accuracy, '${(((widget.stats.moves - widget.stats.errors) / widget.stats.moves) * 100).toStringAsFixed(2)}%', ), - _StatsRow( - context.l10n.stormCombo, - widget.stats.comboBest.toString(), - ), - _StatsRow( - context.l10n.stormTime, - '${widget.stats.time.inSeconds}s', - ), + _StatsRow(context.l10n.stormCombo, widget.stats.comboBest.toString()), + _StatsRow(context.l10n.stormTime, '${widget.stats.time.inSeconds}s'), _StatsRow( context.l10n.stormTimePerMove, '${widget.stats.timePerMove.toStringAsFixed(1)}s', ), - _StatsRow( - context.l10n.stormHighestSolved, - widget.stats.highest.toString(), - ), + _StatsRow(context.l10n.stormHighestSolved, widget.stats.highest.toString()), ], ), const SizedBox(height: 10.0), @@ -741,23 +666,19 @@ class _RunStatsPopupState extends ConsumerState<_RunStatsPopup> { children: [ Row( children: [ - Text( - context.l10n.stormPuzzlesPlayed, - style: Styles.sectionTitle, - ), + Text(context.l10n.stormPuzzlesPlayed, style: Styles.sectionTitle), const Spacer(), Tooltip( excludeFromSemantics: true, message: context.l10n.stormFailedPuzzles, child: PlatformIconButton( semanticsLabel: context.l10n.stormFailedPuzzles, - icon: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoIcons.clear_fill - : Icons.close, - onTap: () => setState( - () => - filter = filter.copyWith(failed: !filter.failed), - ), + icon: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoIcons.clear_fill + : Icons.close, + onTap: + () => setState(() => filter = filter.copyWith(failed: !filter.failed)), highlighted: filter.failed, ), ), @@ -766,12 +687,11 @@ class _RunStatsPopupState extends ConsumerState<_RunStatsPopup> { excludeFromSemantics: true, child: PlatformIconButton( semanticsLabel: context.l10n.stormSlowPuzzles, - icon: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoIcons.hourglass - : Icons.hourglass_bottom, - onTap: () => setState( - () => filter = filter.copyWith(slow: !filter.slow), - ), + icon: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoIcons.hourglass + : Icons.hourglass_bottom, + onTap: () => setState(() => filter = filter.copyWith(slow: !filter.slow)), highlighted: filter.slow, ), ), @@ -781,10 +701,7 @@ class _RunStatsPopupState extends ConsumerState<_RunStatsPopup> { if (puzzleList.isNotEmpty) PuzzleHistoryPreview(puzzleList) else - Center( - child: - Text(context.l10n.mobilePuzzleStormFilterNothingToShow), - ), + Center(child: Text(context.l10n.mobilePuzzleStormFilterNothingToShow)), ], ), ), @@ -819,10 +736,7 @@ class _StatsRow extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(label), - if (value != null) Text(value!), - ], + children: [Text(label), if (value != null) Text(value!)], ), ); } @@ -855,11 +769,10 @@ class _StormDashboardButton extends ConsumerWidget { return const SizedBox.shrink(); } - void _showDashboard(BuildContext context, AuthSessionState session) => - pushPlatformRoute( - context, - rootNavigator: true, - fullscreenDialog: true, - builder: (_) => StormDashboardModal(user: session.user), - ); + void _showDashboard(BuildContext context, AuthSessionState session) => pushPlatformRoute( + context, + rootNavigator: true, + fullscreenDialog: true, + builder: (_) => StormDashboardModal(user: session.user), + ); } diff --git a/lib/src/view/puzzle/streak_screen.dart b/lib/src/view/puzzle/streak_screen.dart index db086e705c..f41847b616 100644 --- a/lib/src/view/puzzle/streak_screen.dart +++ b/lib/src/view/puzzle/streak_screen.dart @@ -39,10 +39,7 @@ class StreakScreen extends StatelessWidget { Widget build(BuildContext context) { return const WakelockWidget( child: PlatformScaffold( - appBar: PlatformAppBar( - actions: [ToggleSoundButton()], - title: Text('Puzzle Streak'), - ), + appBar: PlatformAppBar(actions: [ToggleSoundButton()], title: Text('Puzzle Streak')), body: _Load(), ), ); @@ -70,9 +67,7 @@ class _Load extends ConsumerWidget { }, loading: () => const Center(child: CircularProgressIndicator.adaptive()), error: (e, s) { - debugPrint( - 'SEVERE: [StreakScreen] could not load streak; $e\n$s', - ); + debugPrint('SEVERE: [StreakScreen] could not load streak; $e\n$s'); return Center( child: BoardTable( topTable: kEmptyWidget, @@ -88,29 +83,28 @@ class _Load extends ConsumerWidget { } class _Body extends ConsumerWidget { - const _Body({ - required this.initialPuzzleContext, - required this.streak, - }); + const _Body({required this.initialPuzzleContext, required this.streak}); final PuzzleContext initialPuzzleContext; final PuzzleStreak streak; @override Widget build(BuildContext context, WidgetRef ref) { - final ctrlProvider = - puzzleControllerProvider(initialPuzzleContext, initialStreak: streak); + final ctrlProvider = puzzleControllerProvider(initialPuzzleContext, initialStreak: streak); final puzzleState = ref.watch(ctrlProvider); - ref.listen(ctrlProvider.select((s) => s.nextPuzzleStreakFetchError), - (_, shouldShowDialog) { + ref.listen(ctrlProvider.select((s) => s.nextPuzzleStreakFetchError), ( + _, + shouldShowDialog, + ) { if (shouldShowDialog) { showAdaptiveDialog( context: context, - builder: (context) => _RetryFetchPuzzleDialog( - initialPuzzleContext: initialPuzzleContext, - streak: streak, - ), + builder: + (context) => _RetryFetchPuzzleDialog( + initialPuzzleContext: initialPuzzleContext, + streak: streak, + ), ); } }); @@ -125,14 +119,14 @@ class _Body extends ConsumerWidget { fen: puzzleState.fen, lastMove: puzzleState.lastMove as NormalMove?, gameData: GameData( - playerSide: puzzleState.mode == PuzzleMode.load || - puzzleState.position.isGameOver - ? PlayerSide.none - : puzzleState.mode == PuzzleMode.view + playerSide: + puzzleState.mode == PuzzleMode.load || puzzleState.position.isGameOver + ? PlayerSide.none + : puzzleState.mode == PuzzleMode.view ? PlayerSide.both : puzzleState.pov == Side.white - ? PlayerSide.white - : PlayerSide.black, + ? PlayerSide.white + : PlayerSide.black, isCheck: puzzleState.position.isCheck, sideToMove: puzzleState.position.turn, validMoves: puzzleState.validMoves, @@ -146,9 +140,7 @@ class _Body extends ConsumerWidget { ), topTable: Center( child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10.0, - ), + padding: const EdgeInsets.symmetric(horizontal: 10.0), child: PuzzleFeedbackWidget( puzzle: puzzleState.puzzle, state: puzzleState, @@ -157,21 +149,13 @@ class _Body extends ConsumerWidget { ), ), bottomTable: Padding( - padding: const EdgeInsets.only( - top: 10.0, - left: 10.0, - right: 10.0, - ), + padding: const EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Row( children: [ - Icon( - LichessIcons.streak, - size: 40.0, - color: context.lichessColors.brag, - ), + Icon(LichessIcons.streak, size: 40.0, color: context.lichessColors.brag), const SizedBox(width: 8.0), Text( puzzleState.streak!.index.toString(), @@ -183,11 +167,7 @@ class _Body extends ConsumerWidget { ), ], ), - Text( - context.l10n.puzzleRatingX( - puzzleState.puzzle.puzzle.rating.toString(), - ), - ), + Text(context.l10n.puzzleRatingX(puzzleState.puzzle.puzzle.rating.toString())), ], ), ), @@ -195,10 +175,7 @@ class _Body extends ConsumerWidget { ), ), ), - _BottomBar( - initialPuzzleContext: initialPuzzleContext, - streak: streak, - ), + _BottomBar(initialPuzzleContext: initialPuzzleContext, streak: streak), ], ); @@ -211,16 +188,16 @@ class _Body extends ConsumerWidget { final NavigatorState navigator = Navigator.of(context); final shouldPop = await showAdaptiveDialog( context: context, - builder: (context) => YesNoDialog( - title: Text(context.l10n.mobileAreYouSure), - content: - const Text('No worries, your score will be saved locally.'), - onYes: () { - ref.read(ctrlProvider.notifier).saveStreakResultLocally(); - return Navigator.of(context).pop(true); - }, - onNo: () => Navigator.of(context).pop(false), - ), + builder: + (context) => YesNoDialog( + title: Text(context.l10n.mobileAreYouSure), + content: const Text('No worries, your score will be saved locally.'), + onYes: () { + ref.read(ctrlProvider.notifier).saveStreakResultLocally(); + return Navigator.of(context).pop(true); + }, + onNo: () => Navigator.of(context).pop(false), + ), ); if (shouldPop ?? false) { navigator.pop(); @@ -232,18 +209,14 @@ class _Body extends ConsumerWidget { } class _BottomBar extends ConsumerWidget { - const _BottomBar({ - required this.initialPuzzleContext, - required this.streak, - }); + const _BottomBar({required this.initialPuzzleContext, required this.streak}); final PuzzleContext initialPuzzleContext; final PuzzleStreak streak; @override Widget build(BuildContext context, WidgetRef ref) { - final ctrlProvider = - puzzleControllerProvider(initialPuzzleContext, initialStreak: streak); + final ctrlProvider = puzzleControllerProvider(initialPuzzleContext, initialStreak: streak); final puzzleState = ref.watch(ctrlProvider); return BottomBar( @@ -260,42 +233,42 @@ class _BottomBar extends ConsumerWidget { icon: Icons.skip_next, label: context.l10n.skipThisMove, showLabel: true, - onTap: puzzleState.streak!.hasSkipped || - puzzleState.mode == PuzzleMode.view - ? null - : () => ref.read(ctrlProvider.notifier).skipMove(), + onTap: + puzzleState.streak!.hasSkipped || puzzleState.mode == PuzzleMode.view + ? null + : () => ref.read(ctrlProvider.notifier).skipMove(), ), if (puzzleState.streak!.finished) BottomBarButton( onTap: () { launchShareDialog( context, - text: lichessUri( - '/training/${puzzleState.puzzle.puzzle.id}', - ).toString(), + text: lichessUri('/training/${puzzleState.puzzle.puzzle.id}').toString(), ); }, label: 'Share this puzzle', - icon: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoIcons.share - : Icons.share, + icon: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoIcons.share + : Icons.share, ), if (puzzleState.streak!.finished) BottomBarButton( onTap: () { pushPlatformRoute( context, - builder: (context) => AnalysisScreen( - options: AnalysisOptions( - orientation: puzzleState.pov, - standalone: ( - pgn: ref.read(ctrlProvider.notifier).makePgn(), - isComputerAnalysisAllowed: true, - variant: Variant.standard, + builder: + (context) => AnalysisScreen( + options: AnalysisOptions( + orientation: puzzleState.pov, + standalone: ( + pgn: ref.read(ctrlProvider.notifier).makePgn(), + isComputerAnalysisAllowed: true, + variant: Variant.standard, + ), + initialMoveCursor: 0, + ), ), - initialMoveCursor: 0, - ), - ), ); }, label: context.l10n.analysis, @@ -303,25 +276,23 @@ class _BottomBar extends ConsumerWidget { ), if (puzzleState.streak!.finished) BottomBarButton( - onTap: puzzleState.canGoBack - ? () => ref.read(ctrlProvider.notifier).userPrevious() - : null, + onTap: + puzzleState.canGoBack ? () => ref.read(ctrlProvider.notifier).userPrevious() : null, label: 'Previous', icon: CupertinoIcons.chevron_back, ), if (puzzleState.streak!.finished) BottomBarButton( - onTap: puzzleState.canGoNext - ? () => ref.read(ctrlProvider.notifier).userNext() - : null, + onTap: puzzleState.canGoNext ? () => ref.read(ctrlProvider.notifier).userNext() : null, label: context.l10n.next, icon: CupertinoIcons.chevron_forward, ), if (puzzleState.streak!.finished) BottomBarButton( - onTap: ref.read(streakProvider).isLoading == false - ? () => ref.invalidate(streakProvider) - : null, + onTap: + ref.read(streakProvider).isLoading == false + ? () => ref.invalidate(streakProvider) + : null, highlighted: true, label: context.l10n.puzzleNewStreak, icon: CupertinoIcons.play_arrow_solid, @@ -333,25 +304,23 @@ class _BottomBar extends ConsumerWidget { Future _streakInfoDialogBuilder(BuildContext context) { return showAdaptiveDialog( context: context, - builder: (context) => PlatformAlertDialog( - title: Text(context.l10n.aboutX('Puzzle Streak')), - content: Text(context.l10n.puzzleStreakDescription), - actions: [ - PlatformDialogAction( - onPressed: () => Navigator.of(context).pop(), - child: Text(context.l10n.mobileOkButton), + builder: + (context) => PlatformAlertDialog( + title: Text(context.l10n.aboutX('Puzzle Streak')), + content: Text(context.l10n.puzzleStreakDescription), + actions: [ + PlatformDialogAction( + onPressed: () => Navigator.of(context).pop(), + child: Text(context.l10n.mobileOkButton), + ), + ], ), - ], - ), ); } } class _RetryFetchPuzzleDialog extends ConsumerWidget { - const _RetryFetchPuzzleDialog({ - required this.initialPuzzleContext, - required this.streak, - }); + const _RetryFetchPuzzleDialog({required this.initialPuzzleContext, required this.streak}); final PuzzleContext initialPuzzleContext; final PuzzleStreak streak; @@ -361,8 +330,7 @@ class _RetryFetchPuzzleDialog extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final ctrlProvider = - puzzleControllerProvider(initialPuzzleContext, initialStreak: streak); + final ctrlProvider = puzzleControllerProvider(initialPuzzleContext, initialStreak: streak); final state = ref.watch(ctrlProvider); Future retryStreakNext() async { @@ -375,18 +343,18 @@ class _RetryFetchPuzzleDialog extends ConsumerWidget { Navigator.of(context).pop(); } if (data != null) { - ref.read(ctrlProvider.notifier).loadPuzzle( + ref + .read(ctrlProvider.notifier) + .loadPuzzle( data, - nextStreak: - state.streak!.copyWith(index: state.streak!.index + 1), + nextStreak: state.streak!.copyWith(index: state.streak!.index + 1), ); } }, ); } - final canRetry = state.nextPuzzleStreakFetchError && - !state.nextPuzzleStreakFetchIsRetrying; + final canRetry = state.nextPuzzleStreakFetchError && !state.nextPuzzleStreakFetchIsRetrying; return PlatformAlertDialog( title: const Text(title), diff --git a/lib/src/view/relation/following_screen.dart b/lib/src/view/relation/following_screen.dart index b27b7ad5f3..8504b58e28 100644 --- a/lib/src/view/relation/following_screen.dart +++ b/lib/src/view/relation/following_screen.dart @@ -18,21 +18,20 @@ import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/platform_scaffold.dart'; import 'package:lichess_mobile/src/widgets/user_list_tile.dart'; -final _getFollowingAndOnlinesProvider = - FutureProvider.autoDispose<(IList, IList)>((ref) async { - final following = await ref.watch(followingProvider.future); - final onlines = await ref.watch(onlineFriendsProvider.future); - return (following, onlines); -}); +final _getFollowingAndOnlinesProvider = FutureProvider.autoDispose<(IList, IList)>( + (ref) async { + final following = await ref.watch(followingProvider.future); + final onlines = await ref.watch(onlineFriendsProvider.future); + return (following, onlines); + }, +); class FollowingScreen extends StatelessWidget { const FollowingScreen({super.key}); @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.friends), - ), + appBar: PlatformAppBar(title: Text(context.l10n.friends)), body: const _Body(), ); } @@ -43,9 +42,7 @@ class _Body extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final followingAndOnlines = ref.watch( - _getFollowingAndOnlinesProvider, - ); + final followingAndOnlines = ref.watch(_getFollowingAndOnlinesProvider); return followingAndOnlines.when( data: (data) { @@ -53,21 +50,19 @@ class _Body extends ConsumerWidget { return StatefulBuilder( builder: (BuildContext context, StateSetter setState) { if (following.isEmpty) { - return Center( - child: Text(context.l10n.mobileNotFollowingAnyUser), - ); + return Center(child: Text(context.l10n.mobileNotFollowingAnyUser)); } return SafeArea( child: ColoredBox( - color: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoColors.systemBackground.resolveFrom(context) - : Colors.transparent, + color: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoColors.systemBackground.resolveFrom(context) + : Colors.transparent, child: ListView.separated( itemCount: following.length, - separatorBuilder: (context, index) => const PlatformDivider( - height: 1, - cupertinoHasLeading: true, - ), + separatorBuilder: + (context, index) => + const PlatformDivider(height: 1, cupertinoHasLeading: true), itemBuilder: (context, index) { final user = following[index]; return Slidable( @@ -80,14 +75,11 @@ class _Body extends ConsumerWidget { onPressed: (BuildContext context) async { final oldState = following; setState(() { - following = following.removeWhere( - (v) => v.id == user.id, - ); + following = following.removeWhere((v) => v.id == user.id); }); try { await ref.withClient( - (client) => RelationRepository(client) - .unfollow(user.id), + (client) => RelationRepository(client).unfollow(user.id), ); } catch (_) { setState(() { @@ -106,14 +98,13 @@ class _Body extends ConsumerWidget { child: UserListTile.fromUser( user, _isOnline(user, data.$2), - onTap: () => { - pushPlatformRoute( - context, - builder: (context) => UserScreen( - user: user.lightUser, - ), - ), - }, + onTap: + () => { + pushPlatformRoute( + context, + builder: (context) => UserScreen(user: user.lightUser), + ), + }, ), ); }, @@ -124,12 +115,8 @@ class _Body extends ConsumerWidget { ); }, error: (error, stackTrace) { - debugPrint( - 'SEVERE: [FollowingScreen] could not load following users; $error\n$stackTrace', - ); - return FullScreenRetryRequest( - onRetry: () => ref.invalidate(followingProvider), - ); + debugPrint('SEVERE: [FollowingScreen] could not load following users; $error\n$stackTrace'); + return FullScreenRetryRequest(onRetry: () => ref.invalidate(followingProvider)); }, loading: () => const CenterLoadingIndicator(), ); diff --git a/lib/src/view/settings/account_preferences_screen.dart b/lib/src/view/settings/account_preferences_screen.dart index f6797646d3..83c65bfa7a 100644 --- a/lib/src/view/settings/account_preferences_screen.dart +++ b/lib/src/view/settings/account_preferences_screen.dart @@ -14,12 +14,10 @@ class AccountPreferencesScreen extends ConsumerStatefulWidget { const AccountPreferencesScreen({super.key}); @override - ConsumerState createState() => - _AccountPreferencesScreenState(); + ConsumerState createState() => _AccountPreferencesScreenState(); } -class _AccountPreferencesScreenState - extends ConsumerState { +class _AccountPreferencesScreenState extends ConsumerState { bool isLoading = false; Future _setPref(Future Function() f) async { @@ -42,9 +40,7 @@ class _AccountPreferencesScreenState final content = accountPrefs.when( data: (data) { if (data == null) { - return Center( - child: Text(context.l10n.mobileMustBeLoggedIn), - ); + return Center(child: Text(context.l10n.mobileMustBeLoggedIn)); } return ListView( @@ -54,9 +50,7 @@ class _AccountPreferencesScreenState hasLeading: false, children: [ SettingsListTile( - settingsLabel: Text( - context.l10n.preferencesZenMode, - ), + settingsLabel: Text(context.l10n.preferencesZenMode), settingsValue: data.zenMode.label(context), showCupertinoTrailingValue: false, onTap: () { @@ -66,17 +60,16 @@ class _AccountPreferencesScreenState choices: Zen.values, selectedItem: data.zenMode, labelBuilder: (t) => Text(t.label(context)), - onSelectedItemChanged: isLoading - ? null - : (Zen? value) { - _setPref( - () => ref - .read( - accountPreferencesProvider.notifier, - ) - .setZen(value ?? data.zenMode), - ); - }, + onSelectedItemChanged: + isLoading + ? null + : (Zen? value) { + _setPref( + () => ref + .read(accountPreferencesProvider.notifier) + .setZen(value ?? data.zenMode), + ); + }, ); } else { pushPlatformRoute( @@ -88,9 +81,7 @@ class _AccountPreferencesScreenState }, ), SettingsListTile( - settingsLabel: Text( - context.l10n.preferencesPgnPieceNotation, - ), + settingsLabel: Text(context.l10n.preferencesPgnPieceNotation), settingsValue: data.pieceNotation.label(context), showCupertinoTrailingValue: false, onTap: () { @@ -100,26 +91,22 @@ class _AccountPreferencesScreenState choices: PieceNotation.values, selectedItem: data.pieceNotation, labelBuilder: (t) => Text(t.label(context)), - onSelectedItemChanged: isLoading - ? null - : (PieceNotation? value) { - _setPref( - () => ref - .read( - accountPreferencesProvider.notifier, - ) - .setPieceNotation( - value ?? data.pieceNotation, - ), - ); - }, + onSelectedItemChanged: + isLoading + ? null + : (PieceNotation? value) { + _setPref( + () => ref + .read(accountPreferencesProvider.notifier) + .setPieceNotation(value ?? data.pieceNotation), + ); + }, ); } else { pushPlatformRoute( context, title: context.l10n.preferencesPgnPieceNotation, - builder: (context) => - const PieceNotationSettingsScreen(), + builder: (context) => const PieceNotationSettingsScreen(), ); } }, @@ -132,57 +119,53 @@ class _AccountPreferencesScreenState textAlign: TextAlign.justify, ), value: data.showRatings.value, - onChanged: isLoading - ? null - : (value) { - _setPref( - () => ref - .read(accountPreferencesProvider.notifier) - .setShowRatings(BooleanPref(value)), - ); - }, + onChanged: + isLoading + ? null + : (value) { + _setPref( + () => ref + .read(accountPreferencesProvider.notifier) + .setShowRatings(BooleanPref(value)), + ); + }, ), ], ), ListSection( - header: - SettingsSectionTitle(context.l10n.preferencesGameBehavior), + header: SettingsSectionTitle(context.l10n.preferencesGameBehavior), hasLeading: false, children: [ SwitchSettingTile( - title: Text( - context.l10n.preferencesPremovesPlayingDuringOpponentTurn, - ), + title: Text(context.l10n.preferencesPremovesPlayingDuringOpponentTurn), value: data.premove.value, - onChanged: isLoading - ? null - : (value) { - _setPref( - () => ref - .read(accountPreferencesProvider.notifier) - .setPremove(BooleanPref(value)), - ); - }, + onChanged: + isLoading + ? null + : (value) { + _setPref( + () => ref + .read(accountPreferencesProvider.notifier) + .setPremove(BooleanPref(value)), + ); + }, ), SwitchSettingTile( - title: Text( - context.l10n.preferencesConfirmResignationAndDrawOffers, - ), + title: Text(context.l10n.preferencesConfirmResignationAndDrawOffers), value: data.confirmResign.value, - onChanged: isLoading - ? null - : (value) { - _setPref( - () => ref - .read(accountPreferencesProvider.notifier) - .setConfirmResign(BooleanPref(value)), - ); - }, + onChanged: + isLoading + ? null + : (value) { + _setPref( + () => ref + .read(accountPreferencesProvider.notifier) + .setConfirmResign(BooleanPref(value)), + ); + }, ), SettingsListTile( - settingsLabel: Text( - context.l10n.preferencesTakebacksWithOpponentApproval, - ), + settingsLabel: Text(context.l10n.preferencesTakebacksWithOpponentApproval), settingsValue: data.takeback.label(context), showCupertinoTrailingValue: false, onTap: () { @@ -192,32 +175,28 @@ class _AccountPreferencesScreenState choices: Takeback.values, selectedItem: data.takeback, labelBuilder: (t) => Text(t.label(context)), - onSelectedItemChanged: isLoading - ? null - : (Takeback? value) { - _setPref( - () => ref - .read( - accountPreferencesProvider.notifier, - ) - .setTakeback(value ?? data.takeback), - ); - }, + onSelectedItemChanged: + isLoading + ? null + : (Takeback? value) { + _setPref( + () => ref + .read(accountPreferencesProvider.notifier) + .setTakeback(value ?? data.takeback), + ); + }, ); } else { pushPlatformRoute( context, - title: context - .l10n.preferencesTakebacksWithOpponentApproval, + title: context.l10n.preferencesTakebacksWithOpponentApproval, builder: (context) => const TakebackSettingsScreen(), ); } }, ), SettingsListTile( - settingsLabel: Text( - context.l10n.preferencesPromoteToQueenAutomatically, - ), + settingsLabel: Text(context.l10n.preferencesPromoteToQueenAutomatically), settingsValue: data.autoQueen.label(context), showCupertinoTrailingValue: false, onTap: () { @@ -227,23 +206,21 @@ class _AccountPreferencesScreenState choices: AutoQueen.values, selectedItem: data.autoQueen, labelBuilder: (t) => Text(t.label(context)), - onSelectedItemChanged: isLoading - ? null - : (AutoQueen? value) { - _setPref( - () => ref - .read( - accountPreferencesProvider.notifier, - ) - .setAutoQueen(value ?? data.autoQueen), - ); - }, + onSelectedItemChanged: + isLoading + ? null + : (AutoQueen? value) { + _setPref( + () => ref + .read(accountPreferencesProvider.notifier) + .setAutoQueen(value ?? data.autoQueen), + ); + }, ); } else { pushPlatformRoute( context, - title: - context.l10n.preferencesPromoteToQueenAutomatically, + title: context.l10n.preferencesPromoteToQueenAutomatically, builder: (context) => const AutoQueenSettingsScreen(), ); } @@ -251,8 +228,7 @@ class _AccountPreferencesScreenState ), SettingsListTile( settingsLabel: Text( - context.l10n - .preferencesClaimDrawOnThreefoldRepetitionAutomatically, + context.l10n.preferencesClaimDrawOnThreefoldRepetitionAutomatically, ), settingsValue: data.autoThreefold.label(context), showCupertinoTrailingValue: false, @@ -263,35 +239,28 @@ class _AccountPreferencesScreenState choices: AutoThreefold.values, selectedItem: data.autoThreefold, labelBuilder: (t) => Text(t.label(context)), - onSelectedItemChanged: isLoading - ? null - : (AutoThreefold? value) { - _setPref( - () => ref - .read( - accountPreferencesProvider.notifier, - ) - .setAutoThreefold( - value ?? data.autoThreefold, - ), - ); - }, + onSelectedItemChanged: + isLoading + ? null + : (AutoThreefold? value) { + _setPref( + () => ref + .read(accountPreferencesProvider.notifier) + .setAutoThreefold(value ?? data.autoThreefold), + ); + }, ); } else { pushPlatformRoute( context, - title: context.l10n - .preferencesClaimDrawOnThreefoldRepetitionAutomatically, - builder: (context) => - const AutoThreefoldSettingsScreen(), + title: context.l10n.preferencesClaimDrawOnThreefoldRepetitionAutomatically, + builder: (context) => const AutoThreefoldSettingsScreen(), ); } }, ), SettingsListTile( - settingsLabel: Text( - context.l10n.preferencesMoveConfirmation, - ), + settingsLabel: Text(context.l10n.preferencesMoveConfirmation), settingsValue: data.submitMove.label(context), showCupertinoTrailingValue: false, onTap: () { @@ -310,8 +279,7 @@ class _AccountPreferencesScreenState } }); }, - explanation: context - .l10n.preferencesExplainCanThenBeTemporarilyDisabled, + explanation: context.l10n.preferencesExplainCanThenBeTemporarilyDisabled, ), ], ), @@ -320,9 +288,7 @@ class _AccountPreferencesScreenState hasLeading: false, children: [ SettingsListTile( - settingsLabel: Text( - context.l10n.preferencesGiveMoreTime, - ), + settingsLabel: Text(context.l10n.preferencesGiveMoreTime), settingsValue: data.moretime.label(context), showCupertinoTrailingValue: false, onTap: () { @@ -332,17 +298,16 @@ class _AccountPreferencesScreenState choices: Moretime.values, selectedItem: data.moretime, labelBuilder: (t) => Text(t.label(context)), - onSelectedItemChanged: isLoading - ? null - : (Moretime? value) { - _setPref( - () => ref - .read( - accountPreferencesProvider.notifier, - ) - .setMoretime(value ?? data.moretime), - ); - }, + onSelectedItemChanged: + isLoading + ? null + : (Moretime? value) { + _setPref( + () => ref + .read(accountPreferencesProvider.notifier) + .setMoretime(value ?? data.moretime), + ); + }, ); } else { pushPlatformRoute( @@ -354,18 +319,18 @@ class _AccountPreferencesScreenState }, ), SwitchSettingTile( - title: - Text(context.l10n.preferencesSoundWhenTimeGetsCritical), + title: Text(context.l10n.preferencesSoundWhenTimeGetsCritical), value: data.clockSound.value, - onChanged: isLoading - ? null - : (value) { - _setPref( - () => ref - .read(accountPreferencesProvider.notifier) - .setClockSound(BooleanPref(value)), - ); - }, + onChanged: + isLoading + ? null + : (value) { + _setPref( + () => ref + .read(accountPreferencesProvider.notifier) + .setClockSound(BooleanPref(value)), + ); + }, ), ], ), @@ -374,24 +339,21 @@ class _AccountPreferencesScreenState hasLeading: false, children: [ SwitchSettingTile( - title: Text( - context.l10n.letOtherPlayersFollowYou, - ), + title: Text(context.l10n.letOtherPlayersFollowYou), value: data.follow.value, - onChanged: isLoading - ? null - : (value) { - _setPref( - () => ref - .read(accountPreferencesProvider.notifier) - .setFollow(BooleanPref(value)), - ); - }, + onChanged: + isLoading + ? null + : (value) { + _setPref( + () => ref + .read(accountPreferencesProvider.notifier) + .setFollow(BooleanPref(value)), + ); + }, ), SettingsListTile( - settingsLabel: Text( - context.l10n.letOtherPlayersChallengeYou, - ), + settingsLabel: Text(context.l10n.letOtherPlayersChallengeYou), settingsValue: data.challenge.label(context), showCupertinoTrailingValue: false, onTap: () { @@ -401,17 +363,16 @@ class _AccountPreferencesScreenState choices: Challenge.values, selectedItem: data.challenge, labelBuilder: (t) => Text(t.label(context)), - onSelectedItemChanged: isLoading - ? null - : (Challenge? value) { - _setPref( - () => ref - .read( - accountPreferencesProvider.notifier, - ) - .setChallenge(value ?? data.challenge), - ); - }, + onSelectedItemChanged: + isLoading + ? null + : (Challenge? value) { + _setPref( + () => ref + .read(accountPreferencesProvider.notifier) + .setChallenge(value ?? data.challenge), + ); + }, ); } else { pushPlatformRoute( @@ -429,18 +390,14 @@ class _AccountPreferencesScreenState }, loading: () => const Center(child: CircularProgressIndicator()), error: (err, _) { - return FullScreenRetryRequest( - onRetry: () => ref.invalidate(accountPreferencesProvider), - ); + return FullScreenRetryRequest(onRetry: () => ref.invalidate(accountPreferencesProvider)); }, ); return PlatformScaffold( appBar: PlatformAppBar( title: Text(context.l10n.preferencesPreferences), - actions: [ - if (isLoading) const PlatformAppBarLoadingIndicator(), - ], + actions: [if (isLoading) const PlatformAppBarLoadingIndicator()], ), body: content, ); @@ -463,15 +420,12 @@ class _ZenSettingsScreenState extends ConsumerState { return accountPrefs.when( data: (data) { if (data == null) { - return Center( - child: Text(context.l10n.mobileMustBeLoggedIn), - ); + return Center(child: Text(context.l10n.mobileMustBeLoggedIn)); } return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - trailing: - isLoading ? const CircularProgressIndicator.adaptive() : null, + trailing: isLoading ? const CircularProgressIndicator.adaptive() : null, ), child: SafeArea( child: ListView( @@ -480,22 +434,23 @@ class _ZenSettingsScreenState extends ConsumerState { choices: Zen.values, selectedItem: data.zenMode, titleBuilder: (t) => Text(t.label(context)), - onSelectedItemChanged: isLoading - ? null - : (Zen? v) async { - setState(() { - isLoading = true; - }); - try { - await ref - .read(accountPreferencesProvider.notifier) - .setZen(v ?? data.zenMode); - } finally { + onSelectedItemChanged: + isLoading + ? null + : (Zen? v) async { setState(() { - isLoading = false; + isLoading = true; }); - } - }, + try { + await ref + .read(accountPreferencesProvider.notifier) + .setZen(v ?? data.zenMode); + } finally { + setState(() { + isLoading = false; + }); + } + }, ), ], ), @@ -512,12 +467,10 @@ class PieceNotationSettingsScreen extends ConsumerStatefulWidget { const PieceNotationSettingsScreen({super.key}); @override - ConsumerState createState() => - _PieceNotationSettingsScreenState(); + ConsumerState createState() => _PieceNotationSettingsScreenState(); } -class _PieceNotationSettingsScreenState - extends ConsumerState { +class _PieceNotationSettingsScreenState extends ConsumerState { Future? _pendingSetPieceNotation; @override @@ -526,9 +479,7 @@ class _PieceNotationSettingsScreenState return accountPrefs.when( data: (data) { if (data == null) { - return Center( - child: Text(context.l10n.mobileMustBeLoggedIn), - ); + return Center(child: Text(context.l10n.mobileMustBeLoggedIn)); } return FutureBuilder( @@ -536,9 +487,10 @@ class _PieceNotationSettingsScreenState builder: (context, snapshot) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - trailing: snapshot.connectionState == ConnectionState.waiting - ? const CircularProgressIndicator.adaptive() - : null, + trailing: + snapshot.connectionState == ConnectionState.waiting + ? const CircularProgressIndicator.adaptive() + : null, ), child: SafeArea( child: ListView( @@ -547,17 +499,17 @@ class _PieceNotationSettingsScreenState choices: PieceNotation.values, selectedItem: data.pieceNotation, titleBuilder: (t) => Text(t.label(context)), - onSelectedItemChanged: snapshot.connectionState == - ConnectionState.waiting - ? null - : (PieceNotation? v) { - final future = ref - .read(accountPreferencesProvider.notifier) - .setPieceNotation(v ?? data.pieceNotation); - setState(() { - _pendingSetPieceNotation = future; - }); - }, + onSelectedItemChanged: + snapshot.connectionState == ConnectionState.waiting + ? null + : (PieceNotation? v) { + final future = ref + .read(accountPreferencesProvider.notifier) + .setPieceNotation(v ?? data.pieceNotation); + setState(() { + _pendingSetPieceNotation = future; + }); + }, ), ], ), @@ -576,12 +528,10 @@ class TakebackSettingsScreen extends ConsumerStatefulWidget { const TakebackSettingsScreen({super.key}); @override - ConsumerState createState() => - _TakebackSettingsScreenState(); + ConsumerState createState() => _TakebackSettingsScreenState(); } -class _TakebackSettingsScreenState - extends ConsumerState { +class _TakebackSettingsScreenState extends ConsumerState { bool isLoading = false; @override @@ -590,15 +540,12 @@ class _TakebackSettingsScreenState return accountPrefs.when( data: (data) { if (data == null) { - return Center( - child: Text(context.l10n.mobileMustBeLoggedIn), - ); + return Center(child: Text(context.l10n.mobileMustBeLoggedIn)); } return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - trailing: - isLoading ? const CircularProgressIndicator.adaptive() : null, + trailing: isLoading ? const CircularProgressIndicator.adaptive() : null, ), child: SafeArea( child: ListView( @@ -607,22 +554,23 @@ class _TakebackSettingsScreenState choices: Takeback.values, selectedItem: data.takeback, titleBuilder: (t) => Text(t.label(context)), - onSelectedItemChanged: isLoading - ? null - : (Takeback? v) async { - setState(() { - isLoading = true; - }); - try { - await ref - .read(accountPreferencesProvider.notifier) - .setTakeback(v ?? data.takeback); - } finally { + onSelectedItemChanged: + isLoading + ? null + : (Takeback? v) async { setState(() { - isLoading = false; + isLoading = true; }); - } - }, + try { + await ref + .read(accountPreferencesProvider.notifier) + .setTakeback(v ?? data.takeback); + } finally { + setState(() { + isLoading = false; + }); + } + }, ), ], ), @@ -639,12 +587,10 @@ class AutoQueenSettingsScreen extends ConsumerStatefulWidget { const AutoQueenSettingsScreen({super.key}); @override - ConsumerState createState() => - _AutoQueenSettingsScreenState(); + ConsumerState createState() => _AutoQueenSettingsScreenState(); } -class _AutoQueenSettingsScreenState - extends ConsumerState { +class _AutoQueenSettingsScreenState extends ConsumerState { Future? _pendingSetAutoQueen; @override @@ -653,9 +599,7 @@ class _AutoQueenSettingsScreenState return accountPrefs.when( data: (data) { if (data == null) { - return Center( - child: Text(context.l10n.mobileMustBeLoggedIn), - ); + return Center(child: Text(context.l10n.mobileMustBeLoggedIn)); } return FutureBuilder( @@ -663,9 +607,10 @@ class _AutoQueenSettingsScreenState builder: (context, snapshot) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - trailing: snapshot.connectionState == ConnectionState.waiting - ? const CircularProgressIndicator.adaptive() - : null, + trailing: + snapshot.connectionState == ConnectionState.waiting + ? const CircularProgressIndicator.adaptive() + : null, ), child: SafeArea( child: ListView( @@ -678,13 +623,13 @@ class _AutoQueenSettingsScreenState snapshot.connectionState == ConnectionState.waiting ? null : (AutoQueen? v) { - final future = ref - .read(accountPreferencesProvider.notifier) - .setAutoQueen(v ?? data.autoQueen); - setState(() { - _pendingSetAutoQueen = future; - }); - }, + final future = ref + .read(accountPreferencesProvider.notifier) + .setAutoQueen(v ?? data.autoQueen); + setState(() { + _pendingSetAutoQueen = future; + }); + }, ), ], ), @@ -703,12 +648,10 @@ class AutoThreefoldSettingsScreen extends ConsumerStatefulWidget { const AutoThreefoldSettingsScreen({super.key}); @override - ConsumerState createState() => - _AutoThreefoldSettingsScreenState(); + ConsumerState createState() => _AutoThreefoldSettingsScreenState(); } -class _AutoThreefoldSettingsScreenState - extends ConsumerState { +class _AutoThreefoldSettingsScreenState extends ConsumerState { Future? _pendingSetAutoThreefold; @override @@ -717,9 +660,7 @@ class _AutoThreefoldSettingsScreenState return accountPrefs.when( data: (data) { if (data == null) { - return Center( - child: Text(context.l10n.mobileMustBeLoggedIn), - ); + return Center(child: Text(context.l10n.mobileMustBeLoggedIn)); } return FutureBuilder( @@ -727,9 +668,10 @@ class _AutoThreefoldSettingsScreenState builder: (context, snapshot) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - trailing: snapshot.connectionState == ConnectionState.waiting - ? const CircularProgressIndicator.adaptive() - : null, + trailing: + snapshot.connectionState == ConnectionState.waiting + ? const CircularProgressIndicator.adaptive() + : null, ), child: SafeArea( child: ListView( @@ -738,17 +680,17 @@ class _AutoThreefoldSettingsScreenState choices: AutoThreefold.values, selectedItem: data.autoThreefold, titleBuilder: (t) => Text(t.label(context)), - onSelectedItemChanged: snapshot.connectionState == - ConnectionState.waiting - ? null - : (AutoThreefold? v) { - final future = ref - .read(accountPreferencesProvider.notifier) - .setAutoThreefold(v ?? data.autoThreefold); - setState(() { - _pendingSetAutoThreefold = future; - }); - }, + onSelectedItemChanged: + snapshot.connectionState == ConnectionState.waiting + ? null + : (AutoThreefold? v) { + final future = ref + .read(accountPreferencesProvider.notifier) + .setAutoThreefold(v ?? data.autoThreefold); + setState(() { + _pendingSetAutoThreefold = future; + }); + }, ), ], ), @@ -767,12 +709,10 @@ class MoretimeSettingsScreen extends ConsumerStatefulWidget { const MoretimeSettingsScreen({super.key}); @override - ConsumerState createState() => - _MoretimeSettingsScreenState(); + ConsumerState createState() => _MoretimeSettingsScreenState(); } -class _MoretimeSettingsScreenState - extends ConsumerState { +class _MoretimeSettingsScreenState extends ConsumerState { Future? _pendingSetMoretime; @override @@ -781,9 +721,7 @@ class _MoretimeSettingsScreenState return accountPrefs.when( data: (data) { if (data == null) { - return Center( - child: Text(context.l10n.mobileMustBeLoggedIn), - ); + return Center(child: Text(context.l10n.mobileMustBeLoggedIn)); } return FutureBuilder( @@ -791,9 +729,10 @@ class _MoretimeSettingsScreenState builder: (context, snapshot) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - trailing: snapshot.connectionState == ConnectionState.waiting - ? const CircularProgressIndicator.adaptive() - : null, + trailing: + snapshot.connectionState == ConnectionState.waiting + ? const CircularProgressIndicator.adaptive() + : null, ), child: SafeArea( child: ListView( @@ -802,16 +741,16 @@ class _MoretimeSettingsScreenState choices: Moretime.values, selectedItem: data.moretime, titleBuilder: (t) => Text(t.label(context)), - onSelectedItemChanged: snapshot.connectionState == - ConnectionState.waiting - ? null - : (Moretime? v) { - setState(() { - _pendingSetMoretime = ref - .read(accountPreferencesProvider.notifier) - .setMoretime(v ?? data.moretime); - }); - }, + onSelectedItemChanged: + snapshot.connectionState == ConnectionState.waiting + ? null + : (Moretime? v) { + setState(() { + _pendingSetMoretime = ref + .read(accountPreferencesProvider.notifier) + .setMoretime(v ?? data.moretime); + }); + }, ), ], ), @@ -830,12 +769,10 @@ class _ChallengeSettingsScreen extends ConsumerStatefulWidget { const _ChallengeSettingsScreen(); @override - ConsumerState<_ChallengeSettingsScreen> createState() => - _ChallengeSettingsScreenState(); + ConsumerState<_ChallengeSettingsScreen> createState() => _ChallengeSettingsScreenState(); } -class _ChallengeSettingsScreenState - extends ConsumerState<_ChallengeSettingsScreen> { +class _ChallengeSettingsScreenState extends ConsumerState<_ChallengeSettingsScreen> { Future? _pendingSetChallenge; @override @@ -844,9 +781,7 @@ class _ChallengeSettingsScreenState return accountPrefs.when( data: (data) { if (data == null) { - return Center( - child: Text(context.l10n.mobileMustBeLoggedIn), - ); + return Center(child: Text(context.l10n.mobileMustBeLoggedIn)); } return FutureBuilder( @@ -854,9 +789,10 @@ class _ChallengeSettingsScreenState builder: (context, snapshot) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - trailing: snapshot.connectionState == ConnectionState.waiting - ? const CircularProgressIndicator.adaptive() - : null, + trailing: + snapshot.connectionState == ConnectionState.waiting + ? const CircularProgressIndicator.adaptive() + : null, ), child: SafeArea( child: ListView( @@ -869,13 +805,13 @@ class _ChallengeSettingsScreenState snapshot.connectionState == ConnectionState.waiting ? null : (Challenge? v) { - final future = ref - .read(accountPreferencesProvider.notifier) - .setChallenge(v ?? data.challenge); - setState(() { - _pendingSetChallenge = future; - }); - }, + final future = ref + .read(accountPreferencesProvider.notifier) + .setChallenge(v ?? data.challenge); + setState(() { + _pendingSetChallenge = future; + }); + }, ), ], ), diff --git a/lib/src/view/settings/app_background_mode_screen.dart b/lib/src/view/settings/app_background_mode_screen.dart index e24084c562..b268bb78a8 100644 --- a/lib/src/view/settings/app_background_mode_screen.dart +++ b/lib/src/view/settings/app_background_mode_screen.dart @@ -11,24 +11,15 @@ class AppBackgroundModeScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return PlatformWidget( - androidBuilder: _androidBuilder, - iosBuilder: _iosBuilder, - ); + return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); } Widget _androidBuilder(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text(context.l10n.background)), - body: _Body(), - ); + return Scaffold(appBar: AppBar(title: Text(context.l10n.background)), body: _Body()); } Widget _iosBuilder(BuildContext context) { - return CupertinoPageScaffold( - navigationBar: const CupertinoNavigationBar(), - child: _Body(), - ); + return CupertinoPageScaffold(navigationBar: const CupertinoNavigationBar(), child: _Body()); } static String themeTitle(BuildContext context, BackgroundThemeMode theme) { @@ -46,9 +37,7 @@ class AppBackgroundModeScreen extends StatelessWidget { class _Body extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final themeMode = ref.watch( - generalPreferencesProvider.select((state) => state.themeMode), - ); + final themeMode = ref.watch(generalPreferencesProvider.select((state) => state.themeMode)); void onChanged(BackgroundThemeMode? value) => ref .read(generalPreferencesProvider.notifier) @@ -60,8 +49,7 @@ class _Body extends ConsumerWidget { ChoicePicker( choices: BackgroundThemeMode.values, selectedItem: themeMode, - titleBuilder: (t) => - Text(AppBackgroundModeScreen.themeTitle(context, t)), + titleBuilder: (t) => Text(AppBackgroundModeScreen.themeTitle(context, t)), onSelectedItemChanged: onChanged, ), ], diff --git a/lib/src/view/settings/board_settings_screen.dart b/lib/src/view/settings/board_settings_screen.dart index 48532353d1..61d3be6fda 100644 --- a/lib/src/view/settings/board_settings_screen.dart +++ b/lib/src/view/settings/board_settings_screen.dart @@ -19,24 +19,15 @@ class BoardSettingsScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return PlatformWidget( - androidBuilder: _androidBuilder, - iosBuilder: _iosBuilder, - ); + return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); } Widget _androidBuilder(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text(context.l10n.board)), - body: const _Body(), - ); + return Scaffold(appBar: AppBar(title: Text(context.l10n.board)), body: const _Body()); } Widget _iosBuilder(BuildContext context) { - return const CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar(), - child: _Body(), - ); + return const CupertinoPageScaffold(navigationBar: CupertinoNavigationBar(), child: _Body()); } } @@ -58,8 +49,7 @@ class _Body extends ConsumerWidget { children: [ SettingsListTile( settingsLabel: Text(context.l10n.preferencesHowDoYouMovePieces), - settingsValue: - pieceShiftMethodl10n(context, boardPrefs.pieceShiftMethod), + settingsValue: pieceShiftMethodl10n(context, boardPrefs.pieceShiftMethod), showCupertinoTrailingValue: false, onTap: () { if (Theme.of(context).platform == TargetPlatform.android) { @@ -71,17 +61,14 @@ class _Body extends ConsumerWidget { onSelectedItemChanged: (PieceShiftMethod? value) { ref .read(boardPreferencesProvider.notifier) - .setPieceShiftMethod( - value ?? PieceShiftMethod.either, - ); + .setPieceShiftMethod(value ?? PieceShiftMethod.either); }, ); } else { pushPlatformRoute( context, title: context.l10n.preferencesHowDoYouMovePieces, - builder: (context) => - const PieceShiftMethodSettingsScreen(), + builder: (context) => const PieceShiftMethodSettingsScreen(), ); } }, @@ -90,9 +77,7 @@ class _Body extends ConsumerWidget { title: Text(context.l10n.mobilePrefMagnifyDraggedPiece), value: boardPrefs.magnifyDraggedPiece, onChanged: (value) { - ref - .read(boardPreferencesProvider.notifier) - .toggleMagnifyDraggedPiece(); + ref.read(boardPreferencesProvider.notifier).toggleMagnifyDraggedPiece(); }, ), SettingsListTile( @@ -112,9 +97,7 @@ class _Body extends ConsumerWidget { onSelectedItemChanged: (DragTargetKind? value) { ref .read(boardPreferencesProvider.notifier) - .setDragTargetKind( - value ?? DragTargetKind.circle, - ); + .setDragTargetKind(value ?? DragTargetKind.circle); }, ); } else { @@ -137,20 +120,14 @@ class _Body extends ConsumerWidget { textAlign: TextAlign.justify, ), onChanged: (value) { - ref - .read(boardPreferencesProvider.notifier) - .toggleHapticFeedback(); + ref.read(boardPreferencesProvider.notifier).toggleHapticFeedback(); }, ), SwitchSettingTile( - title: Text( - context.l10n.preferencesPieceAnimation, - ), + title: Text(context.l10n.preferencesPieceAnimation), value: boardPrefs.pieceAnimation, onChanged: (value) { - ref - .read(boardPreferencesProvider.notifier) - .togglePieceAnimation(); + ref.read(boardPreferencesProvider.notifier).togglePieceAnimation(); }, ), SwitchSettingTile( @@ -164,9 +141,7 @@ class _Body extends ConsumerWidget { ), value: boardPrefs.enableShapeDrawings, onChanged: (value) { - ref - .read(boardPreferencesProvider.notifier) - .toggleEnableShapeDrawings(); + ref.read(boardPreferencesProvider.notifier).toggleEnableShapeDrawings(); }, ), ], @@ -176,25 +151,26 @@ class _Body extends ConsumerWidget { hasLeading: false, showDivider: false, children: [ - if (Theme.of(context).platform == TargetPlatform.android && - !isTabletOrLarger(context)) + if (Theme.of(context).platform == TargetPlatform.android && !isTabletOrLarger(context)) androidVersionAsync.maybeWhen( - data: (version) => version != null && version.sdkInt >= 29 - ? SwitchSettingTile( - title: Text(context.l10n.mobileSettingsImmersiveMode), - subtitle: Text( - context.l10n.mobileSettingsImmersiveModeSubtitle, - textAlign: TextAlign.justify, - maxLines: 5, - ), - value: boardPrefs.immersiveModeWhilePlaying ?? false, - onChanged: (value) { - ref - .read(boardPreferencesProvider.notifier) - .toggleImmersiveModeWhilePlaying(); - }, - ) - : const SizedBox.shrink(), + data: + (version) => + version != null && version.sdkInt >= 29 + ? SwitchSettingTile( + title: Text(context.l10n.mobileSettingsImmersiveMode), + subtitle: Text( + context.l10n.mobileSettingsImmersiveModeSubtitle, + textAlign: TextAlign.justify, + maxLines: 5, + ), + value: boardPrefs.immersiveModeWhilePlaying ?? false, + onChanged: (value) { + ref + .read(boardPreferencesProvider.notifier) + .toggleImmersiveModeWhilePlaying(); + }, + ) + : const SizedBox.shrink(), orElse: () => const SizedBox.shrink(), ), SettingsListTile( @@ -208,9 +184,10 @@ class _Body extends ConsumerWidget { choices: ClockPosition.values, selectedItem: boardPrefs.clockPosition, labelBuilder: (t) => Text(t.label), - onSelectedItemChanged: (ClockPosition? value) => ref - .read(boardPreferencesProvider.notifier) - .setClockPosition(value ?? ClockPosition.right), + onSelectedItemChanged: + (ClockPosition? value) => ref + .read(boardPreferencesProvider.notifier) + .setClockPosition(value ?? ClockPosition.right), ); } else { pushPlatformRoute( @@ -222,31 +199,22 @@ class _Body extends ConsumerWidget { }, ), SwitchSettingTile( - title: Text( - context.l10n.preferencesPieceDestinations, - ), + title: Text(context.l10n.preferencesPieceDestinations), value: boardPrefs.showLegalMoves, onChanged: (value) { - ref - .read(boardPreferencesProvider.notifier) - .toggleShowLegalMoves(); + ref.read(boardPreferencesProvider.notifier).toggleShowLegalMoves(); }, ), SwitchSettingTile( - title: Text( - context.l10n.preferencesBoardHighlights, - ), + title: Text(context.l10n.preferencesBoardHighlights), value: boardPrefs.boardHighlights, onChanged: (value) { - ref - .read(boardPreferencesProvider.notifier) - .toggleBoardHighlights(); + ref.read(boardPreferencesProvider.notifier).toggleBoardHighlights(); }, ), SettingsListTile( settingsLabel: const Text('Material'), //TODO: l10n - settingsValue: boardPrefs.materialDifferenceFormat - .l10n(AppLocalizations.of(context)), + settingsValue: boardPrefs.materialDifferenceFormat.l10n(AppLocalizations.of(context)), onTap: () { if (Theme.of(context).platform == TargetPlatform.android) { showChoicePicker( @@ -254,20 +222,18 @@ class _Body extends ConsumerWidget { choices: MaterialDifferenceFormat.values, selectedItem: boardPrefs.materialDifferenceFormat, labelBuilder: (t) => Text(t.label), - onSelectedItemChanged: (MaterialDifferenceFormat? value) => - ref + onSelectedItemChanged: + (MaterialDifferenceFormat? value) => ref .read(boardPreferencesProvider.notifier) .setMaterialDifferenceFormat( - value ?? - MaterialDifferenceFormat.materialDifference, + value ?? MaterialDifferenceFormat.materialDifference, ), ); } else { pushPlatformRoute( context, title: 'Material', - builder: (context) => - const MaterialDifferenceFormatScreen(), + builder: (context) => const MaterialDifferenceFormatScreen(), ); } }, @@ -285,9 +251,7 @@ class PieceShiftMethodSettingsScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final pieceShiftMethod = ref.watch( - boardPreferencesProvider.select( - (state) => state.pieceShiftMethod, - ), + boardPreferencesProvider.select((state) => state.pieceShiftMethod), ); void onChanged(PieceShiftMethod? value) { @@ -323,9 +287,8 @@ class BoardClockPositionScreen extends ConsumerWidget { final clockPosition = ref.watch( boardPreferencesProvider.select((state) => state.clockPosition), ); - void onChanged(ClockPosition? value) => ref - .read(boardPreferencesProvider.notifier) - .setClockPosition(value ?? ClockPosition.right); + void onChanged(ClockPosition? value) => + ref.read(boardPreferencesProvider.notifier).setClockPosition(value ?? ClockPosition.right); return CupertinoPageScaffold( navigationBar: const CupertinoNavigationBar(), child: SafeArea( @@ -350,13 +313,11 @@ class MaterialDifferenceFormatScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final materialDifferenceFormat = ref.watch( - boardPreferencesProvider - .select((state) => state.materialDifferenceFormat), + boardPreferencesProvider.select((state) => state.materialDifferenceFormat), ); - void onChanged(MaterialDifferenceFormat? value) => - ref.read(boardPreferencesProvider.notifier).setMaterialDifferenceFormat( - value ?? MaterialDifferenceFormat.materialDifference, - ); + void onChanged(MaterialDifferenceFormat? value) => ref + .read(boardPreferencesProvider.notifier) + .setMaterialDifferenceFormat(value ?? MaterialDifferenceFormat.materialDifference); return CupertinoPageScaffold( navigationBar: const CupertinoNavigationBar(), child: ListView( @@ -379,15 +340,11 @@ class DragTargetKindSettingsScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final dragTargetKind = ref.watch( - boardPreferencesProvider.select( - (state) => state.dragTargetKind, - ), + boardPreferencesProvider.select((state) => state.dragTargetKind), ); void onChanged(DragTargetKind? value) { - ref - .read(boardPreferencesProvider.notifier) - .setDragTargetKind(value ?? DragTargetKind.circle); + ref.read(boardPreferencesProvider.notifier).setDragTargetKind(value ?? DragTargetKind.circle); } return CupertinoPageScaffold( @@ -396,11 +353,8 @@ class DragTargetKindSettingsScreen extends ConsumerWidget { child: ListView( children: [ Padding( - padding: - Styles.horizontalBodyPadding.add(Styles.sectionTopPadding), - child: const Text( - 'How the target square is highlighted when dragging a piece.', - ), + padding: Styles.horizontalBodyPadding.add(Styles.sectionTopPadding), + child: const Text('How the target square is highlighted when dragging a piece.'), ), ChoicePicker( notchedTile: true, @@ -416,10 +370,7 @@ class DragTargetKindSettingsScreen extends ConsumerWidget { } } -String pieceShiftMethodl10n( - BuildContext context, - PieceShiftMethod pieceShiftMethod, -) => +String pieceShiftMethodl10n(BuildContext context, PieceShiftMethod pieceShiftMethod) => switch (pieceShiftMethod) { // TODO add this to mobile translations PieceShiftMethod.either => 'Either tap or drag', diff --git a/lib/src/view/settings/board_theme_screen.dart b/lib/src/view/settings/board_theme_screen.dart index c005919905..6e3e128a1f 100644 --- a/lib/src/view/settings/board_theme_screen.dart +++ b/lib/src/view/settings/board_theme_screen.dart @@ -13,60 +13,46 @@ class BoardThemeScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return PlatformWidget( - androidBuilder: _androidBuilder, - iosBuilder: _iosBuilder, - ); + return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); } Widget _androidBuilder(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text(context.l10n.board)), - body: _Body(), - ); + return Scaffold(appBar: AppBar(title: Text(context.l10n.board)), body: _Body()); } Widget _iosBuilder(BuildContext context) { - return CupertinoPageScaffold( - navigationBar: const CupertinoNavigationBar(), - child: _Body(), - ); + return CupertinoPageScaffold(navigationBar: const CupertinoNavigationBar(), child: _Body()); } } class _Body extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final boardTheme = - ref.watch(boardPreferencesProvider.select((p) => p.boardTheme)); + final boardTheme = ref.watch(boardPreferencesProvider.select((p) => p.boardTheme)); - final hasSystemColors = - ref.watch(generalPreferencesProvider.select((p) => p.systemColors)); + final hasSystemColors = ref.watch(generalPreferencesProvider.select((p) => p.systemColors)); - final androidVersion = ref.watch(androidVersionProvider).whenOrNull( - data: (v) => v, - ); + final androidVersion = ref.watch(androidVersionProvider).whenOrNull(data: (v) => v); - final choices = BoardTheme.values - .where( - (t) => - t != BoardTheme.system || - (hasSystemColors && - androidVersion != null && - androidVersion.sdkInt >= 31), - ) - .toList(); + final choices = + BoardTheme.values + .where( + (t) => + t != BoardTheme.system || + (hasSystemColors && androidVersion != null && androidVersion.sdkInt >= 31), + ) + .toList(); - void onChanged(BoardTheme? value) => ref - .read(boardPreferencesProvider.notifier) - .setBoardTheme(value ?? BoardTheme.brown); + void onChanged(BoardTheme? value) => + ref.read(boardPreferencesProvider.notifier).setBoardTheme(value ?? BoardTheme.brown); - final checkedIcon = Theme.of(context).platform == TargetPlatform.android - ? const Icon(Icons.check) - : Icon( - CupertinoIcons.check_mark_circled_solid, - color: CupertinoTheme.of(context).primaryColor, - ); + final checkedIcon = + Theme.of(context).platform == TargetPlatform.android + ? const Icon(Icons.check) + : Icon( + CupertinoIcons.check_mark_circled_solid, + color: CupertinoTheme.of(context).primaryColor, + ); return SafeArea( child: ListView.separated( @@ -80,15 +66,13 @@ class _Body extends ConsumerWidget { onTap: () => onChanged(t), ); }, - separatorBuilder: (_, __) => PlatformDivider( - height: 1, - // on iOS: 14 (default indent) + 16 (padding) - indent: - Theme.of(context).platform == TargetPlatform.iOS ? 14 + 16 : null, - color: Theme.of(context).platform == TargetPlatform.iOS - ? null - : Colors.transparent, - ), + separatorBuilder: + (_, __) => PlatformDivider( + height: 1, + // on iOS: 14 (default indent) + 16 (padding) + indent: Theme.of(context).platform == TargetPlatform.iOS ? 14 + 16 : null, + color: Theme.of(context).platform == TargetPlatform.iOS ? null : Colors.transparent, + ), itemCount: choices.length, ), ); diff --git a/lib/src/view/settings/piece_set_screen.dart b/lib/src/view/settings/piece_set_screen.dart index dcaad14fe2..a28a3163e9 100644 --- a/lib/src/view/settings/piece_set_screen.dart +++ b/lib/src/view/settings/piece_set_screen.dart @@ -53,34 +53,30 @@ class _PieceSetScreenState extends ConsumerState { return PlatformScaffold( appBar: PlatformAppBar( title: Text(context.l10n.pieceSet), - actions: [ - if (isLoading) const PlatformAppBarLoadingIndicator(), - ], + actions: [if (isLoading) const PlatformAppBarLoadingIndicator()], ), body: SafeArea( child: ListView.separated( itemCount: PieceSet.values.length, - separatorBuilder: (_, __) => PlatformDivider( - height: 1, - // on iOS: 14 (default indent) + 16 (padding) - indent: Theme.of(context).platform == TargetPlatform.iOS - ? 14 + 16 - : null, - color: Theme.of(context).platform == TargetPlatform.iOS - ? null - : Colors.transparent, - ), + separatorBuilder: + (_, __) => PlatformDivider( + height: 1, + // on iOS: 14 (default indent) + 16 (padding) + indent: Theme.of(context).platform == TargetPlatform.iOS ? 14 + 16 : null, + color: Theme.of(context).platform == TargetPlatform.iOS ? null : Colors.transparent, + ), itemBuilder: (context, index) { final pieceSet = PieceSet.values[index]; return PlatformListTile( - trailing: boardPrefs.pieceSet == pieceSet - ? Theme.of(context).platform == TargetPlatform.android - ? const Icon(Icons.check) - : Icon( - CupertinoIcons.check_mark_circled_solid, - color: CupertinoTheme.of(context).primaryColor, - ) - : null, + trailing: + boardPrefs.pieceSet == pieceSet + ? Theme.of(context).platform == TargetPlatform.android + ? const Icon(Icons.check) + : Icon( + CupertinoIcons.check_mark_circled_solid, + color: CupertinoTheme.of(context).primaryColor, + ) + : null, title: Text(pieceSet.label), subtitle: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 264), @@ -89,11 +85,7 @@ class _PieceSetScreenState extends ConsumerState { boardPrefs.boardTheme.thumbnail, Row( children: [ - for (final img in getPieceImages(pieceSet)) - Image( - image: img, - height: 44, - ), + for (final img in getPieceImages(pieceSet)) Image(image: img, height: 44), ], ), ], diff --git a/lib/src/view/settings/settings_tab_screen.dart b/lib/src/view/settings/settings_tab_screen.dart index 15d755668c..f1dbdc0cfb 100644 --- a/lib/src/view/settings/settings_tab_screen.dart +++ b/lib/src/view/settings/settings_tab_screen.dart @@ -54,9 +54,7 @@ class SettingsTabScreen extends ConsumerWidget { } }, child: Scaffold( - appBar: AppBar( - title: Text(context.l10n.settingsSettings), - ), + appBar: AppBar(title: Text(context.l10n.settingsSettings)), body: SafeArea(child: _Body()), ), ); @@ -66,13 +64,8 @@ class SettingsTabScreen extends ConsumerWidget { return CupertinoPageScaffold( child: CustomScrollView( slivers: [ - CupertinoSliverNavigationBar( - largeTitle: Text(context.l10n.settingsSettings), - ), - SliverSafeArea( - top: false, - sliver: _Body(), - ), + CupertinoSliverNavigationBar(largeTitle: Text(context.l10n.settingsSettings)), + SliverSafeArea(top: false, sliver: _Body()), ], ), ); @@ -92,36 +85,34 @@ class _Body extends ConsumerWidget { final boardPrefs = ref.watch(boardPreferencesProvider); final authController = ref.watch(authControllerProvider); final userSession = ref.watch(authSessionProvider); - final packageInfo = - ref.read(preloadedDataProvider).requireValue.packageInfo; + final packageInfo = ref.read(preloadedDataProvider).requireValue.packageInfo; final dbSize = ref.watch(getDbSizeInBytesProvider); final Widget? donateButton = userSession == null || userSession.user.isPatron != true ? PlatformListTile( - leading: Icon( - LichessIcons.patron, - semanticLabel: context.l10n.patronLichessPatron, - color: context.lichessColors.brag, - ), - title: Text( - context.l10n.patronDonate, - style: TextStyle(color: context.lichessColors.brag), - ), - trailing: Theme.of(context).platform == TargetPlatform.iOS - ? const CupertinoListTileChevron() - : null, - onTap: () { - launchUrl(Uri.parse('https://lichess.org/patron')); - }, - ) + leading: Icon( + LichessIcons.patron, + semanticLabel: context.l10n.patronLichessPatron, + color: context.lichessColors.brag, + ), + title: Text( + context.l10n.patronDonate, + style: TextStyle(color: context.lichessColors.brag), + ), + trailing: + Theme.of(context).platform == TargetPlatform.iOS + ? const CupertinoListTileChevron() + : null, + onTap: () { + launchUrl(Uri.parse('https://lichess.org/patron')); + }, + ) : null; final List content = [ ListSection( - header: userSession != null - ? UserFullNameWidget(user: userSession.user) - : null, + header: userSession != null ? UserFullNameWidget(user: userSession.user) : null, hasLeading: true, showDivider: true, children: [ @@ -129,9 +120,10 @@ class _Body extends ConsumerWidget { PlatformListTile( leading: const Icon(Icons.person_outline), title: Text(context.l10n.profile), - trailing: Theme.of(context).platform == TargetPlatform.iOS - ? const CupertinoListTileChevron() - : null, + trailing: + Theme.of(context).platform == TargetPlatform.iOS + ? const CupertinoListTileChevron() + : null, onTap: () { ref.invalidate(accountActivityProvider); pushPlatformRoute( @@ -144,9 +136,10 @@ class _Body extends ConsumerWidget { PlatformListTile( leading: const Icon(Icons.manage_accounts_outlined), title: Text(context.l10n.preferencesPreferences), - trailing: Theme.of(context).platform == TargetPlatform.iOS - ? const CupertinoListTileChevron() - : null, + trailing: + Theme.of(context).platform == TargetPlatform.iOS + ? const CupertinoListTileChevron() + : null, onTap: () { pushPlatformRoute( context, @@ -183,8 +176,7 @@ class _Body extends ConsumerWidget { }, ), ], - if (Theme.of(context).platform == TargetPlatform.android && - donateButton != null) + if (Theme.of(context).platform == TargetPlatform.android && donateButton != null) donateButton, ], ), @@ -208,21 +200,18 @@ class _Body extends ConsumerWidget { SettingsListTile( icon: const Icon(Icons.brightness_medium_outlined), settingsLabel: Text(context.l10n.background), - settingsValue: AppBackgroundModeScreen.themeTitle( - context, - generalPrefs.themeMode, - ), + settingsValue: AppBackgroundModeScreen.themeTitle(context, generalPrefs.themeMode), onTap: () { if (Theme.of(context).platform == TargetPlatform.android) { showChoicePicker( context, choices: BackgroundThemeMode.values, selectedItem: generalPrefs.themeMode, - labelBuilder: (t) => - Text(AppBackgroundModeScreen.themeTitle(context, t)), - onSelectedItemChanged: (BackgroundThemeMode? value) => ref - .read(generalPreferencesProvider.notifier) - .setThemeMode(value ?? BackgroundThemeMode.system), + labelBuilder: (t) => Text(AppBackgroundModeScreen.themeTitle(context, t)), + onSelectedItemChanged: + (BackgroundThemeMode? value) => ref + .read(generalPreferencesProvider.notifier) + .setThemeMode(value ?? BackgroundThemeMode.system), ); } else { pushPlatformRoute( @@ -236,22 +225,18 @@ class _Body extends ConsumerWidget { SettingsListTile( icon: const Icon(Icons.palette_outlined), settingsLabel: Text(context.l10n.mobileTheme), - settingsValue: - '${boardPrefs.boardTheme.label} / ${boardPrefs.pieceSet.label}', + settingsValue: '${boardPrefs.boardTheme.label} / ${boardPrefs.pieceSet.label}', onTap: () { - pushPlatformRoute( - context, - title: 'Theme', - builder: (context) => const ThemeScreen(), - ); + pushPlatformRoute(context, title: 'Theme', builder: (context) => const ThemeScreen()); }, ), PlatformListTile( leading: const Icon(LichessIcons.chess_board), title: Text(context.l10n.board), - trailing: Theme.of(context).platform == TargetPlatform.iOS - ? const CupertinoListTileChevron() - : null, + trailing: + Theme.of(context).platform == TargetPlatform.iOS + ? const CupertinoListTileChevron() + : null, onTap: () { pushPlatformRoute( context, @@ -271,12 +256,11 @@ class _Body extends ConsumerWidget { showChoicePicker( context, choices: kSupportedLocales, - selectedItem: - generalPrefs.locale ?? Localizations.localeOf(context), + selectedItem: generalPrefs.locale ?? Localizations.localeOf(context), labelBuilder: (t) => Text(localeToLocalizedName(t)), - onSelectedItemChanged: (Locale? locale) => ref - .read(generalPreferencesProvider.notifier) - .setLocale(locale), + onSelectedItemChanged: + (Locale? locale) => + ref.read(generalPreferencesProvider.notifier).setLocale(locale), ); } else { AppSettings.openAppSettings(); @@ -360,11 +344,11 @@ class _Body extends ConsumerWidget { PlatformListTile( leading: const Icon(Icons.storage_outlined), title: const Text('Local database size'), - subtitle: Theme.of(context).platform == TargetPlatform.iOS - ? null - : Text(_getSizeString(dbSize.value)), - additionalInfo: - dbSize.hasValue ? Text(_getSizeString(dbSize.value)) : null, + subtitle: + Theme.of(context).platform == TargetPlatform.iOS + ? null + : Text(_getSizeString(dbSize.value)), + additionalInfo: dbSize.hasValue ? Text(_getSizeString(dbSize.value)) : null, ), ], ), @@ -375,10 +359,7 @@ class _Body extends ConsumerWidget { children: [ LichessMessage(style: Theme.of(context).textTheme.bodyMedium), const SizedBox(height: 10), - Text( - 'v${packageInfo.version}', - style: Theme.of(context).textTheme.bodySmall, - ), + Text('v${packageInfo.version}', style: Theme.of(context).textTheme.bodySmall), ], ), ), @@ -411,18 +392,14 @@ class _Body extends ConsumerWidget { title: Text(context.l10n.logOut), actions: [ TextButton( - style: TextButton.styleFrom( - textStyle: Theme.of(context).textTheme.labelLarge, - ), + style: TextButton.styleFrom(textStyle: Theme.of(context).textTheme.labelLarge), child: Text(context.l10n.cancel), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( - style: TextButton.styleFrom( - textStyle: Theme.of(context).textTheme.labelLarge, - ), + style: TextButton.styleFrom(textStyle: Theme.of(context).textTheme.labelLarge), child: Text(context.l10n.mobileOkButton), onPressed: () async { Navigator.of(context).pop(); @@ -436,8 +413,7 @@ class _Body extends ConsumerWidget { } } - String _getSizeString(int? bytes) => - '${_bytesToMB(bytes ?? 0).toStringAsFixed(2)}MB'; + String _getSizeString(int? bytes) => '${_bytesToMB(bytes ?? 0).toStringAsFixed(2)}MB'; double _bytesToMB(int bytes) => bytes * 0.000001; @@ -454,9 +430,10 @@ class _OpenInNewIcon extends StatelessWidget { return Icon( Icons.open_in_new, size: 18, - color: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoColors.systemGrey2.resolveFrom(context) - : null, + color: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoColors.systemGrey2.resolveFrom(context) + : null, ); } } diff --git a/lib/src/view/settings/sound_settings_screen.dart b/lib/src/view/settings/sound_settings_screen.dart index 75010c06c4..19648fbb44 100644 --- a/lib/src/view/settings/sound_settings_screen.dart +++ b/lib/src/view/settings/sound_settings_screen.dart @@ -8,43 +8,22 @@ import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; import 'package:lichess_mobile/src/widgets/settings.dart'; -const kMasterVolumeValues = [ - 0.0, - 0.1, - 0.2, - 0.3, - 0.4, - 0.5, - 0.6, - 0.7, - 0.8, - 0.9, - 1.0, -]; +const kMasterVolumeValues = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]; class SoundSettingsScreen extends StatelessWidget { const SoundSettingsScreen({super.key}); @override Widget build(BuildContext context) { - return PlatformWidget( - androidBuilder: _androidBuilder, - iosBuilder: _iosBuilder, - ); + return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); } Widget _androidBuilder(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text(context.l10n.sound)), - body: _Body(), - ); + return Scaffold(appBar: AppBar(title: Text(context.l10n.sound)), body: _Body()); } Widget _iosBuilder(BuildContext context) { - return CupertinoPageScaffold( - navigationBar: const CupertinoNavigationBar(), - child: _Body(), - ); + return CupertinoPageScaffold(navigationBar: const CupertinoNavigationBar(), child: _Body()); } } @@ -61,13 +40,8 @@ class _Body extends ConsumerWidget { final generalPrefs = ref.watch(generalPreferencesProvider); void onChanged(SoundTheme? value) { - ref - .read(generalPreferencesProvider.notifier) - .setSoundTheme(value ?? SoundTheme.standard); - ref.read(soundServiceProvider).changeTheme( - value ?? SoundTheme.standard, - playSound: true, - ); + ref.read(generalPreferencesProvider.notifier).setSoundTheme(value ?? SoundTheme.standard); + ref.read(soundServiceProvider).changeTheme(value ?? SoundTheme.standard, playSound: true); } return ListView( @@ -79,9 +53,7 @@ class _Body extends ConsumerWidget { value: generalPrefs.masterVolume, values: kMasterVolumeValues, onChangeEnd: (value) { - ref - .read(generalPreferencesProvider.notifier) - .setMasterVolume(value); + ref.read(generalPreferencesProvider.notifier).setMasterVolume(value); }, labelBuilder: volumeLabel, ), diff --git a/lib/src/view/settings/theme_screen.dart b/lib/src/view/settings/theme_screen.dart index 2e16832dc7..2e1307a8e0 100644 --- a/lib/src/view/settings/theme_screen.dart +++ b/lib/src/view/settings/theme_screen.dart @@ -23,24 +23,18 @@ class ThemeScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return PlatformScaffold( - appBar: const PlatformAppBar(title: Text('Theme')), - body: _Body(), - ); + return PlatformScaffold(appBar: const PlatformAppBar(title: Text('Theme')), body: _Body()); } } -String shapeColorL10n( - BuildContext context, - ShapeColor shapeColor, -) => - // TODO add l10n - switch (shapeColor) { - ShapeColor.green => 'Green', - ShapeColor.red => 'Red', - ShapeColor.blue => 'Blue', - ShapeColor.yellow => 'Yellow', - }; +String shapeColorL10n(BuildContext context, ShapeColor shapeColor) => +// TODO add l10n +switch (shapeColor) { + ShapeColor.green => 'Green', + ShapeColor.red => 'Red', + ShapeColor.blue => 'Blue', + ShapeColor.yellow => 'Yellow', +}; class _Body extends ConsumerWidget { @override @@ -60,33 +54,26 @@ class _Body extends ConsumerWidget { constraints.biggest.shortestSide - horizontalPadding * 2, ); return Padding( - padding: const EdgeInsets.symmetric( - horizontal: horizontalPadding, - vertical: 16, - ), + padding: const EdgeInsets.symmetric(horizontal: horizontalPadding, vertical: 16), child: Center( child: Chessboard.fixed( size: boardSize, orientation: Side.white, lastMove: const NormalMove(from: Square.e2, to: Square.e4), - fen: - 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1', - shapes: { - Circle( - color: boardPrefs.shapeColor.color, - orig: Square.fromName('b8'), - ), - Arrow( - color: boardPrefs.shapeColor.color, - orig: Square.fromName('b8'), - dest: Square.fromName('c6'), - ), - }.lock, + fen: 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1', + shapes: + { + Circle(color: boardPrefs.shapeColor.color, orig: Square.fromName('b8')), + Arrow( + color: boardPrefs.shapeColor.color, + orig: Square.fromName('b8'), + dest: Square.fromName('c6'), + ), + }.lock, settings: boardPrefs.toBoardSettings().copyWith( - borderRadius: - const BorderRadius.all(Radius.circular(4.0)), - boxShadow: boardShadows, - ), + borderRadius: const BorderRadius.all(Radius.circular(4.0)), + boxShadow: boardShadows, + ), ), ), ); @@ -97,18 +84,18 @@ class _Body extends ConsumerWidget { children: [ if (Theme.of(context).platform == TargetPlatform.android) androidVersionAsync.maybeWhen( - data: (version) => version != null && version.sdkInt >= 31 - ? SwitchSettingTile( - leading: const Icon(Icons.colorize_outlined), - title: Text(context.l10n.mobileSystemColors), - value: generalPrefs.systemColors, - onChanged: (value) { - ref - .read(generalPreferencesProvider.notifier) - .toggleSystemColors(); - }, - ) - : const SizedBox.shrink(), + data: + (version) => + version != null && version.sdkInt >= 31 + ? SwitchSettingTile( + leading: const Icon(Icons.colorize_outlined), + title: Text(context.l10n.mobileSystemColors), + value: generalPrefs.systemColors, + onChanged: (value) { + ref.read(generalPreferencesProvider.notifier).toggleSystemColors(); + }, + ) + : const SizedBox.shrink(), orElse: () => const SizedBox.shrink(), ), SettingsListTile( @@ -144,23 +131,16 @@ class _Body extends ConsumerWidget { context, choices: ShapeColor.values, selectedItem: boardPrefs.shapeColor, - labelBuilder: (t) => Text.rich( - TextSpan( - children: [ + labelBuilder: + (t) => Text.rich( TextSpan( - text: shapeColorL10n(context, t), + children: [ + TextSpan(text: shapeColorL10n(context, t)), + const TextSpan(text: ' '), + WidgetSpan(child: Container(width: 15, height: 15, color: t.color)), + ], ), - const TextSpan(text: ' '), - WidgetSpan( - child: Container( - width: 15, - height: 15, - color: t.color, - ), - ), - ], - ), - ), + ), onSelectedItemChanged: (ShapeColor? value) { ref .read(boardPreferencesProvider.notifier) @@ -171,9 +151,7 @@ class _Body extends ConsumerWidget { ), SwitchSettingTile( leading: const Icon(Icons.location_on), - title: Text( - context.l10n.preferencesBoardCoordinates, - ), + title: Text(context.l10n.preferencesBoardCoordinates), value: boardPrefs.coordinates, onChanged: (value) { ref.read(boardPreferencesProvider.notifier).toggleCoordinates(); diff --git a/lib/src/view/settings/toggle_sound_button.dart b/lib/src/view/settings/toggle_sound_button.dart index b3abc10487..f22b286bbf 100644 --- a/lib/src/view/settings/toggle_sound_button.dart +++ b/lib/src/view/settings/toggle_sound_button.dart @@ -10,16 +10,13 @@ class ToggleSoundButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final isSoundEnabled = ref.watch( - generalPreferencesProvider.select( - (prefs) => prefs.isSoundEnabled, - ), + generalPreferencesProvider.select((prefs) => prefs.isSoundEnabled), ); return AppBarIconButton( // TODO: i18n semanticsLabel: 'Toggle sound', - onPressed: () => - ref.read(generalPreferencesProvider.notifier).toggleSoundEnabled(), + onPressed: () => ref.read(generalPreferencesProvider.notifier).toggleSoundEnabled(), icon: Icon(isSoundEnabled ? Icons.volume_up : Icons.volume_off), ); } diff --git a/lib/src/view/study/study_bottom_bar.dart b/lib/src/view/study/study_bottom_bar.dart index ab2de9d8c6..4a1cf84ccb 100644 --- a/lib/src/view/study/study_bottom_bar.dart +++ b/lib/src/view/study/study_bottom_bar.dart @@ -14,18 +14,14 @@ import 'package:lichess_mobile/src/widgets/buttons.dart'; import 'package:lichess_mobile/src/widgets/list.dart'; class StudyBottomBar extends ConsumerWidget { - const StudyBottomBar({ - required this.id, - }); + const StudyBottomBar({required this.id}); final StudyId id; @override Widget build(BuildContext context, WidgetRef ref) { final gamebook = ref.watch( - studyControllerProvider(id).select( - (s) => s.requireValue.gamebookActive, - ), + studyControllerProvider(id).select((s) => s.requireValue.gamebookActive), ); return gamebook ? _GamebookBottomBar(id: id) : _AnalysisBottomBar(id: id); @@ -33,9 +29,7 @@ class StudyBottomBar extends ConsumerWidget { } class _AnalysisBottomBar extends ConsumerWidget { - const _AnalysisBottomBar({ - required this.id, - }); + const _AnalysisBottomBar({required this.id}); final StudyId id; @@ -46,12 +40,10 @@ class _AnalysisBottomBar extends ConsumerWidget { return const BottomBar(children: []); } - final onGoForward = state.canGoNext - ? ref.read(studyControllerProvider(id).notifier).userNext - : null; - final onGoBack = state.canGoBack - ? ref.read(studyControllerProvider(id).notifier).userPrevious - : null; + final onGoForward = + state.canGoNext ? ref.read(studyControllerProvider(id).notifier).userNext : null; + final onGoBack = + state.canGoBack ? ref.read(studyControllerProvider(id).notifier).userPrevious : null; return BottomBar( children: [ @@ -60,9 +52,7 @@ class _AnalysisBottomBar extends ConsumerWidget { id: id, chapterId: state.study.chapter.id, hasNextChapter: state.hasNextChapter, - blink: !state.isIntroductoryChapter && - state.isAtEndOfChapter && - state.hasNextChapter, + blink: !state.isIntroductoryChapter && state.isAtEndOfChapter && state.hasNextChapter, ), RepeatButton( onLongPress: onGoBack, @@ -92,9 +82,7 @@ class _AnalysisBottomBar extends ConsumerWidget { } class _GamebookBottomBar extends ConsumerWidget { - const _GamebookBottomBar({ - required this.id, - }); + const _GamebookBottomBar({required this.id}); final StudyId id; @@ -107,77 +95,75 @@ class _GamebookBottomBar extends ConsumerWidget { _ChapterButton(state: state), ...switch (state.gamebookState) { GamebookState.findTheMove => [ - if (!state.currentNode.isRoot) - BottomBarButton( - onTap: ref.read(studyControllerProvider(id).notifier).reset, - icon: Icons.skip_previous, - label: 'Back', - showLabel: true, - ), + if (!state.currentNode.isRoot) BottomBarButton( - icon: Icons.help, - label: context.l10n.viewTheSolution, + onTap: ref.read(studyControllerProvider(id).notifier).reset, + icon: Icons.skip_previous, + label: 'Back', showLabel: true, - onTap: ref - .read(studyControllerProvider(id).notifier) - .showGamebookSolution, ), - ], + BottomBarButton( + icon: Icons.help, + label: context.l10n.viewTheSolution, + showLabel: true, + onTap: ref.read(studyControllerProvider(id).notifier).showGamebookSolution, + ), + ], GamebookState.startLesson || GamebookState.correctMove => [ + BottomBarButton( + onTap: ref.read(studyControllerProvider(id).notifier).userNext, + icon: Icons.play_arrow, + label: context.l10n.studyNext, + showLabel: true, + blink: state.gamebookComment != null && !state.isIntroductoryChapter, + ), + ], + GamebookState.incorrectMove => [ + BottomBarButton( + onTap: ref.read(studyControllerProvider(id).notifier).userPrevious, + label: context.l10n.retry, + showLabel: true, + icon: Icons.refresh, + blink: state.gamebookComment != null, + ), + ], + GamebookState.lessonComplete => [ + if (!state.isIntroductoryChapter) BottomBarButton( - onTap: ref.read(studyControllerProvider(id).notifier).userNext, - icon: Icons.play_arrow, - label: context.l10n.studyNext, + onTap: ref.read(studyControllerProvider(id).notifier).reset, + icon: Icons.refresh, + label: context.l10n.studyPlayAgain, showLabel: true, - blink: state.gamebookComment != null && - !state.isIntroductoryChapter, ), - ], - GamebookState.incorrectMove => [ + _NextChapterButton( + id: id, + chapterId: state.study.chapter.id, + hasNextChapter: state.hasNextChapter, + blink: !state.isIntroductoryChapter && state.hasNextChapter, + ), + if (!state.isIntroductoryChapter) BottomBarButton( onTap: - ref.read(studyControllerProvider(id).notifier).userPrevious, - label: context.l10n.retry, + () => pushPlatformRoute( + context, + rootNavigator: true, + builder: + (context) => AnalysisScreen( + options: AnalysisOptions( + orientation: state.pov, + standalone: ( + pgn: state.pgn, + isComputerAnalysisAllowed: true, + variant: state.variant, + ), + ), + ), + ), + icon: Icons.biotech, + label: context.l10n.analysis, showLabel: true, - icon: Icons.refresh, - blink: state.gamebookComment != null, ), - ], - GamebookState.lessonComplete => [ - if (!state.isIntroductoryChapter) - BottomBarButton( - onTap: ref.read(studyControllerProvider(id).notifier).reset, - icon: Icons.refresh, - label: context.l10n.studyPlayAgain, - showLabel: true, - ), - _NextChapterButton( - id: id, - chapterId: state.study.chapter.id, - hasNextChapter: state.hasNextChapter, - blink: !state.isIntroductoryChapter && state.hasNextChapter, - ), - if (!state.isIntroductoryChapter) - BottomBarButton( - onTap: () => pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => AnalysisScreen( - options: AnalysisOptions( - orientation: state.pov, - standalone: ( - pgn: state.pgn, - isComputerAnalysisAllowed: true, - variant: state.variant, - ), - ), - ), - ), - icon: Icons.biotech, - label: context.l10n.analysis, - showLabel: true, - ), - ], + ], }, ], ); @@ -217,19 +203,18 @@ class _NextChapterButtonState extends ConsumerState<_NextChapterButton> { return isLoading ? const Center(child: CircularProgressIndicator()) : BottomBarButton( - onTap: widget.hasNextChapter - ? () { - ref - .read(studyControllerProvider(widget.id).notifier) - .nextChapter(); + onTap: + widget.hasNextChapter + ? () { + ref.read(studyControllerProvider(widget.id).notifier).nextChapter(); setState(() => isLoading = true); } - : null, - icon: Icons.play_arrow, - label: context.l10n.studyNextChapter, - showLabel: true, - blink: widget.blink, - ); + : null, + icon: Icons.play_arrow, + label: context.l10n.studyNextChapter, + showLabel: true, + blink: widget.blink, + ); } } @@ -241,24 +226,26 @@ class _ChapterButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return BottomBarButton( - onTap: () => showAdaptiveBottomSheet( - context: context, - showDragHandle: true, - isScrollControlled: true, - isDismissible: true, - builder: (_) => DraggableScrollableSheet( - initialChildSize: 0.6, - maxChildSize: 0.6, - snap: true, - expand: false, - builder: (context, scrollController) { - return _StudyChaptersMenu( - id: state.study.id, - scrollController: scrollController, - ); - }, - ), - ), + onTap: + () => showAdaptiveBottomSheet( + context: context, + showDragHandle: true, + isScrollControlled: true, + isDismissible: true, + builder: + (_) => DraggableScrollableSheet( + initialChildSize: 0.6, + maxChildSize: 0.6, + snap: true, + expand: false, + builder: (context, scrollController) { + return _StudyChaptersMenu( + id: state.study.id, + scrollController: scrollController, + ); + }, + ), + ), label: context.l10n.studyNbChapters(state.study.chapters.length), showLabel: true, icon: Icons.menu_book, @@ -267,10 +254,7 @@ class _ChapterButton extends ConsumerWidget { } class _StudyChaptersMenu extends ConsumerStatefulWidget { - const _StudyChaptersMenu({ - required this.id, - required this.scrollController, - }); + const _StudyChaptersMenu({required this.id, required this.scrollController}); final StudyId id; final ScrollController scrollController; @@ -289,10 +273,7 @@ class _StudyChaptersMenuState extends ConsumerState<_StudyChaptersMenu> { // Scroll to the current chapter WidgetsBinding.instance.addPostFrameCallback((_) { if (currentChapterKey.currentContext != null) { - Scrollable.ensureVisible( - currentChapterKey.currentContext!, - alignment: 0.5, - ); + Scrollable.ensureVisible(currentChapterKey.currentContext!, alignment: 0.5); } }); @@ -309,14 +290,10 @@ class _StudyChaptersMenuState extends ConsumerState<_StudyChaptersMenu> { const SizedBox(height: 16), for (final chapter in state.study.chapters) PlatformListTile( - key: chapter.id == state.currentChapter.id - ? currentChapterKey - : null, + key: chapter.id == state.currentChapter.id ? currentChapterKey : null, title: Text(chapter.name, maxLines: 2), onTap: () { - ref.read(studyControllerProvider(widget.id).notifier).goToChapter( - chapter.id, - ); + ref.read(studyControllerProvider(widget.id).notifier).goToChapter(chapter.id); Navigator.of(context).pop(); }, selected: chapter.id == state.currentChapter.id, diff --git a/lib/src/view/study/study_gamebook.dart b/lib/src/view/study/study_gamebook.dart index 67b6d17583..c12c09ed5e 100644 --- a/lib/src/view/study/study_gamebook.dart +++ b/lib/src/view/study/study_gamebook.dart @@ -8,9 +8,7 @@ import 'package:lichess_mobile/src/widgets/buttons.dart'; import 'package:url_launcher/url_launcher.dart'; class StudyGamebook extends StatelessWidget { - const StudyGamebook( - this.id, - ); + const StudyGamebook(this.id); final StudyId id; @@ -26,10 +24,7 @@ class StudyGamebook extends StatelessWidget { padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _Comment(id: id), - _Hint(id: id), - ], + children: [_Comment(id: id), _Hint(id: id)], ), ), ), @@ -41,9 +36,7 @@ class StudyGamebook extends StatelessWidget { } class _Comment extends ConsumerWidget { - const _Comment({ - required this.id, - }); + const _Comment({required this.id}); final StudyId id; @@ -51,14 +44,14 @@ class _Comment extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(studyControllerProvider(id)).requireValue; - final comment = state.gamebookComment ?? + final comment = + state.gamebookComment ?? switch (state.gamebookState) { GamebookState.findTheMove => context.l10n.studyWhatWouldYouPlay, GamebookState.correctMove => context.l10n.studyGoodMove, GamebookState.incorrectMove => context.l10n.puzzleNotTheMove, - GamebookState.lessonComplete => - context.l10n.studyYouCompletedThisLesson, - _ => '' + GamebookState.lessonComplete => context.l10n.studyYouCompletedThisLesson, + _ => '', }; return Expanded( @@ -68,9 +61,7 @@ class _Comment extends ConsumerWidget { padding: const EdgeInsets.only(right: 5), child: Linkify( text: comment, - style: const TextStyle( - fontSize: 16, - ), + style: const TextStyle(fontSize: 16), onOpen: (link) async { launchUrl(Uri.parse(link.url)); }, @@ -83,9 +74,7 @@ class _Comment extends ConsumerWidget { } class _Hint extends ConsumerStatefulWidget { - const _Hint({ - required this.id, - }); + const _Hint({required this.id}); final StudyId id; @@ -98,15 +87,15 @@ class _HintState extends ConsumerState<_Hint> { @override Widget build(BuildContext context) { - final hint = - ref.watch(studyControllerProvider(widget.id)).requireValue.gamebookHint; + final hint = ref.watch(studyControllerProvider(widget.id)).requireValue.gamebookHint; return hint == null ? const SizedBox.shrink() : SizedBox( - height: 40, - child: showHint - ? Center(child: Text(hint)) - : TextButton( + height: 40, + child: + showHint + ? Center(child: Text(hint)) + : TextButton( onPressed: () { setState(() { showHint = true; @@ -114,7 +103,7 @@ class _HintState extends ConsumerState<_Hint> { }, child: Text(context.l10n.getAHint), ), - ); + ); } } @@ -159,10 +148,7 @@ class GamebookButton extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Text( label, - style: TextStyle( - fontSize: 16.0, - color: highlighted ? primary : null, - ), + style: TextStyle(fontSize: 16.0, color: highlighted ? primary : null), ), ), ], diff --git a/lib/src/view/study/study_list_screen.dart b/lib/src/view/study/study_list_screen.dart index f209737c6d..a824f99ab8 100644 --- a/lib/src/view/study/study_list_screen.dart +++ b/lib/src/view/study/study_list_screen.dart @@ -32,9 +32,7 @@ class StudyListScreen extends ConsumerWidget { final isLoggedIn = ref.watch(authSessionProvider)?.user.id != null; final filter = ref.watch(studyFilterProvider); - final title = Text( - isLoggedIn ? filter.category.l10n(context.l10n) : context.l10n.studyMenu, - ); + final title = Text(isLoggedIn ? filter.category.l10n(context.l10n) : context.l10n.studyMenu); return PlatformScaffold( appBar: PlatformAppBar( @@ -44,14 +42,13 @@ class StudyListScreen extends ConsumerWidget { icon: const Icon(Icons.tune), // TODO: translate semanticsLabel: 'Filter studies', - onPressed: () => showAdaptiveBottomSheet( - context: context, - isScrollControlled: true, - showDragHandle: true, - builder: (_) => _StudyFilterSheet( - isLoggedIn: isLoggedIn, - ), - ), + onPressed: + () => showAdaptiveBottomSheet( + context: context, + isScrollControlled: true, + showDragHandle: true, + builder: (_) => _StudyFilterSheet(isLoggedIn: isLoggedIn), + ), ), ], ), @@ -79,8 +76,8 @@ class _StudyFilterSheet extends ConsumerWidget { choices: StudyCategory.values, choiceSelected: (choice) => filter.category == choice, choiceLabel: (category) => Text(category.l10n(context.l10n)), - onSelected: (value, selected) => - ref.read(studyFilterProvider.notifier).setCategory(value), + onSelected: + (value, selected) => ref.read(studyFilterProvider.notifier).setCategory(value), ), const PlatformDivider(thickness: 1, indent: 0), const SizedBox(height: 10.0), @@ -92,8 +89,7 @@ class _StudyFilterSheet extends ConsumerWidget { choices: StudyListOrder.values, choiceSelected: (choice) => filter.order == choice, choiceLabel: (order) => Text(order.l10n(context.l10n)), - onSelected: (value, selected) => - ref.read(studyFilterProvider.notifier).setOrder(value), + onSelected: (value, selected) => ref.read(studyFilterProvider.notifier).setOrder(value), ), ], ); @@ -101,9 +97,7 @@ class _StudyFilterSheet extends ConsumerWidget { } class _Body extends ConsumerStatefulWidget { - const _Body({ - required this.filter, - }); + const _Body({required this.filter}); final StudyFilterState filter; @@ -121,10 +115,7 @@ class _BodyState extends ConsumerState<_Body> { bool requestedNextPage = false; StudyListPaginatorProvider get paginatorProvider => - StudyListPaginatorProvider( - filter: widget.filter, - search: search, - ); + StudyListPaginatorProvider(filter: widget.filter, search: search); @override void initState() { @@ -142,8 +133,7 @@ class _BodyState extends ConsumerState<_Body> { void _scrollListener() { if (!requestedNextPage && - _scrollController.position.pixels >= - _scrollController.position.maxScrollExtent - 300) { + _scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 300) { final studiesList = ref.read(paginatorProvider); if (!studiesList.isLoading) { @@ -176,10 +166,11 @@ class _BodyState extends ConsumerState<_Body> { padding: Styles.bodySectionPadding, child: PlatformSearchBar( controller: _searchController, - onClear: () => setState(() { - search = null; - _searchController.clear(); - }), + onClear: + () => setState(() { + search = null; + _searchController.clear(); + }), hintText: search ?? context.l10n.searchSearch, onSubmitted: (term) { setState(() { @@ -195,29 +186,23 @@ class _BodyState extends ConsumerState<_Body> { shrinkWrap: true, itemCount: studies.studies.length + 1, controller: _scrollController, - separatorBuilder: (context, index) => index == 0 - ? const SizedBox.shrink() - : Theme.of(context).platform == TargetPlatform.iOS - ? const PlatformDivider( - height: 1, - cupertinoHasLeading: true, - ) - : const PlatformDivider( - height: 1, - color: Colors.transparent, - ), - itemBuilder: (context, index) => index == 0 - ? searchBar - : _StudyListItem(study: studies.studies[index - 1]), + separatorBuilder: + (context, index) => + index == 0 + ? const SizedBox.shrink() + : Theme.of(context).platform == TargetPlatform.iOS + ? const PlatformDivider(height: 1, cupertinoHasLeading: true) + : const PlatformDivider(height: 1, color: Colors.transparent), + itemBuilder: + (context, index) => + index == 0 ? searchBar : _StudyListItem(study: studies.studies[index - 1]), ); }, loading: () { return Column( children: [ searchBar, - const Expanded( - child: Center(child: CircularProgressIndicator.adaptive()), - ), + const Expanded(child: Center(child: CircularProgressIndicator.adaptive())), ], ); }, @@ -230,41 +215,29 @@ class _BodyState extends ConsumerState<_Body> { } class _StudyListItem extends StatelessWidget { - const _StudyListItem({ - required this.study, - }); + const _StudyListItem({required this.study}); final StudyPageData study; @override Widget build(BuildContext context) { return PlatformListTile( - padding: Theme.of(context).platform == TargetPlatform.iOS - ? const EdgeInsets.symmetric( - horizontal: 14.0, - vertical: 12.0, - ) - : null, - leading: _StudyFlair( - flair: study.flair, - size: 30, - ), + padding: + Theme.of(context).platform == TargetPlatform.iOS + ? const EdgeInsets.symmetric(horizontal: 14.0, vertical: 12.0) + : null, + leading: _StudyFlair(flair: study.flair, size: 30), title: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - study.name, - overflow: TextOverflow.ellipsis, - maxLines: 2, - ), - ], + children: [Text(study.name, overflow: TextOverflow.ellipsis, maxLines: 2)], ), subtitle: _StudySubtitle(study: study), - onTap: () => pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => StudyScreen(id: study.id), - ), + onTap: + () => pushPlatformRoute( + context, + rootNavigator: true, + builder: (context) => StudyScreen(id: study.id), + ), onLongPress: () { showAdaptiveBottomSheet( context: context, @@ -272,9 +245,7 @@ class _StudyListItem extends StatelessWidget { isDismissible: true, isScrollControlled: true, showDragHandle: true, - constraints: BoxConstraints( - minHeight: MediaQuery.sizeOf(context).height * 0.5, - ), + constraints: BoxConstraints(minHeight: MediaQuery.sizeOf(context).height * 0.5), builder: (context) => _ContextMenu(study: study), ); }, @@ -283,9 +254,7 @@ class _StudyListItem extends StatelessWidget { } class _ContextMenu extends ConsumerWidget { - const _ContextMenu({ - required this.study, - }); + const _ContextMenu({required this.study}); final StudyPageData study; @@ -303,9 +272,7 @@ class _ContextMenu extends ConsumerWidget { } class _StudyChapters extends StatelessWidget { - const _StudyChapters({ - required this.study, - }); + const _StudyChapters({required this.study}); final StudyPageData study; @@ -326,9 +293,7 @@ class _StudyChapters extends StatelessWidget { size: DefaultTextStyle.of(context).style.fontSize, ), ), - TextSpan( - text: ' $chapter', - ), + TextSpan(text: ' $chapter'), ], ), ), @@ -339,9 +304,7 @@ class _StudyChapters extends StatelessWidget { } class _StudyMembers extends StatelessWidget { - const _StudyMembers({ - required this.study, - }); + const _StudyMembers({required this.study}); final StudyPageData study; @@ -359,19 +322,14 @@ class _StudyMembers extends StatelessWidget { WidgetSpan( alignment: PlaceholderAlignment.middle, child: Icon( - member.role == 'w' - ? LichessIcons.radio_tower_lichess - : Icons.remove_red_eye, + member.role == 'w' ? LichessIcons.radio_tower_lichess : Icons.remove_red_eye, size: DefaultTextStyle.of(context).style.fontSize, ), ), const TextSpan(text: ' '), WidgetSpan( alignment: PlaceholderAlignment.bottom, - child: UserFullNameWidget( - user: member.user, - showFlair: false, - ), + child: UserFullNameWidget(user: member.user, showFlair: false), ), ], ), @@ -391,18 +349,15 @@ class _StudyFlair extends StatelessWidget { @override Widget build(BuildContext context) { - final iconIfNoFlair = Icon( - LichessIcons.study, - size: size, - ); + final iconIfNoFlair = Icon(LichessIcons.study, size: size); return (flair != null) ? CachedNetworkImage( - imageUrl: lichessFlairSrc(flair!), - errorWidget: (_, __, ___) => iconIfNoFlair, - width: size, - height: size, - ) + imageUrl: lichessFlairSrc(flair!), + errorWidget: (_, __, ___) => iconIfNoFlair, + width: size, + height: size, + ) : iconIfNoFlair; } } @@ -419,28 +374,18 @@ class _StudySubtitle extends StatelessWidget { children: [ WidgetSpan( alignment: PlaceholderAlignment.middle, - child: Icon( - study.liked ? Icons.favorite : Icons.favorite_outline, - size: 14, - ), + child: Icon(study.liked ? Icons.favorite : Icons.favorite_outline, size: 14), ), TextSpan(text: ' ${study.likes}'), const TextSpan(text: ' • '), if (study.owner != null) ...[ WidgetSpan( alignment: PlaceholderAlignment.middle, - child: UserFullNameWidget( - user: study.owner, - showFlair: false, - ), + child: UserFullNameWidget(user: study.owner, showFlair: false), ), const TextSpan(text: ' • '), ], - TextSpan( - text: timeago.format( - study.updatedAt, - ), - ), + TextSpan(text: timeago.format(study.updatedAt)), ], ), ); diff --git a/lib/src/view/study/study_screen.dart b/lib/src/view/study/study_screen.dart index ed66346eb7..9024b0a01b 100644 --- a/lib/src/view/study/study_screen.dart +++ b/lib/src/view/study/study_screen.dart @@ -48,59 +48,44 @@ class StudyScreen extends ConsumerWidget { final boardPrefs = ref.watch(boardPreferencesProvider); switch (ref.watch(studyControllerProvider(id))) { case AsyncData(:final value): - return _StudyScreen( - id: id, - studyState: value, - ); + return _StudyScreen(id: id, studyState: value); case AsyncError(:final error, :final stackTrace): _logger.severe('Cannot load study: $error', stackTrace); return PlatformScaffold( - appBar: const PlatformAppBar( - title: Text(''), - ), + appBar: const PlatformAppBar(title: Text('')), body: DefaultTabController( length: 1, child: AnalysisLayout( - boardBuilder: (context, boardSize, borderRadius) => - Chessboard.fixed( - size: boardSize, - settings: boardPrefs.toBoardSettings().copyWith( + boardBuilder: + (context, boardSize, borderRadius) => Chessboard.fixed( + size: boardSize, + settings: boardPrefs.toBoardSettings().copyWith( borderRadius: borderRadius, - boxShadow: borderRadius != null - ? boardShadows - : const [], + boxShadow: borderRadius != null ? boardShadows : const [], ), - orientation: Side.white, - fen: kEmptyFEN, - ), - children: const [ - Center( - child: Text('Failed to load study.'), - ), - ], + orientation: Side.white, + fen: kEmptyFEN, + ), + children: const [Center(child: Text('Failed to load study.'))], ), ), ); case _: return PlatformScaffold( - appBar: const PlatformAppBar( - title: Text(''), - ), + appBar: const PlatformAppBar(title: Text('')), body: DefaultTabController( length: 1, child: AnalysisLayout( - boardBuilder: (context, boardSize, borderRadius) => - Chessboard.fixed( - size: boardSize, - settings: boardPrefs.toBoardSettings().copyWith( + boardBuilder: + (context, boardSize, borderRadius) => Chessboard.fixed( + size: boardSize, + settings: boardPrefs.toBoardSettings().copyWith( borderRadius: borderRadius, - boxShadow: borderRadius != null - ? boardShadows - : const [], + boxShadow: borderRadius != null ? boardShadows : const [], ), - orientation: Side.white, - fen: kEmptyFEN, - ), + orientation: Side.white, + fen: kEmptyFEN, + ), children: const [SizedBox.shrink()], ), ), @@ -110,10 +95,7 @@ class StudyScreen extends ConsumerWidget { } class _StudyScreen extends ConsumerStatefulWidget { - const _StudyScreen({ - required this.id, - required this.studyState, - }); + const _StudyScreen({required this.id, required this.studyState}); final StudyId id; final StudyState studyState; @@ -122,8 +104,7 @@ class _StudyScreen extends ConsumerStatefulWidget { ConsumerState<_StudyScreen> createState() => _StudyScreenState(); } -class _StudyScreenState extends ConsumerState<_StudyScreen> - with TickerProviderStateMixin { +class _StudyScreenState extends ConsumerState<_StudyScreen> with TickerProviderStateMixin { late List tabs; late TabController _tabController; @@ -136,11 +117,7 @@ class _StudyScreenState extends ConsumerState<_StudyScreen> AnalysisTab.moves, ]; - _tabController = TabController( - vsync: this, - initialIndex: tabs.length - 1, - length: tabs.length, - ); + _tabController = TabController(vsync: this, initialIndex: tabs.length - 1, length: tabs.length); } @override @@ -151,10 +128,7 @@ class _StudyScreenState extends ConsumerState<_StudyScreen> // anymore, we keep the tabs as they are. // In theory, studies mixing chapters with and without opening explorer should be pretty rare. if (tabs.length < 2 && widget.studyState.isOpeningExplorerAvailable) { - tabs = [ - AnalysisTab.opening, - AnalysisTab.moves, - ]; + tabs = [AnalysisTab.opening, AnalysisTab.moves]; _tabController = TabController( vsync: this, initialIndex: tabs.length - 1, @@ -181,11 +155,7 @@ class _StudyScreenState extends ConsumerState<_StudyScreen> overflow: TextOverflow.ellipsis, ), actions: [ - if (tabs.length > 1) - AppBarAnalysisTabIndicator( - tabs: tabs, - controller: _tabController, - ), + if (tabs.length > 1) AppBarAnalysisTabIndicator(tabs: tabs, controller: _tabController), _StudyMenu(id: widget.id), ], ), @@ -211,25 +181,19 @@ class _StudyMenu extends ConsumerWidget { icon: Icons.settings, label: context.l10n.settingsSettings, onPressed: () { - pushPlatformRoute( - context, - screen: StudySettings(id), - ); + pushPlatformRoute(context, screen: StudySettings(id)); }, ), AppBarMenuAction( icon: state.study.liked ? Icons.favorite : Icons.favorite_border, - label: state.study.liked - ? context.l10n.studyUnlike - : context.l10n.studyLike, + label: state.study.liked ? context.l10n.studyUnlike : context.l10n.studyLike, onPressed: () { ref.read(studyControllerProvider(id).notifier).toggleLike(); }, ), AppBarMenuAction( - icon: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoIcons.share - : Icons.share, + icon: + Theme.of(context).platform == TargetPlatform.iOS ? CupertinoIcons.share : Icons.share, label: context.l10n.studyShareAndExport, onPressed: () { showAdaptiveActionSheet( @@ -238,21 +202,15 @@ class _StudyMenu extends ConsumerWidget { BottomSheetAction( makeLabel: (context) => Text(context.l10n.studyStudyUrl), onPressed: (context) async { - launchShareDialog( - context, - uri: lichessUri('/study/${state.study.id}'), - ); + launchShareDialog(context, uri: lichessUri('/study/${state.study.id}')); }, ), BottomSheetAction( - makeLabel: (context) => - Text(context.l10n.studyCurrentChapterUrl), + makeLabel: (context) => Text(context.l10n.studyCurrentChapterUrl), onPressed: (context) async { launchShareDialog( context, - uri: lichessUri( - '/study/${state.study.id}/${state.study.chapter.id}', - ), + uri: lichessUri('/study/${state.study.id}/${state.study.chapter.id}'), ); }, ), @@ -261,15 +219,11 @@ class _StudyMenu extends ConsumerWidget { makeLabel: (context) => Text(context.l10n.studyStudyPgn), onPressed: (context) async { try { - final pgn = - await ref.read(studyRepositoryProvider).getStudyPgn( - state.study.id, - ); + final pgn = await ref + .read(studyRepositoryProvider) + .getStudyPgn(state.study.id); if (context.mounted) { - launchShareDialog( - context, - text: pgn, - ); + launchShareDialog(context, text: pgn); } } catch (e) { if (context.mounted) { @@ -285,32 +239,23 @@ class _StudyMenu extends ConsumerWidget { BottomSheetAction( makeLabel: (context) => Text(context.l10n.studyChapterPgn), onPressed: (context) async { - launchShareDialog( - context, - text: state.pgn, - ); + launchShareDialog(context, text: state.pgn); }, ), if (state.position != null) BottomSheetAction( - makeLabel: (context) => - Text(context.l10n.screenshotCurrentPosition), + makeLabel: (context) => Text(context.l10n.screenshotCurrentPosition), onPressed: (_) async { try { final image = await ref .read(gameShareServiceProvider) - .screenshotPosition( - state.pov, - state.position!.fen, - state.lastMove, - ); + .screenshotPosition(state.pov, state.position!.fen, state.lastMove); if (context.mounted) { launchShareDialog( context, files: [image], subject: context.l10n.puzzleFromGameLink( - lichessUri('/study/${state.study.id}') - .toString(), + lichessUri('/study/${state.study.id}').toString(), ), ); } @@ -329,11 +274,9 @@ class _StudyMenu extends ConsumerWidget { makeLabel: (context) => const Text('GIF'), onPressed: (_) async { try { - final gif = - await ref.read(gameShareServiceProvider).chapterGif( - state.study.id, - state.study.chapter.id, - ); + final gif = await ref + .read(gameShareServiceProvider) + .chapterGif(state.study.id, state.study.chapter.id); if (context.mounted) { launchShareDialog( context, @@ -366,11 +309,7 @@ class _StudyMenu extends ConsumerWidget { } class _Body extends ConsumerWidget { - const _Body({ - required this.id, - required this.tabController, - required this.tabs, - }); + const _Body({required this.id, required this.tabController, required this.tabs}); final StudyId id; final TabController tabController; @@ -384,14 +323,11 @@ class _Body extends ConsumerWidget { return DefaultTabController( length: 1, child: AnalysisLayout( - boardBuilder: (context, boardSize, borderRadius) => SizedBox.square( - dimension: boardSize, - child: Center( - child: Text( - '${variant.label} is not supported yet.', + boardBuilder: + (context, boardSize, borderRadius) => SizedBox.square( + dimension: boardSize, + child: Center(child: Text('${variant.label} is not supported yet.')), ), - ), - ), children: const [SizedBox.shrink()], ), ); @@ -411,68 +347,55 @@ class _Body extends ConsumerWidget { return AnalysisLayout( tabController: tabController, - boardBuilder: (context, boardSize, borderRadius) => _StudyBoard( - id: id, - boardSize: boardSize, - borderRadius: borderRadius, - ), - engineGaugeBuilder: isComputerAnalysisAllowed && - showEvaluationGauge && - engineGaugeParams != null - ? (context, orientation) { - return orientation == Orientation.portrait - ? EngineGauge( + boardBuilder: + (context, boardSize, borderRadius) => + _StudyBoard(id: id, boardSize: boardSize, borderRadius: borderRadius), + engineGaugeBuilder: + isComputerAnalysisAllowed && showEvaluationGauge && engineGaugeParams != null + ? (context, orientation) { + return orientation == Orientation.portrait + ? EngineGauge( displayMode: EngineGaugeDisplayMode.horizontal, params: engineGaugeParams, ) - : Container( + : Container( clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4.0), - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(4.0)), child: EngineGauge( displayMode: EngineGaugeDisplayMode.vertical, params: engineGaugeParams, ), ); - } - : null, - engineLines: isComputerAnalysisAllowed && - isLocalEvaluationEnabled && - numEvalLines > 0 - ? EngineLines( - clientEval: currentNode.eval, - isGameOver: currentNode.position?.isGameOver ?? false, - onTapMove: ref - .read( - studyControllerProvider(id).notifier, - ) - .onUserMove, - ) - : null, + } + : null, + engineLines: + isComputerAnalysisAllowed && isLocalEvaluationEnabled && numEvalLines > 0 + ? EngineLines( + clientEval: currentNode.eval, + isGameOver: currentNode.position?.isGameOver ?? false, + onTapMove: ref.read(studyControllerProvider(id).notifier).onUserMove, + ) + : null, bottomBar: StudyBottomBar(id: id), - children: tabs.map((tab) { - switch (tab) { - case AnalysisTab.opening: - if (studyState.isOpeningExplorerAvailable && - studyState.currentNode.position != null) { - return OpeningExplorerView( - position: studyState.currentNode.position!, - onMoveSelected: (move) { - ref - .read(studyControllerProvider(id).notifier) - .onUserMove(move); - }, - ); - } else { - return const Center( - child: Text('Opening explorer not available.'), - ); + children: + tabs.map((tab) { + switch (tab) { + case AnalysisTab.opening: + if (studyState.isOpeningExplorerAvailable && + studyState.currentNode.position != null) { + return OpeningExplorerView( + position: studyState.currentNode.position!, + onMoveSelected: (move) { + ref.read(studyControllerProvider(id).notifier).onUserMove(move); + }, + ); + } else { + return const Center(child: Text('Opening explorer not available.')); + } + case _: + return bottomChild; } - case _: - return bottomChild; - } - }).toList(), + }).toList(), ); } } @@ -486,21 +409,13 @@ extension on PgnCommentShape { CommentShapeColor.yellow => ShapeColor.yellow, }; return from != to - ? Arrow( - color: shapeColor.color, - orig: from, - dest: to, - ) + ? Arrow(color: shapeColor.color, orig: from, dest: to) : Circle(color: shapeColor.color, orig: from); } } class _StudyBoard extends ConsumerStatefulWidget { - const _StudyBoard({ - required this.id, - required this.boardSize, - this.borderRadius, - }); + const _StudyBoard({required this.id, required this.boardSize, this.borderRadius}); final StudyId id; @@ -519,9 +434,7 @@ class _StudyBoardState extends ConsumerState<_StudyBoard> { Widget build(BuildContext context) { // Clear shapes when switching to a new chapter. // This avoids "leftover" shapes from the previous chapter when the engine has not evaluated the new position yet. - ref.listen( - studyControllerProvider(widget.id).select((state) => state.hasValue), - (prev, next) { + ref.listen(studyControllerProvider(widget.id).select((state) => state.hasValue), (prev, next) { if (prev != next) { setState(() { userShapes = ISet(); @@ -530,34 +443,24 @@ class _StudyBoardState extends ConsumerState<_StudyBoard> { }); final boardPrefs = ref.watch(boardPreferencesProvider); - final studyState = - ref.watch(studyControllerProvider(widget.id)).requireValue; + final studyState = ref.watch(studyControllerProvider(widget.id)).requireValue; final currentNode = studyState.currentNode; final position = currentNode.position; - final showVariationArrows = ref.watch( - studyPreferencesProvider.select( - (prefs) => prefs.showVariationArrows, - ), - ) && + final showVariationArrows = + ref.watch(studyPreferencesProvider.select((prefs) => prefs.showVariationArrows)) && !studyState.gamebookActive && currentNode.children.length > 1; - final pgnShapes = ISet( - studyState.pgnShapes.map((shape) => shape.chessground), - ); + final pgnShapes = ISet(studyState.pgnShapes.map((shape) => shape.chessground)); final variationArrows = ISet( showVariationArrows ? currentNode.children.mapIndexed((i, move) { - final color = Colors.white.withValues(alpha: i == 0 ? 0.9 : 0.5); - return Arrow( - color: color, - orig: (move as NormalMove).from, - dest: move.to, - ); - }).toList() + final color = Colors.white.withValues(alpha: i == 0 ? 0.9 : 0.5); + return Arrow(color: color, orig: (move as NormalMove).from, dest: move.to); + }).toList() : [], ); @@ -566,20 +469,16 @@ class _StudyBoardState extends ConsumerState<_StudyBoard> { ); final showBestMoveArrow = ref.watch( - analysisPreferencesProvider.select( - (value) => value.showBestMoveArrow, - ), - ); - final bestMoves = ref.watch( - engineEvaluationProvider.select((s) => s.eval?.bestMoves), + analysisPreferencesProvider.select((value) => value.showBestMoveArrow), ); + final bestMoves = ref.watch(engineEvaluationProvider.select((s) => s.eval?.bestMoves)); final ISet bestMoveShapes = showBestMoveArrow && studyState.isEngineAvailable && bestMoves != null ? computeBestMoveShapes( - bestMoves, - currentNode.position!.turn, - boardPrefs.pieceSet.assets, - ) + bestMoves, + currentNode.position!.turn, + boardPrefs.pieceSet.assets, + ) : ISet(); final sanMove = currentNode.sanMove; @@ -588,53 +487,41 @@ class _StudyBoardState extends ConsumerState<_StudyBoard> { return Chessboard( size: widget.boardSize, settings: boardPrefs.toBoardSettings().copyWith( - borderRadius: widget.borderRadius, - boxShadow: widget.borderRadius != null - ? boardShadows - : const [], - drawShape: DrawShapeOptions( - enable: true, - onCompleteShape: _onCompleteShape, - onClearShapes: _onClearShapes, - newShapeColor: boardPrefs.shapeColor.color, - ), - ), - fen: studyState.position?.board.fen ?? - studyState.study.currentChapterMeta.fen ?? - kInitialFEN, + borderRadius: widget.borderRadius, + boxShadow: widget.borderRadius != null ? boardShadows : const [], + drawShape: DrawShapeOptions( + enable: true, + onCompleteShape: _onCompleteShape, + onClearShapes: _onClearShapes, + newShapeColor: boardPrefs.shapeColor.color, + ), + ), + fen: studyState.position?.board.fen ?? studyState.study.currentChapterMeta.fen ?? kInitialFEN, lastMove: studyState.lastMove as NormalMove?, orientation: studyState.pov, - shapes: pgnShapes - .union(userShapes) - .union(variationArrows) - .union(bestMoveShapes), + shapes: pgnShapes.union(userShapes).union(variationArrows).union(bestMoveShapes), annotations: showAnnotationsOnBoard && sanMove != null && annotation != null ? altCastles.containsKey(sanMove.move.uci) - ? IMap({ - Move.parse(altCastles[sanMove.move.uci]!)!.to: annotation, - }) + ? IMap({Move.parse(altCastles[sanMove.move.uci]!)!.to: annotation}) : IMap({sanMove.move.to: annotation}) : null, - game: position != null - ? GameData( - playerSide: studyState.playerSide, - isCheck: position.isCheck, - sideToMove: position.turn, - validMoves: makeLegalMoves(position), - promotionMove: studyState.promotionMove, - onMove: (move, {isDrop, captured}) { - ref - .read(studyControllerProvider(widget.id).notifier) - .onUserMove(move); - }, - onPromotionSelection: (role) { - ref - .read(studyControllerProvider(widget.id).notifier) - .onPromotionSelection(role); - }, - ) - : null, + game: + position != null + ? GameData( + playerSide: studyState.playerSide, + isCheck: position.isCheck, + sideToMove: position.turn, + validMoves: makeLegalMoves(position), + promotionMove: studyState.promotionMove, + onMove: (move, {isDrop, captured}) { + ref.read(studyControllerProvider(widget.id).notifier).onUserMove(move); + }, + onPromotionSelection: (role) { + ref.read(studyControllerProvider(widget.id).notifier).onPromotionSelection(role); + }, + ) + : null, ); } diff --git a/lib/src/view/study/study_settings.dart b/lib/src/view/study/study_settings.dart index 74bc0eef8a..ee8c4ac90a 100644 --- a/lib/src/view/study/study_settings.dart +++ b/lib/src/view/study/study_settings.dart @@ -34,37 +34,33 @@ class StudySettings extends ConsumerWidget { ); return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.settingsSettings), - ), + appBar: PlatformAppBar(title: Text(context.l10n.settingsSettings)), body: ListView( children: [ if (isComputerAnalysisAllowed) StockfishSettingsWidget( - onToggleLocalEvaluation: () => - ref.read(studyController.notifier).toggleLocalEvaluation(), - onSetEngineSearchTime: (value) => - ref.read(studyController.notifier).setEngineSearchTime(value), - onSetNumEvalLines: (value) => - ref.read(studyController.notifier).setNumEvalLines(value), - onSetEngineCores: (value) => - ref.read(studyController.notifier).setEngineCores(value), + onToggleLocalEvaluation: + () => ref.read(studyController.notifier).toggleLocalEvaluation(), + onSetEngineSearchTime: + (value) => ref.read(studyController.notifier).setEngineSearchTime(value), + onSetNumEvalLines: + (value) => ref.read(studyController.notifier).setNumEvalLines(value), + onSetEngineCores: (value) => ref.read(studyController.notifier).setEngineCores(value), ), ListSection( children: [ SwitchSettingTile( title: Text(context.l10n.showVariationArrows), value: studyPrefs.showVariationArrows, - onChanged: (value) => ref - .read(studyPreferencesProvider.notifier) - .toggleShowVariationArrows(), + onChanged: + (value) => + ref.read(studyPreferencesProvider.notifier).toggleShowVariationArrows(), ), SwitchSettingTile( title: Text(context.l10n.toggleGlyphAnnotations), value: analysisPrefs.showAnnotations, - onChanged: (_) => ref - .read(analysisPreferencesProvider.notifier) - .toggleAnnotations(), + onChanged: + (_) => ref.read(analysisPreferencesProvider.notifier).toggleAnnotations(), ), ], ), @@ -72,21 +68,20 @@ class StudySettings extends ConsumerWidget { children: [ PlatformListTile( title: Text(context.l10n.openingExplorer), - onTap: () => showAdaptiveBottomSheet( - context: context, - isScrollControlled: true, - showDragHandle: true, - isDismissible: true, - builder: (_) => const OpeningExplorerSettings(), - ), + onTap: + () => showAdaptiveBottomSheet( + context: context, + isScrollControlled: true, + showDragHandle: true, + isDismissible: true, + builder: (_) => const OpeningExplorerSettings(), + ), ), SwitchSettingTile( title: Text(context.l10n.sound), value: isSoundEnabled, onChanged: (value) { - ref - .read(generalPreferencesProvider.notifier) - .toggleSoundEnabled(); + ref.read(generalPreferencesProvider.notifier).toggleSoundEnabled(); }, ), ], diff --git a/lib/src/view/study/study_tree_view.dart b/lib/src/view/study/study_tree_view.dart index 031a2c599c..b6ff6857a9 100644 --- a/lib/src/view/study/study_tree_view.dart +++ b/lib/src/view/study/study_tree_view.dart @@ -11,30 +11,24 @@ import 'package:lichess_mobile/src/widgets/pgn.dart'; const kNextChapterButtonHeight = 32.0; class StudyTreeView extends ConsumerWidget { - const StudyTreeView( - this.id, - ); + const StudyTreeView(this.id); final StudyId id; @override Widget build(BuildContext context, WidgetRef ref) { - final root = ref.watch( - studyControllerProvider(id) - .select((value) => value.requireValue.root), - ) ?? + final root = + ref.watch(studyControllerProvider(id).select((value) => value.requireValue.root)) ?? // If root is null, the study chapter's position is illegal. // We still want to display the root comments though, so create a dummy root. const ViewRoot(position: Chess.initial, children: IList.empty()); final currentPath = ref.watch( - studyControllerProvider(id) - .select((value) => value.requireValue.currentPath), + studyControllerProvider(id).select((value) => value.requireValue.currentPath), ); final pgnRootComments = ref.watch( - studyControllerProvider(id) - .select((value) => value.requireValue.pgnRootComments), + studyControllerProvider(id).select((value) => value.requireValue.pgnRootComments), ); final analysisPrefs = ref.watch(analysisPreferencesProvider); diff --git a/lib/src/view/tools/load_position_screen.dart b/lib/src/view/tools/load_position_screen.dart index 3c48bf6ad7..9f753a7981 100644 --- a/lib/src/view/tools/load_position_screen.dart +++ b/lib/src/view/tools/load_position_screen.dart @@ -19,9 +19,7 @@ class LoadPositionScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.loadPosition), - ), + appBar: PlatformAppBar(title: Text(context.l10n.loadPosition)), body: const _Body(), ); } @@ -81,28 +79,27 @@ class _BodyState extends State<_Body> { children: [ FatButton( semanticsLabel: context.l10n.analysis, - onPressed: parsedInput != null - ? () => pushPlatformRoute( + onPressed: + parsedInput != null + ? () => pushPlatformRoute( context, rootNavigator: true, - builder: (context) => AnalysisScreen( - options: parsedInput!.options, - ), + builder: (context) => AnalysisScreen(options: parsedInput!.options), ) - : null, + : null, child: Text(context.l10n.analysis), ), const SizedBox(height: 16.0), FatButton( semanticsLabel: context.l10n.boardEditor, - onPressed: parsedInput != null - ? () => pushPlatformRoute( + onPressed: + parsedInput != null + ? () => pushPlatformRoute( context, rootNavigator: true, - builder: (context) => - BoardEditorScreen(initialFen: parsedInput!.fen), + builder: (context) => BoardEditorScreen(initialFen: parsedInput!.fen), ) - : null, + : null, child: Text(context.l10n.boardEditor), ), ], @@ -137,9 +134,9 @@ class _BodyState extends State<_Body> { isComputerAnalysisAllowed: true, variant: Variant.standard, ), - ) + ), ); - } catch (_, __) {} + } catch (_) {} // try to parse as PGN try { @@ -170,9 +167,9 @@ class _BodyState extends State<_Body> { variant: rule != null ? Variant.fromRule(rule) : Variant.standard, ), initialMoveCursor: mainlineMoves.isEmpty ? 0 : 1, - ) + ), ); - } catch (_, __) {} + } catch (_) {} return null; } diff --git a/lib/src/view/tools/tools_tab_screen.dart b/lib/src/view/tools/tools_tab_screen.dart index f00a896a22..40e2abde94 100644 --- a/lib/src/view/tools/tools_tab_screen.dart +++ b/lib/src/view/tools/tools_tab_screen.dart @@ -42,15 +42,8 @@ class ToolsTabScreen extends ConsumerWidget { } }, child: Scaffold( - appBar: AppBar( - title: Text(context.l10n.tools), - ), - body: const Column( - children: [ - ConnectivityBanner(), - Expanded(child: _Body()), - ], - ), + appBar: AppBar(title: Text(context.l10n.tools)), + body: const Column(children: [ConnectivityBanner(), Expanded(child: _Body())]), ), ); } @@ -62,10 +55,7 @@ class ToolsTabScreen extends ConsumerWidget { slivers: [ CupertinoSliverNavigationBar(largeTitle: Text(context.l10n.tools)), const SliverToBoxAdapter(child: ConnectivityBanner()), - const SliverSafeArea( - top: false, - sliver: _Body(), - ), + const SliverSafeArea(top: false, sliver: _Body()), ], ), ); @@ -73,11 +63,7 @@ class ToolsTabScreen extends ConsumerWidget { } class _ToolsButton extends StatelessWidget { - const _ToolsButton({ - required this.icon, - required this.title, - required this.onTap, - }); + const _ToolsButton({required this.icon, required this.title, required this.onTap}); final IconData icon; @@ -87,31 +73,32 @@ class _ToolsButton extends StatelessWidget { @override Widget build(BuildContext context) { - final tilePadding = Theme.of(context).platform == TargetPlatform.iOS - ? const EdgeInsets.symmetric(vertical: 8.0) - : EdgeInsets.zero; + final tilePadding = + Theme.of(context).platform == TargetPlatform.iOS + ? const EdgeInsets.symmetric(vertical: 8.0) + : EdgeInsets.zero; return Padding( - padding: Theme.of(context).platform == TargetPlatform.android - ? const EdgeInsets.only(bottom: 16.0) - : EdgeInsets.zero, + padding: + Theme.of(context).platform == TargetPlatform.android + ? const EdgeInsets.only(bottom: 16.0) + : EdgeInsets.zero, child: Opacity( opacity: onTap == null ? 0.5 : 1.0, child: PlatformListTile( leading: Icon( icon, size: Styles.mainListTileIconSize, - color: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoTheme.of(context).primaryColor - : Theme.of(context).colorScheme.primary, - ), - title: Padding( - padding: tilePadding, - child: Text(title, style: Styles.callout), + color: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoTheme.of(context).primaryColor + : Theme.of(context).colorScheme.primary, ), - trailing: Theme.of(context).platform == TargetPlatform.iOS - ? const CupertinoListTileChevron() - : null, + title: Padding(padding: tilePadding, child: Text(title, style: Styles.callout)), + trailing: + Theme.of(context).platform == TargetPlatform.iOS + ? const CupertinoListTileChevron() + : null, onTap: onTap, ), ), @@ -124,96 +111,97 @@ class _Body extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isOnline = - ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? false; + final isOnline = ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? false; final content = [ - if (Theme.of(context).platform == TargetPlatform.android) - const SizedBox(height: 16.0), + if (Theme.of(context).platform == TargetPlatform.android) const SizedBox(height: 16.0), ListSection( hasLeading: true, children: [ _ToolsButton( icon: Icons.upload_file, title: context.l10n.loadPosition, - onTap: () => pushPlatformRoute( - context, - builder: (context) => const LoadPositionScreen(), - ), + onTap: + () => pushPlatformRoute(context, builder: (context) => const LoadPositionScreen()), ), _ToolsButton( icon: Icons.biotech, title: context.l10n.analysis, - onTap: () => pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => const AnalysisScreen( - options: AnalysisOptions( - orientation: Side.white, - standalone: ( - pgn: '', - isComputerAnalysisAllowed: true, - variant: Variant.standard, - ), - ), - ), - ), - ), - _ToolsButton( - icon: Icons.explore_outlined, - title: context.l10n.openingExplorer, - onTap: isOnline - ? () => pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => const OpeningExplorerScreen( + onTap: + () => pushPlatformRoute( + context, + rootNavigator: true, + builder: + (context) => const AnalysisScreen( options: AnalysisOptions( orientation: Side.white, standalone: ( pgn: '', - isComputerAnalysisAllowed: false, + isComputerAnalysisAllowed: true, variant: Variant.standard, ), ), ), + ), + ), + _ToolsButton( + icon: Icons.explore_outlined, + title: context.l10n.openingExplorer, + onTap: + isOnline + ? () => pushPlatformRoute( + context, + rootNavigator: true, + builder: + (context) => const OpeningExplorerScreen( + options: AnalysisOptions( + orientation: Side.white, + standalone: ( + pgn: '', + isComputerAnalysisAllowed: false, + variant: Variant.standard, + ), + ), + ), ) - : null, + : null, ), if (isOnline) _ToolsButton( icon: LichessIcons.study, title: context.l10n.studyMenu, - onTap: () => pushPlatformRoute( - context, - builder: (context) => const StudyListScreen(), - ), + onTap: + () => pushPlatformRoute(context, builder: (context) => const StudyListScreen()), ), _ToolsButton( icon: Icons.edit_outlined, title: context.l10n.boardEditor, - onTap: () => pushPlatformRoute( - context, - builder: (context) => const BoardEditorScreen(), - rootNavigator: true, - ), + onTap: + () => pushPlatformRoute( + context, + builder: (context) => const BoardEditorScreen(), + rootNavigator: true, + ), ), _ToolsButton( icon: Icons.where_to_vote_outlined, title: 'Coordinate Training', // TODO l10n - onTap: () => pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => const CoordinateTrainingScreen(), - ), + onTap: + () => pushPlatformRoute( + context, + rootNavigator: true, + builder: (context) => const CoordinateTrainingScreen(), + ), ), _ToolsButton( icon: Icons.alarm, title: context.l10n.clock, - onTap: () => pushPlatformRoute( - context, - builder: (context) => const ClockToolScreen(), - rootNavigator: true, - ), + onTap: + () => pushPlatformRoute( + context, + builder: (context) => const ClockToolScreen(), + rootNavigator: true, + ), ), ], ), @@ -221,9 +209,6 @@ class _Body extends ConsumerWidget { return Theme.of(context).platform == TargetPlatform.iOS ? SliverList(delegate: SliverChildListDelegate(content)) - : ListView( - controller: puzzlesScrollController, - children: content, - ); + : ListView(controller: puzzlesScrollController, children: content); } } diff --git a/lib/src/view/user/challenge_requests_screen.dart b/lib/src/view/user/challenge_requests_screen.dart index f00783d2eb..990dd32b00 100644 --- a/lib/src/view/user/challenge_requests_screen.dart +++ b/lib/src/view/user/challenge_requests_screen.dart @@ -21,9 +21,7 @@ class ChallengeRequestsScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.preferencesNotifyChallenge), - ), + appBar: PlatformAppBar(title: Text(context.l10n.preferencesNotifyChallenge)), body: _Body(), ); } @@ -45,8 +43,8 @@ class _Body extends ConsumerWidget { return ListView.separated( itemCount: list.length, - separatorBuilder: (context, index) => - const PlatformDivider(height: 1, cupertinoHasLeading: true), + separatorBuilder: + (context, index) => const PlatformDivider(height: 1, cupertinoHasLeading: true), itemBuilder: (context, index) { final challenge = list[index]; final user = challenge.challenger?.user; @@ -56,52 +54,43 @@ class _Body extends ConsumerWidget { Future acceptChallenge(BuildContext context) async { final challengeRepo = ref.read(challengeRepositoryProvider); await challengeRepo.accept(challenge.id); - final fullId = await challengeRepo.show(challenge.id).then( - (challenge) => challenge.gameFullId, - ); + final fullId = await challengeRepo + .show(challenge.id) + .then((challenge) => challenge.gameFullId); if (!context.mounted) return; pushPlatformRoute( context, rootNavigator: true, builder: (BuildContext context) { - return GameScreen( - initialGameId: fullId, - ); + return GameScreen(initialGameId: fullId); }, ); } - Future declineChallenge( - ChallengeDeclineReason? reason, - ) async { - ref - .read(challengeRepositoryProvider) - .decline(challenge.id, reason: reason); - ref - .read(notificationServiceProvider) - .cancel(challenge.id.value.hashCode); + Future declineChallenge(ChallengeDeclineReason? reason) async { + ref.read(challengeRepositoryProvider).decline(challenge.id, reason: reason); + ref.read(notificationServiceProvider).cancel(challenge.id.value.hashCode); } void confirmDialog() { showAdaptiveActionSheet( context: context, - title: challenge.variant.isPlaySupported - ? const Text('Do you accept the challenge?') - : null, + title: + challenge.variant.isPlaySupported + ? const Text('Do you accept the challenge?') + : null, actions: [ if (challenge.variant.isPlaySupported) BottomSheetAction( makeLabel: (context) => Text(context.l10n.accept), - leading: - Icon(Icons.check, color: context.lichessColors.good), + leading: Icon(Icons.check, color: context.lichessColors.good), isDefaultAction: true, onPressed: (context) => acceptChallenge(context), ), ...ChallengeDeclineReason.values.map( (reason) => BottomSheetAction( makeLabel: (context) => Text(reason.label(context.l10n)), - leading: - Icon(Icons.close, color: context.lichessColors.error), + leading: Icon(Icons.close, color: context.lichessColors.error), isDestructiveAction: true, onPressed: (_) { declineChallenge(reason); @@ -113,33 +102,30 @@ class _Body extends ConsumerWidget { } void showMissingAccountMessage() { - showPlatformSnackbar( - context, - context.l10n.youNeedAnAccountToDoThat, - ); + showPlatformSnackbar(context, context.l10n.youNeedAnAccountToDoThat); } return ChallengeListItem( challenge: challenge, challengerUser: user, - onPressed: challenge.direction == ChallengeDirection.inward - ? session == null - ? showMissingAccountMessage - : confirmDialog - : null, - onAccept: challenge.direction == ChallengeDirection.outward || - !challenge.variant.isPlaySupported - ? null - : session == null + onPressed: + challenge.direction == ChallengeDirection.inward + ? session == null + ? showMissingAccountMessage + : confirmDialog + : null, + onAccept: + challenge.direction == ChallengeDirection.outward || + !challenge.variant.isPlaySupported + ? null + : session == null ? showMissingAccountMessage : () => acceptChallenge(context), - onCancel: challenge.direction == ChallengeDirection.outward - ? () => - ref.read(challengeRepositoryProvider).cancel(challenge.id) - : null, - onDecline: challenge.direction == ChallengeDirection.inward - ? declineChallenge - : null, + onCancel: + challenge.direction == ChallengeDirection.outward + ? () => ref.read(challengeRepositoryProvider).cancel(challenge.id) + : null, + onDecline: challenge.direction == ChallengeDirection.inward ? declineChallenge : null, ); }, ); @@ -147,8 +133,7 @@ class _Body extends ConsumerWidget { loading: () { return const Center(child: CircularProgressIndicator.adaptive()); }, - error: (error, stack) => - const Center(child: Text('Error loading challenges')), + error: (error, stack) => const Center(child: Text('Error loading challenges')), ); } } diff --git a/lib/src/view/user/game_history_screen.dart b/lib/src/view/user/game_history_screen.dart index 93cd532e69..5ed1dc176d 100644 --- a/lib/src/view/user/game_history_screen.dart +++ b/lib/src/view/user/game_history_screen.dart @@ -30,16 +30,15 @@ class GameHistoryScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final filtersInUse = ref.watch(gameFilterProvider(filter: gameFilter)); - final nbGamesAsync = ref.watch( - userNumberOfGamesProvider(user, isOnline: isOnline), - ); - final title = filtersInUse.count == 0 - ? nbGamesAsync.when( - data: (nbGames) => Text(context.l10n.nbGames(nbGames)), - loading: () => const ButtonLoadingIndicator(), - error: (e, s) => Text(context.l10n.mobileAllGames), - ) - : Text(filtersInUse.selectionLabel(context)); + final nbGamesAsync = ref.watch(userNumberOfGamesProvider(user, isOnline: isOnline)); + final title = + filtersInUse.count == 0 + ? nbGamesAsync.when( + data: (nbGames) => Text(context.l10n.nbGames(nbGames)), + loading: () => const ButtonLoadingIndicator(), + error: (e, s) => Text(context.l10n.mobileAllGames), + ) + : Text(filtersInUse.selectionLabel(context)); final filterBtn = AppBarIconButton( icon: Badge.count( backgroundColor: Theme.of(context).colorScheme.secondary, @@ -52,38 +51,31 @@ class GameHistoryScreen extends ConsumerWidget { child: const Icon(Icons.tune), ), semanticsLabel: context.l10n.filterGames, - onPressed: () => showAdaptiveBottomSheet( - context: context, - isScrollControlled: true, - builder: (_) => _FilterGames( - filter: ref.read(gameFilterProvider(filter: gameFilter)), - user: user, - ), - ).then((value) { - if (value != null) { - ref - .read(gameFilterProvider(filter: gameFilter).notifier) - .setFilter(value); - } - }), + onPressed: + () => showAdaptiveBottomSheet( + context: context, + isScrollControlled: true, + builder: + (_) => _FilterGames( + filter: ref.read(gameFilterProvider(filter: gameFilter)), + user: user, + ), + ).then((value) { + if (value != null) { + ref.read(gameFilterProvider(filter: gameFilter).notifier).setFilter(value); + } + }), ); return PlatformScaffold( - appBar: PlatformAppBar( - title: title, - actions: [filterBtn], - ), + appBar: PlatformAppBar(title: title, actions: [filterBtn]), body: _Body(user: user, isOnline: isOnline, gameFilter: gameFilter), ); } } class _Body extends ConsumerStatefulWidget { - const _Body({ - required this.user, - required this.isOnline, - required this.gameFilter, - }); + const _Body({required this.user, required this.isOnline, required this.gameFilter}); final LightUser? user; final bool isOnline; @@ -110,8 +102,7 @@ class _BodyState extends ConsumerState<_Body> { } void _scrollListener() { - if (_scrollController.position.pixels >= - _scrollController.position.maxScrollExtent - 300) { + if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 300) { final state = ref.read( userGameHistoryProvider( widget.user?.id, @@ -143,14 +134,9 @@ class _BodyState extends ConsumerState<_Body> { @override Widget build(BuildContext context) { - final gameFilterState = - ref.watch(gameFilterProvider(filter: widget.gameFilter)); + final gameFilterState = ref.watch(gameFilterProvider(filter: widget.gameFilter)); final gameListState = ref.watch( - userGameHistoryProvider( - widget.user?.id, - isOnline: widget.isOnline, - filter: gameFilterState, - ), + userGameHistoryProvider(widget.user?.id, isOnline: widget.isOnline, filter: gameFilterState), ); return gameListState.when( @@ -159,64 +145,45 @@ class _BodyState extends ConsumerState<_Body> { return list.isEmpty ? const Padding( - padding: EdgeInsets.symmetric(vertical: 32.0), - child: Center( - child: Text( - 'No games found', - ), - ), - ) + padding: EdgeInsets.symmetric(vertical: 32.0), + child: Center(child: Text('No games found')), + ) : ListView.separated( - controller: _scrollController, - separatorBuilder: (context, index) => - Theme.of(context).platform == TargetPlatform.iOS - ? const PlatformDivider( - height: 1, - cupertinoHasLeading: true, - ) - : const PlatformDivider( - height: 1, - color: Colors.transparent, - ), - itemCount: list.length + (state.isLoading ? 1 : 0), - itemBuilder: (context, index) { - if (state.isLoading && index == list.length) { - return const Padding( - padding: EdgeInsets.symmetric(vertical: 32.0), - child: CenterLoadingIndicator(), - ); - } else if (state.hasError && - state.hasMore && - index == list.length) { - // TODO: add a retry button - return const Padding( - padding: EdgeInsets.symmetric(vertical: 32.0), - child: Center( - child: Text( - 'Could not load more games', - ), - ), - ); - } - - return ExtendedGameListTile( - item: list[index], - userId: widget.user?.id, - // see: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/cupertino/list_tile.dart#L30 for horizontal padding value - padding: Theme.of(context).platform == TargetPlatform.iOS - ? const EdgeInsets.symmetric( - horizontal: 14.0, - vertical: 12.0, - ) - : null, + controller: _scrollController, + separatorBuilder: + (context, index) => + Theme.of(context).platform == TargetPlatform.iOS + ? const PlatformDivider(height: 1, cupertinoHasLeading: true) + : const PlatformDivider(height: 1, color: Colors.transparent), + itemCount: list.length + (state.isLoading ? 1 : 0), + itemBuilder: (context, index) { + if (state.isLoading && index == list.length) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 32.0), + child: CenterLoadingIndicator(), + ); + } else if (state.hasError && state.hasMore && index == list.length) { + // TODO: add a retry button + return const Padding( + padding: EdgeInsets.symmetric(vertical: 32.0), + child: Center(child: Text('Could not load more games')), ); - }, - ); + } + + return ExtendedGameListTile( + item: list[index], + userId: widget.user?.id, + // see: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/cupertino/list_tile.dart#L30 for horizontal padding value + padding: + Theme.of(context).platform == TargetPlatform.iOS + ? const EdgeInsets.symmetric(horizontal: 14.0, vertical: 12.0) + : null, + ); + }, + ); }, error: (e, s) { - debugPrint( - 'SEVERE: [GameHistoryScreen] could not load game list', - ); + debugPrint('SEVERE: [GameHistoryScreen] could not load game list'); return const Center(child: Text('Could not load Game History')); }, loading: () => const CenterLoadingIndicator(), @@ -225,10 +192,7 @@ class _BodyState extends ConsumerState<_Body> { } class _FilterGames extends ConsumerStatefulWidget { - const _FilterGames({ - required this.filter, - required this.user, - }); + const _FilterGames({required this.filter, required this.user}); final GameFilterState filter; final LightUser? user; @@ -269,15 +233,16 @@ class _FilterGamesState extends ConsumerState<_FilterGames> { final session = ref.read(authSessionProvider); final userId = widget.user?.id ?? session?.user.id; - final Widget filters = userId != null - ? ref.watch(userProvider(id: userId)).when( - data: (user) => perfFilter(availablePerfs(user)), - loading: () => const Center( - child: CircularProgressIndicator.adaptive(), - ), - error: (_, __) => perfFilter(gamePerfs), - ) - : perfFilter(gamePerfs); + final Widget filters = + userId != null + ? ref + .watch(userProvider(id: userId)) + .when( + data: (user) => perfFilter(availablePerfs(user)), + loading: () => const Center(child: CircularProgressIndicator.adaptive()), + error: (_, __) => perfFilter(gamePerfs), + ) + : perfFilter(gamePerfs); return BottomSheetScrollableContainer( padding: const EdgeInsets.all(16.0), @@ -291,15 +256,15 @@ class _FilterGamesState extends ConsumerState<_FilterGames> { filterType: FilterType.singleChoice, choices: Side.values, choiceSelected: (choice) => filter.side == choice, - choiceLabel: (t) => switch (t) { - Side.white => Text(context.l10n.white), - Side.black => Text(context.l10n.black), - }, - onSelected: (value, selected) => setState( - () { - filter = filter.copyWith(side: selected ? value : null); - }, - ), + choiceLabel: + (t) => switch (t) { + Side.white => Text(context.l10n.white), + Side.black => Text(context.l10n.black), + }, + onSelected: + (value, selected) => setState(() { + filter = filter.copyWith(side: selected ? value : null); + }), ), Row( mainAxisAlignment: MainAxisAlignment.end, @@ -320,31 +285,30 @@ class _FilterGamesState extends ConsumerState<_FilterGames> { } List availablePerfs(User user) { - final perfs = gamePerfs.where((perf) { - final p = user.perfs[perf]; - return p != null && p.numberOfGamesOrRuns > 0; - }).toList(growable: false); + final perfs = gamePerfs + .where((perf) { + final p = user.perfs[perf]; + return p != null && p.numberOfGamesOrRuns > 0; + }) + .toList(growable: false); perfs.sort( - (p1, p2) => user.perfs[p2]!.numberOfGamesOrRuns - .compareTo(user.perfs[p1]!.numberOfGamesOrRuns), + (p1, p2) => + user.perfs[p2]!.numberOfGamesOrRuns.compareTo(user.perfs[p1]!.numberOfGamesOrRuns), ); return perfs; } Widget perfFilter(List choices) => Filter( - filterName: context.l10n.variant, - filterType: FilterType.multipleChoice, - choices: choices, - choiceSelected: (choice) => filter.perfs.contains(choice), - choiceLabel: (t) => Text(t.shortTitle), - onSelected: (value, selected) => setState( - () { - filter = filter.copyWith( - perfs: selected - ? filter.perfs.add(value) - : filter.perfs.remove(value), - ); - }, - ), - ); + filterName: context.l10n.variant, + filterType: FilterType.multipleChoice, + choices: choices, + choiceSelected: (choice) => filter.perfs.contains(choice), + choiceLabel: (t) => Text(t.shortTitle), + onSelected: + (value, selected) => setState(() { + filter = filter.copyWith( + perfs: selected ? filter.perfs.add(value) : filter.perfs.remove(value), + ); + }), + ); } diff --git a/lib/src/view/user/leaderboard_screen.dart b/lib/src/view/user/leaderboard_screen.dart index c544eecc24..db62d23f29 100644 --- a/lib/src/view/user/leaderboard_screen.dart +++ b/lib/src/view/user/leaderboard_screen.dart @@ -21,9 +21,7 @@ class LeaderboardScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.leaderboard), - ), + appBar: PlatformAppBar(title: Text(context.l10n.leaderboard)), body: const _Body(), ); } @@ -42,43 +40,15 @@ class _Body extends ConsumerWidget { _Leaderboard(data.bullet, LichessIcons.bullet, 'BULLET'), _Leaderboard(data.blitz, LichessIcons.blitz, 'BLITZ'), _Leaderboard(data.rapid, LichessIcons.rapid, 'RAPID'), - _Leaderboard( - data.classical, - LichessIcons.classical, - 'CLASSICAL', - ), - _Leaderboard( - data.ultrabullet, - LichessIcons.ultrabullet, - 'ULTRA BULLET', - ), - _Leaderboard( - data.crazyhouse, - LichessIcons.h_square, - 'CRAZYHOUSE', - ), - _Leaderboard( - data.chess960, - LichessIcons.die_six, - 'CHESS 960', - ), - _Leaderboard( - data.kingOfThehill, - LichessIcons.bullet, - 'KING OF THE HILL', - ), - _Leaderboard( - data.threeCheck, - LichessIcons.three_check, - 'THREE CHECK', - ), + _Leaderboard(data.classical, LichessIcons.classical, 'CLASSICAL'), + _Leaderboard(data.ultrabullet, LichessIcons.ultrabullet, 'ULTRA BULLET'), + _Leaderboard(data.crazyhouse, LichessIcons.h_square, 'CRAZYHOUSE'), + _Leaderboard(data.chess960, LichessIcons.die_six, 'CHESS 960'), + _Leaderboard(data.kingOfThehill, LichessIcons.bullet, 'KING OF THE HILL'), + _Leaderboard(data.threeCheck, LichessIcons.three_check, 'THREE CHECK'), _Leaderboard(data.atomic, LichessIcons.atom, 'ATOMIC'), _Leaderboard(data.horde, LichessIcons.horde, 'HORDE'), - _Leaderboard( - data.antichess, - LichessIcons.antichess, - 'ANTICHESS', - ), + _Leaderboard(data.antichess, LichessIcons.antichess, 'ANTICHESS'), _Leaderboard( data.racingKings, LichessIcons.racing_kings, @@ -91,17 +61,10 @@ class _Body extends ConsumerWidget { child: SingleChildScrollView( child: LayoutBuilder( builder: (context, constraints) { - final crossAxisCount = - math.min(3, (constraints.maxWidth / 300).floor()); + final crossAxisCount = math.min(3, (constraints.maxWidth / 300).floor()); return LayoutGrid( - columnSizes: List.generate( - crossAxisCount, - (_) => 1.fr, - ), - rowSizes: List.generate( - (list.length / crossAxisCount).ceil(), - (_) => auto, - ), + columnSizes: List.generate(crossAxisCount, (_) => 1.fr), + rowSizes: List.generate((list.length / crossAxisCount).ceil(), (_) => auto), children: list, ); }, @@ -110,8 +73,7 @@ class _Body extends ConsumerWidget { ); }, loading: () => const Center(child: CircularProgressIndicator.adaptive()), - error: (error, stack) => - const Center(child: Text('Could not load leaderboard.')), + error: (error, stack) => const Center(child: Text('Could not load leaderboard.')), ); } } @@ -134,19 +96,12 @@ class LeaderboardListTile extends StatelessWidget { child: UserFullNameWidget(user: user.lightUser), ), subtitle: perfIcon != null ? Text(user.rating.toString()) : null, - trailing: perfIcon != null - ? _Progress(user.progress) - : Text(user.rating.toString()), + trailing: perfIcon != null ? _Progress(user.progress) : Text(user.rating.toString()), ); } void _handleTap(BuildContext context) { - pushPlatformRoute( - context, - builder: (context) => UserScreen( - user: user.lightUser, - ), - ); + pushPlatformRoute(context, builder: (context) => UserScreen(user: user.lightUser)); } } @@ -162,22 +117,16 @@ class _Progress extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Icon( - progress > 0 - ? LichessIcons.arrow_full_upperright - : LichessIcons.arrow_full_lowerright, + progress > 0 ? LichessIcons.arrow_full_upperright : LichessIcons.arrow_full_lowerright, size: 16, - color: progress > 0 - ? context.lichessColors.good - : context.lichessColors.error, + color: progress > 0 ? context.lichessColors.good : context.lichessColors.error, ), Text( '${progress.abs()}', maxLines: 1, style: TextStyle( fontSize: 12, - color: progress > 0 - ? context.lichessColors.good - : context.lichessColors.error, + color: progress > 0 ? context.lichessColors.good : context.lichessColors.error, ), ), ], @@ -186,12 +135,7 @@ class _Progress extends StatelessWidget { } class _Leaderboard extends StatelessWidget { - const _Leaderboard( - this.userList, - this.iconData, - this.title, { - this.showDivider = true, - }); + const _Leaderboard(this.userList, this.iconData, this.title, {this.showDivider = true}); final List userList; final IconData iconData; final String title; @@ -211,8 +155,7 @@ class _Leaderboard extends StatelessWidget { Text(title), ], ), - children: - userList.map((user) => LeaderboardListTile(user: user)).toList(), + children: userList.map((user) => LeaderboardListTile(user: user)).toList(), ), ); } diff --git a/lib/src/view/user/leaderboard_widget.dart b/lib/src/view/user/leaderboard_widget.dart index 2f35923970..6ab071ad7e 100644 --- a/lib/src/view/user/leaderboard_widget.dart +++ b/lib/src/view/user/leaderboard_widget.dart @@ -22,9 +22,7 @@ class LeaderboardWidget extends ConsumerWidget { data: (data) { return ListSection( hasLeading: true, - header: Text( - context.l10n.leaderboard, - ), + header: Text(context.l10n.leaderboard), headerTrailing: NoPaddingTextButton( onPressed: () { pushPlatformRoute( @@ -33,16 +31,11 @@ class LeaderboardWidget extends ConsumerWidget { builder: (context) => const LeaderboardScreen(), ); }, - child: Text( - context.l10n.more, - ), + child: Text(context.l10n.more), ), children: [ for (final entry in data.entries) - LeaderboardListTile( - user: entry.value, - perfIcon: entry.key.icon, - ), + LeaderboardListTile(user: entry.value, perfIcon: entry.key.icon), ], ); }, @@ -55,15 +48,13 @@ class LeaderboardWidget extends ConsumerWidget { child: Text('Could not load leaderboard.'), ); }, - loading: () => Shimmer( - child: ShimmerLoading( - isLoading: true, - child: ListSection.loading( - itemsNumber: 5, - header: true, + loading: + () => Shimmer( + child: ShimmerLoading( + isLoading: true, + child: ListSection.loading(itemsNumber: 5, header: true), + ), ), - ), - ), ); } } diff --git a/lib/src/view/user/perf_cards.dart b/lib/src/view/user/perf_cards.dart index 8aea390e7d..32dff9b364 100644 --- a/lib/src/view/user/perf_cards.dart +++ b/lib/src/view/user/perf_cards.dart @@ -15,12 +15,7 @@ import 'package:lichess_mobile/src/widgets/rating.dart'; /// A widget that displays the performance cards of a user. class PerfCards extends StatelessWidget { - const PerfCards({ - required this.user, - required this.isMe, - this.padding, - super.key, - }); + const PerfCards({required this.user, required this.isMe, this.padding, super.key}); final User user; @@ -31,32 +26,34 @@ class PerfCards extends StatelessWidget { @override Widget build(BuildContext context) { const puzzlePerfsSet = {Perf.puzzle, Perf.streak, Perf.storm}; - final List gamePerfs = Perf.values.where((element) { - if (puzzlePerfsSet.contains(element)) { - return false; - } - final p = user.perfs[element]; - return p != null && - p.numberOfGamesOrRuns > 0 && - p.ratingDeviation < kClueLessDeviation; - }).toList(growable: false); + final List gamePerfs = Perf.values + .where((element) { + if (puzzlePerfsSet.contains(element)) { + return false; + } + final p = user.perfs[element]; + return p != null && p.numberOfGamesOrRuns > 0 && p.ratingDeviation < kClueLessDeviation; + }) + .toList(growable: false); gamePerfs.sort( - (p1, p2) => user.perfs[p2]!.numberOfGamesOrRuns - .compareTo(user.perfs[p1]!.numberOfGamesOrRuns), + (p1, p2) => + user.perfs[p2]!.numberOfGamesOrRuns.compareTo(user.perfs[p1]!.numberOfGamesOrRuns), ); - final List puzzlePerfs = Perf.values.where((element) { - if (!puzzlePerfsSet.contains(element)) { - return false; - } - final p = user.perfs[element]; - return p != null && p.numberOfGamesOrRuns > 0; - }).toList(growable: false); + final List puzzlePerfs = Perf.values + .where((element) { + if (!puzzlePerfsSet.contains(element)) { + return false; + } + final p = user.perfs[element]; + return p != null && p.numberOfGamesOrRuns > 0; + }) + .toList(growable: false); puzzlePerfs.sort( - (p1, p2) => user.perfs[p2]!.numberOfGamesOrRuns - .compareTo(user.perfs[p1]!.numberOfGamesOrRuns), + (p1, p2) => + user.perfs[p2]!.numberOfGamesOrRuns.compareTo(user.perfs[p1]!.numberOfGamesOrRuns), ); final userPerfs = [...gamePerfs, ...puzzlePerfs]; @@ -84,18 +81,13 @@ class PerfCards extends StatelessWidget { child: PlatformCard( child: AdaptiveInkWell( borderRadius: const BorderRadius.all(Radius.circular(10)), - onTap: isPerfWithoutStats - ? null - : () => _handlePerfCardTap(context, perf), + onTap: isPerfWithoutStats ? null : () => _handlePerfCardTap(context, perf), child: Padding( padding: const EdgeInsets.all(6.0), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Text( - perf.shortTitle, - style: TextStyle(color: textShade(context, 0.7)), - ), + Text(perf.shortTitle, style: TextStyle(color: textShade(context, 0.7))), Icon(perf.icon, color: textShade(context, 0.6)), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -114,17 +106,19 @@ class PerfCards extends StatelessWidget { userPerf.progression > 0 ? LichessIcons.arrow_full_upperright : LichessIcons.arrow_full_lowerright, - color: userPerf.progression > 0 - ? context.lichessColors.good - : context.lichessColors.error, + color: + userPerf.progression > 0 + ? context.lichessColors.good + : context.lichessColors.error, size: 12, ), Text( userPerf.progression.abs().toString(), style: TextStyle( - color: userPerf.progression > 0 - ? context.lichessColors.good - : context.lichessColors.error, + color: + userPerf.progression > 0 + ? context.lichessColors.good + : context.lichessColors.error, fontSize: 11, ), ), @@ -153,10 +147,7 @@ class PerfCards extends StatelessWidget { case Perf.storm: return StormDashboardModal(user: user.lightUser); default: - return PerfStatsScreen( - user: user, - perf: perf, - ); + return PerfStatsScreen(user: user, perf: perf); } }, ); diff --git a/lib/src/view/user/perf_stats_screen.dart b/lib/src/view/user/perf_stats_screen.dart index b26f90f383..0c4bf74f56 100644 --- a/lib/src/view/user/perf_stats_screen.dart +++ b/lib/src/view/user/perf_stats_screen.dart @@ -43,11 +43,7 @@ const _defaultValueFontSize = 18.0; const _mainValueStyle = TextStyle(fontWeight: FontWeight.bold, fontSize: 30); class PerfStatsScreen extends StatelessWidget { - const PerfStatsScreen({ - required this.user, - required this.perf, - super.key, - }); + const PerfStatsScreen({required this.user, required this.perf, super.key}); final User user; final Perf perf; @@ -55,10 +51,7 @@ class PerfStatsScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - appBar: PlatformAppBar( - androidTitleSpacing: 0, - title: _Title(user: user, perf: perf), - ), + appBar: PlatformAppBar(androidTitleSpacing: 0, title: _Title(user: user, perf: perf)), body: _Body(user: user, perf: perf), ); } @@ -72,60 +65,62 @@ class _Title extends StatelessWidget { @override Widget build(BuildContext context) { - final allPerfs = Perf.values.where((element) { - if ([perf, Perf.storm, Perf.streak, Perf.fromPosition] - .contains(element)) { - return false; - } - final p = user.perfs[element]; - return p != null && - p.games != null && - p.games! > 0 && - p.ratingDeviation < kClueLessDeviation; - }).toList(growable: false); + final allPerfs = Perf.values + .where((element) { + if ([perf, Perf.storm, Perf.streak, Perf.fromPosition].contains(element)) { + return false; + } + final p = user.perfs[element]; + return p != null && + p.games != null && + p.games! > 0 && + p.ratingDeviation < kClueLessDeviation; + }) + .toList(growable: false); return AppBarTextButton( child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(perf.icon), - Text( - ' ${context.l10n.perfStatPerfStats(perf.title)}', - overflow: TextOverflow.ellipsis, - ), + Text(' ${context.l10n.perfStatPerfStats(perf.title)}', overflow: TextOverflow.ellipsis), const Icon(Icons.arrow_drop_down), ], ), onPressed: () { showAdaptiveActionSheet( context: context, - actions: allPerfs.map((p) { - return BottomSheetAction( - makeLabel: (context) => Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - p.icon, - color: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoTheme.of(context).primaryColor - : null, - ), - const SizedBox(width: 6), - Text( - context.l10n.perfStatPerfStats(p.title), - overflow: TextOverflow.ellipsis, - ), - ], - ), - onPressed: (ctx) { - pushReplacementPlatformRoute( - context, - builder: (ctx) { - return PerfStatsScreen(user: user, perf: p); + actions: allPerfs + .map((p) { + return BottomSheetAction( + makeLabel: + (context) => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + p.icon, + color: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoTheme.of(context).primaryColor + : null, + ), + const SizedBox(width: 6), + Text( + context.l10n.perfStatPerfStats(p.title), + overflow: TextOverflow.ellipsis, + ), + ], + ), + onPressed: (ctx) { + pushReplacementPlatformRoute( + context, + builder: (ctx) { + return PerfStatsScreen(user: user, perf: p); + }, + ); }, ); - }, - ); - }).toList(growable: false), + }) + .toList(growable: false), ); }, ); @@ -133,10 +128,7 @@ class _Title extends StatelessWidget { } class _Body extends ConsumerWidget { - const _Body({ - required this.user, - required this.perf, - }); + const _Body({required this.user, required this.perf}); final User user; final Perf perf; @@ -157,11 +149,11 @@ class _Body extends ConsumerWidget { children: [ ratingHistory.when( data: (ratingHistoryData) { - final ratingHistoryPerfData = ratingHistoryData - .firstWhereOrNull((element) => element.perf == perf); + final ratingHistoryPerfData = ratingHistoryData.firstWhereOrNull( + (element) => element.perf == perf, + ); - if (ratingHistoryPerfData == null || - ratingHistoryPerfData.points.isEmpty) { + if (ratingHistoryPerfData == null || ratingHistoryPerfData.points.isEmpty) { return const SizedBox.shrink(); } return _EloChart(ratingHistoryPerfData); @@ -180,10 +172,7 @@ class _Body extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: [ - Text( - '${context.l10n.rating} ', - style: Styles.sectionTitle, - ), + Text('${context.l10n.rating} ', style: Styles.sectionTitle), RatingWidget( rating: data.rating, deviation: data.deviation, @@ -197,38 +186,35 @@ class _Body extends ConsumerWidget { Text( (loggedInUser != null && loggedInUser.user.id == user.id) ? context.l10n.youAreBetterThanPercentOfPerfTypePlayers( - '${data.percentile!.toStringAsFixed(2)}%', - perf.title, - ) + '${data.percentile!.toStringAsFixed(2)}%', + perf.title, + ) : context.l10n.userIsBetterThanPercentOfPerfTypePlayers( - user.username, - '${data.percentile!.toStringAsFixed(2)}%', - perf.title, - ), + user.username, + '${data.percentile!.toStringAsFixed(2)}%', + perf.title, + ), style: TextStyle(color: textShade(context, 0.7)), ), subStatSpace, // The number '12' here is not arbitrary, since the API returns the progression for the last 12 games (as far as I know). StatCard( - context.l10n - .perfStatProgressOverLastXGames('12') - .replaceAll(':', ''), + context.l10n.perfStatProgressOverLastXGames('12').replaceAll(':', ''), child: ProgressionWidget(data.progress), ), StatCardRow([ if (data.rank != null) StatCard( context.l10n.rank, - value: data.rank == null - ? '?' - : NumberFormat.decimalPattern( - Intl.getCurrentLocale(), - ).format(data.rank), + value: + data.rank == null + ? '?' + : NumberFormat.decimalPattern( + Intl.getCurrentLocale(), + ).format(data.rank), ), StatCard( - context.l10n - .perfStatRatingDeviation('') - .replaceAll(': .', ''), + context.l10n.perfStatRatingDeviation('').replaceAll(': .', ''), value: data.deviation.toStringAsFixed(2), ), ]), @@ -263,11 +249,12 @@ class _Body extends ConsumerWidget { onTap: () { pushPlatformRoute( context, - builder: (context) => GameHistoryScreen( - user: user.lightUser, - isOnline: true, - gameFilter: GameFilterState(perfs: ISet({perf})), - ), + builder: + (context) => GameHistoryScreen( + user: user.lightUser, + isOnline: true, + gameFilter: GameFilterState(perfs: ISet({perf})), + ), ); }, child: Padding( @@ -277,8 +264,7 @@ class _Body extends ConsumerWidget { textBaseline: TextBaseline.alphabetic, children: [ Text( - '${context.l10n.perfStatTotalGames} ' - .localizeNumbers(), + '${context.l10n.perfStatTotalGames} '.localizeNumbers(), style: Styles.sectionTitle, ), Text( @@ -286,12 +272,8 @@ class _Body extends ConsumerWidget { style: _mainValueStyle, ), Text( - String.fromCharCode( - Icons.arrow_forward_ios.codePoint, - ), - style: Styles.sectionTitle.copyWith( - fontFamily: 'MaterialIcons', - ), + String.fromCharCode(Icons.arrow_forward_ios.codePoint), + style: Styles.sectionTitle.copyWith(fontFamily: 'MaterialIcons'), ), ], ), @@ -330,47 +312,32 @@ class _Body extends ConsumerWidget { StatCardRow([ StatCard( context.l10n.rated, - child: _PercentageValueWidget( - data.ratedGames, - data.totalGames, - ), + child: _PercentageValueWidget(data.ratedGames, data.totalGames), ), StatCard( context.l10n.tournament, - child: _PercentageValueWidget( - data.tournamentGames, - data.totalGames, - ), + child: _PercentageValueWidget(data.tournamentGames, data.totalGames), ), StatCard( context.l10n.perfStatBerserkedGames.replaceAll( ' ${context.l10n.games.toLowerCase()}', '', ), - child: _PercentageValueWidget( - data.berserkGames, - data.totalGames, - ), + child: _PercentageValueWidget(data.berserkGames, data.totalGames), ), StatCard( context.l10n.perfStatDisconnections, - child: _PercentageValueWidget( - data.disconnections, - data.totalGames, - ), + child: _PercentageValueWidget(data.disconnections, data.totalGames), ), ]), StatCardRow([ StatCard( context.l10n.averageOpponent, - value: data.avgOpponent == null - ? '?' - : data.avgOpponent.toString(), + value: data.avgOpponent == null ? '?' : data.avgOpponent.toString(), ), StatCard( context.l10n.perfStatTimeSpentPlaying, - value: data.timePlayed - .toDaysHoursMinutes(AppLocalizations.of(context)), + value: data.timePlayed.toDaysHoursMinutes(AppLocalizations.of(context)), ), ]), StatCard( @@ -403,10 +370,7 @@ class _Body extends ConsumerWidget { games: data.bestWins!, perf: perf, user: user, - header: Text( - context.l10n.perfStatBestRated, - style: Styles.sectionTitle, - ), + header: Text(context.l10n.perfStatBestRated, style: Styles.sectionTitle), ), ], ], @@ -414,9 +378,7 @@ class _Body extends ConsumerWidget { ); }, error: (error, stackTrace) { - debugPrint( - 'SEVERE: [PerfStatsScreen] could not load data; $error\n$stackTrace', - ); + debugPrint('SEVERE: [PerfStatsScreen] could not load data; $error\n$stackTrace'); return const Center(child: Text('Could not load user stats.')); }, loading: () => const CenterLoadingIndicator(), @@ -441,10 +403,7 @@ class _UserGameWidget extends StatelessWidget { return game == null ? Text('?', style: defaultDateStyle) - : Text( - _dateFormatter.format(game!.finishedAt), - style: defaultDateStyle, - ); + : Text(_dateFormatter.format(game!.finishedAt), style: defaultDateStyle); } } @@ -460,15 +419,15 @@ class _RatingWidget extends StatelessWidget { return (rating == null) ? const Text('?', style: TextStyle(fontSize: _defaultValueFontSize)) : Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - rating.toString(), - style: TextStyle(fontSize: _defaultValueFontSize, color: color), - ), - _UserGameWidget(game), - ], - ); + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + rating.toString(), + style: TextStyle(fontSize: _defaultValueFontSize, color: color), + ), + _UserGameWidget(game), + ], + ); } } @@ -478,12 +437,7 @@ class _PercentageValueWidget extends StatelessWidget { final Color? color; final bool isShaded; - const _PercentageValueWidget( - this.value, - this.denominator, { - this.color, - this.isShaded = false, - }); + const _PercentageValueWidget(this.value, this.denominator, {this.color, this.isShaded = false}); String _getPercentageString(num numerator, num denominator) { return '${((numerator / denominator) * 100).round()}%'; @@ -502,9 +456,10 @@ class _PercentageValueWidget extends StatelessWidget { _getPercentageString(value, denominator), style: TextStyle( fontSize: _defaultValueFontSize, - color: isShaded - ? textShade(context, _customOpacity / 2) - : textShade(context, _customOpacity), + color: + isShaded + ? textShade(context, _customOpacity / 2) + : textShade(context, _customOpacity), ), ), ], @@ -528,83 +483,76 @@ class _StreakWidget extends StatelessWidget { color: textShade(context, _customOpacity), ); - final longestStreakStr = - context.l10n.perfStatLongestStreak('').replaceAll(':', ''); - final currentStreakStr = - context.l10n.perfStatCurrentStreak('').replaceAll(':', ''); - - final List streakWidgets = - [maxStreak, curStreak].mapIndexed((index, streak) { - final streakTitle = Text( - index == 0 ? longestStreakStr : currentStreakStr, - style: streakTitleStyle, - ); - - if (streak == null || streak.isValueEmpty) { - return Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - streakTitle, - Text( - '-', - style: const TextStyle(fontSize: _defaultValueFontSize), - semanticsLabel: context.l10n.none, - ), - ], - ), - ); - } + final longestStreakStr = context.l10n.perfStatLongestStreak('').replaceAll(':', ''); + final currentStreakStr = context.l10n.perfStatCurrentStreak('').replaceAll(':', ''); - final Text valueText = streak.map( - timeStreak: (UserTimeStreak streak) { - return Text( - streak.timePlayed.toDaysHoursMinutes(AppLocalizations.of(context)), - style: valueStyle, - textAlign: TextAlign.center, - ); - }, - gameStreak: (UserGameStreak streak) { - return Text( - context.l10n.nbGames(streak.gamesPlayed), - style: valueStyle, - textAlign: TextAlign.center, + final List streakWidgets = [maxStreak, curStreak] + .mapIndexed((index, streak) { + final streakTitle = Text( + index == 0 ? longestStreakStr : currentStreakStr, + style: streakTitleStyle, ); - }, - ); - return Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - streakTitle, - valueText, - if (streak.startGame != null && streak.endGame != null) - Column( + if (streak == null || streak.isValueEmpty) { + return Expanded( + child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const SizedBox(height: 5.0), - _UserGameWidget(streak.startGame), - Icon( - Icons.arrow_downward_rounded, - color: textShade(context, _customOpacity), + streakTitle, + Text( + '-', + style: const TextStyle(fontSize: _defaultValueFontSize), + semanticsLabel: context.l10n.none, ), - _UserGameWidget(streak.endGame), ], ), - ], - ), - ); - }).toList(growable: false); + ); + } + + final Text valueText = streak.map( + timeStreak: (UserTimeStreak streak) { + return Text( + streak.timePlayed.toDaysHoursMinutes(AppLocalizations.of(context)), + style: valueStyle, + textAlign: TextAlign.center, + ); + }, + gameStreak: (UserGameStreak streak) { + return Text( + context.l10n.nbGames(streak.gamesPlayed), + style: valueStyle, + textAlign: TextAlign.center, + ); + }, + ); + + return Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + streakTitle, + valueText, + if (streak.startGame != null && streak.endGame != null) + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 5.0), + _UserGameWidget(streak.startGame), + Icon(Icons.arrow_downward_rounded, color: textShade(context, _customOpacity)), + _UserGameWidget(streak.endGame), + ], + ), + ], + ), + ); + }) + .toList(growable: false); return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 5.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: streakWidgets, - ), + Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: streakWidgets), ], ); } @@ -637,35 +585,23 @@ class _GameListWidget extends ConsumerWidget { final list = await ref.withClient( (client) => GameRepository(client).getGamesByIds(gameIds), ); - final gameData = - list.firstWhereOrNull((g) => g.id == game.gameId); - if (context.mounted && - gameData != null && - gameData.variant.isReadSupported) { + final gameData = list.firstWhereOrNull((g) => g.id == game.gameId); + if (context.mounted && gameData != null && gameData.variant.isReadSupported) { pushPlatformRoute( context, rootNavigator: true, - builder: (context) => ArchivedGameScreen( - gameData: gameData, - orientation: user.id == gameData.white.user?.id - ? Side.white - : Side.black, - ), + builder: + (context) => ArchivedGameScreen( + gameData: gameData, + orientation: user.id == gameData.white.user?.id ? Side.white : Side.black, + ), ); } else if (context.mounted && gameData != null) { - showPlatformSnackbar( - context, - 'This variant is not supported yet', - ); + showPlatformSnackbar(context, 'This variant is not supported yet'); } }, - playerTitle: UserFullNameWidget( - user: game.opponent, - rating: game.opponentRating, - ), - subtitle: Text( - _dateFormatter.format(game.finishedAt), - ), + playerTitle: UserFullNameWidget(user: game.opponent, rating: game.opponentRating), + subtitle: Text(_dateFormatter.format(game.finishedAt)), ), ], ); @@ -673,11 +609,7 @@ class _GameListWidget extends ConsumerWidget { } class _GameListTile extends StatelessWidget { - const _GameListTile({ - required this.playerTitle, - this.subtitle, - this.onTap, - }); + const _GameListTile({required this.playerTitle, this.subtitle, this.onTap}); final Widget playerTitle; final Widget? subtitle; @@ -688,14 +620,13 @@ class _GameListTile extends StatelessWidget { return PlatformListTile( onTap: onTap, title: playerTitle, - subtitle: subtitle != null - ? DefaultTextStyle.merge( - child: subtitle!, - style: TextStyle( - color: textShade(context, Styles.subtitleOpacity), - ), - ) - : null, + subtitle: + subtitle != null + ? DefaultTextStyle.merge( + child: subtitle!, + style: TextStyle(color: textShade(context, Styles.subtitleOpacity)), + ) + : null, ); } } @@ -714,11 +645,8 @@ class _EloChartState extends State<_EloChart> { late List _allFlSpot; - List get _flSpot => _allFlSpot - .where( - (element) => element.x >= _minX && element.x <= _maxX, - ) - .toList(); + List get _flSpot => + _allFlSpot.where((element) => element.x >= _minX && element.x <= _maxX).toList(); IList get _points => widget.value.points; @@ -726,24 +654,21 @@ class _EloChartState extends State<_EloChart> { DateTime get _lastDate => _points.last.date; - double get _minY => - (_flSpot.map((e) => e.y).reduce(min) / 100).floorToDouble() * 100; + double get _minY => (_flSpot.map((e) => e.y).reduce(min) / 100).floorToDouble() * 100; - double get _maxY => - (_flSpot.map((e) => e.y).reduce(max) / 100).ceilToDouble() * 100; + double get _maxY => (_flSpot.map((e) => e.y).reduce(max) / 100).ceilToDouble() * 100; - double get _minX => - _startDate(_selectedRange).difference(_firstDate).inDays.toDouble(); + double get _minX => _startDate(_selectedRange).difference(_firstDate).inDays.toDouble(); double get _maxX => _allFlSpot.last.x; DateTime _startDate(DateRange dateRange) => switch (dateRange) { - DateRange.oneWeek => _lastDate.subtract(const Duration(days: 7)), - DateRange.oneMonth => _lastDate.copyWith(month: _lastDate.month - 1), - DateRange.threeMonths => _lastDate.copyWith(month: _lastDate.month - 3), - DateRange.oneYear => _lastDate.copyWith(year: _lastDate.year - 1), - DateRange.allTime => _firstDate, - }; + DateRange.oneWeek => _lastDate.subtract(const Duration(days: 7)), + DateRange.oneMonth => _lastDate.copyWith(month: _lastDate.month - 1), + DateRange.threeMonths => _lastDate.copyWith(month: _lastDate.month - 3), + DateRange.oneYear => _lastDate.copyWith(year: _lastDate.year - 1), + DateRange.allTime => _firstDate, + }; bool _dateIsInRange(DateRange dateRange) => _firstDate.isBefore(_startDate(dateRange)) || @@ -766,22 +691,20 @@ class _EloChartState extends State<_EloChart> { j += 1; } else { pointsHistoryRatingCompleted.add( - UserRatingHistoryPoint( - date: currentDate, - elo: _points[j - 1].elo, - ), + UserRatingHistoryPoint(date: currentDate, elo: _points[j - 1].elo), ); } } - _allFlSpot = pointsHistoryRatingCompleted - .map( - (element) => FlSpot( - element.date.difference(_firstDate).inDays.toDouble(), - element.elo.toDouble(), - ), - ) - .toList(); + _allFlSpot = + pointsHistoryRatingCompleted + .map( + (element) => FlSpot( + element.date.difference(_firstDate).inDays.toDouble(), + element.elo.toDouble(), + ), + ) + .toList(); if (_dateIsInRange(DateRange.threeMonths)) { _selectedRange = DateRange.threeMonths; @@ -796,8 +719,7 @@ class _EloChartState extends State<_EloChart> { @override Widget build(BuildContext context) { - final borderColor = - Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5); + final borderColor = Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5); final chartColor = Theme.of(context).colorScheme.tertiary; final chartDateFormatter = switch (_selectedRange) { DateRange.oneWeek => DateFormat.MMMd(), @@ -807,24 +729,18 @@ class _EloChartState extends State<_EloChart> { DateRange.allTime => DateFormat.yMMM(), }; - String formatDateFromTimestamp(double nbDays) => chartDateFormatter.format( - _firstDate.add(Duration(days: nbDays.toInt())), - ); + String formatDateFromTimestamp(double nbDays) => + chartDateFormatter.format(_firstDate.add(Duration(days: nbDays.toInt()))); String formatDateFromTimestampForTooltip(double nbDays) => - DateFormat.yMMMd().format( - _firstDate.add(Duration(days: nbDays.toInt())), - ); + DateFormat.yMMMd().format(_firstDate.add(Duration(days: nbDays.toInt()))); Widget leftTitlesWidget(double value, TitleMeta meta) { return SideTitleWidget( axisSide: meta.axisSide, child: Text( value.toInt().toString(), - style: const TextStyle( - color: Colors.grey, - fontSize: 10, - ), + style: const TextStyle(color: Colors.grey, fontSize: 10), ), ); } @@ -836,10 +752,7 @@ class _EloChartState extends State<_EloChart> { axisSide: meta.axisSide, child: Text( formatDateFromTimestamp(value), - style: const TextStyle( - color: Colors.grey, - fontSize: 10, - ), + style: const TextStyle(color: Colors.grey, fontSize: 10), ), ); } @@ -849,9 +762,7 @@ class _EloChartState extends State<_EloChart> { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - const SizedBox( - width: 25, - ), + const SizedBox(width: 25), ...DateRange.values .where((dateRange) => _dateIsInRange(dateRange)) .map( @@ -880,10 +791,7 @@ class _EloChartState extends State<_EloChart> { spots: _flSpot, dotData: const FlDotData(show: false), color: chartColor, - belowBarData: BarAreaData( - color: chartColor.withValues(alpha: 0.2), - show: true, - ), + belowBarData: BarAreaData(color: chartColor.withValues(alpha: 0.2), show: true), barWidth: 1.5, ), ], @@ -897,10 +805,7 @@ class _EloChartState extends State<_EloChart> { gridData: FlGridData( show: true, drawVerticalLine: false, - getDrawingHorizontalLine: (value) => FlLine( - color: borderColor, - strokeWidth: 0.5, - ), + getDrawingHorizontalLine: (value) => FlLine(color: borderColor, strokeWidth: 0.5), ), lineTouchData: LineTouchData( touchSpotThreshold: double.infinity, @@ -916,13 +821,8 @@ class _EloChartState extends State<_EloChart> { Styles.bold, children: [ TextSpan( - text: formatDateFromTimestampForTooltip( - touchedSpot.x, - ), - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 10, - ), + text: formatDateFromTimestampForTooltip(touchedSpot.x), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 10), ), ], ), @@ -933,17 +833,11 @@ class _EloChartState extends State<_EloChart> { getTouchedSpotIndicator: (barData, spotIndexes) { return spotIndexes.map((spotIndex) { return TouchedSpotIndicatorData( - FlLine( - color: chartColor, - strokeWidth: 2, - ), + FlLine(color: chartColor, strokeWidth: 2), FlDotData( show: true, getDotPainter: (spot, percent, barData, index) { - return FlDotCirclePainter( - radius: 5, - color: chartColor, - ); + return FlDotCirclePainter(radius: 5, color: chartColor); }, ), ); @@ -951,10 +845,8 @@ class _EloChartState extends State<_EloChart> { }, ), titlesData: FlTitlesData( - rightTitles: - const AxisTitles(sideTitles: SideTitles(showTitles: false)), - topTitles: - const AxisTitles(sideTitles: SideTitles(showTitles: false)), + rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), + topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, @@ -983,11 +875,7 @@ class _EloChartState extends State<_EloChart> { } class _RangeButton extends StatelessWidget { - const _RangeButton({ - required this.text, - required this.onPressed, - this.selected = false, - }); + const _RangeButton({required this.text, required this.onPressed, this.selected = false}); final String text; final VoidCallback onPressed; @@ -1005,8 +893,7 @@ class _RangeButton extends StatelessWidget { onTap: onPressed, child: Center( child: Padding( - padding: - const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), + padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Text(text), ), ), @@ -1024,10 +911,10 @@ enum DateRange { @override String toString() => switch (this) { - DateRange.oneWeek => '1W', - DateRange.oneMonth => '1M', - DateRange.threeMonths => '3M', - DateRange.oneYear => '1Y', - DateRange.allTime => 'ALL', - }; + DateRange.oneWeek => '1W', + DateRange.oneMonth => '1M', + DateRange.threeMonths => '3M', + DateRange.oneYear => '1Y', + DateRange.allTime => 'ALL', + }; } diff --git a/lib/src/view/user/player_screen.dart b/lib/src/view/user/player_screen.dart index a947bacaf4..ef9408f0d3 100644 --- a/lib/src/view/user/player_screen.dart +++ b/lib/src/view/user/player_screen.dart @@ -35,9 +35,7 @@ class PlayerScreen extends ConsumerWidget { } }, child: PlatformScaffold( - appBar: PlatformAppBar( - title: Text(context.l10n.players), - ), + appBar: PlatformAppBar(title: Text(context.l10n.players)), body: _Body(), ), ); @@ -51,10 +49,7 @@ class _Body extends ConsumerWidget { return ListView( children: [ - const Padding( - padding: Styles.bodySectionPadding, - child: _SearchButton(), - ), + const Padding(padding: Styles.bodySectionPadding, child: _SearchButton()), if (session != null) _OnlineFriendsWidget(), RatingPrefAware(child: LeaderboardWidget()), ], @@ -72,19 +67,18 @@ class _SearchButton extends StatelessWidget { @override Widget build(BuildContext context) { - void onUserTap(LightUser user) => pushPlatformRoute( - context, - builder: (ctx) => UserScreen(user: user), - ); + void onUserTap(LightUser user) => + pushPlatformRoute(context, builder: (ctx) => UserScreen(user: user)); return PlatformSearchBar( hintText: context.l10n.searchSearch, focusNode: AlwaysDisabledFocusNode(), - onTap: () => pushPlatformRoute( - context, - fullscreenDialog: true, - builder: (_) => SearchScreen(onUserTap: onUserTap), - ), + onTap: + () => pushPlatformRoute( + context, + fullscreenDialog: true, + builder: (_) => SearchScreen(onUserTap: onUserTap), + ), ); } } @@ -98,21 +92,18 @@ class _OnlineFriendsWidget extends ConsumerWidget { data: (data) { return ListSection( header: Text(context.l10n.nbFriendsOnline(data.length)), - headerTrailing: data.isEmpty - ? null - : NoPaddingTextButton( - onPressed: () => _handleTap(context, data), - child: Text( - context.l10n.more, + headerTrailing: + data.isEmpty + ? null + : NoPaddingTextButton( + onPressed: () => _handleTap(context, data), + child: Text(context.l10n.more), ), - ), children: [ if (data.isEmpty) PlatformListTile( title: Text(context.l10n.friends), - trailing: const Icon( - Icons.chevron_right, - ), + trailing: const Icon(Icons.chevron_right), onTap: () => _handleTap(context, data), ), for (final user in data) @@ -121,13 +112,12 @@ class _OnlineFriendsWidget extends ConsumerWidget { padding: const EdgeInsets.only(right: 5.0), child: UserFullNameWidget(user: user), ), - onTap: () => pushPlatformRoute( - context, - title: user.name, - builder: (_) => UserScreen( - user: user, - ), - ), + onTap: + () => pushPlatformRoute( + context, + title: user.name, + builder: (_) => UserScreen(user: user), + ), ), ], ); @@ -136,19 +126,15 @@ class _OnlineFriendsWidget extends ConsumerWidget { debugPrint( 'SEVERE: [PlayerScreen] could not load following online users; $error\n $stackTrace', ); - return const Center( - child: Text('Could not load online friends'), - ); + return const Center(child: Text('Could not load online friends')); }, - loading: () => Shimmer( - child: ShimmerLoading( - isLoading: true, - child: ListSection.loading( - itemsNumber: 3, - header: true, + loading: + () => Shimmer( + child: ShimmerLoading( + isLoading: true, + child: ListSection.loading(itemsNumber: 3, header: true), + ), ), - ), - ), ); } diff --git a/lib/src/view/user/recent_games.dart b/lib/src/view/user/recent_games.dart index 7a7651e7cb..ecae5bcb05 100644 --- a/lib/src/view/user/recent_games.dart +++ b/lib/src/view/user/recent_games.dart @@ -28,16 +28,15 @@ class RecentGamesWidget extends ConsumerWidget { final session = ref.watch(authSessionProvider); final userId = user?.id ?? session?.user.id; - final recentGames = user != null - ? ref.watch(userRecentGamesProvider(userId: user!.id)) - : ref.watch(myRecentGamesProvider); + final recentGames = + user != null + ? ref.watch(userRecentGamesProvider(userId: user!.id)) + : ref.watch(myRecentGamesProvider); - final nbOfGames = ref + final nbOfGames = + ref .watch( - userNumberOfGamesProvider( - user, - isOnline: connectivity.valueOrNull?.isOnline == true, - ), + userNumberOfGamesProvider(user, isOnline: connectivity.valueOrNull?.isOnline == true), ) .valueOrNull ?? 0; @@ -50,45 +49,42 @@ class RecentGamesWidget extends ConsumerWidget { return ListSection( header: Text(context.l10n.recentGames), hasLeading: true, - headerTrailing: nbOfGames > data.length - ? NoPaddingTextButton( - onPressed: () { - pushPlatformRoute( - context, - builder: (context) => GameHistoryScreen( - user: user, - isOnline: connectivity.valueOrNull?.isOnline == true, - ), - ); - }, - child: Text( - context.l10n.more, - ), - ) - : null, - children: data.map((item) { - return ExtendedGameListTile(item: item, userId: userId); - }).toList(), + headerTrailing: + nbOfGames > data.length + ? NoPaddingTextButton( + onPressed: () { + pushPlatformRoute( + context, + builder: + (context) => GameHistoryScreen( + user: user, + isOnline: connectivity.valueOrNull?.isOnline == true, + ), + ); + }, + child: Text(context.l10n.more), + ) + : null, + children: + data.map((item) { + return ExtendedGameListTile(item: item, userId: userId); + }).toList(), ); }, error: (error, stackTrace) { - debugPrint( - 'SEVERE: [RecentGames] could not recent games; $error\n$stackTrace', - ); + debugPrint('SEVERE: [RecentGames] could not recent games; $error\n$stackTrace'); return const Padding( padding: Styles.bodySectionPadding, child: Text('Could not load recent games.'), ); }, - loading: () => Shimmer( - child: ShimmerLoading( - isLoading: true, - child: ListSection.loading( - itemsNumber: 10, - header: true, + loading: + () => Shimmer( + child: ShimmerLoading( + isLoading: true, + child: ListSection.loading(itemsNumber: 10, header: true), + ), ), - ), - ), ); } } diff --git a/lib/src/view/user/search_screen.dart b/lib/src/view/user/search_screen.dart index 790729686f..9640201853 100644 --- a/lib/src/view/user/search_screen.dart +++ b/lib/src/view/user/search_screen.dart @@ -17,9 +17,7 @@ import 'package:lichess_mobile/src/widgets/user_list_tile.dart'; const _kSaveHistoryDebouncTimer = Duration(seconds: 2); class SearchScreen extends ConsumerStatefulWidget { - const SearchScreen({ - this.onUserTap, - }); + const SearchScreen({this.onUserTap}); final void Function(LightUser)? onUserTap; @@ -80,27 +78,26 @@ class _SearchScreenState extends ConsumerState { final body = _Body(_term, setSearchText, widget.onUserTap); return PlatformWidget( - androidBuilder: (context) => Scaffold( - appBar: AppBar( - toolbarHeight: 80, // Custom height to fit the search bar - title: searchBar, - ), - body: body, - ), - iosBuilder: (context) => CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - automaticallyImplyLeading: false, - middle: SizedBox( - height: 36.0, - child: searchBar, + androidBuilder: + (context) => Scaffold( + appBar: AppBar( + toolbarHeight: 80, // Custom height to fit the search bar + title: searchBar, + ), + body: body, ), - trailing: NoPaddingTextButton( - child: Text(context.l10n.close), - onPressed: () => Navigator.pop(context), + iosBuilder: + (context) => CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + automaticallyImplyLeading: false, + middle: SizedBox(height: 36.0, child: searchBar), + trailing: NoPaddingTextButton( + child: Text(context.l10n.close), + onPressed: () => Navigator.pop(context), + ), + ), + child: body, ), - ), - child: body, - ), ); } } @@ -114,34 +111,33 @@ class _Body extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { if (term != null) { - return SafeArea( - child: _UserList(term!, onUserTap), - ); + return SafeArea(child: _UserList(term!, onUserTap)); } else { final searchHistory = ref.watch(searchHistoryProvider).history; return SafeArea( child: SingleChildScrollView( - child: searchHistory.isEmpty - ? kEmptyWidget - : ListSection( - header: Text(context.l10n.mobileRecentSearches), - headerTrailing: NoPaddingTextButton( - child: Text(context.l10n.mobileClearButton), - onPressed: () => - ref.read(searchHistoryProvider.notifier).clear(), + child: + searchHistory.isEmpty + ? kEmptyWidget + : ListSection( + header: Text(context.l10n.mobileRecentSearches), + headerTrailing: NoPaddingTextButton( + child: Text(context.l10n.mobileClearButton), + onPressed: () => ref.read(searchHistoryProvider.notifier).clear(), + ), + showDividerBetweenTiles: true, + hasLeading: true, + children: + searchHistory + .map( + (term) => PlatformListTile( + leading: const Icon(Icons.history), + title: Text(term), + onTap: () => onRecentSearchTap(term), + ), + ) + .toList(), ), - showDividerBetweenTiles: true, - hasLeading: true, - children: searchHistory - .map( - (term) => PlatformListTile( - leading: const Icon(Icons.history), - title: Text(term), - onTap: () => onRecentSearchTap(term), - ), - ) - .toList(), - ), ), ); } @@ -159,36 +155,39 @@ class _UserList extends ConsumerWidget { final autoComplete = ref.watch(autoCompleteUserProvider(term)); return SingleChildScrollView( child: autoComplete.when( - data: (userList) => userList.isNotEmpty - ? ListSection( - header: Row( - children: [ - const Icon(Icons.person), - const SizedBox(width: 8), - Text(context.l10n.mobilePlayersMatchingSearchTerm(term)), - ], - ), - hasLeading: true, - showDividerBetweenTiles: true, - children: userList - .map( - (user) => UserListTile.fromLightUser( - user, - onTap: () { - if (onUserTap != null) { - onUserTap!.call(user); - } - }, + data: + (userList) => + userList.isNotEmpty + ? ListSection( + header: Row( + children: [ + const Icon(Icons.person), + const SizedBox(width: 8), + Text(context.l10n.mobilePlayersMatchingSearchTerm(term)), + ], ), + hasLeading: true, + showDividerBetweenTiles: true, + children: + userList + .map( + (user) => UserListTile.fromLightUser( + user, + onTap: () { + if (onUserTap != null) { + onUserTap!.call(user); + } + }, + ), + ) + .toList(), ) - .toList(), - ) - : Column( - children: [ - const SizedBox(height: 16.0), - Center(child: Text(context.l10n.mobileNoSearchResults)), - ], - ), + : Column( + children: [ + const SizedBox(height: 16.0), + Center(child: Text(context.l10n.mobileNoSearchResults)), + ], + ), error: (e, _) { debugPrint('Error loading search results: $e'); return const Column( @@ -198,12 +197,7 @@ class _UserList extends ConsumerWidget { ], ); }, - loading: () => const Column( - children: [ - SizedBox(height: 16.0), - CenterLoadingIndicator(), - ], - ), + loading: () => const Column(children: [SizedBox(height: 16.0), CenterLoadingIndicator()]), ), ); } diff --git a/lib/src/view/user/user_activity.dart b/lib/src/view/user/user_activity.dart index 99d073b7af..ab57c5c966 100644 --- a/lib/src/view/user/user_activity.dart +++ b/lib/src/view/user/user_activity.dart @@ -22,9 +22,10 @@ class UserActivityWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final activity = user != null - ? ref.watch(userActivityProvider(id: user!.id)) - : ref.watch(accountActivityProvider); + final activity = + user != null + ? ref.watch(userActivityProvider(id: user!.id)) + : ref.watch(accountActivityProvider); return activity.when( data: (data) { @@ -33,30 +34,23 @@ class UserActivityWidget extends ConsumerWidget { return const SizedBox.shrink(); } return ListSection( - header: - Text(context.l10n.activityActivity, style: Styles.sectionTitle), + header: Text(context.l10n.activityActivity, style: Styles.sectionTitle), hasLeading: true, - children: nonEmptyActivities - .take(10) - .map((entry) => UserActivityEntry(entry: entry)) - .toList(), + children: + nonEmptyActivities.take(10).map((entry) => UserActivityEntry(entry: entry)).toList(), ); }, error: (error, stackTrace) { - debugPrint( - 'SEVERE: [UserScreen] could not load user activity; $error\n$stackTrace', - ); + debugPrint('SEVERE: [UserScreen] could not load user activity; $error\n$stackTrace'); return const Text('Could not load user activity'); }, - loading: () => Shimmer( - child: ShimmerLoading( - isLoading: true, - child: ListSection.loading( - itemsNumber: 10, - header: true, + loading: + () => Shimmer( + child: ShimmerLoading( + isLoading: true, + child: ListSection.loading(itemsNumber: 10, header: true), + ), ), - ), - ), ); } } @@ -70,8 +64,7 @@ class UserActivityEntry extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); final leadingIconSize = theme.platform == TargetPlatform.iOS ? 26.0 : 36.0; - final emptySubtitle = - theme.platform == TargetPlatform.iOS ? const SizedBox.shrink() : null; + final emptySubtitle = theme.platform == TargetPlatform.iOS ? const SizedBox.shrink() : null; final redColor = theme.extension()?.error; final greenColor = theme.extension()?.good; @@ -80,68 +73,45 @@ class UserActivityEntry extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( - padding: const EdgeInsets.only( - left: 14.0, - top: 16.0, - right: 14.0, - bottom: 4.0, - ), + padding: const EdgeInsets.only(left: 14.0, top: 16.0, right: 14.0, bottom: 4.0), child: Text( _dateFormatter.format(entry.startTime), - style: TextStyle( - color: context.lichessColors.brag, - fontWeight: FontWeight.bold, - ), + style: TextStyle(color: context.lichessColors.brag, fontWeight: FontWeight.bold), ), ), if (entry.games != null) for (final gameEntry in entry.games!.entries) _UserActivityListTile( - leading: Icon( - gameEntry.key.icon, - size: leadingIconSize, - ), + leading: Icon(gameEntry.key.icon, size: leadingIconSize), title: context.l10n.activityPlayedNbGames( - gameEntry.value.win + - gameEntry.value.draw + - gameEntry.value.loss, + gameEntry.value.win + gameEntry.value.draw + gameEntry.value.loss, gameEntry.key.title, ), subtitle: RatingPrefAware( child: Row( children: [ - RatingWidget( - deviation: 0, - rating: gameEntry.value.ratingAfter, - ), + RatingWidget(deviation: 0, rating: gameEntry.value.ratingAfter), const SizedBox(width: 3), - if (gameEntry.value.ratingAfter - - gameEntry.value.ratingBefore != - 0) ...[ + if (gameEntry.value.ratingAfter - gameEntry.value.ratingBefore != 0) ...[ Icon( - gameEntry.value.ratingAfter - - gameEntry.value.ratingBefore > - 0 + gameEntry.value.ratingAfter - gameEntry.value.ratingBefore > 0 ? LichessIcons.arrow_full_upperright : LichessIcons.arrow_full_lowerright, - color: gameEntry.value.ratingAfter - - gameEntry.value.ratingBefore > - 0 - ? greenColor - : redColor, + color: + gameEntry.value.ratingAfter - gameEntry.value.ratingBefore > 0 + ? greenColor + : redColor, size: 12, ), Text( - (gameEntry.value.ratingAfter - - gameEntry.value.ratingBefore) + (gameEntry.value.ratingAfter - gameEntry.value.ratingBefore) .abs() .toString(), style: TextStyle( - color: gameEntry.value.ratingAfter - - gameEntry.value.ratingBefore > - 0 - ? greenColor - : redColor, + color: + gameEntry.value.ratingAfter - gameEntry.value.ratingBefore > 0 + ? greenColor + : redColor, fontSize: 11, ), ), @@ -157,46 +127,31 @@ class UserActivityEntry extends ConsumerWidget { ), if (entry.puzzles != null) _UserActivityListTile( - leading: Icon( - LichessIcons.target, - size: leadingIconSize, - ), - title: context.l10n.activitySolvedNbPuzzles( - entry.puzzles!.win + entry.puzzles!.loss, - ), + leading: Icon(LichessIcons.target, size: leadingIconSize), + title: context.l10n.activitySolvedNbPuzzles(entry.puzzles!.win + entry.puzzles!.loss), subtitle: RatingPrefAware( child: Row( children: [ - RatingWidget( - deviation: 0, - rating: entry.puzzles!.ratingAfter, - ), + RatingWidget(deviation: 0, rating: entry.puzzles!.ratingAfter), const SizedBox(width: 3), - if (entry.puzzles!.ratingAfter - - entry.puzzles!.ratingBefore != - 0) ...[ + if (entry.puzzles!.ratingAfter - entry.puzzles!.ratingBefore != 0) ...[ Icon( - entry.puzzles!.ratingAfter - entry.puzzles!.ratingBefore > - 0 + entry.puzzles!.ratingAfter - entry.puzzles!.ratingBefore > 0 ? LichessIcons.arrow_full_upperright : LichessIcons.arrow_full_lowerright, - color: entry.puzzles!.ratingAfter - - entry.puzzles!.ratingBefore > - 0 - ? greenColor - : redColor, + color: + entry.puzzles!.ratingAfter - entry.puzzles!.ratingBefore > 0 + ? greenColor + : redColor, size: 12, ), Text( - (entry.puzzles!.ratingAfter - entry.puzzles!.ratingBefore) - .abs() - .toString(), + (entry.puzzles!.ratingAfter - entry.puzzles!.ratingBefore).abs().toString(), style: TextStyle( - color: entry.puzzles!.ratingAfter - - entry.puzzles!.ratingBefore > - 0 - ? greenColor - : redColor, + color: + entry.puzzles!.ratingAfter - entry.puzzles!.ratingBefore > 0 + ? greenColor + : redColor, fontSize: 11, ), ), @@ -212,44 +167,21 @@ class UserActivityEntry extends ConsumerWidget { ), if (entry.streak != null) _UserActivityListTile( - leading: Icon( - LichessIcons.streak, - size: leadingIconSize, - ), - title: context.l10n.stormPlayedNbRunsOfPuzzleStorm( - entry.streak!.runs, - 'Puzzle Streak', - ), + leading: Icon(LichessIcons.streak, size: leadingIconSize), + title: context.l10n.stormPlayedNbRunsOfPuzzleStorm(entry.streak!.runs, 'Puzzle Streak'), subtitle: emptySubtitle, - trailing: BriefGameResultBox( - win: entry.streak!.score, - draw: 0, - loss: 0, - ), + trailing: BriefGameResultBox(win: entry.streak!.score, draw: 0, loss: 0), ), if (entry.storm != null) _UserActivityListTile( - leading: Icon( - LichessIcons.storm, - size: leadingIconSize, - ), - title: context.l10n.stormPlayedNbRunsOfPuzzleStorm( - entry.storm!.runs, - 'Puzzle Storm', - ), + leading: Icon(LichessIcons.storm, size: leadingIconSize), + title: context.l10n.stormPlayedNbRunsOfPuzzleStorm(entry.storm!.runs, 'Puzzle Storm'), subtitle: emptySubtitle, - trailing: BriefGameResultBox( - win: entry.storm!.score, - draw: 0, - loss: 0, - ), + trailing: BriefGameResultBox(win: entry.storm!.score, draw: 0, loss: 0), ), if (entry.correspondenceEnds != null) _UserActivityListTile( - leading: Icon( - LichessIcons.correspondence, - size: leadingIconSize, - ), + leading: Icon(LichessIcons.correspondence, size: leadingIconSize), title: context.l10n.activityCompletedNbGames( entry.correspondenceEnds!.win + entry.correspondenceEnds!.draw + @@ -262,49 +194,34 @@ class UserActivityEntry extends ConsumerWidget { loss: entry.correspondenceEnds!.loss, ), ), - if (entry.correspondenceMovesNb != null && - entry.correspondenceGamesNb != null) + if (entry.correspondenceMovesNb != null && entry.correspondenceGamesNb != null) _UserActivityListTile( - leading: Icon( - LichessIcons.correspondence, - size: leadingIconSize, - ), - title: context.l10n.activityPlayedNbMoves( - entry.correspondenceMovesNb!, - ), + leading: Icon(LichessIcons.correspondence, size: leadingIconSize), + title: context.l10n.activityPlayedNbMoves(entry.correspondenceMovesNb!), subtitle: Text( - context.l10n.activityInNbCorrespondenceGames( - entry.correspondenceGamesNb!, - ), + context.l10n.activityInNbCorrespondenceGames(entry.correspondenceGamesNb!), ), ), if (entry.tournamentNb != null) _UserActivityListTile( - leading: Icon( - Icons.emoji_events, - size: leadingIconSize, - ), - title: context.l10n.activityCompetedInNbTournaments( - entry.tournamentNb!, - ), - subtitle: entry.bestTournament != null - ? Text( - context.l10n.activityRankedInTournament( - entry.bestTournament!.rank, - entry.bestTournament!.rankPercent.toString(), - entry.bestTournament!.nbGames.toString(), - entry.bestTournament!.name, - ), - maxLines: 2, - ) - : emptySubtitle, + leading: Icon(Icons.emoji_events, size: leadingIconSize), + title: context.l10n.activityCompetedInNbTournaments(entry.tournamentNb!), + subtitle: + entry.bestTournament != null + ? Text( + context.l10n.activityRankedInTournament( + entry.bestTournament!.rank, + entry.bestTournament!.rankPercent.toString(), + entry.bestTournament!.nbGames.toString(), + entry.bestTournament!.name, + ), + maxLines: 2, + ) + : emptySubtitle, ), if (entry.followInNb != null) _UserActivityListTile( - leading: Icon( - Icons.thumb_up, - size: leadingIconSize, - ), + leading: Icon(Icons.thumb_up, size: leadingIconSize), title: context.l10n.activityGainedNbFollowers(entry.followInNb!), subtitle: emptySubtitle, ), @@ -314,12 +231,7 @@ class UserActivityEntry extends ConsumerWidget { } class _UserActivityListTile extends StatelessWidget { - const _UserActivityListTile({ - required this.title, - this.subtitle, - this.trailing, - this.leading, - }); + const _UserActivityListTile({required this.title, this.subtitle, this.trailing, this.leading}); final String title; final Widget? subtitle; @@ -347,10 +259,7 @@ const _gameStatsFontStyle = TextStyle( ); class _ResultBox extends StatelessWidget { - const _ResultBox({ - required this.number, - required this.color, - }); + const _ResultBox({required this.number, required this.color}); final int number; final Color color; @@ -368,10 +277,7 @@ class _ResultBox extends StatelessWidget { padding: const EdgeInsets.all(1.0), child: FittedBox( fit: BoxFit.contain, - child: Text( - number.toString(), - style: _gameStatsFontStyle, - ), + child: Text(number.toString(), style: _gameStatsFontStyle), ), ), ); @@ -379,11 +285,7 @@ class _ResultBox extends StatelessWidget { } class BriefGameResultBox extends StatelessWidget { - const BriefGameResultBox({ - required this.win, - required this.draw, - required this.loss, - }); + const BriefGameResultBox({required this.win, required this.draw, required this.loss}); final int win; final int draw; @@ -395,39 +297,27 @@ class BriefGameResultBox extends StatelessWidget { padding: const EdgeInsets.only(left: 5.0), child: SizedBox( height: 20, - width: (win != 0 ? 1 : 0) * _boxSize + + width: + (win != 0 ? 1 : 0) * _boxSize + (draw != 0 ? 1 : 0) * _boxSize + (loss != 0 ? 1 : 0) * _boxSize + - ((win != 0 ? 1 : 0) + - (draw != 0 ? 1 : 0) + - (loss != 0 ? 1 : 0) - - 1) * - _spaceWidth, + ((win != 0 ? 1 : 0) + (draw != 0 ? 1 : 0) + (loss != 0 ? 1 : 0) - 1) * _spaceWidth, child: Row( children: [ if (win != 0) _ResultBox( number: win, - color: Theme.of(context).extension()?.good ?? - LichessColors.green, - ), - if (win != 0 && draw != 0) - const SizedBox( - width: _spaceWidth, - ), - if (draw != 0) - _ResultBox( - number: draw, - color: context.lichessColors.brag, + color: Theme.of(context).extension()?.good ?? LichessColors.green, ), + if (win != 0 && draw != 0) const SizedBox(width: _spaceWidth), + if (draw != 0) _ResultBox(number: draw, color: context.lichessColors.brag), if ((draw != 0 && loss != 0) || (win != 0 && loss != 0)) - const SizedBox( - width: _spaceWidth, - ), + const SizedBox(width: _spaceWidth), if (loss != 0) _ResultBox( number: loss, - color: Theme.of(context).extension()?.error ?? + color: + Theme.of(context).extension()?.error ?? context.lichessColors.error, ), ], diff --git a/lib/src/view/user/user_profile.dart b/lib/src/view/user/user_profile.dart index 78862eb3cd..24a05b38c0 100644 --- a/lib/src/view/user/user_profile.dart +++ b/lib/src/view/user/user_profile.dart @@ -24,10 +24,7 @@ import 'countries.dart'; const _userNameStyle = TextStyle(fontSize: 20, fontWeight: FontWeight.w500); class UserProfileWidget extends ConsumerWidget { - const UserProfileWidget({ - required this.user, - this.bioMaxLines = 10, - }); + const UserProfileWidget({required this.user, this.bioMaxLines = 10}); final User user; @@ -36,12 +33,10 @@ class UserProfileWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final userFullName = user.profile?.realName != null - ? Text( - user.profile!.realName!, - style: _userNameStyle, - ) - : null; + final userFullName = + user.profile?.realName != null + ? Text(user.profile!.realName!, style: _userNameStyle) + : null; return Padding( padding: Styles.horizontalBodyPadding.add(Styles.sectionTopPadding), @@ -54,9 +49,7 @@ class UserProfileWidget extends ConsumerWidget { child: Row( children: [ Icon(Icons.error, color: context.lichessColors.error), - const SizedBox( - width: 5, - ), + const SizedBox(width: 5), Flexible( child: Text( context.l10n.thisAccountViolatedTos, @@ -70,10 +63,7 @@ class UserProfileWidget extends ConsumerWidget { ), ), if (userFullName != null) - Padding( - padding: const EdgeInsets.only(bottom: 5), - child: userFullName, - ), + Padding(padding: const EdgeInsets.only(bottom: 5), child: userFullName), if (user.profile?.bio != null) Linkify( onOpen: (link) async { @@ -81,52 +71,37 @@ class UserProfileWidget extends ConsumerWidget { final username = link.originText.substring(1); pushPlatformRoute( context, - builder: (ctx) => UserScreen( - user: LightUser( - id: UserId.fromUserName(username), - name: username, - ), - ), + builder: + (ctx) => UserScreen( + user: LightUser(id: UserId.fromUserName(username), name: username), + ), ); } else { launchUrl(Uri.parse(link.url)); } }, - linkifiers: const [ - UrlLinkifier(), - EmailLinkifier(), - UserTagLinkifier(), - ], + linkifiers: const [UrlLinkifier(), EmailLinkifier(), UserTagLinkifier()], text: user.profile!.bio!.replaceAll('\n', ' '), maxLines: bioMaxLines, style: bioStyle, overflow: TextOverflow.ellipsis, - linkStyle: const TextStyle( - color: Colors.blueAccent, - decoration: TextDecoration.none, - ), + linkStyle: const TextStyle(color: Colors.blueAccent, decoration: TextDecoration.none), ), const SizedBox(height: 10), if (user.profile?.fideRating != null) Padding( padding: const EdgeInsets.only(bottom: 5), - child: Text( - '${context.l10n.xRating('FIDE')}: ${user.profile!.fideRating}', - ), + child: Text('${context.l10n.xRating('FIDE')}: ${user.profile!.fideRating}'), ), if (user.profile?.uscfRating != null) Padding( padding: const EdgeInsets.only(bottom: 5), - child: Text( - '${context.l10n.xRating('USCF')}: ${user.profile!.uscfRating}', - ), + child: Text('${context.l10n.xRating('USCF')}: ${user.profile!.uscfRating}'), ), if (user.profile?.ecfRating != null) Padding( padding: const EdgeInsets.only(bottom: 5), - child: Text( - '${context.l10n.xRating('ECF')}: ${user.profile!.ecfRating}', - ), + child: Text('${context.l10n.xRating('ECF')}: ${user.profile!.ecfRating}'), ), if (user.profile != null) Padding( @@ -134,9 +109,7 @@ class UserProfileWidget extends ConsumerWidget { child: Location(profile: user.profile!), ), if (user.createdAt != null) - Text( - '${context.l10n.memberSince} ${DateFormat.yMMMMd().format(user.createdAt!)}', - ), + Text('${context.l10n.memberSince} ${DateFormat.yMMMMd().format(user.createdAt!)}'), if (user.seenAt != null) ...[ const SizedBox(height: 5), Text(context.l10n.lastSeenActive(timeago.format(user.seenAt!))), @@ -145,8 +118,7 @@ class UserProfileWidget extends ConsumerWidget { const SizedBox(height: 5), Text( context.l10n.tpTimeSpentPlaying( - user.playTime!.total - .toDaysHoursMinutes(AppLocalizations.of(context)), + user.playTime!.total.toDaysHoursMinutes(AppLocalizations.of(context)), ), ), ], diff --git a/lib/src/view/user/user_screen.dart b/lib/src/view/user/user_screen.dart index 7ae829104d..416ed1a3d9 100644 --- a/lib/src/view/user/user_screen.dart +++ b/lib/src/view/user/user_screen.dart @@ -53,9 +53,7 @@ class _UserScreenState extends ConsumerState { user: updatedLightUser ?? widget.user, shouldShowOnline: updatedLightUser != null, ), - actions: [ - if (isLoading) const PlatformAppBarLoadingIndicator(), - ], + actions: [if (isLoading) const PlatformAppBarLoadingIndicator()], ), body: asyncUser.when( data: (data) => _UserProfileListView(data.$1, isLoading, setIsLoading), @@ -91,22 +89,15 @@ class _UserProfileListView extends ConsumerWidget { final session = ref.watch(authSessionProvider); if (user.disabled == true) { - return Center( - child: Text( - context.l10n.settingsThisAccountIsClosed, - style: Styles.bold, - ), - ); + return Center(child: Text(context.l10n.settingsThisAccountIsClosed, style: Styles.bold)); } - Future userAction( - Future Function(LichessClient client) action, - ) async { + Future userAction(Future Function(LichessClient client) action) async { setIsLoading(true); try { - await ref.withClient(action).then( - (_) => ref.invalidate(userAndStatusProvider(id: user.id)), - ); + await ref + .withClient(action) + .then((_) => ref.invalidate(userAndStatusProvider(id: user.id))); } finally { setIsLoading(false); } @@ -127,8 +118,7 @@ class _UserProfileListView extends ConsumerWidget { onTap: () { pushPlatformRoute( context, - builder: (context) => - CreateChallengeScreen(user.lightUser), + builder: (context) => CreateChallengeScreen(user.lightUser), ); }, ), @@ -136,56 +126,46 @@ class _UserProfileListView extends ConsumerWidget { PlatformListTile( leading: const Icon(Icons.person_add), title: Text(context.l10n.follow), - onTap: isLoading - ? null - : () => userAction( - (client) => - RelationRepository(client).follow(user.id), - ), + onTap: + isLoading + ? null + : () => + userAction((client) => RelationRepository(client).follow(user.id)), ) else if (user.following == true) PlatformListTile( leading: const Icon(Icons.person_remove), title: Text(context.l10n.unfollow), - onTap: isLoading - ? null - : () => userAction( - (client) => - RelationRepository(client).unfollow(user.id), - ), + onTap: + isLoading + ? null + : () => + userAction((client) => RelationRepository(client).unfollow(user.id)), ), if (user.following != true && user.blocking != true) PlatformListTile( leading: const Icon(Icons.block), title: Text(context.l10n.block), - onTap: isLoading - ? null - : () => userAction( - (client) => - RelationRepository(client).block(user.id), - ), + onTap: + isLoading + ? null + : () => userAction((client) => RelationRepository(client).block(user.id)), ) else if (user.blocking == true) PlatformListTile( leading: const Icon(Icons.block), title: Text(context.l10n.unblock), - onTap: isLoading - ? null - : () => userAction( - (client) => - RelationRepository(client).unblock(user.id), - ), + onTap: + isLoading + ? null + : () => + userAction((client) => RelationRepository(client).unblock(user.id)), ), PlatformListTile( leading: const Icon(Icons.report_problem), title: Text(context.l10n.reportXToModerators(user.username)), onTap: () { - launchUrl( - lichessUri('/report', { - 'username': user.id, - 'login': session.user.id, - }), - ); + launchUrl(lichessUri('/report', {'username': user.id, 'login': session.user.id})); }, ), ], diff --git a/lib/src/view/watch/live_tv_channels_screen.dart b/lib/src/view/watch/live_tv_channels_screen.dart index 2b3eec375a..fdb96f412f 100644 --- a/lib/src/view/watch/live_tv_channels_screen.dart +++ b/lib/src/view/watch/live_tv_channels_screen.dart @@ -26,9 +26,7 @@ class LiveTvChannelsScreen extends ConsumerWidget { } }, child: const PlatformScaffold( - appBar: PlatformAppBar( - title: Text('Lichess TV'), - ), + appBar: PlatformAppBar(title: Text('Lichess TV')), body: _Body(), ), ); @@ -56,10 +54,9 @@ class _Body extends ConsumerWidget { pushPlatformRoute( context, rootNavigator: true, - builder: (_) => TvScreen( - channel: game.channel, - initialGame: (game.id, game.orientation), - ), + builder: + (_) => + TvScreen(channel: game.channel, initialGame: (game.id, game.orientation)), ); }, orientation: game.orientation, @@ -70,15 +67,8 @@ class _Body extends ConsumerWidget { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Text( - game.channel.label, - style: Styles.boardPreviewTitle, - ), - Icon( - game.channel.icon, - color: context.lichessColors.brag, - size: 30, - ), + Text(game.channel.label, style: Styles.boardPreviewTitle), + Icon(game.channel.icon, color: context.lichessColors.brag, size: 30), UserFullNameWidget.player( user: game.player.asPlayer.user, aiLevel: game.player.asPlayer.aiLevel, @@ -90,12 +80,8 @@ class _Body extends ConsumerWidget { }, ); }, - loading: () => const Center( - child: CircularProgressIndicator(), - ), - error: (error, stackTrace) => Center( - child: Text(error.toString()), - ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stackTrace) => Center(child: Text(error.toString())), ); } } diff --git a/lib/src/view/watch/streamer_screen.dart b/lib/src/view/watch/streamer_screen.dart index ff83ce9183..4ab5b4ebbf 100644 --- a/lib/src/view/watch/streamer_screen.dart +++ b/lib/src/view/watch/streamer_screen.dart @@ -21,21 +21,13 @@ class StreamerScreen extends StatelessWidget { Widget _buildAndroid(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(context.l10n.mobileLiveStreamers), - ), + appBar: AppBar(title: Text(context.l10n.mobileLiveStreamers)), body: ListView( children: [ ListSection( showDividerBetweenTiles: true, children: streamers - .map( - (e) => StreamerListTile( - streamer: e, - showSubtitle: true, - maxSubtitleLines: 4, - ), - ) + .map((e) => StreamerListTile(streamer: e, showSubtitle: true, maxSubtitleLines: 4)) .toList(growable: false), ), ], @@ -45,9 +37,7 @@ class StreamerScreen extends StatelessWidget { Widget _buildIos(BuildContext context) { return CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: Text(context.l10n.mobileLiveStreamers), - ), + navigationBar: CupertinoNavigationBar(middle: Text(context.l10n.mobileLiveStreamers)), child: CustomScrollView( slivers: [ SliverSafeArea( @@ -55,15 +45,16 @@ class StreamerScreen extends StatelessWidget { delegate: SliverChildListDelegate([ ListSection( hasLeading: true, - children: streamers - .map( - (e) => StreamerListTile( - streamer: e, - showSubtitle: true, - maxSubtitleLines: 4, - ), - ) - .toList(), + children: + streamers + .map( + (e) => StreamerListTile( + streamer: e, + showSubtitle: true, + maxSubtitleLines: 4, + ), + ) + .toList(), ), ]), ), @@ -89,19 +80,16 @@ class StreamerListTile extends StatelessWidget { Widget build(BuildContext context) { return PlatformListTile( onTap: () async { - final url = - streamer.platform == 'twitch' ? streamer.twitch : streamer.youTube; - if (!await launchUrl( - Uri.parse(url!), - mode: LaunchMode.externalApplication, - )) { + final url = streamer.platform == 'twitch' ? streamer.twitch : streamer.youTube; + if (!await launchUrl(Uri.parse(url!), mode: LaunchMode.externalApplication)) { debugPrint('ERROR: [StreamerWidget] Could not launch $url'); } }, leading: Padding( - padding: Theme.of(context).platform == TargetPlatform.android - ? const EdgeInsets.all(5.0) - : EdgeInsets.zero, + padding: + Theme.of(context).platform == TargetPlatform.android + ? const EdgeInsets.all(5.0) + : EdgeInsets.zero, child: Image.network(streamer.image), ), title: Padding( @@ -111,22 +99,15 @@ class StreamerListTile extends StatelessWidget { if (streamer.title != null) ...[ Text( streamer.title!, - style: TextStyle( - color: context.lichessColors.brag, - fontWeight: FontWeight.bold, - ), + style: TextStyle(color: context.lichessColors.brag, fontWeight: FontWeight.bold), ), const SizedBox(width: 5), ], - Flexible( - child: Text(streamer.username, overflow: TextOverflow.ellipsis), - ), + Flexible(child: Text(streamer.username, overflow: TextOverflow.ellipsis)), ], ), ), - subtitle: showSubtitle - ? Text(streamer.status, maxLines: maxSubtitleLines) - : null, + subtitle: showSubtitle ? Text(streamer.status, maxLines: maxSubtitleLines) : null, isThreeLine: showSubtitle && maxSubtitleLines >= 2, trailing: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, diff --git a/lib/src/view/watch/tv_screen.dart b/lib/src/view/watch/tv_screen.dart index 98df340be2..64e2a639c0 100644 --- a/lib/src/view/watch/tv_screen.dart +++ b/lib/src/view/watch/tv_screen.dart @@ -27,8 +27,7 @@ class TvScreen extends ConsumerStatefulWidget { } class _TvScreenState extends ConsumerState { - TvControllerProvider get _tvGameCtrl => - tvControllerProvider(widget.channel, widget.initialGame); + TvControllerProvider get _tvGameCtrl => tvControllerProvider(widget.channel, widget.initialGame); final _whiteClockKey = GlobalKey(debugLabel: 'whiteClockOnTvScreen'); final _blackClockKey = GlobalKey(debugLabel: 'blackClockOnTvScreen'); @@ -47,9 +46,7 @@ class _TvScreenState extends ConsumerState { child: PlatformScaffold( appBar: PlatformAppBar( title: Text('${widget.channel.label} TV'), - actions: const [ - ToggleSoundButton(), - ], + actions: const [ToggleSoundButton()], ), body: _Body( widget.channel, @@ -86,47 +83,46 @@ class _Body extends ConsumerWidget { child: asyncGame.when( data: (gameState) { final game = gameState.game; - final position = - gameState.game.positionAt(gameState.stepCursor); + final position = gameState.game.positionAt(gameState.stepCursor); final blackPlayerWidget = GamePlayer( player: game.black.setOnGame(true), - clock: gameState.game.clock != null - ? CountdownClockBuilder( - key: blackClockKey, - timeLeft: gameState.game.clock!.black, - delay: gameState.game.clock!.lag ?? - const Duration(milliseconds: 10), - clockUpdatedAt: gameState.game.clock!.at, - active: gameState.activeClockSide == Side.black, - builder: (context, timeLeft) { - return Clock( - timeLeft: timeLeft, - active: gameState.activeClockSide == Side.black, - ); - }, - ) - : null, + clock: + gameState.game.clock != null + ? CountdownClockBuilder( + key: blackClockKey, + timeLeft: gameState.game.clock!.black, + delay: gameState.game.clock!.lag ?? const Duration(milliseconds: 10), + clockUpdatedAt: gameState.game.clock!.at, + active: gameState.activeClockSide == Side.black, + builder: (context, timeLeft) { + return Clock( + timeLeft: timeLeft, + active: gameState.activeClockSide == Side.black, + ); + }, + ) + : null, materialDiff: game.lastMaterialDiffAt(Side.black), ); final whitePlayerWidget = GamePlayer( player: game.white.setOnGame(true), - clock: gameState.game.clock != null - ? CountdownClockBuilder( - key: whiteClockKey, - timeLeft: gameState.game.clock!.white, - clockUpdatedAt: gameState.game.clock!.at, - delay: gameState.game.clock!.lag ?? - const Duration(milliseconds: 10), - active: gameState.activeClockSide == Side.white, - builder: (context, timeLeft) { - return Clock( - timeLeft: timeLeft, - active: gameState.activeClockSide == Side.white, - ); - }, - ) - : null, + clock: + gameState.game.clock != null + ? CountdownClockBuilder( + key: whiteClockKey, + timeLeft: gameState.game.clock!.white, + clockUpdatedAt: gameState.game.clock!.at, + delay: gameState.game.clock!.lag ?? const Duration(milliseconds: 10), + active: gameState.activeClockSide == Side.white, + builder: (context, timeLeft) { + return Clock( + timeLeft: timeLeft, + active: gameState.activeClockSide == Side.white, + ); + }, + ) + : null, materialDiff: game.lastMaterialDiffAt(Side.white), ); @@ -136,31 +132,25 @@ class _Body extends ConsumerWidget { boardSettingsOverrides: const BoardSettingsOverrides( animationDuration: Duration.zero, ), - topTable: gameState.orientation == Side.white - ? blackPlayerWidget - : whitePlayerWidget, - bottomTable: gameState.orientation == Side.white - ? whitePlayerWidget - : blackPlayerWidget, - moves: game.steps - .skip(1) - .map((e) => e.sanMove!.san) - .toList(growable: false), + topTable: + gameState.orientation == Side.white ? blackPlayerWidget : whitePlayerWidget, + bottomTable: + gameState.orientation == Side.white ? whitePlayerWidget : blackPlayerWidget, + moves: game.steps.skip(1).map((e) => e.sanMove!.san).toList(growable: false), currentMoveIndex: gameState.stepCursor, lastMove: game.moveAt(gameState.stepCursor), ); }, - loading: () => const BoardTable( - topTable: kEmptyWidget, - bottomTable: kEmptyWidget, - orientation: Side.white, - fen: kEmptyFEN, - showMoveListPlaceholder: true, - ), + loading: + () => const BoardTable( + topTable: kEmptyWidget, + bottomTable: kEmptyWidget, + orientation: Side.white, + fen: kEmptyFEN, + showMoveListPlaceholder: true, + ), error: (err, stackTrace) { - debugPrint( - 'SEVERE: [TvScreen] could not load stream; $err\n$stackTrace', - ); + debugPrint('SEVERE: [TvScreen] could not load stream; $err\n$stackTrace'); return const BoardTable( topTable: kEmptyWidget, bottomTable: kEmptyWidget, @@ -173,20 +163,14 @@ class _Body extends ConsumerWidget { ), ), ), - _BottomBar( - tvChannel: channel, - game: initialGame, - ), + _BottomBar(tvChannel: channel, game: initialGame), ], ); } } class _BottomBar extends ConsumerWidget { - const _BottomBar({ - required this.tvChannel, - required this.game, - }); + const _BottomBar({required this.tvChannel, required this.game}); final TvChannel tvChannel; final (GameId id, Side orientation)? game; @@ -200,42 +184,34 @@ class _BottomBar extends ConsumerWidget { icon: CupertinoIcons.arrow_2_squarepath, ), RepeatButton( - onLongPress: ref - .read(tvControllerProvider(tvChannel, game).notifier) - .canGoBack() - ? () => _moveBackward(ref) - : null, + onLongPress: + ref.read(tvControllerProvider(tvChannel, game).notifier).canGoBack() + ? () => _moveBackward(ref) + : null, child: BottomBarButton( key: const ValueKey('goto-previous'), - onTap: ref - .read( - tvControllerProvider(tvChannel, game).notifier, - ) - .canGoBack() - ? () => _moveBackward(ref) - : null, + onTap: + ref.read(tvControllerProvider(tvChannel, game).notifier).canGoBack() + ? () => _moveBackward(ref) + : null, label: 'Previous', icon: CupertinoIcons.chevron_back, showTooltip: false, ), ), RepeatButton( - onLongPress: ref - .read(tvControllerProvider(tvChannel, game).notifier) - .canGoForward() - ? () => _moveForward(ref) - : null, + onLongPress: + ref.read(tvControllerProvider(tvChannel, game).notifier).canGoForward() + ? () => _moveForward(ref) + : null, child: BottomBarButton( key: const ValueKey('goto-next'), icon: CupertinoIcons.chevron_forward, label: context.l10n.next, - onTap: ref - .read( - tvControllerProvider(tvChannel, game).notifier, - ) - .canGoForward() - ? () => _moveForward(ref) - : null, + onTap: + ref.read(tvControllerProvider(tvChannel, game).notifier).canGoForward() + ? () => _moveForward(ref) + : null, showTooltip: false, ), ), diff --git a/lib/src/view/watch/watch_tab_screen.dart b/lib/src/view/watch/watch_tab_screen.dart index 4ce43c81f3..45daf9673b 100644 --- a/lib/src/view/watch/watch_tab_screen.dart +++ b/lib/src/view/watch/watch_tab_screen.dart @@ -38,30 +38,26 @@ const _featuredChannelsSet = ISetConst({ TvChannel.rapid, }); -final featuredChannelsProvider = - FutureProvider.autoDispose>((ref) async { - return ref.withClientCacheFor( - (client) async { - final channels = await TvRepository(client).channels(); - return channels.entries - .where((channel) => _featuredChannelsSet.contains(channel.key)) - .map( - (entry) => TvGameSnapshot( - channel: entry.key, - id: entry.value.id, - orientation: entry.value.side ?? Side.white, - player: FeaturedPlayer( - name: entry.value.user.name, - title: entry.value.user.title, - side: entry.value.side ?? Side.white, - rating: entry.value.rating, - ), +final featuredChannelsProvider = FutureProvider.autoDispose>((ref) async { + return ref.withClientCacheFor((client) async { + final channels = await TvRepository(client).channels(); + return channels.entries + .where((channel) => _featuredChannelsSet.contains(channel.key)) + .map( + (entry) => TvGameSnapshot( + channel: entry.key, + id: entry.value.id, + orientation: entry.value.side ?? Side.white, + player: FeaturedPlayer( + name: entry.value.user.name, + title: entry.value.user.title, + side: entry.value.side ?? Side.white, + rating: entry.value.rating, ), - ) - .toIList(); - }, - const Duration(minutes: 5), - ); + ), + ) + .toIList(); + }, const Duration(minutes: 5)); }); class WatchTabScreen extends ConsumerStatefulWidget { @@ -82,11 +78,7 @@ class _WatchScreenState extends ConsumerState { } }); - return ConsumerPlatformWidget( - ref: ref, - androidBuilder: _buildAndroid, - iosBuilder: _buildIos, - ); + return ConsumerPlatformWidget(ref: ref, androidBuilder: _buildAndroid, iosBuilder: _buildIos); } Widget _buildAndroid(BuildContext context, WidgetRef ref) { @@ -98,9 +90,7 @@ class _WatchScreenState extends ConsumerState { } }, child: Scaffold( - appBar: AppBar( - title: Text(context.l10n.watch), - ), + appBar: AppBar(title: Text(context.l10n.watch)), body: OrientationBuilder( builder: (context, orientation) { return RefreshIndicator( @@ -122,18 +112,10 @@ class _WatchScreenState extends ConsumerState { controller: watchScrollController, slivers: [ const CupertinoSliverNavigationBar( - padding: EdgeInsetsDirectional.only( - start: 16.0, - end: 8.0, - ), - ), - CupertinoSliverRefreshControl( - onRefresh: refreshData, - ), - SliverSafeArea( - top: false, - sliver: _Body(orientation), + padding: EdgeInsetsDirectional.only(start: 16.0, end: 8.0), ), + CupertinoSliverRefreshControl(onRefresh: refreshData), + SliverSafeArea(top: false, sliver: _Body(orientation)), ], ); }, @@ -193,31 +175,27 @@ class _BodyState extends ConsumerState<_Body> { final featuredChannels = ref.watch(featuredChannelsProvider); final streamers = ref.watch(liveStreamersProvider); - final content = widget.orientation == Orientation.portrait - ? [ - _BroadcastWidget(broadcastList), - _WatchTvWidget(featuredChannels), - _StreamerWidget(streamers), - ] - : [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded(child: _BroadcastWidget(broadcastList)), - Expanded(child: _WatchTvWidget(featuredChannels)), - ], - ), - _StreamerWidget(streamers), - ]; + final content = + widget.orientation == Orientation.portrait + ? [ + _BroadcastWidget(broadcastList), + _WatchTvWidget(featuredChannels), + _StreamerWidget(streamers), + ] + : [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _BroadcastWidget(broadcastList)), + Expanded(child: _WatchTvWidget(featuredChannels)), + ], + ), + _StreamerWidget(streamers), + ]; return Theme.of(context).platform == TargetPlatform.iOS - ? SliverList( - delegate: SliverChildListDelegate(content), - ) - : ListView( - controller: watchScrollController, - children: content, - ); + ? SliverList(delegate: SliverChildListDelegate(content)) + : ListView(controller: watchScrollController, children: content); } } @@ -244,48 +222,38 @@ class _BroadcastWidget extends ConsumerWidget { header: Text(context.l10n.broadcastBroadcasts), headerTrailing: NoPaddingTextButton( onPressed: () { - pushPlatformRoute( - context, - builder: (context) => const BroadcastListScreen(), - ); + pushPlatformRoute(context, builder: (context) => const BroadcastListScreen()); }, - child: Text( - context.l10n.more, - ), + child: Text(context.l10n.more), ), children: [ - ...CombinedIterableView([data.active, data.past]) - .take(numberOfItems) - .map((broadcast) => _BroadcastTile(broadcast: broadcast)), + ...CombinedIterableView([ + data.active, + data.past, + ]).take(numberOfItems).map((broadcast) => _BroadcastTile(broadcast: broadcast)), ], ); }, error: (error, stackTrace) { - debugPrint( - 'SEVERE: [BroadcastWidget] could not load broadcast data; $error\n $stackTrace', - ); + debugPrint('SEVERE: [BroadcastWidget] could not load broadcast data; $error\n $stackTrace'); return const Padding( padding: Styles.bodySectionPadding, child: Text('Could not load broadcasts'), ); }, - loading: () => Shimmer( - child: ShimmerLoading( - isLoading: true, - child: ListSection.loading( - itemsNumber: numberOfItems, - header: true, + loading: + () => Shimmer( + child: ShimmerLoading( + isLoading: true, + child: ListSection.loading(itemsNumber: numberOfItems, header: true), + ), ), - ), - ), ); } } class _BroadcastTile extends ConsumerWidget { - const _BroadcastTile({ - required this.broadcast, - }); + const _BroadcastTile({required this.broadcast}); final Broadcast broadcast; @@ -308,10 +276,7 @@ class _BroadcastTile extends ConsumerWidget { const SizedBox(width: 5.0), Text( 'LIVE', - style: TextStyle( - color: context.lichessColors.error, - fontWeight: FontWeight.bold, - ), + style: TextStyle(color: context.lichessColors.error, fontWeight: FontWeight.bold), ), ] else if (broadcast.round.startsAt != null) ...[ const SizedBox(width: 5.0), @@ -321,11 +286,7 @@ class _BroadcastTile extends ConsumerWidget { ), title: Padding( padding: const EdgeInsets.only(right: 5.0), - child: Text( - broadcast.title, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + child: Text(broadcast.title, maxLines: 1, overflow: TextOverflow.ellipsis), ), ); } @@ -347,50 +308,48 @@ class _WatchTvWidget extends ConsumerWidget { header: const Text('Lichess TV'), hasLeading: true, headerTrailing: NoPaddingTextButton( - onPressed: () => pushPlatformRoute( - context, - builder: (context) => const LiveTvChannelsScreen(), - ).then((_) => _refreshData(ref)), - child: Text( - context.l10n.more, - ), + onPressed: + () => pushPlatformRoute( + context, + builder: (context) => const LiveTvChannelsScreen(), + ).then((_) => _refreshData(ref)), + child: Text(context.l10n.more), ), - children: data.map((snapshot) { - return PlatformListTile( - leading: Icon(snapshot.channel.icon), - title: Text(snapshot.channel.label), - subtitle: UserFullNameWidget.player( - user: snapshot.player.asPlayer.user, - aiLevel: snapshot.player.asPlayer.aiLevel, - rating: snapshot.player.rating, - ), - onTap: () => pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => TvScreen(channel: snapshot.channel), - ).then((_) => _refreshData(ref)), - ); - }).toList(growable: false), + children: data + .map((snapshot) { + return PlatformListTile( + leading: Icon(snapshot.channel.icon), + title: Text(snapshot.channel.label), + subtitle: UserFullNameWidget.player( + user: snapshot.player.asPlayer.user, + aiLevel: snapshot.player.asPlayer.aiLevel, + rating: snapshot.player.rating, + ), + onTap: + () => pushPlatformRoute( + context, + rootNavigator: true, + builder: (context) => TvScreen(channel: snapshot.channel), + ).then((_) => _refreshData(ref)), + ); + }) + .toList(growable: false), ); }, error: (error, stackTrace) { - debugPrint( - 'SEVERE: [StreamerWidget] could not load channels data; $error\n $stackTrace', - ); + debugPrint('SEVERE: [StreamerWidget] could not load channels data; $error\n $stackTrace'); return const Padding( padding: Styles.bodySectionPadding, child: Text('Could not load TV channels'), ); }, - loading: () => Shimmer( - child: ShimmerLoading( - isLoading: true, - child: ListSection.loading( - itemsNumber: 4, - header: true, + loading: + () => Shimmer( + child: ShimmerLoading( + isLoading: true, + child: ListSection.loading(itemsNumber: 4, header: true), + ), ), - ), - ), ); } } @@ -413,13 +372,12 @@ class _StreamerWidget extends ConsumerWidget { header: Text(context.l10n.streamerLichessStreamers), hasLeading: true, headerTrailing: NoPaddingTextButton( - onPressed: () => pushPlatformRoute( - context, - builder: (context) => StreamerScreen(streamers: data), - ), - child: Text( - context.l10n.more, - ), + onPressed: + () => pushPlatformRoute( + context, + builder: (context) => StreamerScreen(streamers: data), + ), + child: Text(context.l10n.more), ), children: [ ...data @@ -429,23 +387,19 @@ class _StreamerWidget extends ConsumerWidget { ); }, error: (error, stackTrace) { - debugPrint( - 'SEVERE: [StreamerWidget] could not load streamer data; $error\n $stackTrace', - ); + debugPrint('SEVERE: [StreamerWidget] could not load streamer data; $error\n $stackTrace'); return const Padding( padding: Styles.bodySectionPadding, child: Text('Could not load live streamers'), ); }, - loading: () => Shimmer( - child: ShimmerLoading( - isLoading: true, - child: ListSection.loading( - itemsNumber: numberOfItems, - header: true, + loading: + () => Shimmer( + child: ShimmerLoading( + isLoading: true, + child: ListSection.loading(itemsNumber: numberOfItems, header: true), + ), ), - ), - ), ); } } diff --git a/lib/src/widgets/adaptive_action_sheet.dart b/lib/src/widgets/adaptive_action_sheet.dart index 50ad66219b..c7187a47d5 100644 --- a/lib/src/widgets/adaptive_action_sheet.dart +++ b/lib/src/widgets/adaptive_action_sheet.dart @@ -64,18 +64,14 @@ Future showConfirmDialog( title: title, actions: [ TextButton( - style: TextButton.styleFrom( - textStyle: Theme.of(context).textTheme.labelLarge, - ), + style: TextButton.styleFrom(textStyle: Theme.of(context).textTheme.labelLarge), child: Text(context.l10n.cancel), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( - style: TextButton.styleFrom( - textStyle: Theme.of(context).textTheme.labelLarge, - ), + style: TextButton.styleFrom(textStyle: Theme.of(context).textTheme.labelLarge), child: Text(context.l10n.mobileOkButton), onPressed: () { Navigator.of(context).pop(); @@ -101,28 +97,29 @@ Future showCupertinoActionSheet({ builder: (BuildContext context) { return CupertinoActionSheet( title: title, - actions: actions - .map( - // Builder is used to retrieve the context immediately surrounding the button - // This is necessary to get the correct context for the iPad share dialog - // which needs the position of the action to display the share dialog - (action) => Builder( - builder: (context) { - return CupertinoActionSheetAction( - onPressed: () { - if (action.dismissOnPress) { - Navigator.of(context).pop(); - } - action.onPressed(context); + actions: + actions + .map( + // Builder is used to retrieve the context immediately surrounding the button + // This is necessary to get the correct context for the iPad share dialog + // which needs the position of the action to display the share dialog + (action) => Builder( + builder: (context) { + return CupertinoActionSheetAction( + onPressed: () { + if (action.dismissOnPress) { + Navigator.of(context).pop(); + } + action.onPressed(context); + }, + isDestructiveAction: action.isDestructiveAction, + isDefaultAction: action.isDefaultAction, + child: action.makeLabel(context), + ); }, - isDestructiveAction: action.isDestructiveAction, - isDefaultAction: action.isDefaultAction, - child: action.makeLabel(context), - ); - }, - ), - ) - .toList(), + ), + ) + .toList(), cancelButton: CupertinoActionSheetAction( isDefaultAction: true, onPressed: () { @@ -141,8 +138,7 @@ Future showMaterialActionSheet({ required List actions, bool isDismissible = true, }) { - final actionTextStyle = - Theme.of(context).textTheme.titleMedium ?? const TextStyle(fontSize: 18); + final actionTextStyle = Theme.of(context).textTheme.titleMedium ?? const TextStyle(fontSize: 18); final screenWidth = MediaQuery.of(context).size.width; return showDialog( @@ -158,20 +154,13 @@ Future showMaterialActionSheet({ mainAxisSize: MainAxisSize.min, children: [ if (title != null) ...[ - Padding( - padding: const EdgeInsets.all(16.0), - child: Center(child: title), - ), + Padding(padding: const EdgeInsets.all(16.0), child: Center(child: title)), ], ...actions.mapIndexed((index, action) { return InkWell( borderRadius: BorderRadius.vertical( - top: Radius.circular( - index == 0 ? 28 : 0, - ), - bottom: Radius.circular( - index == actions.length - 1 ? 28 : 0, - ), + top: Radius.circular(index == 0 ? 28 : 0), + bottom: Radius.circular(index == actions.length - 1 ? 28 : 0), ), onTap: () { if (action.dismissOnPress) { @@ -190,9 +179,8 @@ Future showMaterialActionSheet({ Expanded( child: DefaultTextStyle( style: actionTextStyle, - textAlign: action.leading != null - ? TextAlign.start - : TextAlign.center, + textAlign: + action.leading != null ? TextAlign.start : TextAlign.center, child: action.makeLabel(context), ), ), diff --git a/lib/src/widgets/adaptive_autocomplete.dart b/lib/src/widgets/adaptive_autocomplete.dart index a561aad408..20fc79a014 100644 --- a/lib/src/widgets/adaptive_autocomplete.dart +++ b/lib/src/widgets/adaptive_autocomplete.dart @@ -25,77 +25,74 @@ class AdaptiveAutoComplete extends StatelessWidget { Widget build(BuildContext context) { return Theme.of(context).platform == TargetPlatform.iOS ? RawAutocomplete( - initialValue: initialValue, - optionsBuilder: optionsBuilder, - fieldViewBuilder: ( - BuildContext context, - TextEditingController textEditingController, - FocusNode focusNode, - VoidCallback onFieldSubmitted, - ) { - return CupertinoTextField( - controller: textEditingController, - decoration: cupertinoDecoration, - textInputAction: textInputAction, - focusNode: focusNode, - onSubmitted: (String value) { - onFieldSubmitted(); - }, - ); - }, - optionsViewBuilder: ( - BuildContext context, - AutocompleteOnSelected onSelected, - Iterable options, - ) { - return Align( - alignment: Alignment.topLeft, - child: ColoredBox( - color: CupertinoColors.secondarySystemGroupedBackground - .resolveFrom(context), - child: SizedBox( - height: 200.0, - child: ListView.builder( - padding: EdgeInsets.zero, - itemCount: options.length, - itemBuilder: (BuildContext context, int index) { - final T option = options.elementAt(index); - return AdaptiveInkWell( - onTap: () { - onSelected(option); - }, - child: ListTile( - title: Text(displayStringForOption(option)), - ), - ); - }, - ), + initialValue: initialValue, + optionsBuilder: optionsBuilder, + fieldViewBuilder: ( + BuildContext context, + TextEditingController textEditingController, + FocusNode focusNode, + VoidCallback onFieldSubmitted, + ) { + return CupertinoTextField( + controller: textEditingController, + decoration: cupertinoDecoration, + textInputAction: textInputAction, + focusNode: focusNode, + onSubmitted: (String value) { + onFieldSubmitted(); + }, + ); + }, + optionsViewBuilder: ( + BuildContext context, + AutocompleteOnSelected onSelected, + Iterable options, + ) { + return Align( + alignment: Alignment.topLeft, + child: ColoredBox( + color: CupertinoColors.secondarySystemGroupedBackground.resolveFrom(context), + child: SizedBox( + height: 200.0, + child: ListView.builder( + padding: EdgeInsets.zero, + itemCount: options.length, + itemBuilder: (BuildContext context, int index) { + final T option = options.elementAt(index); + return AdaptiveInkWell( + onTap: () { + onSelected(option); + }, + child: ListTile(title: Text(displayStringForOption(option))), + ); + }, ), ), - ); - }, - onSelected: onSelected, - ) + ), + ); + }, + onSelected: onSelected, + ) : Autocomplete( - initialValue: initialValue, - optionsBuilder: optionsBuilder, - onSelected: onSelected, - displayStringForOption: displayStringForOption, - fieldViewBuilder: ( - BuildContext context, - TextEditingController textEditingController, - FocusNode focusNode, - VoidCallback onFieldSubmitted, - ) { - return TextField( - controller: textEditingController, - textInputAction: textInputAction, - focusNode: focusNode, - onSubmitted: (String value) { - onFieldSubmitted(); - }, - ); - }, - ); + initialValue: initialValue, + optionsBuilder: optionsBuilder, + onSelected: onSelected, + displayStringForOption: displayStringForOption, + fieldViewBuilder: ( + BuildContext context, + TextEditingController textEditingController, + FocusNode focusNode, + VoidCallback onFieldSubmitted, + ) { + return TextField( + controller: textEditingController, + textInputAction: textInputAction, + focusNode: focusNode, + onSubmitted: (String value) { + onFieldSubmitted(); + }, + ); + }, + ); } } diff --git a/lib/src/widgets/adaptive_bottom_sheet.dart b/lib/src/widgets/adaptive_bottom_sheet.dart index 6404c44dd3..16e964563a 100644 --- a/lib/src/widgets/adaptive_bottom_sheet.dart +++ b/lib/src/widgets/adaptive_bottom_sheet.dart @@ -21,20 +21,20 @@ Future showAdaptiveBottomSheet({ isScrollControlled: isScrollControlled, useRootNavigator: useRootNavigator, useSafeArea: useSafeArea, - shape: Theme.of(context).platform == TargetPlatform.iOS - ? const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(10.0), - ), - ) - : null, + shape: + Theme.of(context).platform == TargetPlatform.iOS + ? const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(10.0)), + ) + : null, constraints: constraints, - backgroundColor: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoDynamicColor.resolve( - CupertinoColors.tertiarySystemGroupedBackground, - context, - ) - : null, + backgroundColor: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoDynamicColor.resolve( + CupertinoColors.tertiarySystemGroupedBackground, + context, + ) + : null, elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0 : null, builder: builder, ); @@ -63,9 +63,7 @@ class BottomSheetScrollableContainer extends StatelessWidget { child: SingleChildScrollView( controller: scrollController, padding: padding, - child: ListBody( - children: children, - ), + child: ListBody(children: children), ), ); } @@ -87,8 +85,9 @@ class BottomSheetContextMenuAction extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformListTile( - cupertinoBackgroundColor: - CupertinoColors.tertiarySystemGroupedBackground.resolveFrom(context), + cupertinoBackgroundColor: CupertinoColors.tertiarySystemGroupedBackground.resolveFrom( + context, + ), leading: Icon(icon), title: child, onTap: () { diff --git a/lib/src/widgets/adaptive_choice_picker.dart b/lib/src/widgets/adaptive_choice_picker.dart index 933ceaeff8..89a65fa1ad 100644 --- a/lib/src/widgets/adaptive_choice_picker.dart +++ b/lib/src/widgets/adaptive_choice_picker.dart @@ -25,32 +25,28 @@ Future showChoicePicker( scrollable: true, content: Builder( builder: (context) { - final List choiceWidgets = choices.map((value) { - return RadioListTile( - title: labelBuilder(value), - value: value, - groupValue: selectedItem, - onChanged: (value) { - if (value != null && onSelectedItemChanged != null) { - onSelectedItemChanged(value); - Navigator.of(context).pop(); - } - }, - ); - }).toList(growable: false); + final List choiceWidgets = choices + .map((value) { + return RadioListTile( + title: labelBuilder(value), + value: value, + groupValue: selectedItem, + onChanged: (value) { + if (value != null && onSelectedItemChanged != null) { + onSelectedItemChanged(value); + Navigator.of(context).pop(); + } + }, + ); + }) + .toList(growable: false); return choiceWidgets.length >= 10 ? SizedBox( - width: double.maxFinite, - height: deviceHeight * 0.6, - child: ListView( - shrinkWrap: true, - children: choiceWidgets, - ), - ) - : Column( - mainAxisSize: MainAxisSize.min, - children: choiceWidgets, - ); + width: double.maxFinite, + height: deviceHeight * 0.6, + child: ListView(shrinkWrap: true, children: choiceWidgets), + ) + : Column(mainAxisSize: MainAxisSize.min, children: choiceWidgets); }, ), actions: [ @@ -68,17 +64,18 @@ Future showChoicePicker( context: context, builder: (context) { return CupertinoActionSheet( - actions: choices.map((value) { - return CupertinoActionSheetAction( - onPressed: () { - if (onSelectedItemChanged != null) { - onSelectedItemChanged(value); - } - Navigator.of(context).pop(); - }, - child: labelBuilder(value), - ); - }).toList(), + actions: + choices.map((value) { + return CupertinoActionSheetAction( + onPressed: () { + if (onSelectedItemChanged != null) { + onSelectedItemChanged(value); + } + Navigator.of(context).pop(); + }, + child: labelBuilder(value), + ); + }).toList(), cancelButton: CupertinoActionSheetAction( isDefaultAction: true, onPressed: () => Navigator.of(context).pop(), @@ -94,8 +91,7 @@ Future showChoicePicker( return NotificationListener( onNotification: (ScrollEndNotification notification) { if (onSelectedItemChanged != null) { - final index = - (notification.metrics as FixedExtentMetrics).itemIndex; + final index = (notification.metrics as FixedExtentMetrics).itemIndex; onSelectedItemChanged(choices[index]); } return false; @@ -110,11 +106,10 @@ Future showChoicePicker( scrollController: FixedExtentScrollController( initialItem: choices.indexWhere((t) => t == selectedItem), ), - children: choices.map((value) { - return Center( - child: labelBuilder(value), - ); - }).toList(), + children: + choices.map((value) { + return Center(child: labelBuilder(value)); + }).toList(), onSelectedItemChanged: (_) {}, ), ), @@ -144,46 +139,47 @@ Future?> showMultipleChoicesPicker( builder: (BuildContext context, StateSetter setState) { return Column( mainAxisSize: MainAxisSize.min, - children: choices.map((choice) { - return CheckboxListTile.adaptive( - title: labelBuilder(choice), - value: items.contains(choice), - onChanged: (value) { - if (value != null) { - setState(() { - items = value - ? items.union({choice}) - : items.difference({choice}); - }); - } - }, - ); - }).toList(growable: false), + children: choices + .map((choice) { + return CheckboxListTile.adaptive( + title: labelBuilder(choice), + value: items.contains(choice), + onChanged: (value) { + if (value != null) { + setState(() { + items = value ? items.union({choice}) : items.difference({choice}); + }); + } + }, + ); + }) + .toList(growable: false), ); }, ), - actions: Theme.of(context).platform == TargetPlatform.iOS - ? [ - CupertinoDialogAction( - onPressed: () => Navigator.of(context).pop(), - child: Text(context.l10n.cancel), - ), - CupertinoDialogAction( - isDefaultAction: true, - child: Text(context.l10n.mobileOkButton), - onPressed: () => Navigator.of(context).pop(items), - ), - ] - : [ - TextButton( - child: Text(context.l10n.cancel), - onPressed: () => Navigator.of(context).pop(), - ), - TextButton( - child: Text(context.l10n.mobileOkButton), - onPressed: () => Navigator.of(context).pop(items), - ), - ], + actions: + Theme.of(context).platform == TargetPlatform.iOS + ? [ + CupertinoDialogAction( + onPressed: () => Navigator.of(context).pop(), + child: Text(context.l10n.cancel), + ), + CupertinoDialogAction( + isDefaultAction: true, + child: Text(context.l10n.mobileOkButton), + onPressed: () => Navigator.of(context).pop(items), + ), + ] + : [ + TextButton( + child: Text(context.l10n.cancel), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + child: Text(context.l10n.mobileOkButton), + onPressed: () => Navigator.of(context).pop(items), + ), + ], ); }, ); diff --git a/lib/src/widgets/adaptive_date_picker.dart b/lib/src/widgets/adaptive_date_picker.dart index 0ab55691f7..1bbc828592 100644 --- a/lib/src/widgets/adaptive_date_picker.dart +++ b/lib/src/widgets/adaptive_date_picker.dart @@ -18,8 +18,7 @@ void showAdaptiveDatePicker( return SizedBox( height: 250, child: CupertinoDatePicker( - backgroundColor: - CupertinoColors.systemBackground.resolveFrom(context), + backgroundColor: CupertinoColors.systemBackground.resolveFrom(context), initialDateTime: initialDate, minimumDate: firstDate, maximumDate: lastDate, diff --git a/lib/src/widgets/adaptive_text_field.dart b/lib/src/widgets/adaptive_text_field.dart index 3e77a84155..483f814dea 100644 --- a/lib/src/widgets/adaptive_text_field.dart +++ b/lib/src/widgets/adaptive_text_field.dart @@ -40,8 +40,7 @@ class AdaptiveTextField extends StatelessWidget { final void Function(String)? onChanged; final void Function(String)? onSubmitted; final GestureTapCallback? onTap; - final Widget? - suffix; //used only for iOS, suffix should be put in InputDecoration for android + final Widget? suffix; //used only for iOS, suffix should be put in InputDecoration for android final BoxDecoration? cupertinoDecoration; final InputDecoration? materialDecoration; @@ -75,10 +74,7 @@ class AdaptiveTextField extends StatelessWidget { maxLines: maxLines, maxLength: maxLength, expands: expands, - decoration: materialDecoration ?? - InputDecoration( - hintText: placeholder, - ), + decoration: materialDecoration ?? InputDecoration(hintText: placeholder), controller: controller, focusNode: focusNode, textInputAction: textInputAction, diff --git a/lib/src/widgets/board_carousel_item.dart b/lib/src/widgets/board_carousel_item.dart index 16d2896753..a6e12f65c1 100644 --- a/lib/src/widgets/board_carousel_item.dart +++ b/lib/src/widgets/board_carousel_item.dart @@ -9,8 +9,7 @@ import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/widgets/buttons.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; -const _kBoardCarouselItemMargin = - EdgeInsets.symmetric(vertical: 8.0, horizontal: 6.0); +const _kBoardCarouselItemMargin = EdgeInsets.symmetric(vertical: 8.0, horizontal: 6.0); class BoardCarouselItem extends ConsumerWidget { const BoardCarouselItem({ @@ -51,13 +50,13 @@ class BoardCarouselItem extends ConsumerWidget { return LayoutBuilder( builder: (context, constraints) { - final boardSize = constraints.biggest.shortestSide - - _kBoardCarouselItemMargin.horizontal; + final boardSize = constraints.biggest.shortestSide - _kBoardCarouselItemMargin.horizontal; final card = PlatformCard( color: backgroundColor, - margin: Theme.of(context).platform == TargetPlatform.iOS - ? EdgeInsets.zero - : _kBoardCarouselItemMargin, + margin: + Theme.of(context).platform == TargetPlatform.iOS + ? EdgeInsets.zero + : _kBoardCarouselItemMargin, child: AdaptiveInkWell( splashColor: splashColor, borderRadius: BorderRadius.circular(10), @@ -101,9 +100,7 @@ class BoardCarouselItem extends ConsumerWidget { left: 0, bottom: 8, child: DefaultTextStyle.merge( - style: const TextStyle( - color: Colors.white, - ), + style: const TextStyle(color: Colors.white), child: description, ), ), @@ -114,20 +111,17 @@ class BoardCarouselItem extends ConsumerWidget { return Theme.of(context).platform == TargetPlatform.iOS ? Padding( - padding: _kBoardCarouselItemMargin, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.0), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.05), - blurRadius: 6.0, - ), - ], - ), - child: card, + padding: _kBoardCarouselItemMargin, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.0), + boxShadow: [ + BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 6.0), + ], ), - ) + child: card, + ), + ) : card; }, ); @@ -178,10 +172,10 @@ class BoardsPageView extends StatefulWidget { this.scrollBehavior, this.padding = EdgeInsets.zero, }) : childrenDelegate = SliverChildBuilderDelegate( - itemBuilder, - findChildIndexCallback: findChildIndexCallback, - childCount: itemCount, - ); + itemBuilder, + findChildIndexCallback: findChildIndexCallback, + childCount: itemCount, + ); final bool allowImplicitScrolling; final String? restorationId; @@ -241,11 +235,8 @@ class _BoardsPageViewState extends State { case Axis.horizontal: assert(debugCheckHasDirectionality(context)); final TextDirection textDirection = Directionality.of(context); - final AxisDirection axisDirection = - textDirectionToAxisDirection(textDirection); - return widget.reverse - ? flipAxisDirection(axisDirection) - : axisDirection; + final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection); + return widget.reverse ? flipAxisDirection(axisDirection) : axisDirection; case Axis.vertical: return widget.reverse ? AxisDirection.up : AxisDirection.down; } @@ -259,9 +250,8 @@ class _BoardsPageViewState extends State { ).applyTo( widget.pageSnapping ? _kPagePhysics.applyTo( - widget.physics ?? - widget.scrollBehavior?.getScrollPhysics(context), - ) + widget.physics ?? widget.scrollBehavior?.getScrollPhysics(context), + ) : widget.physics ?? widget.scrollBehavior?.getScrollPhysics(context), ); @@ -285,8 +275,8 @@ class _BoardsPageViewState extends State { controller: _controller, physics: physics, restorationId: widget.restorationId, - scrollBehavior: widget.scrollBehavior ?? - ScrollConfiguration.of(context).copyWith(scrollbars: false), + scrollBehavior: + widget.scrollBehavior ?? ScrollConfiguration.of(context).copyWith(scrollbars: false), viewportBuilder: (BuildContext context, ViewportOffset position) { return Viewport( cacheExtent: 0, @@ -309,11 +299,8 @@ class _BoardsPageViewState extends State { } class _SliverFillViewport extends StatelessWidget { - const _SliverFillViewport({ - required this.delegate, - this.viewportFraction = 1.0, - this.padding, - }) : assert(viewportFraction > 0.0); + const _SliverFillViewport({required this.delegate, this.viewportFraction = 1.0, this.padding}) + : assert(viewportFraction > 0.0); final double viewportFraction; @@ -333,8 +320,7 @@ class _SliverFillViewport extends StatelessWidget { } } -class _SliverFillViewportRenderObjectWidget - extends SliverMultiBoxAdaptorWidget { +class _SliverFillViewportRenderObjectWidget extends SliverMultiBoxAdaptorWidget { const _SliverFillViewportRenderObjectWidget({ required super.delegate, this.viewportFraction = 1.0, @@ -344,28 +330,18 @@ class _SliverFillViewportRenderObjectWidget @override RenderSliverFillViewport createRenderObject(BuildContext context) { - final SliverMultiBoxAdaptorElement element = - context as SliverMultiBoxAdaptorElement; - return RenderSliverFillViewport( - childManager: element, - viewportFraction: viewportFraction, - ); + final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; + return RenderSliverFillViewport(childManager: element, viewportFraction: viewportFraction); } @override - void updateRenderObject( - BuildContext context, - RenderSliverFillViewport renderObject, - ) { + void updateRenderObject(BuildContext context, RenderSliverFillViewport renderObject) { renderObject.viewportFraction = viewportFraction; } } class _ForceImplicitScrollPhysics extends ScrollPhysics { - const _ForceImplicitScrollPhysics({ - required this.allowImplicitScrolling, - super.parent, - }); + const _ForceImplicitScrollPhysics({required this.allowImplicitScrolling, super.parent}); @override _ForceImplicitScrollPhysics applyTo(ScrollPhysics? ancestor) { diff --git a/lib/src/widgets/board_preview.dart b/lib/src/widgets/board_preview.dart index c52b9e22b6..531bc0a6d9 100644 --- a/lib/src/widgets/board_preview.dart +++ b/lib/src/widgets/board_preview.dart @@ -18,14 +18,13 @@ class SmallBoardPreview extends ConsumerStatefulWidget { this.onTap, }) : _showLoadingPlaceholder = false; - const SmallBoardPreview.loading({ - this.padding, - }) : orientation = Side.white, - fen = kEmptyFEN, - lastMove = null, - description = const SizedBox.shrink(), - onTap = null, - _showLoadingPlaceholder = true; + const SmallBoardPreview.loading({this.padding}) + : orientation = Side.white, + fen = kEmptyFEN, + lastMove = null, + description = const SizedBox.shrink(), + onTap = null, + _showLoadingPlaceholder = true; /// Side by which the board is oriented. final Side orientation; @@ -57,21 +56,19 @@ class _SmallBoardPreviewState extends ConsumerState { final content = LayoutBuilder( builder: (context, constraints) { - final boardSize = constraints.biggest.shortestSide - - (constraints.biggest.shortestSide / 1.618); + final boardSize = + constraints.biggest.shortestSide - (constraints.biggest.shortestSide / 1.618); return Container( decoration: BoxDecoration( - color: _isPressed - ? CupertinoDynamicColor.resolve( - CupertinoColors.systemGrey5, - context, - ) - : null, + color: + _isPressed + ? CupertinoDynamicColor.resolve(CupertinoColors.systemGrey5, context) + : null, ), child: Padding( - padding: widget.padding ?? - Styles.horizontalBodyPadding - .add(const EdgeInsets.symmetric(vertical: 8.0)), + padding: + widget.padding ?? + Styles.horizontalBodyPadding.add(const EdgeInsets.symmetric(vertical: 8.0)), child: SizedBox( height: boardSize, child: Row( @@ -93,8 +90,7 @@ class _SmallBoardPreviewState extends ConsumerState { lastMove: widget.lastMove as NormalMove?, settings: ChessboardSettings( enableCoordinates: false, - borderRadius: - const BorderRadius.all(Radius.circular(4.0)), + borderRadius: const BorderRadius.all(Radius.circular(4.0)), boxShadow: boardShadows, animationDuration: const Duration(milliseconds: 150), pieceAssets: boardPrefs.pieceSet.assets, @@ -116,8 +112,7 @@ class _SmallBoardPreviewState extends ConsumerState { width: double.infinity, decoration: const BoxDecoration( color: Colors.black, - borderRadius: - BorderRadius.all(Radius.circular(4.0)), + borderRadius: BorderRadius.all(Radius.circular(4.0)), ), ), const SizedBox(height: 4.0), @@ -126,8 +121,7 @@ class _SmallBoardPreviewState extends ConsumerState { width: MediaQuery.sizeOf(context).width / 3, decoration: const BoxDecoration( color: Colors.black, - borderRadius: - BorderRadius.all(Radius.circular(4.0)), + borderRadius: BorderRadius.all(Radius.circular(4.0)), ), ), ], @@ -137,8 +131,7 @@ class _SmallBoardPreviewState extends ConsumerState { width: 44.0, decoration: const BoxDecoration( color: Colors.black, - borderRadius: - BorderRadius.all(Radius.circular(4.0)), + borderRadius: BorderRadius.all(Radius.circular(4.0)), ), ), Container( @@ -146,8 +139,7 @@ class _SmallBoardPreviewState extends ConsumerState { width: double.infinity, decoration: const BoxDecoration( color: Colors.black, - borderRadius: - BorderRadius.all(Radius.circular(4.0)), + borderRadius: BorderRadius.all(Radius.circular(4.0)), ), ), ], @@ -166,16 +158,13 @@ class _SmallBoardPreviewState extends ConsumerState { return widget.onTap != null ? Theme.of(context).platform == TargetPlatform.iOS ? GestureDetector( - onTapDown: (_) => setState(() => _isPressed = true), - onTapUp: (_) => setState(() => _isPressed = false), - onTapCancel: () => setState(() => _isPressed = false), - onTap: widget.onTap, - child: content, - ) - : InkWell( - onTap: widget.onTap, - child: content, - ) + onTapDown: (_) => setState(() => _isPressed = true), + onTapUp: (_) => setState(() => _isPressed = false), + onTapCancel: () => setState(() => _isPressed = false), + onTap: widget.onTap, + child: content, + ) + : InkWell(onTap: widget.onTap, child: content) : content; } } diff --git a/lib/src/widgets/board_table.dart b/lib/src/widgets/board_table.dart index 375961ee68..a9d8f06f02 100644 --- a/lib/src/widgets/board_table.dart +++ b/lib/src/widgets/board_table.dart @@ -44,30 +44,30 @@ class BoardTable extends ConsumerStatefulWidget { this.zenMode = false, super.key, }) : assert( - moves == null || currentMoveIndex != null, - 'You must provide `currentMoveIndex` along with `moves`', - ); + moves == null || currentMoveIndex != null, + 'You must provide `currentMoveIndex` along with `moves`', + ); /// Creates an empty board table (useful for loading). const BoardTable.empty({ this.showMoveListPlaceholder = false, this.showEngineGaugePlaceholder = false, this.errorMessage, - }) : fen = kEmptyBoardFEN, - orientation = Side.white, - gameData = null, - lastMove = null, - boardSettingsOverrides = null, - topTable = const SizedBox.shrink(), - bottomTable = const SizedBox.shrink(), - shapes = null, - engineGauge = null, - moves = null, - currentMoveIndex = null, - onSelectMove = null, - boardOverlay = null, - boardKey = null, - zenMode = false; + }) : fen = kEmptyBoardFEN, + orientation = Side.white, + gameData = null, + lastMove = null, + boardSettingsOverrides = null, + topTable = const SizedBox.shrink(), + bottomTable = const SizedBox.shrink(), + shapes = null, + engineGauge = null, + moves = null, + currentMoveIndex = null, + onSelectMove = null, + boardOverlay = null, + boardKey = null, + zenMode = false; final String fen; @@ -132,39 +132,40 @@ class _BoardTableState extends ConsumerState { return LayoutBuilder( builder: (context, constraints) { - final orientation = constraints.maxWidth > constraints.maxHeight - ? Orientation.landscape - : Orientation.portrait; + final orientation = + constraints.maxWidth > constraints.maxHeight + ? Orientation.landscape + : Orientation.portrait; final isTablet = isTabletOrLarger(context); final defaultSettings = boardPrefs.toBoardSettings().copyWith( - borderRadius: isTablet - ? const BorderRadius.all(Radius.circular(4.0)) - : BorderRadius.zero, - boxShadow: isTablet ? boardShadows : const [], - drawShape: DrawShapeOptions( - enable: boardPrefs.enableShapeDrawings, - onCompleteShape: _onCompleteShape, - onClearShapes: _onClearShapes, - newShapeColor: boardPrefs.shapeColor.color, - ), - ); + borderRadius: isTablet ? const BorderRadius.all(Radius.circular(4.0)) : BorderRadius.zero, + boxShadow: isTablet ? boardShadows : const [], + drawShape: DrawShapeOptions( + enable: boardPrefs.enableShapeDrawings, + onCompleteShape: _onCompleteShape, + onClearShapes: _onClearShapes, + newShapeColor: boardPrefs.shapeColor.color, + ), + ); - final settings = widget.boardSettingsOverrides != null - ? widget.boardSettingsOverrides!.merge(defaultSettings) - : defaultSettings; + final settings = + widget.boardSettingsOverrides != null + ? widget.boardSettingsOverrides!.merge(defaultSettings) + : defaultSettings; final shapes = userShapes.union(widget.shapes ?? ISet()); final slicedMoves = widget.moves?.asMap().entries.slices(2); if (orientation == Orientation.landscape) { - final defaultBoardSize = constraints.biggest.shortestSide - - (kTabletBoardTableSidePadding * 2); + final defaultBoardSize = + constraints.biggest.shortestSide - (kTabletBoardTableSidePadding * 2); final sideWidth = constraints.biggest.longestSide - defaultBoardSize; - final boardSize = sideWidth >= 250 - ? defaultBoardSize - : constraints.biggest.longestSide / kGoldenRatio - - (kTabletBoardTableSidePadding * 2); + final boardSize = + sideWidth >= 250 + ? defaultBoardSize + : constraints.biggest.longestSide / kGoldenRatio - + (kTabletBoardTableSidePadding * 2); return Padding( padding: const EdgeInsets.all(kTabletBoardTableSidePadding), child: Row( @@ -222,30 +223,25 @@ class _BoardTableState extends ConsumerState { ); } else { final defaultBoardSize = constraints.biggest.shortestSide; - final double boardSize = isTablet - ? defaultBoardSize - kTabletBoardTableSidePadding * 2 - : defaultBoardSize; + final double boardSize = + isTablet ? defaultBoardSize - kTabletBoardTableSidePadding * 2 : defaultBoardSize; // vertical space left on portrait mode to check if we can display the // move list - final verticalSpaceLeftBoardOnPortrait = - constraints.biggest.height - boardSize; + final verticalSpaceLeftBoardOnPortrait = constraints.biggest.height - boardSize; return Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [ - if (!widget.zenMode && - slicedMoves != null && - verticalSpaceLeftBoardOnPortrait >= 130) + if (!widget.zenMode && slicedMoves != null && verticalSpaceLeftBoardOnPortrait >= 130) MoveList( type: MoveListType.inline, slicedMoves: slicedMoves, currentMoveIndex: widget.currentMoveIndex ?? 0, onSelectMove: widget.onSelectMove, ) - else if (widget.showMoveListPlaceholder && - verticalSpaceLeftBoardOnPortrait >= 130) + else if (widget.showMoveListPlaceholder && verticalSpaceLeftBoardOnPortrait >= 130) const SizedBox(height: 40), Expanded( child: Padding( @@ -257,11 +253,10 @@ class _BoardTableState extends ConsumerState { ), if (widget.engineGauge != null) Padding( - padding: isTablet - ? const EdgeInsets.symmetric( - horizontal: kTabletBoardTableSidePadding, - ) - : EdgeInsets.zero, + padding: + isTablet + ? const EdgeInsets.symmetric(horizontal: kTabletBoardTableSidePadding) + : EdgeInsets.zero, child: EngineGauge( params: widget.engineGauge!, displayMode: EngineGaugeDisplayMode.horizontal, @@ -270,11 +265,10 @@ class _BoardTableState extends ConsumerState { else if (widget.showEngineGaugePlaceholder) const SizedBox(height: kEvalGaugeSize), Padding( - padding: isTablet - ? const EdgeInsets.symmetric( - horizontal: kTabletBoardTableSidePadding, - ) - : EdgeInsets.zero, + padding: + isTablet + ? const EdgeInsets.symmetric(horizontal: kTabletBoardTableSidePadding) + : EdgeInsets.zero, child: _BoardWidget( size: boardSize, boardPrefs: boardPrefs, @@ -386,15 +380,7 @@ class _BoardWidget extends StatelessWidget { } else if (error != null) { return SizedBox.square( dimension: size, - child: Stack( - children: [ - board, - _ErrorWidget( - errorMessage: error!, - boardSize: size, - ), - ], - ), + child: Stack(children: [board, _ErrorWidget(errorMessage: error!, boardSize: size)]), ); } @@ -403,10 +389,7 @@ class _BoardWidget extends StatelessWidget { } class _ErrorWidget extends StatelessWidget { - const _ErrorWidget({ - required this.errorMessage, - required this.boardSize, - }); + const _ErrorWidget({required this.errorMessage, required this.boardSize}); final double boardSize; final String errorMessage; @@ -419,16 +402,13 @@ class _ErrorWidget extends StatelessWidget { padding: const EdgeInsets.all(16.0), child: Container( decoration: BoxDecoration( - color: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoColors.secondarySystemBackground - .resolveFrom(context) - : Theme.of(context).colorScheme.surface, + color: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoColors.secondarySystemBackground.resolveFrom(context) + : Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.all(Radius.circular(10.0)), ), - child: Padding( - padding: const EdgeInsets.all(10.0), - child: Text(errorMessage), - ), + child: Padding(padding: const EdgeInsets.all(10.0), child: Text(errorMessage)), ), ), ), diff --git a/lib/src/widgets/board_thumbnail.dart b/lib/src/widgets/board_thumbnail.dart index 2095e4d797..0607cad0dc 100644 --- a/lib/src/widgets/board_thumbnail.dart +++ b/lib/src/widgets/board_thumbnail.dart @@ -18,15 +18,12 @@ class BoardThumbnail extends ConsumerStatefulWidget { this.animationDuration, }); - const BoardThumbnail.loading({ - required this.size, - this.header, - this.footer, - }) : orientation = Side.white, - fen = kInitialFEN, - lastMove = null, - onTap = null, - animationDuration = null; + const BoardThumbnail.loading({required this.size, this.header, this.footer}) + : orientation = Side.white, + fen = kInitialFEN, + lastMove = null, + onTap = null, + animationDuration = null; /// Size of the board. final double size; @@ -72,56 +69,58 @@ class _BoardThumbnailState extends ConsumerState { Widget build(BuildContext context) { final boardPrefs = ref.watch(boardPreferencesProvider); - final board = widget.animationDuration != null - ? Chessboard.fixed( - size: widget.size, - fen: widget.fen, - orientation: widget.orientation, - lastMove: widget.lastMove as NormalMove?, - settings: ChessboardSettings( + final board = + widget.animationDuration != null + ? Chessboard.fixed( + size: widget.size, + fen: widget.fen, + orientation: widget.orientation, + lastMove: widget.lastMove as NormalMove?, + settings: ChessboardSettings( + enableCoordinates: false, + borderRadius: const BorderRadius.all(Radius.circular(4.0)), + boxShadow: boardShadows, + animationDuration: widget.animationDuration!, + pieceAssets: boardPrefs.pieceSet.assets, + colorScheme: boardPrefs.boardTheme.colors, + ), + ) + : StaticChessboard( + size: widget.size, + fen: widget.fen, + orientation: widget.orientation, + lastMove: widget.lastMove as NormalMove?, enableCoordinates: false, borderRadius: const BorderRadius.all(Radius.circular(4.0)), boxShadow: boardShadows, - animationDuration: widget.animationDuration!, pieceAssets: boardPrefs.pieceSet.assets, colorScheme: boardPrefs.boardTheme.colors, - ), - ) - : StaticChessboard( - size: widget.size, - fen: widget.fen, - orientation: widget.orientation, - lastMove: widget.lastMove as NormalMove?, - enableCoordinates: false, - borderRadius: const BorderRadius.all(Radius.circular(4.0)), - boxShadow: boardShadows, - pieceAssets: boardPrefs.pieceSet.assets, - colorScheme: boardPrefs.boardTheme.colors, - ); - - final maybeTappableBoard = widget.onTap != null - ? GestureDetector( - onTap: widget.onTap, - onTapDown: (_) => _onTapDown(), - onTapCancel: _onTapCancel, - onTapUp: (_) => _onTapCancel(), - child: AnimatedScale( - scale: scale, - duration: const Duration(milliseconds: 100), - child: board, - ), - ) - : board; + ); + + final maybeTappableBoard = + widget.onTap != null + ? GestureDetector( + onTap: widget.onTap, + onTapDown: (_) => _onTapDown(), + onTapCancel: _onTapCancel, + onTapUp: (_) => _onTapCancel(), + child: AnimatedScale( + scale: scale, + duration: const Duration(milliseconds: 100), + child: board, + ), + ) + : board; return widget.header != null || widget.footer != null ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (widget.header != null) widget.header!, - maybeTappableBoard, - if (widget.footer != null) widget.footer!, - ], - ) + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.header != null) widget.header!, + maybeTappableBoard, + if (widget.footer != null) widget.footer!, + ], + ) : maybeTappableBoard; } } diff --git a/lib/src/widgets/bottom_bar.dart b/lib/src/widgets/bottom_bar.dart index 5a96c93e4e..d08bb3428e 100644 --- a/lib/src/widgets/bottom_bar.dart +++ b/lib/src/widgets/bottom_bar.dart @@ -13,9 +13,9 @@ class BottomBar extends StatelessWidget { }); const BottomBar.empty() - : children = const [], - expandChildren = true, - mainAxisAlignment = MainAxisAlignment.spaceAround; + : children = const [], + expandChildren = true, + mainAxisAlignment = MainAxisAlignment.spaceAround; /// Children to display in the bottom bar's [Row]. Typically instances of [BottomBarButton]. final List children; @@ -38,9 +38,10 @@ class BottomBar extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: mainAxisAlignment, - children: expandChildren - ? children.map((child) => Expanded(child: child)).toList() - : children, + children: + expandChildren + ? children.map((child) => Expanded(child: child)).toList() + : children, ), ), ), @@ -52,9 +53,8 @@ class BottomBar extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0), child: Row( mainAxisAlignment: mainAxisAlignment, - children: expandChildren - ? children.map((child) => Expanded(child: child)).toList() - : children, + children: + expandChildren ? children.map((child) => Expanded(child: child)).toList() : children, ), ); } diff --git a/lib/src/widgets/bottom_bar_button.dart b/lib/src/widgets/bottom_bar_button.dart index 7fb33f1461..ae9247711e 100644 --- a/lib/src/widgets/bottom_bar_button.dart +++ b/lib/src/widgets/bottom_bar_button.dart @@ -36,9 +36,10 @@ class BottomBarButton extends StatelessWidget { Widget build(BuildContext context) { final primary = Theme.of(context).colorScheme.primary; - final labelFontSize = Theme.of(context).platform == TargetPlatform.iOS - ? 11.0 - : Theme.of(context).textTheme.bodySmall?.fontSize; + final labelFontSize = + Theme.of(context).platform == TargetPlatform.iOS + ? 11.0 + : Theme.of(context).textTheme.bodySmall?.fontSize; return Semantics( container: true, @@ -49,9 +50,7 @@ class BottomBarButton extends StatelessWidget { child: Tooltip( excludeFromSemantics: true, message: label, - triggerMode: showTooltip - ? TooltipTriggerMode.longPress - : TooltipTriggerMode.manual, + triggerMode: showTooltip ? TooltipTriggerMode.longPress : TooltipTriggerMode.manual, child: AdaptiveInkWell( borderRadius: BorderRadius.zero, onTap: onTap, @@ -65,9 +64,8 @@ class BottomBarButton extends StatelessWidget { _BlinkIcon( badgeLabel: badgeLabel, icon: icon, - color: highlighted - ? primary - : Theme.of(context).iconTheme.color ?? Colors.black, + color: + highlighted ? primary : Theme.of(context).iconTheme.color ?? Colors.black, ) else Badge( @@ -103,11 +101,7 @@ class BottomBarButton extends StatelessWidget { } class _BlinkIcon extends StatefulWidget { - const _BlinkIcon({ - this.badgeLabel, - required this.icon, - required this.color, - }); + const _BlinkIcon({this.badgeLabel, required this.icon, required this.color}); final String? badgeLabel; final IconData icon; @@ -117,8 +111,7 @@ class _BlinkIcon extends StatefulWidget { _BlinkIconState createState() => _BlinkIconState(); } -class _BlinkIconState extends State<_BlinkIcon> - with SingleTickerProviderStateMixin { +class _BlinkIconState extends State<_BlinkIcon> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _colorAnimation; @@ -126,20 +119,16 @@ class _BlinkIconState extends State<_BlinkIcon> void initState() { super.initState(); - _controller = AnimationController( - duration: const Duration(milliseconds: 500), - vsync: this, - ); + _controller = AnimationController(duration: const Duration(milliseconds: 500), vsync: this); - _colorAnimation = - ColorTween(begin: widget.color, end: null).animate(_controller) - ..addStatusListener((status) { - if (_controller.status == AnimationStatus.completed) { - _controller.reverse(); - } else if (_controller.status == AnimationStatus.dismissed) { - _controller.forward(); - } - }); + _colorAnimation = ColorTween(begin: widget.color, end: null).animate(_controller) + ..addStatusListener((status) { + if (_controller.status == AnimationStatus.completed) { + _controller.reverse(); + } else if (_controller.status == AnimationStatus.dismissed) { + _controller.forward(); + } + }); _controller.forward(); } @@ -163,10 +152,7 @@ class _BlinkIconState extends State<_BlinkIcon> ), isLabelVisible: widget.badgeLabel != null, label: widget.badgeLabel != null ? Text(widget.badgeLabel!) : null, - child: Icon( - widget.icon, - color: _colorAnimation.value ?? Colors.transparent, - ), + child: Icon(widget.icon, color: _colorAnimation.value ?? Colors.transparent), ); }, ); diff --git a/lib/src/widgets/buttons.dart b/lib/src/widgets/buttons.dart index 38d0193698..8d0e7eff1a 100644 --- a/lib/src/widgets/buttons.dart +++ b/lib/src/widgets/buttons.dart @@ -29,12 +29,10 @@ class FatButton extends StatelessWidget { button: true, label: semanticsLabel, excludeSemantics: true, - child: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoButton.tinted(onPressed: onPressed, child: child) - : FilledButton( - onPressed: onPressed, - child: child, - ), + child: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoButton.tinted(onPressed: onPressed, child: child) + : FilledButton(onPressed: onPressed, child: child), ); } } @@ -60,25 +58,20 @@ class SecondaryButton extends StatefulWidget { State createState() => _SecondaryButtonState(); } -class _SecondaryButtonState extends State - with SingleTickerProviderStateMixin { +class _SecondaryButtonState extends State with SingleTickerProviderStateMixin { late final AnimationController _controller; late final Animation _animation; @override void initState() { super.initState(); - _controller = AnimationController( - duration: const Duration(seconds: 1), - vsync: this, - ); + _controller = AnimationController(duration: const Duration(seconds: 1), vsync: this); _animation = (defaultTargetPlatform == TargetPlatform.iOS - ? Tween(begin: 0.5, end: 1.0) - : Tween(begin: 0.0, end: 0.3)) - .animate(_controller) - ..addListener(() { - setState(() {}); - }); + ? Tween(begin: 0.5, end: 1.0) + : Tween(begin: 0.0, end: 0.3)) + .animate(_controller)..addListener(() { + setState(() {}); + }); if (widget.glowing) { _controller.repeat(reverse: true); @@ -111,40 +104,38 @@ class _SecondaryButtonState extends State button: true, label: widget.semanticsLabel, excludeSemantics: true, - child: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoButton( - color: widget.glowing - ? CupertinoTheme.of(context) - .primaryColor - .withValues(alpha: _animation.value) - : null, - onPressed: widget.onPressed, - child: widget.child, - ) - : OutlinedButton( - onPressed: widget.onPressed, - style: OutlinedButton.styleFrom( - textStyle: widget.textStyle, - backgroundColor: widget.glowing - ? Theme.of(context) - .colorScheme - .primary - .withValues(alpha: _animation.value) - : null, + child: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoButton( + color: + widget.glowing + ? CupertinoTheme.of( + context, + ).primaryColor.withValues(alpha: _animation.value) + : null, + onPressed: widget.onPressed, + child: widget.child, + ) + : OutlinedButton( + onPressed: widget.onPressed, + style: OutlinedButton.styleFrom( + textStyle: widget.textStyle, + backgroundColor: + widget.glowing + ? Theme.of( + context, + ).colorScheme.primary.withValues(alpha: _animation.value) + : null, + ), + child: widget.child, ), - child: widget.child, - ), ); } } /// Platform agnostic text button to appear in the app bar. class AppBarTextButton extends StatelessWidget { - const AppBarTextButton({ - required this.child, - required this.onPressed, - super.key, - }); + const AppBarTextButton({required this.child, required this.onPressed, super.key}); final VoidCallback? onPressed; final Widget child; @@ -152,15 +143,8 @@ class AppBarTextButton extends StatelessWidget { @override Widget build(BuildContext context) { return Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoButton( - padding: EdgeInsets.zero, - onPressed: onPressed, - child: child, - ) - : TextButton( - onPressed: onPressed, - child: child, - ); + ? CupertinoButton(padding: EdgeInsets.zero, onPressed: onPressed, child: child) + : TextButton(onPressed: onPressed, child: child); } } @@ -181,16 +165,12 @@ class AppBarIconButton extends StatelessWidget { Widget build(BuildContext context) { return Theme.of(context).platform == TargetPlatform.iOS ? CupertinoIconButton( - padding: EdgeInsets.zero, - semanticsLabel: semanticsLabel, - onPressed: onPressed, - icon: icon, - ) - : IconButton( - tooltip: semanticsLabel, - icon: icon, - onPressed: onPressed, - ); + padding: EdgeInsets.zero, + semanticsLabel: semanticsLabel, + onPressed: onPressed, + icon: icon, + ) + : IconButton(tooltip: semanticsLabel, icon: icon, onPressed: onPressed); } } @@ -231,11 +211,7 @@ class AppBarNotificationIconButton extends StatelessWidget { } class AdaptiveTextButton extends StatelessWidget { - const AdaptiveTextButton({ - required this.child, - required this.onPressed, - super.key, - }); + const AdaptiveTextButton({required this.child, required this.onPressed, super.key}); final VoidCallback? onPressed; final Widget child; @@ -243,25 +219,15 @@ class AdaptiveTextButton extends StatelessWidget { @override Widget build(BuildContext context) { return Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoButton( - onPressed: onPressed, - child: child, - ) - : TextButton( - onPressed: onPressed, - child: child, - ); + ? CupertinoButton(onPressed: onPressed, child: child) + : TextButton(onPressed: onPressed, child: child); } } /// Button that explicitly reduce padding, thus does not conform to accessibility /// guidelines. So use sparingly. class NoPaddingTextButton extends StatelessWidget { - const NoPaddingTextButton({ - required this.child, - required this.onPressed, - super.key, - }); + const NoPaddingTextButton({required this.child, required this.onPressed, super.key}); final VoidCallback? onPressed; final Widget child; @@ -269,21 +235,16 @@ class NoPaddingTextButton extends StatelessWidget { @override Widget build(BuildContext context) { return Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoButton( - padding: EdgeInsets.zero, - onPressed: onPressed, - minSize: 23, - child: child, - ) + ? CupertinoButton(padding: EdgeInsets.zero, onPressed: onPressed, minSize: 23, child: child) : TextButton( - onPressed: onPressed, - style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 5.0), - minimumSize: Size.zero, - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - child: child, - ); + onPressed: onPressed, + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 5.0), + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: child, + ); } } @@ -316,10 +277,7 @@ class CupertinoIconButton extends StatelessWidget { child: CupertinoButton( padding: padding, onPressed: onPressed, - child: IconTheme.merge( - data: const IconThemeData(size: 24.0), - child: icon, - ), + child: IconTheme.merge(data: const IconThemeData(size: 24.0), child: icon), ), ); } @@ -360,11 +318,11 @@ class AdaptiveInkWell extends StatelessWidget { onTapCancel: onTapCancel, onLongPress: onLongPress, borderRadius: borderRadius, - splashColor: platform == TargetPlatform.iOS - ? splashColor ?? CupertinoColors.systemGrey5.resolveFrom(context) - : splashColor, - splashFactory: - platform == TargetPlatform.iOS ? NoSplash.splashFactory : null, + splashColor: + platform == TargetPlatform.iOS + ? splashColor ?? CupertinoColors.systemGrey5.resolveFrom(context) + : splashColor, + splashFactory: platform == TargetPlatform.iOS ? NoSplash.splashFactory : null, child: child, ); } @@ -467,10 +425,7 @@ class PlatformIconButton extends StatelessWidget { this.color, this.iconSize, this.padding, - }) : assert( - color == null || !highlighted, - 'Cannot provide both color and highlighted', - ); + }) : assert(color == null || !highlighted, 'Cannot provide both color and highlighted'); final IconData icon; final String semanticsLabel; @@ -499,18 +454,12 @@ class PlatformIconButton extends StatelessWidget { case TargetPlatform.iOS: final themeData = CupertinoTheme.of(context); return CupertinoTheme( - data: themeData.copyWith( - primaryColor: themeData.textTheme.textStyle.color, - ), + data: themeData.copyWith(primaryColor: themeData.textTheme.textStyle.color), child: CupertinoIconButton( onPressed: onTap, semanticsLabel: semanticsLabel, padding: padding, - icon: Icon( - icon, - color: highlighted ? themeData.primaryColor : color, - size: iconSize, - ), + icon: Icon(icon, color: highlighted ? themeData.primaryColor : color, size: iconSize), ), ); default: @@ -542,18 +491,19 @@ class PlatformAppBarMenuButton extends StatelessWidget { @override Widget build(BuildContext context) { if (Theme.of(context).platform == TargetPlatform.iOS) { - final menuActions = actions.map((action) { - return CupertinoContextMenuAction( - onPressed: () { - if (action.dismissOnPress) { - Navigator.of(context).pop(); - } - action.onPressed(); - }, - trailingIcon: action.icon, - child: Text(action.label), - ); - }).toList(); + final menuActions = + actions.map((action) { + return CupertinoContextMenuAction( + onPressed: () { + if (action.dismissOnPress) { + Navigator.of(context).pop(); + } + action.onPressed(); + }, + trailingIcon: action.icon, + child: Text(action.label), + ); + }).toList(); return AppBarIconButton( onPressed: () { showPopover( @@ -565,30 +515,26 @@ class PlatformAppBarMenuButton extends StatelessWidget { width: _kMenuWidth, child: IntrinsicHeight( child: ClipRRect( - borderRadius: - const BorderRadius.all(Radius.circular(13.0)), + borderRadius: const BorderRadius.all(Radius.circular(13.0)), child: ColoredBox( color: CupertinoDynamicColor.resolve( CupertinoContextMenu.kBackgroundColor, context, ), child: ScrollConfiguration( - behavior: ScrollConfiguration.of(context) - .copyWith(scrollbars: false), + behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), child: CupertinoScrollbar( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ menuActions.first, - for (final Widget action - in menuActions.skip(1)) + for (final Widget action in menuActions.skip(1)) DecoratedBox( decoration: BoxDecoration( border: Border( top: BorderSide( - color: - CupertinoDynamicColor.resolve( + color: CupertinoDynamicColor.resolve( _kBorderColor, context, ), @@ -623,17 +569,17 @@ class PlatformAppBarMenuButton extends StatelessWidget { } return MenuAnchor( - menuChildren: actions.map((action) { - return MenuItemButton( - leadingIcon: Icon(action.icon), - semanticsLabel: action.label, - closeOnActivate: action.dismissOnPress, - onPressed: action.onPressed, - child: Text(action.label), - ); - }).toList(), - builder: - (BuildContext context, MenuController controller, Widget? child) { + menuChildren: + actions.map((action) { + return MenuItemButton( + leadingIcon: Icon(action.icon), + semanticsLabel: action.label, + closeOnActivate: action.dismissOnPress, + onPressed: action.onPressed, + child: Text(action.label), + ); + }).toList(), + builder: (BuildContext context, MenuController controller, Widget? child) { return AppBarIconButton( onPressed: () { if (controller.isOpen) { diff --git a/lib/src/widgets/clock.dart b/lib/src/widgets/clock.dart index 7de23af3e0..091bb04d49 100644 --- a/lib/src/widgets/clock.dart +++ b/lib/src/widgets/clock.dart @@ -46,20 +46,16 @@ class Clock extends StatelessWidget { final mins = timeLeft.inMinutes.remainder(60); final secs = timeLeft.inSeconds.remainder(60).toString().padLeft(2, '0'); final showTenths = timeLeft < _showTenthsThreshold; - final isEmergency = - emergencyThreshold != null && timeLeft <= emergencyThreshold!; + final isEmergency = emergencyThreshold != null && timeLeft <= emergencyThreshold!; final remainingHeight = estimateRemainingHeightLeftBoard(context); - final hoursDisplay = - padLeft ? hours.toString().padLeft(2, '0') : hours.toString(); - final minsDisplay = - padLeft ? mins.toString().padLeft(2, '0') : mins.toString(); + final hoursDisplay = padLeft ? hours.toString().padLeft(2, '0') : hours.toString(); + final minsDisplay = padLeft ? mins.toString().padLeft(2, '0') : mins.toString(); final brightness = Theme.of(context).brightness; - final activeClockStyle = clockStyle ?? - (brightness == Brightness.dark - ? ClockStyle.darkThemeStyle - : ClockStyle.lightThemeStyle); + final activeClockStyle = + clockStyle ?? + (brightness == Brightness.dark ? ClockStyle.darkThemeStyle : ClockStyle.lightThemeStyle); return LayoutBuilder( builder: (context, constraints) { @@ -69,11 +65,12 @@ class Clock extends StatelessWidget { return Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(5.0)), - color: active - ? isEmergency - ? activeClockStyle.emergencyBackgroundColor - : activeClockStyle.activeBackgroundColor - : activeClockStyle.backgroundColor, + color: + active + ? isEmergency + ? activeClockStyle.emergencyBackgroundColor + : activeClockStyle.activeBackgroundColor + : activeClockStyle.backgroundColor, ), child: Padding( padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 5.0), @@ -81,40 +78,31 @@ class Clock extends StatelessWidget { maxScaleFactor: kMaxClockTextScaleFactor, child: RichText( text: TextSpan( - text: hours > 0 - ? '$hoursDisplay:${mins.toString().padLeft(2, '0')}:$secs' - : '$minsDisplay:$secs', + text: + hours > 0 + ? '$hoursDisplay:${mins.toString().padLeft(2, '0')}:$secs' + : '$minsDisplay:$secs', style: TextStyle( - color: active - ? isEmergency - ? activeClockStyle.emergencyTextColor - : activeClockStyle.activeTextColor - : activeClockStyle.textColor, + color: + active + ? isEmergency + ? activeClockStyle.emergencyTextColor + : activeClockStyle.activeTextColor + : activeClockStyle.textColor, fontSize: _kClockFontSize * fontScaleFactor, - height: remainingHeight < - kSmallRemainingHeightLeftBoardThreshold - ? 1.0 - : null, - fontFeatures: const [ - FontFeature.tabularFigures(), - ], + height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 1.0 : null, + fontFeatures: const [FontFeature.tabularFigures()], ), children: [ if (showTenths) TextSpan( - text: - '.${timeLeft.inMilliseconds.remainder(1000) ~/ 100}', - style: TextStyle( - fontSize: _kClockTenthFontSize * fontScaleFactor, - ), + text: '.${timeLeft.inMilliseconds.remainder(1000) ~/ 100}', + style: TextStyle(fontSize: _kClockTenthFontSize * fontScaleFactor), ), if (!active && timeLeft < const Duration(seconds: 1)) TextSpan( - text: - '${timeLeft.inMilliseconds.remainder(1000) ~/ 10 % 10}', - style: TextStyle( - fontSize: _kClockHundredsFontSize * fontScaleFactor, - ), + text: '${timeLeft.inMilliseconds.remainder(1000) ~/ 10 % 10}', + style: TextStyle(fontSize: _kClockHundredsFontSize * fontScaleFactor), ), ], ), @@ -309,8 +297,6 @@ class _CountdownClockState extends State { @override Widget build(BuildContext context) { - return RepaintBoundary( - child: widget.builder(context, timeLeft), - ); + return RepaintBoundary(child: widget.builder(context, timeLeft)); } } diff --git a/lib/src/widgets/expanded_section.dart b/lib/src/widgets/expanded_section.dart index 6f8709033f..169d589c12 100644 --- a/lib/src/widgets/expanded_section.dart +++ b/lib/src/widgets/expanded_section.dart @@ -10,8 +10,7 @@ class ExpandedSection extends StatefulWidget { _ExpandedSectionState createState() => _ExpandedSectionState(); } -class _ExpandedSectionState extends State - with SingleTickerProviderStateMixin { +class _ExpandedSectionState extends State with SingleTickerProviderStateMixin { late AnimationController expandController; late Animation animation; @@ -23,10 +22,7 @@ class _ExpandedSectionState extends State value: widget.expand ? 1.0 : 0.0, duration: const Duration(milliseconds: 300), ); - animation = CurvedAnimation( - parent: expandController, - curve: Curves.fastOutSlowIn, - ); + animation = CurvedAnimation(parent: expandController, curve: Curves.fastOutSlowIn); } void _runExpandCheck() { @@ -51,10 +47,6 @@ class _ExpandedSectionState extends State @override Widget build(BuildContext context) { - return SizeTransition( - axisAlignment: 1.0, - sizeFactor: animation, - child: widget.child, - ); + return SizeTransition(axisAlignment: 1.0, sizeFactor: animation, child: widget.child); } } diff --git a/lib/src/widgets/feedback.dart b/lib/src/widgets/feedback.dart index 5c64cdb703..7cb4583903 100644 --- a/lib/src/widgets/feedback.dart +++ b/lib/src/widgets/feedback.dart @@ -25,10 +25,7 @@ class LagIndicator extends StatelessWidget { /// Whether to show a loading indicator when the lag rating is 0. final bool showLoadingIndicator; - static const spinKit = SpinKitThreeBounce( - color: Colors.grey, - size: 15, - ); + static const spinKit = SpinKitThreeBounce(color: Colors.grey, size: 15); static const cupertinoLevels = { 0: CupertinoColors.systemRed, @@ -37,12 +34,7 @@ class LagIndicator extends StatelessWidget { 3: CupertinoColors.systemGreen, }; - static const materialLevels = { - 0: Colors.red, - 1: Colors.yellow, - 2: Colors.green, - 3: Colors.green, - }; + static const materialLevels = {0: Colors.red, 1: Colors.yellow, 2: Colors.green, 3: Colors.green}; @override Widget build(BuildContext context) { @@ -56,15 +48,15 @@ class LagIndicator extends StatelessWidget { maxValue: 4, value: lagRating, size: size, - inactiveColor: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoDynamicColor.resolve( - CupertinoColors.systemGrey, - context, - ).withValues(alpha: 0.2) - : Colors.grey.withValues(alpha: 0.2), - levels: Theme.of(context).platform == TargetPlatform.iOS - ? cupertinoLevels - : materialLevels, + inactiveColor: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoDynamicColor.resolve( + CupertinoColors.systemGrey, + context, + ).withValues(alpha: 0.2) + : Colors.grey.withValues(alpha: 0.2), + levels: + Theme.of(context).platform == TargetPlatform.iOS ? cupertinoLevels : materialLevels, ), if (showLoadingIndicator && lagRating == 0) spinKit, ], @@ -88,9 +80,10 @@ class ConnectivityBanner extends ConsumerWidget { } return Container( height: 45, - color: theme.platform == TargetPlatform.iOS - ? cupertinoTheme.scaffoldBackgroundColor - : theme.colorScheme.surfaceContainer, + color: + theme.platform == TargetPlatform.iOS + ? cupertinoTheme.scaffoldBackgroundColor + : theme.colorScheme.surfaceContainer, child: Padding( padding: Styles.horizontalBodyPadding, child: Row( @@ -103,9 +96,8 @@ class ConnectivityBanner extends ConsumerWidget { maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( - color: theme.platform == TargetPlatform.iOS - ? null - : theme.colorScheme.onSurface, + color: + theme.platform == TargetPlatform.iOS ? null : theme.colorScheme.onSurface, ), ), ), @@ -130,9 +122,7 @@ class ButtonLoadingIndicator extends StatelessWidget { return const SizedBox( height: 20, width: 20, - child: CircularProgressIndicator.adaptive( - strokeWidth: 2, - ), + child: CircularProgressIndicator.adaptive(strokeWidth: 2), ); } } @@ -143,9 +133,7 @@ class CenterLoadingIndicator extends StatelessWidget { @override Widget build(BuildContext context) { - return const Center( - child: CircularProgressIndicator.adaptive(), - ); + return const Center(child: CircularProgressIndicator.adaptive()); } } @@ -154,10 +142,7 @@ class CenterLoadingIndicator extends StatelessWidget { /// This widget is intended to be used when a request fails and the user can /// retry it. class FullScreenRetryRequest extends StatelessWidget { - const FullScreenRetryRequest({ - super.key, - required this.onRetry, - }); + const FullScreenRetryRequest({super.key, required this.onRetry}); final VoidCallback onRetry; @@ -169,10 +154,7 @@ class FullScreenRetryRequest extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - context.l10n.mobileSomethingWentWrong, - style: Styles.sectionTitle, - ), + Text(context.l10n.mobileSomethingWentWrong, style: Styles.sectionTitle), const SizedBox(height: 10), SecondaryButton( onPressed: onRetry, @@ -185,11 +167,7 @@ class FullScreenRetryRequest extends StatelessWidget { } } -enum SnackBarType { - error, - info, - success, -} +enum SnackBarType { error, info, success } void showPlatformSnackbar( BuildContext context, @@ -202,19 +180,13 @@ void showPlatformSnackbar( SnackBar( content: Text( message, - style: type == SnackBarType.error - ? const TextStyle(color: Colors.white) - : null, + style: type == SnackBarType.error ? const TextStyle(color: Colors.white) : null, ), backgroundColor: type == SnackBarType.error ? Colors.red : null, ), ); case TargetPlatform.iOS: - showCupertinoSnackBar( - context: context, - message: message, - type: type, - ); + showCupertinoSnackBar(context: context, message: message, type: type); default: assert(false, 'Unexpected platform ${Theme.of(context).platform}'); } @@ -228,30 +200,28 @@ void showCupertinoSnackBar({ Duration duration = const Duration(milliseconds: 4000), }) { final overlayEntry = OverlayEntry( - builder: (context) => Positioned( - // default iOS tab bar height + 10 - bottom: 60.0, - left: 8.0, - right: 8.0, - child: _CupertinoSnackBarManager( - snackBar: CupertinoSnackBar( - message: message, - backgroundColor: (type == SnackBarType.error - ? context.lichessColors.error - : type == SnackBarType.success + builder: + (context) => Positioned( + // default iOS tab bar height + 10 + bottom: 60.0, + left: 8.0, + right: 8.0, + child: _CupertinoSnackBarManager( + snackBar: CupertinoSnackBar( + message: message, + backgroundColor: (type == SnackBarType.error + ? context.lichessColors.error + : type == SnackBarType.success ? context.lichessColors.good : CupertinoColors.systemGrey.resolveFrom(context)) - .withValues(alpha: 0.6), - textStyle: const TextStyle(color: Colors.white), + .withValues(alpha: 0.6), + textStyle: const TextStyle(color: Colors.white), + ), + duration: duration, + ), ), - duration: duration, - ), - ), - ); - Future.delayed( - duration + _snackBarAnimationDuration * 2, - overlayEntry.remove, ); + Future.delayed(duration + _snackBarAnimationDuration * 2, overlayEntry.remove); Overlay.of(context).insert(overlayEntry); } @@ -260,11 +230,7 @@ class CupertinoSnackBar extends StatelessWidget { final TextStyle? textStyle; final Color? backgroundColor; - const CupertinoSnackBar({ - required this.message, - this.textStyle, - this.backgroundColor, - }); + const CupertinoSnackBar({required this.message, this.textStyle, this.backgroundColor}); @override Widget build(BuildContext context) { @@ -273,15 +239,8 @@ class CupertinoSnackBar extends StatelessWidget { child: ColoredBox( color: backgroundColor ?? CupertinoColors.systemGrey, child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 12.0, - ), - child: Text( - message, - style: textStyle, - textAlign: TextAlign.center, - ), + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + child: Text(message, style: textStyle, textAlign: TextAlign.center), ), ), ); @@ -294,10 +253,7 @@ class _CupertinoSnackBarManager extends StatefulWidget { final CupertinoSnackBar snackBar; final Duration duration; - const _CupertinoSnackBarManager({ - required this.snackBar, - required this.duration, - }); + const _CupertinoSnackBarManager({required this.snackBar, required this.duration}); @override State<_CupertinoSnackBarManager> createState() => _CupertinoSnackBarState(); @@ -310,14 +266,11 @@ class _CupertinoSnackBarState extends State<_CupertinoSnackBarManager> { void initState() { super.initState(); Future.microtask(() => setState(() => _show = true)); - Future.delayed( - widget.duration, - () { - if (mounted) { - setState(() => _show = false); - } - }, - ); + Future.delayed(widget.duration, () { + if (mounted) { + setState(() => _show = false); + } + }); } @override diff --git a/lib/src/widgets/filter.dart b/lib/src/widgets/filter.dart index cfabd0de83..4c204784a5 100644 --- a/lib/src/widgets/filter.dart +++ b/lib/src/widgets/filter.dart @@ -58,17 +58,17 @@ class Filter extends StatelessWidget { .map( (choice) => switch (filterType) { FilterType.singleChoice => ChoiceChip( - label: choiceLabel(choice), - selected: choiceSelected(choice), - onSelected: (value) => onSelected(choice, value), - showCheckmark: showCheckmark, - ), + label: choiceLabel(choice), + selected: choiceSelected(choice), + onSelected: (value) => onSelected(choice, value), + showCheckmark: showCheckmark, + ), FilterType.multipleChoice => FilterChip( - label: choiceLabel(choice), - selected: choiceSelected(choice), - onSelected: (value) => onSelected(choice, value), - showCheckmark: showCheckmark, - ), + label: choiceLabel(choice), + selected: choiceSelected(choice), + onSelected: (value) => onSelected(choice, value), + showCheckmark: showCheckmark, + ), }, ) .toList(growable: false), diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index 3d16e587b2..d7f4afe2d1 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -22,24 +22,19 @@ class ListSection extends StatelessWidget { this.cupertinoClipBehavior = Clip.hardEdge, }) : _isLoading = false; - ListSection.loading({ - required int itemsNumber, - bool header = false, - this.margin, - }) : children = [ - for (int i = 0; i < itemsNumber; i++) const SizedBox.shrink(), - ], - headerTrailing = null, - header = header ? const SizedBox.shrink() : null, - hasLeading = false, - showDivider = false, - showDividerBetweenTiles = false, - dense = false, - cupertinoAdditionalDividerMargin = null, - cupertinoBackgroundColor = null, - cupertinoBorderRadius = null, - cupertinoClipBehavior = Clip.hardEdge, - _isLoading = true; + ListSection.loading({required int itemsNumber, bool header = false, this.margin}) + : children = [for (int i = 0; i < itemsNumber; i++) const SizedBox.shrink()], + headerTrailing = null, + header = header ? const SizedBox.shrink() : null, + hasLeading = false, + showDivider = false, + showDividerBetweenTiles = false, + dense = false, + cupertinoAdditionalDividerMargin = null, + cupertinoBackgroundColor = null, + cupertinoBorderRadius = null, + cupertinoClipBehavior = Clip.hardEdge, + _isLoading = true; /// Usually a list of [PlatformListTile] widgets final List children; @@ -81,139 +76,125 @@ class ListSection extends StatelessWidget { case TargetPlatform.android: return _isLoading ? Padding( - padding: margin ?? Styles.sectionBottomPadding, - child: Column( - children: [ - if (header != null) - Padding( - padding: const EdgeInsets.symmetric( - vertical: 10.0, - horizontal: 16.0, - ), - child: Container( - width: double.infinity, - height: 25, - decoration: const BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.all(Radius.circular(16)), - ), + padding: margin ?? Styles.sectionBottomPadding, + child: Column( + children: [ + if (header != null) + Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Container( + width: double.infinity, + height: 25, + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(16)), ), ), - for (int i = 0; i < children.length; i++) - Padding( - padding: const EdgeInsets.symmetric( - vertical: 10.0, - horizontal: 16.0, - ), - child: Container( - width: double.infinity, - height: 50, - decoration: const BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.all(Radius.circular(10)), - ), + ), + for (int i = 0; i < children.length; i++) + Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Container( + width: double.infinity, + height: 50, + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(10)), ), ), - ], - ), - ) + ), + ], + ), + ) : Padding( - padding: margin ?? Styles.sectionBottomPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (header != null) - ListTile( - dense: true, - title: DefaultTextStyle.merge( - style: Styles.sectionTitle, - child: header!, - ), - trailing: headerTrailing, - ), - if (showDividerBetweenTiles) - ...ListTile.divideTiles( - context: context, - tiles: children, - ) - else - ...children, - if (showDivider) - const Padding( - padding: EdgeInsets.only(top: 10.0), - child: Divider(thickness: 0), - ), - ], - ), - ); + padding: margin ?? Styles.sectionBottomPadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (header != null) + ListTile( + dense: true, + title: DefaultTextStyle.merge(style: Styles.sectionTitle, child: header!), + trailing: headerTrailing, + ), + if (showDividerBetweenTiles) + ...ListTile.divideTiles(context: context, tiles: children) + else + ...children, + if (showDivider) + const Padding( + padding: EdgeInsets.only(top: 10.0), + child: Divider(thickness: 0), + ), + ], + ), + ); case TargetPlatform.iOS: return _isLoading ? Padding( - padding: margin ?? Styles.bodySectionPadding, - child: Column( - children: [ - if (header != null) - // ignore: avoid-wrapping-in-padding - Padding( - padding: const EdgeInsets.only(top: 10.0, bottom: 16.0), - child: Container( - width: double.infinity, - height: 24, - decoration: const BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.all(Radius.circular(16)), - ), + padding: margin ?? Styles.bodySectionPadding, + child: Column( + children: [ + if (header != null) + // ignore: avoid-wrapping-in-padding + Padding( + padding: const EdgeInsets.only(top: 10.0, bottom: 16.0), + child: Container( + width: double.infinity, + height: 24, + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(16)), ), ), - Container( - width: double.infinity, - height: children.length * 54, - decoration: const BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.all(Radius.circular(10)), - ), ), - ], - ), - ) + Container( + width: double.infinity, + height: children.length * 54, + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + ), + ], + ), + ) : Padding( - padding: margin ?? Styles.bodySectionPadding, - child: Column( - children: [ - if (header != null) - Padding( - padding: const EdgeInsets.only(bottom: 6.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - DefaultTextStyle.merge( - style: Styles.sectionTitle, - child: header!, - ), - if (headerTrailing != null) headerTrailing!, - ], - ), - ), - CupertinoListSection.insetGrouped( - clipBehavior: cupertinoClipBehavior, - backgroundColor: cupertinoBackgroundColor ?? - CupertinoTheme.of(context).scaffoldBackgroundColor, - decoration: BoxDecoration( - color: cupertinoBackgroundColor ?? - Styles.cupertinoCardColor.resolveFrom(context), - borderRadius: cupertinoBorderRadius ?? - const BorderRadius.all(Radius.circular(10.0)), + padding: margin ?? Styles.bodySectionPadding, + child: Column( + children: [ + if (header != null) + Padding( + padding: const EdgeInsets.only(bottom: 6.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + DefaultTextStyle.merge(style: Styles.sectionTitle, child: header!), + if (headerTrailing != null) headerTrailing!, + ], ), - separatorColor: - Styles.cupertinoSeparatorColor.resolveFrom(context), - margin: EdgeInsets.zero, - hasLeading: hasLeading, - additionalDividerMargin: cupertinoAdditionalDividerMargin, - children: children, ), - ], - ), - ); + CupertinoListSection.insetGrouped( + clipBehavior: cupertinoClipBehavior, + backgroundColor: + cupertinoBackgroundColor ?? + CupertinoTheme.of(context).scaffoldBackgroundColor, + decoration: BoxDecoration( + color: + cupertinoBackgroundColor ?? + Styles.cupertinoCardColor.resolveFrom(context), + borderRadius: + cupertinoBorderRadius ?? const BorderRadius.all(Radius.circular(10.0)), + ), + separatorColor: Styles.cupertinoSeparatorColor.resolveFrom(context), + margin: EdgeInsets.zero, + hasLeading: hasLeading, + additionalDividerMargin: cupertinoAdditionalDividerMargin, + children: children, + ), + ], + ), + ); default: assert(false, 'Unexpected platform ${Theme.of(context).platform}'); return const SizedBox.shrink(); @@ -249,21 +230,21 @@ class PlatformDivider extends StatelessWidget { Widget build(BuildContext context) { return Theme.of(context).platform == TargetPlatform.android ? Divider( - height: height, - thickness: thickness, - indent: indent, - endIndent: endIndent, - color: color, - ) + height: height, + thickness: thickness, + indent: indent, + endIndent: endIndent, + color: color, + ) : Divider( - height: height, - thickness: thickness ?? 0.0, - // see: - // https://github.com/flutter/flutter/blob/bff6b93683de8be01d53a39b6183f230518541ac/packages/flutter/lib/src/cupertino/list_section.dart#L53 - indent: indent ?? (cupertinoHasLeading ? 14 + 44.0 : 14.0), - endIndent: endIndent, - color: color ?? CupertinoColors.separator.resolveFrom(context), - ); + height: height, + thickness: thickness ?? 0.0, + // see: + // https://github.com/flutter/flutter/blob/bff6b93683de8be01d53a39b6183f230518541ac/packages/flutter/lib/src/cupertino/list_section.dart#L53 + indent: indent ?? (cupertinoHasLeading ? 14 + 44.0 : 14.0), + endIndent: endIndent, + color: color ?? CupertinoColors.separator.resolveFrom(context), + ); } } @@ -328,14 +309,13 @@ class PlatformListTile extends StatelessWidget { leading: leading, title: title, iconColor: Theme.of(context).colorScheme.outline, - subtitle: subtitle != null - ? DefaultTextStyle.merge( - child: subtitle!, - style: TextStyle( - color: textShade(context, Styles.subtitleOpacity), - ), - ) - : null, + subtitle: + subtitle != null + ? DefaultTextStyle.merge( + child: subtitle!, + style: TextStyle(color: textShade(context, Styles.subtitleOpacity)), + ) + : null, trailing: trailing, dense: dense, visualDensity: visualDensity, @@ -347,31 +327,27 @@ class PlatformListTile extends StatelessWidget { ); case TargetPlatform.iOS: return IconTheme( - data: CupertinoIconThemeData( - color: CupertinoColors.systemGrey.resolveFrom(context), - ), + data: CupertinoIconThemeData(color: CupertinoColors.systemGrey.resolveFrom(context)), child: GestureDetector( onLongPress: onLongPress, child: CupertinoListTile.notched( - backgroundColor: selected == true - ? CupertinoColors.systemGrey4.resolveFrom(context) - : cupertinoBackgroundColor, + backgroundColor: + selected == true + ? CupertinoColors.systemGrey4.resolveFrom(context) + : cupertinoBackgroundColor, leading: leading, - title: harmonizeCupertinoTitleStyle - ? DefaultTextStyle.merge( - // see: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/cupertino/list_tile.dart - style: const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16.0, - ), - child: title, - ) - : title, + title: + harmonizeCupertinoTitleStyle + ? DefaultTextStyle.merge( + // see: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/cupertino/list_tile.dart + style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16.0), + child: title, + ) + : title, subtitle: subtitle, - trailing: trailing ?? - (selected == true - ? const Icon(CupertinoIcons.check_mark_circled_solid) - : null), + trailing: + trailing ?? + (selected == true ? const Icon(CupertinoIcons.check_mark_circled_solid) : null), additionalInfo: additionalInfo, padding: padding, onTap: onTap, @@ -415,9 +391,8 @@ class AdaptiveListTile extends StatelessWidget { color: Colors.transparent, child: Theme( data: Theme.of(context).copyWith( - splashFactory: Theme.of(context).platform == TargetPlatform.iOS - ? NoSplash.splashFactory - : null, + splashFactory: + Theme.of(context).platform == TargetPlatform.iOS ? NoSplash.splashFactory : null, ), child: ListTile( leading: leading, @@ -431,11 +406,8 @@ class AdaptiveListTile extends StatelessWidget { } } -typedef RemovedItemBuilder = Widget Function( - T item, - BuildContext context, - Animation animation, -); +typedef RemovedItemBuilder = + Widget Function(T item, BuildContext context, Animation animation); /// Keeps a Dart [List] in sync with an [AnimatedList] or [SliverAnimatedList]. /// @@ -447,8 +419,8 @@ class AnimatedListModel { required this.removedItemBuilder, Iterable? initialItems, int? itemsOffset, - }) : _items = List.from(initialItems ?? []), - itemsOffset = itemsOffset ?? 0; + }) : _items = List.from(initialItems ?? []), + itemsOffset = itemsOffset ?? 0; final GlobalKey listKey; final RemovedItemBuilder removedItemBuilder; @@ -470,12 +442,9 @@ class AnimatedListModel { E removeAt(int index) { final E removedItem = _items.removeAt(index - itemsOffset); if (removedItem != null) { - _animatedList!.removeItem( - index, - (BuildContext context, Animation animation) { - return removedItemBuilder(removedItem, context, animation); - }, - ); + _animatedList!.removeItem(index, (BuildContext context, Animation animation) { + return removedItemBuilder(removedItem, context, animation); + }); } return removedItem; } @@ -497,8 +466,8 @@ class SliverAnimatedListModel { required this.removedItemBuilder, Iterable? initialItems, int? itemsOffset, - }) : _items = List.from(initialItems ?? []), - itemsOffset = itemsOffset ?? 0; + }) : _items = List.from(initialItems ?? []), + itemsOffset = itemsOffset ?? 0; final GlobalKey listKey; final RemovedItemBuilder removedItemBuilder; @@ -520,12 +489,9 @@ class SliverAnimatedListModel { E removeAt(int index) { final E removedItem = _items.removeAt(index - itemsOffset); if (removedItem != null) { - _animatedList!.removeItem( - index, - (BuildContext context, Animation animation) { - return removedItemBuilder(removedItem, context, animation); - }, - ); + _animatedList!.removeItem(index, (BuildContext context, Animation animation) { + return removedItemBuilder(removedItem, context, animation); + }); } return removedItem; } diff --git a/lib/src/widgets/misc.dart b/lib/src/widgets/misc.dart index fe1e3e6a80..5bce81f586 100644 --- a/lib/src/widgets/misc.dart +++ b/lib/src/widgets/misc.dart @@ -4,11 +4,7 @@ import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:url_launcher/url_launcher.dart'; class LichessMessage extends StatefulWidget { - const LichessMessage({ - super.key, - this.style, - this.textAlign = TextAlign.start, - }); + const LichessMessage({super.key, this.style, this.textAlign = TextAlign.start}); final TextStyle? style; final TextAlign textAlign; @@ -38,10 +34,7 @@ class _LichessMessageState extends State { @override Widget build(BuildContext context) { - final trans = context.l10n.xIsAFreeYLibreOpenSourceChessServer( - 'Lichess', - context.l10n.really, - ); + final trans = context.l10n.xIsAFreeYLibreOpenSourceChessServer('Lichess', context.l10n.really); final regexp = RegExp(r'''^([^(]*\()([^)]*)(\).*)$'''); final match = regexp.firstMatch(trans); final List spans = []; @@ -50,9 +43,7 @@ class _LichessMessageState extends State { spans.add( TextSpan( text: match[i], - style: i == 2 - ? TextStyle(color: Theme.of(context).colorScheme.primary) - : null, + style: i == 2 ? TextStyle(color: Theme.of(context).colorScheme.primary) : null, recognizer: i == 2 ? _recognizer : null, ), ); @@ -61,12 +52,6 @@ class _LichessMessageState extends State { spans.add(TextSpan(text: trans)); } - return Text.rich( - TextSpan( - style: widget.style, - children: spans, - ), - textAlign: widget.textAlign, - ); + return Text.rich(TextSpan(style: widget.style, children: spans), textAlign: widget.textAlign); } } diff --git a/lib/src/widgets/move_list.dart b/lib/src/widgets/move_list.dart index 631f2127e3..00ef39b310 100644 --- a/lib/src/widgets/move_list.dart +++ b/lib/src/widgets/move_list.dart @@ -48,10 +48,7 @@ class _MoveListState extends ConsumerState { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { if (currentMoveKey.currentContext != null) { - Scrollable.ensureVisible( - currentMoveKey.currentContext!, - alignment: 0.5, - ); + Scrollable.ensureVisible(currentMoveKey.currentContext!, alignment: 0.5); } }); } @@ -79,107 +76,93 @@ class _MoveListState extends ConsumerState { @override Widget build(BuildContext context) { - final pieceNotation = ref.watch(pieceNotationProvider).maybeWhen( - data: (value) => value, - orElse: () => defaultAccountPreferences.pieceNotation, - ); + final pieceNotation = ref + .watch(pieceNotationProvider) + .maybeWhen(data: (value) => value, orElse: () => defaultAccountPreferences.pieceNotation); return widget.type == MoveListType.inline ? Container( - decoration: widget.inlineDecoration, - padding: const EdgeInsets.only(left: 5), - height: _kMoveListHeight, - width: double.infinity, + decoration: widget.inlineDecoration, + padding: const EdgeInsets.only(left: 5), + height: _kMoveListHeight, + width: double.infinity, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: widget.slicedMoves + .mapIndexed( + (index, moves) => Container( + margin: const EdgeInsets.only(right: 10), + child: Row( + children: [ + InlineMoveCount( + pieceNotation: pieceNotation, + count: index + 1, + color: widget.inlineColor, + ), + ...moves.map((move) { + // cursor index starts at 0, move index starts at 1 + final isCurrentMove = widget.currentMoveIndex == move.key + 1; + return InlineMoveItem( + key: isCurrentMove ? currentMoveKey : null, + move: move, + color: widget.inlineColor, + pieceNotation: pieceNotation, + current: isCurrentMove, + onSelectMove: widget.onSelectMove, + ); + }), + ], + ), + ), + ) + .toList(growable: false), + ), + ), + ) + : PlatformCard( + child: Padding( + padding: const EdgeInsets.all(16.0), child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( + child: Column( children: widget.slicedMoves .mapIndexed( - (index, moves) => Container( - margin: const EdgeInsets.only(right: 10), - child: Row( - children: [ - InlineMoveCount( - pieceNotation: pieceNotation, - count: index + 1, - color: widget.inlineColor, - ), - ...moves.map( - (move) { - // cursor index starts at 0, move index starts at 1 - final isCurrentMove = - widget.currentMoveIndex == move.key + 1; - return InlineMoveItem( - key: isCurrentMove ? currentMoveKey : null, - move: move, - color: widget.inlineColor, - pieceNotation: pieceNotation, - current: isCurrentMove, - onSelectMove: widget.onSelectMove, - ); - }, + (index, moves) => Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + StackedMoveCount(count: index + 1), + Expanded( + child: Row( + children: [ + ...moves.map((move) { + // cursor index starts at 0, move index starts at 1 + final isCurrentMove = widget.currentMoveIndex == move.key + 1; + return Expanded( + child: StackedMoveItem( + key: isCurrentMove ? currentMoveKey : null, + move: move, + current: isCurrentMove, + onSelectMove: widget.onSelectMove, + ), + ); + }), + ], ), - ], - ), + ), + ], ), ) .toList(growable: false), ), ), - ) - : PlatformCard( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: SingleChildScrollView( - child: Column( - children: widget.slicedMoves - .mapIndexed( - (index, moves) => Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - StackedMoveCount(count: index + 1), - Expanded( - child: Row( - children: [ - ...moves.map( - (move) { - // cursor index starts at 0, move index starts at 1 - final isCurrentMove = - widget.currentMoveIndex == - move.key + 1; - return Expanded( - child: StackedMoveItem( - key: isCurrentMove - ? currentMoveKey - : null, - move: move, - current: isCurrentMove, - onSelectMove: widget.onSelectMove, - ), - ); - }, - ), - ], - ), - ), - ], - ), - ) - .toList(growable: false), - ), - ), - ), - ); + ), + ); } } class InlineMoveCount extends StatelessWidget { - const InlineMoveCount({ - required this.count, - required this.pieceNotation, - this.color, - }); + const InlineMoveCount({required this.count, required this.pieceNotation, this.color}); final PieceNotation pieceNotation; final int count; @@ -194,10 +177,8 @@ class InlineMoveCount extends StatelessWidget { '$count.', style: TextStyle( fontWeight: FontWeight.w500, - color: color?.withValues(alpha: _moveListOpacity) ?? - textShade(context, _moveListOpacity), - fontFamily: - pieceNotation == PieceNotation.symbol ? 'ChessFont' : null, + color: color?.withValues(alpha: _moveListOpacity) ?? textShade(context, _moveListOpacity), + fontFamily: pieceNotation == PieceNotation.symbol ? 'ChessFont' : null, ), ), ); @@ -230,14 +211,14 @@ class InlineMoveItem extends StatelessWidget { child: Text( move.value, style: TextStyle( - fontFamily: - pieceNotation == PieceNotation.symbol ? 'ChessFont' : null, + fontFamily: pieceNotation == PieceNotation.symbol ? 'ChessFont' : null, fontWeight: current == true ? FontWeight.bold : FontWeight.w500, - color: current != true - ? color != null - ? color!.withValues(alpha: _moveListOpacity) - : textShade(context, _moveListOpacity) - : Theme.of(context).colorScheme.primary, + color: + current != true + ? color != null + ? color!.withValues(alpha: _moveListOpacity) + : textShade(context, _moveListOpacity) + : Theme.of(context).colorScheme.primary, ), ), ), @@ -256,22 +237,14 @@ class StackedMoveCount extends StatelessWidget { width: 40.0, child: Text( '$count.', - style: TextStyle( - fontWeight: FontWeight.w600, - color: textShade(context, _moveListOpacity), - ), + style: TextStyle(fontWeight: FontWeight.w600, color: textShade(context, _moveListOpacity)), ), ); } } class StackedMoveItem extends StatelessWidget { - const StackedMoveItem({ - required this.move, - this.current, - this.onSelectMove, - super.key, - }); + const StackedMoveItem({required this.move, this.current, this.onSelectMove, super.key}); final MapEntry move; final bool? current; diff --git a/lib/src/widgets/non_linear_slider.dart b/lib/src/widgets/non_linear_slider.dart index 31a9f4a335..288f54f716 100644 --- a/lib/src/widgets/non_linear_slider.dart +++ b/lib/src/widgets/non_linear_slider.dart @@ -9,8 +9,8 @@ class NonLinearSlider extends StatefulWidget { this.onChange, this.onChangeEnd, super.key, - }) : assert(values.length > 1), - assert(values.contains(value)); + }) : assert(values.length > 1), + assert(values.contains(value)); final num value; final List values; @@ -46,27 +46,25 @@ class _NonLinearSliderState extends State { @override Widget build(BuildContext context) { return Opacity( - opacity: Theme.of(context).platform != TargetPlatform.iOS || - widget.onChangeEnd != null - ? 1 - : 0.5, + opacity: + Theme.of(context).platform != TargetPlatform.iOS || widget.onChangeEnd != null ? 1 : 0.5, child: Slider.adaptive( value: _index.toDouble(), min: 0, max: widget.values.length.toDouble() - 1, divisions: widget.values.length - 1, - label: widget.labelBuilder?.call(widget.values[_index]) ?? - widget.values[_index].toString(), - onChanged: widget.onChangeEnd != null - ? (double value) { - final newIndex = value.toInt(); - setState(() { - _index = newIndex; - }); + label: widget.labelBuilder?.call(widget.values[_index]) ?? widget.values[_index].toString(), + onChanged: + widget.onChangeEnd != null + ? (double value) { + final newIndex = value.toInt(); + setState(() { + _index = newIndex; + }); - widget.onChange?.call(widget.values[_index]); - } - : null, + widget.onChange?.call(widget.values[_index]); + } + : null, onChangeEnd: (double value) { widget.onChangeEnd?.call(widget.values[_index]); }, diff --git a/lib/src/widgets/pgn.dart b/lib/src/widgets/pgn.dart index bee99a263d..4c39e43570 100644 --- a/lib/src/widgets/pgn.dart +++ b/lib/src/widgets/pgn.dart @@ -54,30 +54,12 @@ Annotation? makeAnnotation(Iterable? nags) { return null; } return switch (nag) { - 1 => const Annotation( - symbol: '!', - color: Colors.lightGreen, - ), - 3 => const Annotation( - symbol: '!!', - color: Colors.teal, - ), - 5 => const Annotation( - symbol: '!?', - color: Colors.purple, - ), - 6 => const Annotation( - symbol: '?!', - color: LichessColors.cyan, - ), - 2 => const Annotation( - symbol: '?', - color: mistakeColor, - ), - 4 => const Annotation( - symbol: '??', - color: blunderColor, - ), + 1 => const Annotation(symbol: '!', color: Colors.lightGreen), + 3 => const Annotation(symbol: '!!', color: Colors.teal), + 5 => const Annotation(symbol: '!?', color: Colors.purple), + 6 => const Annotation(symbol: '?!', color: LichessColors.cyan), + 2 => const Annotation(symbol: '?', color: mistakeColor), + 4 => const Annotation(symbol: '??', color: blunderColor), int() => null, }; } @@ -145,8 +127,7 @@ class DebouncedPgnTreeView extends ConsumerStatefulWidget { final bool shouldShowComments; @override - ConsumerState createState() => - _DebouncedPgnTreeViewState(); + ConsumerState createState() => _DebouncedPgnTreeViewState(); } class _DebouncedPgnTreeViewState extends ConsumerState { @@ -244,35 +225,33 @@ class _DebouncedPgnTreeViewState extends ConsumerState { /// and ultimately evaluated in the [InlineMove] widget. /// /// Grouped in this record to improve readability. -typedef _PgnTreeViewParams = ({ - /// Path to the currently selected move in the tree. - UciPath pathToCurrentMove, +typedef _PgnTreeViewParams = + ({ + /// Path to the currently selected move in the tree. + UciPath pathToCurrentMove, - /// Path to the last live move in the tree if it is a broadcast game - UciPath? pathToBroadcastLiveMove, + /// Path to the last live move in the tree if it is a broadcast game + UciPath? pathToBroadcastLiveMove, - /// Whether to show analysis variations. - bool shouldShowComputerVariations, + /// Whether to show analysis variations. + bool shouldShowComputerVariations, - /// Whether to show NAG annotations like '!' and '??'. - bool shouldShowAnnotations, + /// Whether to show NAG annotations like '!' and '??'. + bool shouldShowAnnotations, - /// Whether to show comments associated with the moves. - bool shouldShowComments, + /// Whether to show comments associated with the moves. + bool shouldShowComments, - /// Key that will we assigned to the widget corresponding to [pathToCurrentMove]. - /// Can be used e.g. to ensure that the current move is visible on the screen. - GlobalKey currentMoveKey, + /// Key that will we assigned to the widget corresponding to [pathToCurrentMove]. + /// Can be used e.g. to ensure that the current move is visible on the screen. + GlobalKey currentMoveKey, - /// Callbacks for when the user interacts with the tree view, e.g. selecting a different move. - PgnTreeNotifier notifier, -}); + /// Callbacks for when the user interacts with the tree view, e.g. selecting a different move. + PgnTreeNotifier notifier, + }); /// Filter node children when computer analysis is disabled -IList _filteredChildren( - ViewNode node, - bool shouldShowComputerVariations, -) { +IList _filteredChildren(ViewNode node, bool shouldShowComputerVariations) { return node.children .where((c) => shouldShowComputerVariations || !c.isComputerVariation) .toIList(); @@ -292,27 +271,19 @@ bool _displaySideLineAsInline(ViewBranch node, [int depth = 0]) { /// Returns whether this node has a sideline that should not be displayed inline. bool _hasNonInlineSideLine(ViewNode node, _PgnTreeViewParams params) { final children = _filteredChildren(node, params.shouldShowComputerVariations); - return children.length > 2 || - (children.length == 2 && !_displaySideLineAsInline(children[1])); + return children.length > 2 || (children.length == 2 && !_displaySideLineAsInline(children[1])); } /// Splits the mainline into parts, where each part is a sequence of moves that are displayed on the same line. /// /// A part ends when a mainline node has a sideline that should not be displayed inline. -Iterable> _mainlineParts( - ViewRoot root, - _PgnTreeViewParams params, -) => +Iterable> _mainlineParts(ViewRoot root, _PgnTreeViewParams params) => [root, ...root.mainline] .splitAfter((n) => _hasNonInlineSideLine(n, params)) .takeWhile((nodes) => nodes.firstOrNull?.children.isNotEmpty == true); class _PgnTreeView extends StatefulWidget { - const _PgnTreeView({ - required this.root, - required this.rootComments, - required this.params, - }); + const _PgnTreeView({required this.root, required this.rootComments, required this.params}); /// Root of the PGN tree final ViewRoot root; @@ -327,18 +298,19 @@ class _PgnTreeView extends StatefulWidget { } /// A record that holds the rendered parts of a subtree. -typedef _CachedRenderedSubtree = ({ - /// The mainline part of the subtree. - _MainLinePart mainLinePart, - - /// The sidelines part of the subtree. - /// - /// This is nullable since the very last mainline part might not have any sidelines. - _IndentedSideLines? sidelines, - - /// Whether the subtree contains the current move. - bool containsCurrentMove, -}); +typedef _CachedRenderedSubtree = + ({ + /// The mainline part of the subtree. + _MainLinePart mainLinePart, + + /// The sidelines part of the subtree. + /// + /// This is nullable since the very last mainline part might not have any sidelines. + _IndentedSideLines? sidelines, + + /// Whether the subtree contains the current move. + bool containsCurrentMove, + }); class _PgnTreeViewState extends State<_PgnTreeView> { /// Caches the result of [_mainlineParts], it only needs to be recalculated when the root changes, @@ -362,68 +334,62 @@ class _PgnTreeViewState extends State<_PgnTreeView> { return path; } - List<_CachedRenderedSubtree> _buildChangedSubtrees({ - required bool fullRebuild, - }) { + List<_CachedRenderedSubtree> _buildChangedSubtrees({required bool fullRebuild}) { var path = UciPath.empty; - return mainlineParts.mapIndexed( - (i, mainlineNodes) { - final mainlineInitialPath = path; - - final sidelineInitialPath = UciPath.join( - path, - UciPath.fromIds( - mainlineNodes - .take(mainlineNodes.length - 1) - .map((n) => n.children.first.id), - ), - ); - - path = sidelineInitialPath; - if (mainlineNodes.last.children.isNotEmpty) { - path = path + mainlineNodes.last.children.first.id; - } - - final mainlinePartOfCurrentPath = _mainlinePartOfCurrentPath(); - final containsCurrentMove = - mainlinePartOfCurrentPath.size > mainlineInitialPath.size && - mainlinePartOfCurrentPath.size <= path.size; - - if (fullRebuild || - subtrees[i].containsCurrentMove || - containsCurrentMove) { - // Skip the first node which is the continuation of the mainline - final sidelineNodes = mainlineNodes.last.children.skip(1); - return ( - mainLinePart: _MainLinePart( - params: widget.params, - initialPath: mainlineInitialPath, - nodes: mainlineNodes, + return mainlineParts + .mapIndexed((i, mainlineNodes) { + final mainlineInitialPath = path; + + final sidelineInitialPath = UciPath.join( + path, + UciPath.fromIds( + mainlineNodes.take(mainlineNodes.length - 1).map((n) => n.children.first.id), ), - sidelines: sidelineNodes.isNotEmpty - ? _IndentedSideLines( - sidelineNodes, - parent: mainlineNodes.last, - params: widget.params, - initialPath: sidelineInitialPath, - nesting: 1, - ) - : null, - containsCurrentMove: containsCurrentMove, ); - } else { - // Avoid expensive rebuilds ([State.build]) of the entire PGN tree by caching parts of the tree that did not change across a path change - return subtrees[i]; - } - }, - ).toList(growable: false); + + path = sidelineInitialPath; + if (mainlineNodes.last.children.isNotEmpty) { + path = path + mainlineNodes.last.children.first.id; + } + + final mainlinePartOfCurrentPath = _mainlinePartOfCurrentPath(); + final containsCurrentMove = + mainlinePartOfCurrentPath.size > mainlineInitialPath.size && + mainlinePartOfCurrentPath.size <= path.size; + + if (fullRebuild || subtrees[i].containsCurrentMove || containsCurrentMove) { + // Skip the first node which is the continuation of the mainline + final sidelineNodes = mainlineNodes.last.children.skip(1); + return ( + mainLinePart: _MainLinePart( + params: widget.params, + initialPath: mainlineInitialPath, + nodes: mainlineNodes, + ), + sidelines: + sidelineNodes.isNotEmpty + ? _IndentedSideLines( + sidelineNodes, + parent: mainlineNodes.last, + params: widget.params, + initialPath: sidelineInitialPath, + nesting: 1, + ) + : null, + containsCurrentMove: containsCurrentMove, + ); + } else { + // Avoid expensive rebuilds ([State.build]) of the entire PGN tree by caching parts of the tree that did not change across a path change + return subtrees[i]; + } + }) + .toList(growable: false); } void _updateLines({required bool fullRebuild}) { setState(() { if (fullRebuild) { - mainlineParts = - _mainlineParts(widget.root, widget.params).toList(growable: false); + mainlineParts = _mainlineParts(widget.root, widget.params).toList(growable: false); } subtrees = _buildChangedSubtrees(fullRebuild: fullRebuild); @@ -440,13 +406,12 @@ class _PgnTreeViewState extends State<_PgnTreeView> { void didUpdateWidget(covariant _PgnTreeView oldWidget) { super.didUpdateWidget(oldWidget); _updateLines( - fullRebuild: oldWidget.root != widget.root || + fullRebuild: + oldWidget.root != widget.root || oldWidget.params.shouldShowComputerVariations != widget.params.shouldShowComputerVariations || - oldWidget.params.shouldShowComments != - widget.params.shouldShowComments || - oldWidget.params.shouldShowAnnotations != - widget.params.shouldShowAnnotations, + oldWidget.params.shouldShowComments != widget.params.shouldShowComments || + oldWidget.params.shouldShowAnnotations != widget.params.shouldShowAnnotations, ); } @@ -463,21 +428,9 @@ class _PgnTreeViewState extends State<_PgnTreeView> { SizedBox.shrink(key: widget.params.currentMoveKey), if (widget.params.shouldShowComments && rootComments.isNotEmpty) - Text.rich( - TextSpan( - children: _comments( - rootComments, - textStyle: _baseTextStyle, - ), - ), - ), + Text.rich(TextSpan(children: _comments(rootComments, textStyle: _baseTextStyle))), ...subtrees - .map( - (part) => [ - part.mainLinePart, - if (part.sidelines != null) part.sidelines!, - ], - ) + .map((part) => [part.mainLinePart, if (part.sidelines != null) part.sidelines!]) .flattened, ], ), @@ -502,48 +455,34 @@ List _buildInlineSideLine({ var path = initialPath; return [ if (followsComment) const WidgetSpan(child: SizedBox(width: 4.0)), - ...sidelineNodes.mapIndexedAndLast( - (i, node, last) { - final pathToNode = path; - path = path + node.id; - - return [ - if (i == 0) ...[ - if (followsComment) const WidgetSpan(child: SizedBox(width: 4.0)), - TextSpan( - text: '(', - style: textStyle, - ), - ], - ..._moveWithComment( - node, - lineInfo: ( - type: _LineType.inlineSideline, - startLine: i == 0 || - (params.shouldShowComments && - sidelineNodes[i - 1].hasTextComment), - pathToLine: initialPath, - ), - pathToNode: pathToNode, - textStyle: textStyle, - params: params, + ...sidelineNodes.mapIndexedAndLast((i, node, last) { + final pathToNode = path; + path = path + node.id; + + return [ + if (i == 0) ...[ + if (followsComment) const WidgetSpan(child: SizedBox(width: 4.0)), + TextSpan(text: '(', style: textStyle), + ], + ..._moveWithComment( + node, + lineInfo: ( + type: _LineType.inlineSideline, + startLine: i == 0 || (params.shouldShowComments && sidelineNodes[i - 1].hasTextComment), + pathToLine: initialPath, ), - if (last) - TextSpan( - text: ')', - style: textStyle, - ), - ]; - }, - ).flattened, + pathToNode: pathToNode, + textStyle: textStyle, + params: params, + ), + if (last) TextSpan(text: ')', style: textStyle), + ]; + }).flattened, const WidgetSpan(child: SizedBox(width: 4.0)), ]; } -const _baseTextStyle = TextStyle( - fontSize: 16.0, - height: 1.5, -); +const _baseTextStyle = TextStyle(fontSize: 16.0, height: 1.5); /// The different types of lines (move sequences) that are displayed in the tree view. enum _LineType { @@ -625,52 +564,41 @@ class _SideLinePart extends ConsumerWidget { final moves = [ ..._moveWithComment( nodes.first, - lineInfo: ( - type: _LineType.sideline, - startLine: true, - pathToLine: initialPath, - ), + lineInfo: (type: _LineType.sideline, startLine: true, pathToLine: initialPath), firstMoveKey: firstMoveKey, pathToNode: initialPath, textStyle: textStyle, params: params, ), - ...nodes.take(nodes.length - 1).map( - (node) { - final moves = [ - ..._moveWithComment( - node.children.first, - lineInfo: ( - type: _LineType.sideline, - startLine: params.shouldShowComments && node.hasTextComment, - pathToLine: initialPath, - ), - pathToNode: path, + ...nodes.take(nodes.length - 1).map((node) { + final moves = [ + ..._moveWithComment( + node.children.first, + lineInfo: ( + type: _LineType.sideline, + startLine: params.shouldShowComments && node.hasTextComment, + pathToLine: initialPath, + ), + pathToNode: path, + textStyle: textStyle, + params: params, + ), + if (node.children.length == 2 && _displaySideLineAsInline(node.children[1])) + ..._buildInlineSideLine( + followsComment: node.children.first.hasTextComment, + firstNode: node.children[1], + parent: node, + initialPath: path, textStyle: textStyle, params: params, ), - if (node.children.length == 2 && - _displaySideLineAsInline(node.children[1])) - ..._buildInlineSideLine( - followsComment: node.children.first.hasTextComment, - firstNode: node.children[1], - parent: node, - initialPath: path, - textStyle: textStyle, - params: params, - ), - ]; - path = path + node.children.first.id; - return moves; - }, - ).flattened, + ]; + path = path + node.children.first.id; + return moves; + }).flattened, ]; - return Text.rich( - TextSpan( - children: moves, - ), - ); + return Text.rich(TextSpan(children: moves)); } } @@ -684,11 +612,7 @@ class _SideLinePart extends ConsumerWidget { /// |- 1... Nc6 <-- sideline part /// 2. Nf3 Nc6 (2... a5) 3. Bc4 <-- mainline part class _MainLinePart extends ConsumerWidget { - const _MainLinePart({ - required this.initialPath, - required this.params, - required this.nodes, - }); + const _MainLinePart({required this.initialPath, required this.params, required this.nodes}); final UciPath initialPath; @@ -698,56 +622,46 @@ class _MainLinePart extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final textStyle = _baseTextStyle.copyWith( - color: _textColor(context, 0.9), - ); + final textStyle = _baseTextStyle.copyWith(color: _textColor(context, 0.9)); var path = initialPath; return Text.rich( TextSpan( children: nodes .takeWhile( - (node) => - _filteredChildren(node, params.shouldShowComputerVariations) - .isNotEmpty, + (node) => _filteredChildren(node, params.shouldShowComputerVariations).isNotEmpty, ) - .mapIndexed( - (i, node) { - final children = _filteredChildren( - node, - params.shouldShowComputerVariations, - ); - final mainlineNode = children.first; - final moves = [ - _moveWithComment( - mainlineNode, - lineInfo: ( - type: _LineType.mainline, - startLine: i == 0 || - (params.shouldShowComments && - (node as ViewBranch).hasTextComment), - pathToLine: initialPath, - ), - pathToNode: path, + .mapIndexed((i, node) { + final children = _filteredChildren(node, params.shouldShowComputerVariations); + final mainlineNode = children.first; + final moves = [ + _moveWithComment( + mainlineNode, + lineInfo: ( + type: _LineType.mainline, + startLine: + i == 0 || + (params.shouldShowComments && (node as ViewBranch).hasTextComment), + pathToLine: initialPath, + ), + pathToNode: path, + textStyle: textStyle, + params: params, + ), + if (children.length == 2 && _displaySideLineAsInline(children[1])) ...[ + _buildInlineSideLine( + followsComment: mainlineNode.hasTextComment, + firstNode: children[1], + parent: node, + initialPath: path, textStyle: textStyle, params: params, ), - if (children.length == 2 && - _displaySideLineAsInline(children[1])) ...[ - _buildInlineSideLine( - followsComment: mainlineNode.hasTextComment, - firstNode: children[1], - parent: node, - initialPath: path, - textStyle: textStyle, - params: params, - ), - ], - ]; - path = path + mainlineNode.id; - return moves.flattened; - }, - ) + ], + ]; + path = path + mainlineNode.id; + return moves.flattened; + }) .flattened .toList(growable: false), ), @@ -833,11 +747,12 @@ class _IndentPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { if (sideLineStartPositions.isNotEmpty) { - final paint = Paint() - ..strokeWidth = 1.5 - ..color = color - ..strokeCap = StrokeCap.round - ..style = PaintingStyle.stroke; + final paint = + Paint() + ..strokeWidth = 1.5 + ..color = color + ..strokeCap = StrokeCap.round + ..style = PaintingStyle.stroke; final origin = Offset(-padding, 0); @@ -913,18 +828,18 @@ class _IndentedSideLinesState extends State<_IndentedSideLines> { (_) => GlobalKey(), ); WidgetsBinding.instance.addPostFrameCallback((_) { - final RenderBox? columnBox = - _columnKey.currentContext?.findRenderObject() as RenderBox?; - final Offset rowOffset = - columnBox?.localToGlobal(Offset.zero) ?? Offset.zero; - - final positions = _sideLinesStartKeys.map((key) { - final context = key.currentContext; - final renderBox = context?.findRenderObject() as RenderBox?; - final height = renderBox?.size.height ?? 0; - final offset = renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; - return Offset(offset.dx, offset.dy + height / 2) - rowOffset; - }).toList(growable: false); + final RenderBox? columnBox = _columnKey.currentContext?.findRenderObject() as RenderBox?; + final Offset rowOffset = columnBox?.localToGlobal(Offset.zero) ?? Offset.zero; + + final positions = _sideLinesStartKeys + .map((key) { + final context = key.currentContext; + final renderBox = context?.findRenderObject() as RenderBox?; + final height = renderBox?.size.height ?? 0; + final offset = renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; + return Offset(offset.dx, offset.dy + height / 2) - rowOffset; + }) + .toList(growable: false); setState(() { _sideLineStartPositions = positions; @@ -932,8 +847,7 @@ class _IndentedSideLinesState extends State<_IndentedSideLines> { }); } - bool get _hasCollapsedLines => - widget.sideLines.any((node) => node.isCollapsed); + bool get _hasCollapsedLines => widget.sideLines.any((node) => node.isCollapsed); Iterable get _expandedSidelines => widget.sideLines.whereNot((node) => node.isCollapsed); @@ -1001,18 +915,11 @@ class _IndentedSideLinesState extends State<_IndentedSideLines> { } } -Color? _textColor( - BuildContext context, - double opacity, { - int? nag, -}) { - final defaultColor = Theme.of(context).platform == TargetPlatform.android - ? Theme.of(context).textTheme.bodyLarge?.color?.withValues(alpha: opacity) - : CupertinoTheme.of(context) - .textTheme - .textStyle - .color - ?.withValues(alpha: opacity); +Color? _textColor(BuildContext context, double opacity, {int? nag}) { + final defaultColor = + Theme.of(context).platform == TargetPlatform.android + ? Theme.of(context).textTheme.bodyLarge?.color?.withValues(alpha: opacity) + : CupertinoTheme.of(context).textTheme.textStyle.color?.withValues(alpha: opacity); return nag != null && nag > 0 ? nagColor(nag) : defaultColor; } @@ -1048,59 +955,45 @@ class InlineMove extends ConsumerWidget { bool get isBroadcastLiveMove => params.pathToBroadcastLiveMove == path; - BoxDecoration? _boxDecoration( - BuildContext context, - bool isCurrentMove, - bool isLiveMove, - ) { + BoxDecoration? _boxDecoration(BuildContext context, bool isCurrentMove, bool isLiveMove) { return (isCurrentMove || isLiveMove) ? BoxDecoration( - color: isCurrentMove - ? Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoColors.systemGrey3.resolveFrom(context) - : Theme.of(context).focusColor - : null, - shape: BoxShape.rectangle, - borderRadius: borderRadius, - border: - isLiveMove ? Border.all(width: 2, color: Colors.orange) : null, - ) + color: + isCurrentMove + ? Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoColors.systemGrey3.resolveFrom(context) + : Theme.of(context).focusColor + : null, + shape: BoxShape.rectangle, + borderRadius: borderRadius, + border: isLiveMove ? Border.all(width: 2, color: Colors.orange) : null, + ) : null; } @override Widget build(BuildContext context, WidgetRef ref) { - final pieceNotation = ref.watch(pieceNotationProvider).maybeWhen( - data: (value) => value, - orElse: () => defaultAccountPreferences.pieceNotation, - ); - final moveFontFamily = - pieceNotation == PieceNotation.symbol ? 'ChessFont' : null; + final pieceNotation = ref + .watch(pieceNotationProvider) + .maybeWhen(data: (value) => value, orElse: () => defaultAccountPreferences.pieceNotation); + final moveFontFamily = pieceNotation == PieceNotation.symbol ? 'ChessFont' : null; final moveTextStyle = textStyle.copyWith( fontFamily: moveFontFamily, - fontWeight: lineInfo.type == _LineType.inlineSideline - ? FontWeight.normal - : FontWeight.w600, + fontWeight: lineInfo.type == _LineType.inlineSideline ? FontWeight.normal : FontWeight.w600, ); - final indexTextStyle = textStyle.copyWith( - color: _textColor(context, 0.6), - ); + final indexTextStyle = textStyle.copyWith(color: _textColor(context, 0.6)); - final indexText = branch.position.ply.isOdd - ? TextSpan( - text: '${(branch.position.ply / 2).ceil()}. ', - style: indexTextStyle, - ) - : (lineInfo.startLine - ? TextSpan( - text: '${(branch.position.ply / 2).ceil()}… ', - style: indexTextStyle, - ) - : null); - - final moveWithNag = branch.sanMove.san + + final indexText = + branch.position.ply.isOdd + ? TextSpan(text: '${(branch.position.ply / 2).ceil()}. ', style: indexTextStyle) + : (lineInfo.startLine + ? TextSpan(text: '${(branch.position.ply / 2).ceil()}… ', style: indexTextStyle) + : null); + + final moveWithNag = + branch.sanMove.san + (branch.nags != null && params.shouldShowAnnotations ? moveAnnotationChar(branch.nags!) : ''); @@ -1118,15 +1011,17 @@ class InlineMove extends ConsumerWidget { isDismissible: true, isScrollControlled: true, showDragHandle: true, - builder: (context) => _MoveContextMenu( - notifier: params.notifier, - title: ply.isOdd - ? '${(ply / 2).ceil()}. $moveWithNag' - : '${(ply / 2).ceil()}... $moveWithNag', - path: path, - branch: branch, - lineInfo: lineInfo, - ), + builder: + (context) => _MoveContextMenu( + notifier: params.notifier, + title: + ply.isOdd + ? '${(ply / 2).ceil()}. $moveWithNag' + : '${(ply / 2).ceil()}... $moveWithNag', + path: path, + branch: branch, + lineInfo: lineInfo, + ), ); }, child: Container( @@ -1135,17 +1030,11 @@ class InlineMove extends ConsumerWidget { child: Text.rich( TextSpan( children: [ - if (indexText != null) ...[ - indexText, - ], + if (indexText != null) ...[indexText], TextSpan( text: moveWithNag, style: moveTextStyle.copyWith( - color: _textColor( - context, - isCurrentMove ? 1 : 0.9, - nag: nag, - ), + color: _textColor(context, isCurrentMove ? 1 : 0.9, nag: nag), ), ), ], @@ -1181,10 +1070,7 @@ class _MoveContextMenu extends ConsumerWidget { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - title, - style: Theme.of(context).textTheme.titleLarge, - ), + Text(title, style: Theme.of(context).textTheme.titleLarge), if (branch.clock != null) Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1192,14 +1078,11 @@ class _MoveContextMenu extends ConsumerWidget { Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon( - Icons.punch_clock, - ), + const Icon(Icons.punch_clock), const SizedBox(width: 4.0), Text( branch.clock!.toHoursMinutesSeconds( - showTenths: - branch.clock! < const Duration(minutes: 1), + showTenths: branch.clock! < const Duration(minutes: 1), ), ), ], @@ -1209,14 +1092,9 @@ class _MoveContextMenu extends ConsumerWidget { Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon( - Icons.hourglass_bottom, - ), + const Icon(Icons.hourglass_bottom), const SizedBox(width: 4.0), - Text( - branch.elapsedMoveTime! - .toHoursMinutesSeconds(showTenths: true), - ), + Text(branch.elapsedMoveTime!.toHoursMinutesSeconds(showTenths: true)), ], ), ], @@ -1227,13 +1105,8 @@ class _MoveContextMenu extends ConsumerWidget { ), if (branch.hasTextComment) Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - child: Text( - branch.textComments.join(' '), - ), + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Text(branch.textComments.join(' ')), ), const PlatformDivider(indent: 0), if (lineInfo.type != _LineType.mainline) ...[ @@ -1263,17 +1136,9 @@ class _MoveContextMenu extends ConsumerWidget { } } -List _comments( - Iterable comments, { - required TextStyle textStyle, -}) => - comments - .map( - (comment) => TextSpan( - text: comment, - style: textStyle.copyWith( - fontSize: textStyle.fontSize! - 2.0, - ), - ), - ) - .toList(growable: false); +List _comments(Iterable comments, {required TextStyle textStyle}) => comments + .map( + (comment) => + TextSpan(text: comment, style: textStyle.copyWith(fontSize: textStyle.fontSize! - 2.0)), + ) + .toList(growable: false); diff --git a/lib/src/widgets/platform.dart b/lib/src/widgets/platform.dart index 35e6783100..5fbc4e265b 100644 --- a/lib/src/widgets/platform.dart +++ b/lib/src/widgets/platform.dart @@ -5,11 +5,7 @@ import 'package:lichess_mobile/src/styles/styles.dart'; /// A simple widget that builds different things on different platforms. class PlatformWidget extends StatelessWidget { - const PlatformWidget({ - super.key, - required this.androidBuilder, - required this.iosBuilder, - }); + const PlatformWidget({super.key, required this.androidBuilder, required this.iosBuilder}); final WidgetBuilder androidBuilder; final WidgetBuilder iosBuilder; @@ -28,10 +24,7 @@ class PlatformWidget extends StatelessWidget { } } -typedef ConsumerWidgetBuilder = Widget Function( - BuildContext context, - WidgetRef ref, -); +typedef ConsumerWidgetBuilder = Widget Function(BuildContext context, WidgetRef ref); /// A widget that builds different things on different platforms with riverpod. class ConsumerPlatformWidget extends StatelessWidget { @@ -94,39 +87,38 @@ class PlatformCard extends StatelessWidget { Widget build(BuildContext context) { return MediaQuery.withClampedTextScaling( maxScaleFactor: kCardTextScaleFactor, - child: Theme.of(context).platform == TargetPlatform.iOS - ? Card( - margin: margin ?? EdgeInsets.zero, - elevation: elevation ?? 0, - color: color ?? Styles.cupertinoCardColor.resolveFrom(context), - shadowColor: shadowColor, - shape: borderRadius != null - ? RoundedRectangleBorder( - borderRadius: borderRadius!, - ) - : const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(10.0)), - ), - semanticContainer: semanticContainer, - clipBehavior: clipBehavior, - child: child, - ) - : Card( - shape: borderRadius != null - ? RoundedRectangleBorder( - borderRadius: borderRadius!, - ) - : const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(10.0)), - ), - color: color, - shadowColor: shadowColor, - semanticContainer: semanticContainer, - elevation: elevation, - margin: margin, - clipBehavior: clipBehavior, - child: child, - ), + child: + Theme.of(context).platform == TargetPlatform.iOS + ? Card( + margin: margin ?? EdgeInsets.zero, + elevation: elevation ?? 0, + color: color ?? Styles.cupertinoCardColor.resolveFrom(context), + shadowColor: shadowColor, + shape: + borderRadius != null + ? RoundedRectangleBorder(borderRadius: borderRadius!) + : const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + semanticContainer: semanticContainer, + clipBehavior: clipBehavior, + child: child, + ) + : Card( + shape: + borderRadius != null + ? RoundedRectangleBorder(borderRadius: borderRadius!) + : const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + color: color, + shadowColor: shadowColor, + semanticContainer: semanticContainer, + elevation: elevation, + margin: margin, + clipBehavior: clipBehavior, + child: child, + ), ); } } diff --git a/lib/src/widgets/platform_alert_dialog.dart b/lib/src/widgets/platform_alert_dialog.dart index 7b135aacc3..9738f40f27 100644 --- a/lib/src/widgets/platform_alert_dialog.dart +++ b/lib/src/widgets/platform_alert_dialog.dart @@ -23,16 +23,9 @@ class PlatformAlertDialog extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformWidget( - androidBuilder: (context) => AlertDialog( - title: title, - content: content, - actions: actions, - ), - iosBuilder: (context) => CupertinoAlertDialog( - title: title, - content: content, - actions: actions, - ), + androidBuilder: (context) => AlertDialog(title: title, content: content, actions: actions), + iosBuilder: + (context) => CupertinoAlertDialog(title: title, content: content, actions: actions), ); } } @@ -62,16 +55,14 @@ class PlatformDialogAction extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformWidget( - androidBuilder: (context) => TextButton( - onPressed: onPressed, - child: child, - ), - iosBuilder: (context) => CupertinoDialogAction( - onPressed: onPressed, - isDefaultAction: cupertinoIsDefaultAction, - isDestructiveAction: cupertinoIsDestructiveAction, - child: child, - ), + androidBuilder: (context) => TextButton(onPressed: onPressed, child: child), + iosBuilder: + (context) => CupertinoDialogAction( + onPressed: onPressed, + isDefaultAction: cupertinoIsDefaultAction, + isDestructiveAction: cupertinoIsDestructiveAction, + child: child, + ), ); } } diff --git a/lib/src/widgets/platform_scaffold.dart b/lib/src/widgets/platform_scaffold.dart index e8a0913e9c..0edfc5d194 100644 --- a/lib/src/widgets/platform_scaffold.dart +++ b/lib/src/widgets/platform_scaffold.dart @@ -2,10 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; -const kCupertinoAppBarWithActionPadding = EdgeInsetsDirectional.only( - start: 16.0, - end: 8.0, -); +const kCupertinoAppBarWithActionPadding = EdgeInsetsDirectional.only(start: 16.0, end: 8.0); /// Displays an [AppBar] for Android and a [CupertinoNavigationBar] for iOS. /// @@ -56,19 +53,13 @@ class PlatformAppBar extends StatelessWidget { return CupertinoNavigationBar( padding: actions.isNotEmpty ? kCupertinoAppBarWithActionPadding : null, middle: title, - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: actions, - ), + trailing: Row(mainAxisSize: MainAxisSize.min, children: actions), ); } @override Widget build(BuildContext context) { - return PlatformWidget( - androidBuilder: _androidBuilder, - iosBuilder: _iosBuilder, - ); + return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); } } @@ -80,25 +71,21 @@ class PlatformAppBarLoadingIndicator extends StatelessWidget { Widget build(BuildContext context) { return PlatformWidget( iosBuilder: (_) => const CircularProgressIndicator.adaptive(), - androidBuilder: (_) => const Padding( - padding: EdgeInsets.only(right: 16), - child: SizedBox( - height: 24, - width: 24, - child: Center( - child: CircularProgressIndicator(), + androidBuilder: + (_) => const Padding( + padding: EdgeInsets.only(right: 16), + child: SizedBox( + height: 24, + width: 24, + child: Center(child: CircularProgressIndicator()), + ), ), - ), - ), ); } } -class _CupertinoNavBarWrapper extends StatelessWidget - implements ObstructingPreferredSizeWidget { - const _CupertinoNavBarWrapper({ - required this.child, - }); +class _CupertinoNavBarWrapper extends StatelessWidget implements ObstructingPreferredSizeWidget { + const _CupertinoNavBarWrapper({required this.child}); final Widget child; @@ -106,8 +93,7 @@ class _CupertinoNavBarWrapper extends StatelessWidget Widget build(BuildContext context) => child; @override - Size get preferredSize => - const Size.fromHeight(kMinInteractiveDimensionCupertino); + Size get preferredSize => const Size.fromHeight(kMinInteractiveDimensionCupertino); /// True if the navigation bar's background color has no transparency. @override @@ -145,12 +131,10 @@ class PlatformScaffold extends StatelessWidget { Widget _androidBuilder(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: resizeToAvoidBottomInset, - appBar: appBar != null - ? PreferredSize( - preferredSize: const Size.fromHeight(kToolbarHeight), - child: appBar!, - ) - : null, + appBar: + appBar != null + ? PreferredSize(preferredSize: const Size.fromHeight(kToolbarHeight), child: appBar!) + : null, body: body, ); } @@ -158,17 +142,13 @@ class PlatformScaffold extends StatelessWidget { Widget _iosBuilder(BuildContext context) { return CupertinoPageScaffold( resizeToAvoidBottomInset: resizeToAvoidBottomInset, - navigationBar: - appBar != null ? _CupertinoNavBarWrapper(child: appBar!) : null, + navigationBar: appBar != null ? _CupertinoNavBarWrapper(child: appBar!) : null, child: body, ); } @override Widget build(BuildContext context) { - return PlatformWidget( - androidBuilder: _androidBuilder, - iosBuilder: _iosBuilder, - ); + return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); } } diff --git a/lib/src/widgets/platform_search_bar.dart b/lib/src/widgets/platform_search_bar.dart index a5b67b1e86..bc493bce12 100644 --- a/lib/src/widgets/platform_search_bar.dart +++ b/lib/src/widgets/platform_search_bar.dart @@ -45,34 +45,34 @@ class PlatformSearchBar extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformWidget( - androidBuilder: (context) => SearchBar( - controller: controller, - leading: const Icon(Icons.search), - trailing: [ - if (controller?.text.isNotEmpty == true) - IconButton( - onPressed: onClear ?? () => controller?.clear(), - tooltip: 'Clear', - icon: const Icon( - Icons.close, - ), - ), - ], - onTap: onTap, - focusNode: focusNode, - onSubmitted: onSubmitted, - hintText: hintText, - autoFocus: autoFocus, - ), - iosBuilder: (context) => CupertinoSearchTextField( - controller: controller, - onTap: onTap, - focusNode: focusNode, - onSuffixTap: onClear, - onSubmitted: onSubmitted, - placeholder: hintText, - autofocus: autoFocus, - ), + androidBuilder: + (context) => SearchBar( + controller: controller, + leading: const Icon(Icons.search), + trailing: [ + if (controller?.text.isNotEmpty == true) + IconButton( + onPressed: onClear ?? () => controller?.clear(), + tooltip: 'Clear', + icon: const Icon(Icons.close), + ), + ], + onTap: onTap, + focusNode: focusNode, + onSubmitted: onSubmitted, + hintText: hintText, + autoFocus: autoFocus, + ), + iosBuilder: + (context) => CupertinoSearchTextField( + controller: controller, + onTap: onTap, + focusNode: focusNode, + onSuffixTap: onClear, + onSubmitted: onSubmitted, + placeholder: hintText, + autofocus: autoFocus, + ), ); } } diff --git a/lib/src/widgets/progression_widget.dart b/lib/src/widgets/progression_widget.dart index 544c48ecb1..64352d2b10 100644 --- a/lib/src/widgets/progression_widget.dart +++ b/lib/src/widgets/progression_widget.dart @@ -17,30 +17,21 @@ class ProgressionWidget extends StatelessWidget { children: [ if (progress != 0) ...[ Icon( - progress > 0 - ? LichessIcons.arrow_full_upperright - : LichessIcons.arrow_full_lowerright, + progress > 0 ? LichessIcons.arrow_full_upperright : LichessIcons.arrow_full_lowerright, size: fontSize, - color: progress > 0 - ? context.lichessColors.good - : context.lichessColors.error, + color: progress > 0 ? context.lichessColors.good : context.lichessColors.error, ), Text( progress.abs().toString(), style: TextStyle( - color: progress > 0 - ? context.lichessColors.good - : context.lichessColors.error, + color: progress > 0 ? context.lichessColors.good : context.lichessColors.error, fontSize: fontSize, ), ), ] else Text( '0', - style: TextStyle( - color: textShade(context, _customOpacity), - fontSize: fontSize, - ), + style: TextStyle(color: textShade(context, _customOpacity), fontSize: fontSize), ), ], ); diff --git a/lib/src/widgets/settings.dart b/lib/src/widgets/settings.dart index 2100a27873..b9daf76648 100644 --- a/lib/src/widgets/settings.dart +++ b/lib/src/widgets/settings.dart @@ -43,24 +43,21 @@ class SettingsListTile extends StatelessWidget { leading: icon, title: _SettingsTitle(title: settingsLabel), additionalInfo: showCupertinoTrailingValue ? Text(settingsValue) : null, - subtitle: Theme.of(context).platform == TargetPlatform.android - ? Text( - settingsValue, - style: TextStyle( - color: textShade(context, Styles.subtitleOpacity), - ), - ) - : explanation != null + subtitle: + Theme.of(context).platform == TargetPlatform.android + ? Text( + settingsValue, + style: TextStyle(color: textShade(context, Styles.subtitleOpacity)), + ) + : explanation != null ? Text(explanation!, maxLines: 5) : null, onTap: onTap, - trailing: Theme.of(context).platform == TargetPlatform.iOS - ? const CupertinoListTileChevron() - : explanation != null - ? _SettingsInfoTooltip( - message: explanation!, - child: const Icon(Icons.info_outline), - ) + trailing: + Theme.of(context).platform == TargetPlatform.iOS + ? const CupertinoListTileChevron() + : explanation != null + ? _SettingsInfoTooltip(message: explanation!, child: const Icon(Icons.info_outline)) : null, ), ); @@ -89,11 +86,7 @@ class SwitchSettingTile extends StatelessWidget { leading: leading, title: _SettingsTitle(title: title), subtitle: subtitle, - trailing: Switch.adaptive( - value: value, - onChanged: onChanged, - applyCupertinoTheme: true, - ), + trailing: Switch.adaptive(value: value, onChanged: onChanged, applyCupertinoTheme: true), ); } } @@ -127,8 +120,7 @@ class _SliderSettingsTileState extends State { min: 0, max: widget.values.length.toDouble() - 1, divisions: widget.values.length - 1, - label: widget.labelBuilder?.call(widget.values[_index]) ?? - widget.values[_index].toString(), + label: widget.labelBuilder?.call(widget.values[_index]) ?? widget.values[_index].toString(), onChanged: (value) { final newIndex = value.toInt(); setState(() { @@ -143,9 +135,10 @@ class _SliderSettingsTileState extends State { return PlatformListTile( leading: widget.icon, title: slider, - trailing: widget.labelBuilder != null - ? Text(widget.labelBuilder!.call(widget.values[_index])) - : null, + trailing: + widget.labelBuilder != null + ? Text(widget.labelBuilder!.call(widget.values[_index])) + : null, ); } } @@ -159,22 +152,20 @@ class SettingsSectionTitle extends StatelessWidget { Widget build(BuildContext context) { return Text( title, - style: Theme.of(context).platform == TargetPlatform.iOS - ? TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: CupertinoColors.secondaryLabel.resolveFrom(context), - ) - : null, + style: + Theme.of(context).platform == TargetPlatform.iOS + ? TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: CupertinoColors.secondaryLabel.resolveFrom(context), + ) + : null, ); } } class _SettingsInfoTooltip extends StatelessWidget { - const _SettingsInfoTooltip({ - required this.message, - required this.child, - }); + const _SettingsInfoTooltip({required this.message, required this.child}); final String message; final Widget child; @@ -200,20 +191,13 @@ class _SettingsTitle extends StatelessWidget { Widget build(BuildContext context) { return DefaultTextStyle.merge( // forces iOS default font size - style: Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoTheme.of(context).textTheme.textStyle.copyWith( - fontSize: 17.0, - ) - : null, + style: + Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 17.0) + : null, maxLines: 2, overflow: TextOverflow.ellipsis, - child: Text.rich( - TextSpan( - children: [ - title.textSpan ?? TextSpan(text: title.data), - ], - ), - ), + child: Text.rich(TextSpan(children: [title.textSpan ?? TextSpan(text: title.data)])), ); } } @@ -268,9 +252,7 @@ class ChoicePicker extends StatelessWidget { title: titleBuilder(value), subtitle: subtitleBuilder?.call(value), leading: leadingBuilder?.call(value), - onTap: onSelectedItemChanged != null - ? () => onSelectedItemChanged!(value) - : null, + onTap: onSelectedItemChanged != null ? () => onSelectedItemChanged!(value) : null, ); }); return Opacity( @@ -278,47 +260,45 @@ class ChoicePicker extends StatelessWidget { child: Column( children: [ if (showDividerBetweenTiles) - ...ListTile.divideTiles( - context: context, - tiles: tiles, - ) + ...ListTile.divideTiles(context: context, tiles: tiles) else ...tiles, ], ), ); case TargetPlatform.iOS: - final tileConstructor = - notchedTile ? CupertinoListTile.notched : CupertinoListTile.new; + final tileConstructor = notchedTile ? CupertinoListTile.notched : CupertinoListTile.new; return Padding( padding: margin ?? Styles.bodySectionPadding, child: Opacity( opacity: onSelectedItemChanged != null ? 1.0 : 0.5, child: CupertinoListSection.insetGrouped( - backgroundColor: - CupertinoTheme.of(context).scaffoldBackgroundColor, + backgroundColor: CupertinoTheme.of(context).scaffoldBackgroundColor, decoration: BoxDecoration( color: Styles.cupertinoCardColor.resolveFrom(context), borderRadius: const BorderRadius.all(Radius.circular(10.0)), ), - separatorColor: - Styles.cupertinoSeparatorColor.resolveFrom(context), + separatorColor: Styles.cupertinoSeparatorColor.resolveFrom(context), margin: EdgeInsets.zero, additionalDividerMargin: notchedTile ? null : 6.0, hasLeading: leadingBuilder != null, - children: choices.map((value) { - return tileConstructor( - trailing: selectedItem == value - ? const Icon(CupertinoIcons.check_mark_circled_solid) - : null, - title: titleBuilder(value), - subtitle: subtitleBuilder?.call(value), - leading: leadingBuilder?.call(value), - onTap: onSelectedItemChanged != null - ? () => onSelectedItemChanged!(value) - : null, - ); - }).toList(growable: false), + children: choices + .map((value) { + return tileConstructor( + trailing: + selectedItem == value + ? const Icon(CupertinoIcons.check_mark_circled_solid) + : null, + title: titleBuilder(value), + subtitle: subtitleBuilder?.call(value), + leading: leadingBuilder?.call(value), + onTap: + onSelectedItemChanged != null + ? () => onSelectedItemChanged!(value) + : null, + ); + }) + .toList(growable: false), ), ), ); diff --git a/lib/src/widgets/shimmer.dart b/lib/src/widgets/shimmer.dart index a7d3e566d7..b4e86f3be3 100644 --- a/lib/src/widgets/shimmer.dart +++ b/lib/src/widgets/shimmer.dart @@ -5,10 +5,7 @@ class Shimmer extends StatefulWidget { return context.findAncestorStateOfType(); } - const Shimmer({ - super.key, - this.child, - }); + const Shimmer({super.key, this.child}); final Widget? child; @@ -30,26 +27,21 @@ class ShimmerState extends State with SingleTickerProviderStateMixin { } LinearGradient get gradient => LinearGradient( - colors: _defaultGradient.colors, - stops: _defaultGradient.stops, - begin: _defaultGradient.begin, - end: _defaultGradient.end, - transform: - _SlidingGradientTransform(slidePercent: _shimmerController.value), - ); + colors: _defaultGradient.colors, + stops: _defaultGradient.stops, + begin: _defaultGradient.begin, + end: _defaultGradient.end, + transform: _SlidingGradientTransform(slidePercent: _shimmerController.value), + ); Listenable get shimmerChanges => _shimmerController; - bool get isSized => - (context.findRenderObject() as RenderBox?)?.hasSize ?? false; + bool get isSized => (context.findRenderObject() as RenderBox?)?.hasSize ?? false; // ignore: cast_nullable_to_non_nullable Size get size => (context.findRenderObject() as RenderBox).size; - Offset getDescendantOffset({ - required RenderBox descendant, - Offset offset = Offset.zero, - }) { + Offset getDescendantOffset({required RenderBox descendant, Offset offset = Offset.zero}) { // ignore: cast_nullable_to_non_nullable final shimmerBox = context.findRenderObject() as RenderBox; return descendant.localToGlobal(offset, ancestor: shimmerBox); @@ -76,11 +68,7 @@ class ShimmerState extends State with SingleTickerProviderStateMixin { } class ShimmerLoading extends StatefulWidget { - const ShimmerLoading({ - super.key, - required this.isLoading, - required this.child, - }); + const ShimmerLoading({super.key, required this.isLoading, required this.child}); final bool isLoading; final Widget child; @@ -131,12 +119,13 @@ class _ShimmerLoadingState extends State { final shimmerSize = shimmer.size; final gradient = shimmer.gradient; final renderObject = context.findRenderObject(); - final offsetWithinShimmer = renderObject != null - ? shimmer.getDescendantOffset( - // ignore: cast_nullable_to_non_nullable - descendant: renderObject as RenderBox, - ) - : Offset.zero; + final offsetWithinShimmer = + renderObject != null + ? shimmer.getDescendantOffset( + // ignore: cast_nullable_to_non_nullable + descendant: renderObject as RenderBox, + ) + : Offset.zero; return ShaderMask( blendMode: BlendMode.srcATop, @@ -156,41 +145,23 @@ class _ShimmerLoadingState extends State { } const lightShimmerGradient = LinearGradient( - colors: [ - Color(0xFFE3E3E6), - Color(0xFFECECEE), - Color(0xFFE3E3E6), - ], - stops: [ - 0.1, - 0.3, - 0.4, - ], + colors: [Color(0xFFE3E3E6), Color(0xFFECECEE), Color(0xFFE3E3E6)], + stops: [0.1, 0.3, 0.4], begin: Alignment(-1.0, -0.3), end: Alignment(1.0, 0.3), tileMode: TileMode.clamp, ); const darkShimmerGradient = LinearGradient( - colors: [ - Color(0xFF333333), - Color(0xFF3c3c3c), - Color(0xFF333333), - ], - stops: [ - 0.1, - 0.3, - 0.4, - ], + colors: [Color(0xFF333333), Color(0xFF3c3c3c), Color(0xFF333333)], + stops: [0.1, 0.3, 0.4], begin: Alignment(-1.0, -0.3), end: Alignment(1.0, 0.3), tileMode: TileMode.clamp, ); class _SlidingGradientTransform extends GradientTransform { - const _SlidingGradientTransform({ - required this.slidePercent, - }); + const _SlidingGradientTransform({required this.slidePercent}); final double slidePercent; diff --git a/lib/src/widgets/stat_card.dart b/lib/src/widgets/stat_card.dart index 04527305c3..92c5e3fb18 100644 --- a/lib/src/widgets/stat_card.dart +++ b/lib/src/widgets/stat_card.dart @@ -33,8 +33,7 @@ class StatCard extends StatelessWidget { fontSize: statFontSize ?? _defaultStatFontSize, ); - final defaultValueStyle = - TextStyle(fontSize: valueFontSize ?? _defaultValueFontSize); + final defaultValueStyle = TextStyle(fontSize: valueFontSize ?? _defaultValueFontSize); return Padding( padding: padding ?? EdgeInsets.zero, @@ -48,18 +47,10 @@ class StatCard extends StatelessWidget { FittedBox( alignment: Alignment.center, fit: BoxFit.scaleDown, - child: Text( - stat, - style: defaultStatStyle, - textAlign: TextAlign.center, - ), + child: Text(stat, style: defaultStatStyle, textAlign: TextAlign.center), ), if (value != null) - Text( - value!, - style: defaultValueStyle, - textAlign: TextAlign.center, - ) + Text(value!, style: defaultValueStyle, textAlign: TextAlign.center) else if (child != null) child! else @@ -83,9 +74,7 @@ class StatCardRow extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.stretch, - children: _divideRow(cards) - .map((e) => Expanded(child: e)) - .toList(growable: false), + children: _divideRow(cards).map((e) => Expanded(child: e)).toList(growable: false), ), ); } @@ -100,14 +89,8 @@ Iterable _divideRow(Iterable elements) { } Widget wrapElement(Widget el) { - return Container( - margin: const EdgeInsets.only(right: 8), - child: el, - ); + return Container(margin: const EdgeInsets.only(right: 8), child: el); } - return [ - ...list.take(list.length - 1).map(wrapElement), - list.last, - ]; + return [...list.take(list.length - 1).map(wrapElement), list.last]; } diff --git a/lib/src/widgets/user_full_name.dart b/lib/src/widgets/user_full_name.dart index 87fc25dc85..f869aeedf0 100644 --- a/lib/src/widgets/user_full_name.dart +++ b/lib/src/widgets/user_full_name.dart @@ -59,12 +59,10 @@ class UserFullNameWidget extends ConsumerWidget { orElse: () => false, ); - final displayName = user?.name ?? + final displayName = + user?.name ?? (aiLevel != null - ? context.l10n.aiNameLevelAiLevel( - 'Stockfish', - aiLevel.toString(), - ) + ? context.l10n.aiNameLevelAiLevel('Stockfish', aiLevel.toString()) : context.l10n.anonymous); return Row( mainAxisSize: MainAxisSize.min, @@ -74,8 +72,7 @@ class UserFullNameWidget extends ConsumerWidget { padding: const EdgeInsets.only(right: 5), child: Icon( user?.isOnline == true ? Icons.cloud : Icons.cloud_off, - size: style?.fontSize ?? - DefaultTextStyle.of(context).style.fontSize, + size: style?.fontSize ?? DefaultTextStyle.of(context).style.fontSize, color: user?.isOnline == true ? context.lichessColors.good : null, ), ), @@ -84,8 +81,7 @@ class UserFullNameWidget extends ConsumerWidget { padding: const EdgeInsets.only(right: 5), child: Icon( LichessIcons.patron, - size: style?.fontSize ?? - DefaultTextStyle.of(context).style.fontSize, + size: style?.fontSize ?? DefaultTextStyle.of(context).style.fontSize, color: style?.color ?? DefaultTextStyle.of(context).style.color, semanticLabel: context.l10n.patronLichessPatron, ), @@ -94,37 +90,26 @@ class UserFullNameWidget extends ConsumerWidget { Text( user!.title!, style: (style ?? const TextStyle()).copyWith( - color: user?.title == 'BOT' - ? context.lichessColors.fancy - : context.lichessColors.brag, + color: + user?.title == 'BOT' ? context.lichessColors.fancy : context.lichessColors.brag, fontWeight: user?.title == 'BOT' ? null : FontWeight.bold, ), ), const SizedBox(width: 5), ], Flexible( - child: Text( - displayName, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: style, - ), + child: Text(displayName, maxLines: 1, overflow: TextOverflow.ellipsis, style: style), ), if (showFlair && user?.flair != null) ...[ const SizedBox(width: 5), CachedNetworkImage( imageUrl: lichessFlairSrc(user!.flair!), errorWidget: (_, __, ___) => kEmptyWidget, - width: - style?.fontSize ?? DefaultTextStyle.of(context).style.fontSize, - height: - style?.fontSize ?? DefaultTextStyle.of(context).style.fontSize, + width: style?.fontSize ?? DefaultTextStyle.of(context).style.fontSize, + height: style?.fontSize ?? DefaultTextStyle.of(context).style.fontSize, ), ], - if (shouldShowRating && ratingStr != null) ...[ - const SizedBox(width: 5), - Text(ratingStr), - ], + if (shouldShowRating && ratingStr != null) ...[const SizedBox(width: 5), Text(ratingStr)], ], ); } diff --git a/lib/src/widgets/user_list_tile.dart b/lib/src/widgets/user_list_tile.dart index 6e6690eb1c..0346e9288f 100644 --- a/lib/src/widgets/user_list_tile.dart +++ b/lib/src/widgets/user_list_tile.dart @@ -21,11 +21,7 @@ class UserListTile extends StatelessWidget { this.userPerfs, ); - factory UserListTile.fromUser( - User user, - bool isOnline, { - VoidCallback? onTap, - }) { + factory UserListTile.fromUser(User user, bool isOnline, {VoidCallback? onTap}) { return UserListTile._( user.username, user.title, @@ -62,9 +58,7 @@ class UserListTile extends StatelessWidget { Widget build(BuildContext context) { return PlatformListTile( onTap: onTap != null ? () => onTap?.call() : null, - padding: Theme.of(context).platform == TargetPlatform.iOS - ? Styles.bodyPadding - : null, + padding: Theme.of(context).platform == TargetPlatform.iOS ? Styles.bodyPadding : null, leading: Icon( isOnline == true ? Icons.cloud : Icons.cloud_off, color: isOnline == true ? context.lichessColors.good : null, @@ -74,29 +68,17 @@ class UserListTile extends StatelessWidget { child: Row( children: [ if (isPatron == true) ...[ - Icon( - LichessIcons.patron, - semanticLabel: context.l10n.patronLichessPatron, - ), + Icon(LichessIcons.patron, semanticLabel: context.l10n.patronLichessPatron), const SizedBox(width: 5), ], if (title != null) ...[ Text( title!, - style: TextStyle( - color: context.lichessColors.brag, - fontWeight: FontWeight.bold, - ), + style: TextStyle(color: context.lichessColors.brag, fontWeight: FontWeight.bold), ), const SizedBox(width: 5), ], - Flexible( - child: Text( - maxLines: 1, - overflow: TextOverflow.ellipsis, - username, - ), - ), + Flexible(child: Text(maxLines: 1, overflow: TextOverflow.ellipsis, username)), if (flair != null) ...[ const SizedBox(width: 5), CachedNetworkImage( @@ -121,31 +103,23 @@ class _UserRating extends StatelessWidget { @override Widget build(BuildContext context) { - List userPerfs = Perf.values.where((element) { - final p = perfs[element]; - return p != null && - p.numberOfGamesOrRuns > 0 && - p.ratingDeviation < kClueLessDeviation; - }).toList(growable: false); + List userPerfs = Perf.values + .where((element) { + final p = perfs[element]; + return p != null && p.numberOfGamesOrRuns > 0 && p.ratingDeviation < kClueLessDeviation; + }) + .toList(growable: false); if (userPerfs.isEmpty) return const SizedBox.shrink(); userPerfs.sort( - (p1, p2) => perfs[p1]! - .numberOfGamesOrRuns - .compareTo(perfs[p2]!.numberOfGamesOrRuns), + (p1, p2) => perfs[p1]!.numberOfGamesOrRuns.compareTo(perfs[p2]!.numberOfGamesOrRuns), ); userPerfs = userPerfs.reversed.toList(); final rating = perfs[userPerfs.first]?.rating.toString() ?? '?'; final icon = userPerfs.first.icon; - return Row( - children: [ - Icon(icon, size: 16), - const SizedBox(width: 5), - Text(rating), - ], - ); + return Row(children: [Icon(icon, size: 16), const SizedBox(width: 5), Text(rating)]); } } diff --git a/lib/src/widgets/yes_no_dialog.dart b/lib/src/widgets/yes_no_dialog.dart index fb1d0ff1b5..ad5053d0fc 100644 --- a/lib/src/widgets/yes_no_dialog.dart +++ b/lib/src/widgets/yes_no_dialog.dart @@ -25,14 +25,8 @@ class YesNoDialog extends StatelessWidget { title: title, content: content, actions: [ - PlatformDialogAction( - onPressed: onNo, - child: Text(context.l10n.no), - ), - PlatformDialogAction( - onPressed: onYes, - child: Text(context.l10n.yes), - ), + PlatformDialogAction(onPressed: onNo, child: Text(context.l10n.no)), + PlatformDialogAction(onPressed: onYes, child: Text(context.l10n.yes)), ], ); } diff --git a/pubspec.lock b/pubspec.lock index 5d52eebcbf..ac336549b1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -807,10 +807,10 @@ packages: dependency: transitive description: name: image - sha256: "599d08e369969bdf83138f5b4e0a7e823d3f992f23b8a64dd626877c37013533" + sha256: "20842a5ad1555be624c314b0c0cc0566e8ece412f61e859a42efeb6d4101a26c" url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.5.0" intl: dependency: "direct main" description: @@ -1247,18 +1247,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "7f172d1b06de5da47b6264c2692ee2ead20bbbc246690427cdb4fc301cd0c549" + sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.4.0" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -1738,5 +1738,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.5.0 <4.0.0" - flutter: ">=3.27.0-0.1.pre" + dart: ">=3.7.0-209.1.beta <4.0.0" + flutter: ">=3.28.0-0.1.pre" diff --git a/pubspec.yaml b/pubspec.yaml index f2a051efab..959364fde0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,9 +5,9 @@ publish_to: "none" version: 0.13.9+001309 # See README.md for details about versioning environment: - sdk: ">=3.5.0 <4.0.0" + sdk: '^3.7.0-209.1.beta' # We're using the beta channel for the flutter version - flutter: "3.27.0-0.1.pre" + flutter: "3.28.0-0.1.pre" dependencies: app_settings: ^5.1.1 diff --git a/test/app_test.dart b/test/app_test.dart index 6ec425e5d2..916147efbb 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -14,51 +14,35 @@ import 'test_provider_scope.dart'; void main() { testWidgets('App loads', (tester) async { - final app = await makeTestProviderScope( - tester, - child: const Application(), - ); + final app = await makeTestProviderScope(tester, child: const Application()); await tester.pumpWidget(app); expect(find.byType(MaterialApp), findsOneWidget); }); - testWidgets('App loads with system theme, which defaults to light', - (tester) async { - final app = await makeTestProviderScope( - tester, - child: const Application(), - ); + testWidgets('App loads with system theme, which defaults to light', (tester) async { + final app = await makeTestProviderScope(tester, child: const Application()); await tester.pumpWidget(app); - expect( - Theme.of(tester.element(find.byType(MaterialApp))).brightness, - Brightness.light, - ); + expect(Theme.of(tester.element(find.byType(MaterialApp))).brightness, Brightness.light); }); - testWidgets( - 'App will delete a stored session on startup if one request return 401', - (tester) async { + testWidgets('App will delete a stored session on startup if one request return 401', ( + tester, + ) async { int tokenTestRequests = 0; final mockClient = MockClient((request) async { if (request.url.path == '/api/token/test') { tokenTestRequests++; - return mockResponse( - ''' + return mockResponse(''' { "${fakeSession.token}": null } - ''', - 200, - ); + ''', 200); } else if (request.url.path == '/api/account') { - return mockResponse( - '{"error": "Unauthorized"}', - 401, - ); + return mockResponse('{"error": "Unauthorized"}', 401); } return mockResponse('', 404); }); @@ -68,8 +52,7 @@ void main() { child: const Application(), userSession: fakeSession, overrides: [ - httpClientFactoryProvider - .overrideWith((ref) => FakeHttpClientFactory(() => mockClient)), + httpClientFactoryProvider.overrideWith((ref) => FakeHttpClientFactory(() => mockClient)), ], ); @@ -98,10 +81,7 @@ void main() { }); testWidgets('Bottom navigation', (tester) async { - final app = await makeTestProviderScope( - tester, - child: const Application(), - ); + final app = await makeTestProviderScope(tester, child: const Application()); await tester.pumpWidget(app); diff --git a/test/binding.dart b/test/binding.dart index 229f180c2a..6462d79266 100644 --- a/test/binding.dart +++ b/test/binding.dart @@ -36,8 +36,7 @@ class TestLichessBinding extends LichessBinding { } /// The single instance of the binding. - static TestLichessBinding get instance => - LichessBinding.checkInstance(_instance); + static TestLichessBinding get instance => LichessBinding.checkInstance(_instance); static TestLichessBinding? _instance; @override @@ -47,9 +46,7 @@ class TestLichessBinding extends LichessBinding { } /// Set the initial values for shared preferences. - Future setInitialSharedPreferencesValues( - Map values, - ) async { + Future setInitialSharedPreferencesValues(Map values) async { for (final entry in values.entries) { if (entry.value is String) { await sharedPreferences.setString(entry.key, entry.value as String); @@ -60,10 +57,7 @@ class TestLichessBinding extends LichessBinding { } else if (entry.value is int) { await sharedPreferences.setInt(entry.key, entry.value as int); } else if (entry.value is List) { - await sharedPreferences.setStringList( - entry.key, - entry.value as List, - ); + await sharedPreferences.setStringList(entry.key, entry.value as List); } else { throw ArgumentError.value( entry.value, @@ -105,8 +99,7 @@ class TestLichessBinding extends LichessBinding { } @override - Stream get firebaseMessagingOnMessage => - firebaseMessaging.onMessage.stream; + Stream get firebaseMessagingOnMessage => firebaseMessaging.onMessage.stream; @override Stream get firebaseMessagingOnMessageOpenedApp => @@ -201,15 +194,16 @@ class FakeSharedPreferences implements SharedPreferencesWithCache { } } -typedef FirebaseMessagingRequestPermissionCall = ({ - bool alert, - bool announcement, - bool badge, - bool carPlay, - bool criticalAlert, - bool provisional, - bool sound, -}); +typedef FirebaseMessagingRequestPermissionCall = + ({ + bool alert, + bool announcement, + bool badge, + bool carPlay, + bool criticalAlert, + bool provisional, + bool sound, + }); class FakeFirebaseMessaging extends Fake implements FirebaseMessaging { /// Whether [requestPermission] will grant permission. @@ -253,43 +247,30 @@ class FakeFirebaseMessaging extends Fake implements FirebaseMessaging { bool provisional = false, bool sound = true, }) async { - _requestPermissionCalls.add( - ( - alert: alert, - announcement: announcement, - badge: badge, - carPlay: carPlay, - criticalAlert: criticalAlert, - provisional: provisional, - sound: sound, - ), - ); + _requestPermissionCalls.add(( + alert: alert, + announcement: announcement, + badge: badge, + carPlay: carPlay, + criticalAlert: criticalAlert, + provisional: provisional, + sound: sound, + )); return _notificationSettings = NotificationSettings( - alert: alert - ? AppleNotificationSetting.enabled - : AppleNotificationSetting.disabled, - announcement: announcement - ? AppleNotificationSetting.enabled - : AppleNotificationSetting.disabled, - authorizationStatus: _willGrantPermission - ? AuthorizationStatus.authorized - : AuthorizationStatus.denied, - badge: badge - ? AppleNotificationSetting.enabled - : AppleNotificationSetting.disabled, - carPlay: carPlay - ? AppleNotificationSetting.enabled - : AppleNotificationSetting.disabled, + alert: alert ? AppleNotificationSetting.enabled : AppleNotificationSetting.disabled, + announcement: + announcement ? AppleNotificationSetting.enabled : AppleNotificationSetting.disabled, + authorizationStatus: + _willGrantPermission ? AuthorizationStatus.authorized : AuthorizationStatus.denied, + badge: badge ? AppleNotificationSetting.enabled : AppleNotificationSetting.disabled, + carPlay: carPlay ? AppleNotificationSetting.enabled : AppleNotificationSetting.disabled, lockScreen: AppleNotificationSetting.enabled, notificationCenter: AppleNotificationSetting.enabled, showPreviews: AppleShowPreviewSetting.whenAuthenticated, timeSensitive: AppleNotificationSetting.disabled, - criticalAlert: criticalAlert - ? AppleNotificationSetting.enabled - : AppleNotificationSetting.disabled, - sound: sound - ? AppleNotificationSetting.enabled - : AppleNotificationSetting.disabled, + criticalAlert: + criticalAlert ? AppleNotificationSetting.enabled : AppleNotificationSetting.disabled, + sound: sound ? AppleNotificationSetting.enabled : AppleNotificationSetting.disabled, ); } @@ -311,8 +292,7 @@ class FakeFirebaseMessaging extends Fake implements FirebaseMessaging { _tokenController.add(token); } - final StreamController _tokenController = - StreamController.broadcast(); + final StreamController _tokenController = StreamController.broadcast(); @override Future getToken({String? vapidKey}) async { @@ -338,13 +318,11 @@ class FakeFirebaseMessaging extends Fake implements FirebaseMessaging { /// /// Call [StreamController.add] to simulate a user press on a notification message /// sent by FCM. - StreamController onMessageOpenedApp = - StreamController.broadcast(); + StreamController onMessageOpenedApp = StreamController.broadcast(); /// Controller for [onBackgroundMessage]. /// /// Call [StreamController.add] to simulate a message received from FCM while /// the application is in background. - StreamController onBackgroundMessage = - StreamController.broadcast(); + StreamController onBackgroundMessage = StreamController.broadcast(); } diff --git a/test/example_data.dart b/test/example_data.dart index 91a85cd077..51d5b681bc 100644 --- a/test/example_data.dart +++ b/test/example_data.dart @@ -11,30 +11,23 @@ import 'package:lichess_mobile/src/model/game/material_diff.dart'; import 'package:lichess_mobile/src/model/game/player.dart'; import 'package:lichess_mobile/src/model/user/user.dart'; -List generateArchivedGames({ - int count = 100, - String? username, -}) { +List generateArchivedGames({int count = 100, String? username}) { return List.generate(count, (index) { final id = GameId('game${index.toString().padLeft(4, '0')}'); final whitePlayer = Player( - user: username != null && index.isEven - ? LightUser( - id: UserId.fromUserName(username), - name: username, - ) - : username != null + user: + username != null && index.isEven + ? LightUser(id: UserId.fromUserName(username), name: username) + : username != null ? const LightUser(id: UserId('whiteId'), name: 'White') : null, rating: username != null ? 1500 : null, ); final blackPlayer = Player( - user: username != null && index.isOdd - ? LightUser( - id: UserId.fromUserName(username), - name: username, - ) - : username != null + user: + username != null && index.isOdd + ? LightUser(id: UserId.fromUserName(username), name: username) + : username != null ? const LightUser(id: UserId('blackId'), name: 'Black') : null, rating: username != null ? 1500 : null, @@ -60,22 +53,18 @@ List generateArchivedGames({ status: GameStatus.started, white: whitePlayer, black: blackPlayer, - clock: ( - initial: const Duration(minutes: 2), - increment: const Duration(seconds: 3), - ), - ), - steps: _makeSteps( - 'e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2', + clock: (initial: const Duration(minutes: 2), increment: const Duration(seconds: 3)), ), + steps: _makeSteps('e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2'), status: GameStatus.started, white: whitePlayer, black: blackPlayer, - youAre: username != null - ? index.isEven - ? Side.white - : Side.black - : null, + youAre: + username != null + ? index.isEven + ? Side.white + : Side.black + : null, ); }); } diff --git a/test/fake_crashlytics.dart b/test/fake_crashlytics.dart index dff3795c76..9821735fc5 100644 --- a/test/fake_crashlytics.dart +++ b/test/fake_crashlytics.dart @@ -57,9 +57,7 @@ class FakeCrashlytics implements FirebaseCrashlytics { }) async {} @override - Future recordFlutterFatalError( - FlutterErrorDetails flutterErrorDetails, - ) async {} + Future recordFlutterFatalError(FlutterErrorDetails flutterErrorDetails) async {} @override Future sendUnsentReports() async {} diff --git a/test/model/auth/auth_controller_test.dart b/test/model/auth/auth_controller_test.dart index a70502a290..5dd222ea93 100644 --- a/test/model/auth/auth_controller_test.dart +++ b/test/model/auth/auth_controller_test.dart @@ -30,26 +30,17 @@ void main() { const testUserSession = AuthSessionState( token: 'testToken', - user: LightUser( - id: UserId('test'), - name: 'test', - title: 'GM', - isPatron: true, - ), + user: LightUser(id: UserId('test'), name: 'test', title: 'GM', isPatron: true), ); const loading = AsyncLoading(); const nullData = AsyncData(null); final client = MockClient((request) { if (request.url.path == '/api/account') { - return mockResponse( - mockApiAccountResponse(testUserSession.user.name), - 200, - ); + return mockResponse(mockApiAccountResponse(testUserSession.user.name), 200); } else if (request.method == 'DELETE' && request.url.path == '/api/token') { return mockResponse('ok', 200); - } else if (request.method == 'POST' && - request.url.path == '/mobile/unregister') { + } else if (request.method == 'POST' && request.url.path == '/mobile/unregister') { return mockResponse('ok', 200); } return mockResponse('', 404); @@ -75,20 +66,17 @@ void main() { group('AuthController', () { test('sign in', () async { - when(() => mockSessionStorage.read()) - .thenAnswer((_) => Future.value(null)); - when(() => mockFlutterAppAuth.authorizeAndExchangeCode(any())) - .thenAnswer((_) => Future.value(signInResponse)); + when(() => mockSessionStorage.read()).thenAnswer((_) => Future.value(null)); when( - () => mockSessionStorage.write(any()), - ).thenAnswer((_) => Future.value(null)); + () => mockFlutterAppAuth.authorizeAndExchangeCode(any()), + ).thenAnswer((_) => Future.value(signInResponse)); + when(() => mockSessionStorage.write(any())).thenAnswer((_) => Future.value(null)); final container = await makeContainer( overrides: [ appAuthProvider.overrideWithValue(mockFlutterAppAuth), sessionStorageProvider.overrideWithValue(mockSessionStorage), - httpClientFactoryProvider - .overrideWith((_) => FakeHttpClientFactory(() => client)), + httpClientFactoryProvider.overrideWith((_) => FakeHttpClientFactory(() => client)), ], ); @@ -114,17 +102,12 @@ void main() { verifyNoMoreInteractions(listener); // it should successfully write the session - verify( - () => mockSessionStorage.write(testUserSession), - ).called(1); + verify(() => mockSessionStorage.write(testUserSession)).called(1); }); test('sign out', () async { - when(() => mockSessionStorage.read()) - .thenAnswer((_) => Future.value(testUserSession)); - when( - () => mockSessionStorage.delete(), - ).thenAnswer((_) => Future.value(null)); + when(() => mockSessionStorage.read()).thenAnswer((_) => Future.value(testUserSession)); + when(() => mockSessionStorage.delete()).thenAnswer((_) => Future.value(null)); int tokenDeleteCount = 0; int unregisterCount = 0; @@ -133,8 +116,7 @@ void main() { if (request.method == 'DELETE' && request.url.path == '/api/token') { tokenDeleteCount++; return mockResponse('ok', 200); - } else if (request.method == 'POST' && - request.url.path == '/mobile/unregister') { + } else if (request.method == 'POST' && request.url.path == '/mobile/unregister') { unregisterCount++; return mockResponse('ok', 200); } @@ -145,8 +127,7 @@ void main() { overrides: [ appAuthProvider.overrideWithValue(mockFlutterAppAuth), sessionStorageProvider.overrideWithValue(mockSessionStorage), - httpClientFactoryProvider - .overrideWith((_) => FakeHttpClientFactory(() => client)), + httpClientFactoryProvider.overrideWith((_) => FakeHttpClientFactory(() => client)), ], userSession: testUserSession, ); @@ -176,9 +157,7 @@ void main() { expect(unregisterCount, 1, reason: 'device should be unregistered'); // session should be deleted - verify( - () => mockSessionStorage.delete(), - ).called(1); + verify(() => mockSessionStorage.delete()).called(1); }); }); } diff --git a/test/model/auth/fake_auth_repository.dart b/test/model/auth/fake_auth_repository.dart index c82b5b48da..4bcbe9eac8 100644 --- a/test/model/auth/fake_auth_repository.dart +++ b/test/model/auth/fake_auth_repository.dart @@ -28,9 +28,4 @@ final fakeUser = User( }), ); -const _fakePerf = UserPerf( - rating: 1500, - ratingDeviation: 0, - progression: 0, - games: 0, -); +const _fakePerf = UserPerf(rating: 1500, ratingDeviation: 0, progression: 0, games: 0); diff --git a/test/model/challenge/challenge_repository_test.dart b/test/model/challenge/challenge_repository_test.dart index b40f62c9a6..21532543c9 100644 --- a/test/model/challenge/challenge_repository_test.dart +++ b/test/model/challenge/challenge_repository_test.dart @@ -14,10 +14,7 @@ void main() { test('list', () async { final mockClient = MockClient((request) { if (request.url.path == '/api/challenge') { - return mockResponse( - challengesList, - 200, - ); + return mockResponse(challengesList, 200); } return mockResponse('', 404); }); @@ -36,10 +33,7 @@ void main() { test('show', () async { final mockClient = MockClient((request) { if (request.url.path == '/api/challenge/H9fIRZUk/show') { - return mockResponse( - challenge, - 200, - ); + return mockResponse(challenge, 200); } return mockResponse('', 404); }); diff --git a/test/model/challenge/challenge_service_test.dart b/test/model/challenge/challenge_service_test.dart index 25c637853a..b882a278e5 100644 --- a/test/model/challenge/challenge_service_test.dart +++ b/test/model/challenge/challenge_service_test.dart @@ -17,8 +17,7 @@ import '../../network/socket_test.dart'; import '../../test_container.dart'; import '../auth/fake_session_storage.dart'; -class NotificationDisplayMock extends Mock - implements FlutterLocalNotificationsPlugin {} +class NotificationDisplayMock extends Mock implements FlutterLocalNotificationsPlugin {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -31,15 +30,14 @@ void main() { test('exposes a challenges stream', () async { final fakeChannel = FakeWebSocketChannel(); - final socketClient = - makeTestSocketClient(FakeWebSocketChannelFactory((_) => fakeChannel)); + final socketClient = makeTestSocketClient(FakeWebSocketChannelFactory((_) => fakeChannel)); await socketClient.connect(); await socketClient.firstConnection; fakeChannel.addIncomingMessages([ ''' {"t": "challenges", "d": {"in": [ { "socketVersion": 0, "id": "H9fIRZUk", "url": "https://lichess.org/H9fIRZUk", "status": "created", "challenger": { "id": "bot1", "name": "Bot1", "rating": 1500, "title": "BOT", "provisional": true, "online": true, "lag": 4 }, "destUser": { "id": "bobby", "name": "Bobby", "rating": 1635, "title": "GM", "provisional": true, "online": true, "lag": 4 }, "variant": { "key": "standard", "name": "Standard", "short": "Std" }, "rated": true, "speed": "rapid", "timeControl": { "type": "clock", "limit": 600, "increment": 0, "show": "10+0" }, "color": "random", "finalColor": "black", "perf": { "icon": "", "name": "Rapid" }, "direction": "in" } ] }, "v": 0 } -''' +''', ]); await expectLater( @@ -52,23 +50,13 @@ void main() { id: ChallengeId('H9fIRZUk'), status: ChallengeStatus.created, challenger: ( - user: LightUser( - id: UserId('bot1'), - name: 'Bot1', - title: 'BOT', - isOnline: true, - ), + user: LightUser(id: UserId('bot1'), name: 'Bot1', title: 'BOT', isOnline: true), rating: 1500, provisionalRating: true, lagRating: 4, ), destUser: ( - user: LightUser( - id: UserId('bobby'), - name: 'Bobby', - title: 'GM', - isOnline: true, - ), + user: LightUser(id: UserId('bobby'), name: 'Bobby', title: 'GM', isOnline: true), rating: 1635, provisionalRating: true, lagRating: 4, @@ -77,10 +65,7 @@ void main() { rated: true, speed: Speed.rapid, timeControl: ChallengeTimeControlType.clock, - clock: ( - time: Duration(seconds: 600), - increment: Duration.zero, - ), + clock: (time: Duration(seconds: 600), increment: Duration.zero), sideChoice: SideChoice.random, direction: ChallengeDirection.inward, ), @@ -93,23 +78,15 @@ void main() { socketClient.close(); }); - test('Listen to socket and show a notification for any new challenge', - () async { + test('Listen to socket and show a notification for any new challenge', () async { when( - () => notificationDisplayMock.show( - any(), - any(), - any(), - any(), - payload: any(named: 'payload'), - ), + () => + notificationDisplayMock.show(any(), any(), any(), any(), payload: any(named: 'payload')), ).thenAnswer((_) => Future.value()); final container = await makeContainer( userSession: fakeSession, - overrides: [ - notificationDisplayProvider.overrideWithValue(notificationDisplayMock), - ], + overrides: [notificationDisplayProvider.overrideWithValue(notificationDisplayMock)], ); final notificationService = container.read(notificationServiceProvider); @@ -117,8 +94,7 @@ void main() { fakeAsync((async) { final fakeChannel = FakeWebSocketChannel(); - final socketClient = - makeTestSocketClient(FakeWebSocketChannelFactory((_) => fakeChannel)); + final socketClient = makeTestSocketClient(FakeWebSocketChannelFactory((_) => fakeChannel)); socketClient.connect(); notificationService.start(); challengeService.start(); @@ -130,7 +106,7 @@ void main() { fakeChannel.addIncomingMessages([ ''' {"t": "challenges", "d": {"in": [ { "socketVersion": 0, "id": "H9fIRZUk", "url": "https://lichess.org/H9fIRZUk", "status": "created", "challenger": { "id": "bot1", "name": "Bot1", "rating": 1500, "title": "BOT", "provisional": true, "online": true, "lag": 4 }, "destUser": { "id": "bobby", "name": "Bobby", "rating": 1635, "title": "GM", "provisional": true, "online": true, "lag": 4 }, "variant": { "key": "standard", "name": "Standard", "short": "Std" }, "rated": true, "speed": "rapid", "timeControl": { "type": "clock", "limit": 600, "increment": 0, "show": "10+0" }, "color": "random", "finalColor": "black", "perf": { "icon": "", "name": "Rapid" }, "direction": "in" } ] }, "v": 0 } -''' +''', ]); async.flushMicrotasks(); @@ -149,27 +125,15 @@ void main() { expectLater( result.captured[0], isA() - .having( - (details) => details.android?.channelId, - 'channelId', - 'challenge', - ) - .having( - (d) => d.android?.importance, - 'importance', - Importance.max, - ) - .having( - (d) => d.android?.priority, - 'priority', - Priority.high, - ), + .having((details) => details.android?.channelId, 'channelId', 'challenge') + .having((d) => d.android?.importance, 'importance', Importance.max) + .having((d) => d.android?.priority, 'priority', Priority.high), ); fakeChannel.addIncomingMessages([ ''' {"t": "challenges", "d": {"in": [ { "socketVersion": 0, "id": "H9fIRZUk", "url": "https://lichess.org/H9fIRZUk", "status": "created", "challenger": { "id": "bot1", "name": "Bot1", "rating": 1500, "title": "BOT", "provisional": true, "online": true, "lag": 4 }, "destUser": { "id": "bobby", "name": "Bobby", "rating": 1635, "title": "GM", "provisional": true, "online": true, "lag": 4 }, "variant": { "key": "standard", "name": "Standard", "short": "Std" }, "rated": true, "speed": "rapid", "timeControl": { "type": "clock", "limit": 600, "increment": 0, "show": "10+0" }, "color": "random", "finalColor": "black", "perf": { "icon": "", "name": "Rapid" }, "direction": "in" } ] }, "v": 0 } -''' +''', ]); async.flushMicrotasks(); @@ -193,26 +157,15 @@ void main() { test('Cancels the notification for any missing challenge', () async { when( - () => notificationDisplayMock.show( - any(), - any(), - any(), - any(), - payload: any(named: 'payload'), - ), + () => + notificationDisplayMock.show(any(), any(), any(), any(), payload: any(named: 'payload')), ).thenAnswer((_) => Future.value()); - when( - () => notificationDisplayMock.cancel( - any(), - ), - ).thenAnswer((_) => Future.value()); + when(() => notificationDisplayMock.cancel(any())).thenAnswer((_) => Future.value()); final container = await makeContainer( userSession: fakeSession, - overrides: [ - notificationDisplayProvider.overrideWithValue(notificationDisplayMock), - ], + overrides: [notificationDisplayProvider.overrideWithValue(notificationDisplayMock)], ); final notificationService = container.read(notificationServiceProvider); @@ -220,8 +173,7 @@ void main() { fakeAsync((async) { final fakeChannel = FakeWebSocketChannel(); - final socketClient = - makeTestSocketClient(FakeWebSocketChannelFactory((_) => fakeChannel)); + final socketClient = makeTestSocketClient(FakeWebSocketChannelFactory((_) => fakeChannel)); socketClient.connect(); notificationService.start(); challengeService.start(); @@ -233,7 +185,7 @@ void main() { fakeChannel.addIncomingMessages([ ''' {"t": "challenges", "d": {"in": [ { "socketVersion": 0, "id": "H9fIRZUk", "url": "https://lichess.org/H9fIRZUk", "status": "created", "challenger": { "id": "bot1", "name": "Bot1", "rating": 1500, "title": "BOT", "provisional": true, "online": true, "lag": 4 }, "destUser": { "id": "bobby", "name": "Bobby", "rating": 1635, "title": "GM", "provisional": true, "online": true, "lag": 4 }, "variant": { "key": "standard", "name": "Standard", "short": "Std" }, "rated": true, "speed": "rapid", "timeControl": { "type": "clock", "limit": 600, "increment": 0, "show": "10+0" }, "color": "random", "finalColor": "black", "perf": { "icon": "", "name": "Rapid" }, "direction": "in" } ] }, "v": 0 } -''' +''', ]); async.flushMicrotasks(); @@ -251,15 +203,13 @@ void main() { fakeChannel.addIncomingMessages([ ''' {"t": "challenges", "d": {"in": [] }, "v": 0 } -''' +''', ]); async.flushMicrotasks(); verify( - () => notificationDisplayMock.cancel( - const ChallengeId('H9fIRZUk').hashCode, - ), + () => notificationDisplayMock.cancel(const ChallengeId('H9fIRZUk').hashCode), ).called(1); // closing the socket client to be able to flush the timers diff --git a/test/model/common/node_test.dart b/test/model/common/node_test.dart index 8459d79ff1..c8897014f4 100644 --- a/test/model/common/node_test.dart +++ b/test/model/common/node_test.dart @@ -20,10 +20,7 @@ void main() { child.position.fen, equals('rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1'), ); - expect( - child.position, - equals(Chess.initial.playUnchecked(Move.parse('e2e4')!)), - ); + expect(child.position, equals(Chess.initial.playUnchecked(Move.parse('e2e4')!))); }); test('Root.fromPgnGame, flat', () { @@ -40,8 +37,7 @@ void main() { expect(root.position, equals(Chess.initial)); expect(root.children.length, equals(1)); expect(root.mainline.length, equals(5)); - final nodeWithVariation = - root.nodeAt(UciPath.fromUciMoves(['e2e4', 'd7d5', 'e4d5'])); + final nodeWithVariation = root.nodeAt(UciPath.fromUciMoves(['e2e4', 'd7d5', 'e4d5'])); expect(nodeWithVariation.children.length, 2); expect(nodeWithVariation.children[1].sanMove.san, equals('Nf6')); expect(nodeWithVariation.children[1].children.length, 2); @@ -53,10 +49,7 @@ void main() { final nodeList = root.nodesOn(path).toList(); expect(nodeList.length, equals(2)); expect(nodeList[0], equals(root)); - expect( - nodeList[1], - equals(root.nodeAt(path) as Branch), - ); + expect(nodeList[1], equals(root.nodeAt(path) as Branch)); }); test('branchesOn, simple', () { @@ -64,29 +57,21 @@ void main() { final path = UciPath.fromId(UciCharPair.fromUci('e2e4')); final nodeList = root.branchesOn(path); expect(nodeList.length, equals(1)); - expect( - nodeList.first, - equals(root.nodeAt(path) as Branch), - ); + expect(nodeList.first, equals(root.nodeAt(path) as Branch)); }); test('branchesOn, with variation', () { final root = Root.fromPgnMoves('e4 e5 Nf3'); final move = Move.parse('b1c3')!; final (newPath, _) = root.addMoveAt( - UciPath.fromIds( - [UciCharPair.fromUci('e2e4'), UciCharPair.fromUci('e7e5')].lock, - ), + UciPath.fromIds([UciCharPair.fromUci('e2e4'), UciCharPair.fromUci('e7e5')].lock), move, ); final newNode = root.nodeAt(newPath!); // mainline has not changed expect(root.mainline.length, equals(3)); - expect( - root.mainline.last, - equals(root.nodeAt(root.mainlinePath) as Branch), - ); + expect(root.mainline.last, equals(root.nodeAt(root.mainlinePath) as Branch)); final nodeList = root.branchesOn(newPath); expect(nodeList.length, equals(3)); @@ -110,9 +95,7 @@ void main() { final move = Move.parse('b1c3')!; final (newPath, _) = root.addMoveAt( - UciPath.fromIds( - [UciCharPair.fromUci('e2e4'), UciCharPair.fromUci('e7e5')].lock, - ), + UciPath.fromIds([UciCharPair.fromUci('e2e4'), UciCharPair.fromUci('e7e5')].lock), move, ); @@ -120,13 +103,8 @@ void main() { }); test('add child', () { - final root = Root( - position: Chess.initial, - ); - final child = Branch( - sanMove: SanMove('e4', Move.parse('e2e4')!), - position: Chess.initial, - ); + final root = Root(position: Chess.initial); + final child = Branch(sanMove: SanMove('e4', Move.parse('e2e4')!), position: Chess.initial); root.addChild(child); expect(root.children.length, equals(1)); expect(root.children.first, equals(child)); @@ -134,10 +112,7 @@ void main() { test('prepend child', () { final root = Root.fromPgnMoves('e4 e5'); - final child = Branch( - sanMove: SanMove('d4', Move.parse('d2d4')!), - position: Chess.initial, - ); + final child = Branch(sanMove: SanMove('d4', Move.parse('d2d4')!), position: Chess.initial); root.prependChild(child); expect(root.children.length, equals(2)); expect(root.children.first, equals(child)); @@ -152,12 +127,10 @@ void main() { test('nodeAtOrNull', () { final root = Root.fromPgnMoves('e4 e5'); - final branch = - root.nodeAtOrNull(UciPath.fromId(UciCharPair.fromUci('e2e4'))); + final branch = root.nodeAtOrNull(UciPath.fromId(UciCharPair.fromUci('e2e4'))); expect(branch, equals(root.children.first)); - final branch2 = - root.nodeAtOrNull(UciPath.fromId(UciCharPair.fromUci('b1c3'))); + final branch2 = root.nodeAtOrNull(UciPath.fromId(UciCharPair.fromUci('b1c3'))); expect(branch2, isNull); }); @@ -170,28 +143,18 @@ void main() { test('branchAt from branch', () { final root = Root.fromPgnMoves('e4 e5 Nf3'); final branch = root.branchAt(UciPath.fromId(UciCharPair.fromUci('e2e4'))); - final branch2 = - branch!.branchAt(UciPath.fromId(UciCharPair.fromUci('e7e5'))); + final branch2 = branch!.branchAt(UciPath.fromId(UciCharPair.fromUci('e7e5'))); expect(branch2, equals(branch.children.first)); }); test('updateAt', () { final root = Root.fromPgnMoves('e4 e5'); - final branch = Branch( - sanMove: SanMove('Nc6', Move.parse('b8c6')!), - position: Chess.initial, - ); + final branch = Branch(sanMove: SanMove('Nc6', Move.parse('b8c6')!), position: Chess.initial); final fromPath = UciPath.fromId(UciCharPair.fromUci('e2e4')); final (nodePath, _) = root.addNodeAt(fromPath, branch); - expect( - root.branchesOn(nodePath!), - equals([ - root.children.first, - branch, - ]), - ); + expect(root.branchesOn(nodePath!), equals([root.children.first, branch])); final eval = ClientEval( position: branch.position, @@ -209,22 +172,13 @@ void main() { node.eval = eval; }); - expect( - root.branchesOn(nodePath), - equals([ - root.children.first, - newNode!, - ]), - ); + expect(root.branchesOn(nodePath), equals([root.children.first, newNode!])); }); test('updateAll', () { final root = Root.fromPgnMoves('e4 e5 Nf3'); - expect( - root.mainline.map((n) => n.eval), - equals([null, null, null]), - ); + expect(root.mainline.map((n) => n.eval), equals([null, null, null])); final eval = ClientEval( position: root.position, @@ -242,28 +196,20 @@ void main() { node.eval = eval; }); - expect( - root.mainline.map((n) => n.eval), - equals([eval, eval, eval]), - ); + expect(root.mainline.map((n) => n.eval), equals([eval, eval, eval])); }); test('addNodeAt', () { final root = Root.fromPgnMoves('e4 e5'); - final branch = Branch( - sanMove: SanMove('Nc6', Move.parse('b8c6')!), - position: Chess.initial, + final branch = Branch(sanMove: SanMove('Nc6', Move.parse('b8c6')!), position: Chess.initial); + final (newPath, isNewNode) = root.addNodeAt( + UciPath.fromId(UciCharPair.fromUci('e2e4')), + branch, ); - final (newPath, isNewNode) = - root.addNodeAt(UciPath.fromId(UciCharPair.fromUci('e2e4')), branch); expect( newPath, - equals( - UciPath.fromIds( - IList([UciCharPair.fromUci('e2e4'), UciCharPair.fromUci('b8c6')]), - ), - ), + equals(UciPath.fromIds(IList([UciCharPair.fromUci('e2e4'), UciCharPair.fromUci('b8c6')]))), ); expect(isNewNode, isTrue); @@ -275,15 +221,8 @@ void main() { test('addNodeAt, prepend', () { final root = Root.fromPgnMoves('e4 e5'); - final branch = Branch( - sanMove: SanMove('Nc6', Move.parse('b8c6')!), - position: Chess.initial, - ); - root.addNodeAt( - UciPath.fromId(UciCharPair.fromUci('e2e4')), - branch, - prepend: true, - ); + final branch = Branch(sanMove: SanMove('Nc6', Move.parse('b8c6')!), position: Chess.initial); + root.addNodeAt(UciPath.fromId(UciCharPair.fromUci('e2e4')), branch, prepend: true); final testNode = root.nodeAt(UciPath.fromId(UciCharPair.fromUci('e2e4'))); expect(testNode.children.length, equals(2)); @@ -292,20 +231,15 @@ void main() { test('addNodeAt, with an existing node at path', () { final root = Root.fromPgnMoves('e4 e5'); - final branch = Branch( - sanMove: SanMove('e5', Move.parse('e7e5')!), - position: Chess.initial, + final branch = Branch(sanMove: SanMove('e5', Move.parse('e7e5')!), position: Chess.initial); + final (newPath, isNewNode) = root.addNodeAt( + UciPath.fromId(UciCharPair.fromUci('e2e4')), + branch, ); - final (newPath, isNewNode) = - root.addNodeAt(UciPath.fromId(UciCharPair.fromUci('e2e4')), branch); expect( newPath, - equals( - UciPath.fromIds( - IList([UciCharPair.fromUci('e2e4'), UciCharPair.fromUci('e7e5')]), - ), - ), + equals(UciPath.fromIds(IList([UciCharPair.fromUci('e2e4'), UciCharPair.fromUci('e7e5')]))), ); expect(isNewNode, isFalse); @@ -319,18 +253,9 @@ void main() { test('addNodesAt', () { final root = Root.fromPgnMoves('e4 e5'); - final branch = Branch( - sanMove: SanMove('Nc6', Move.parse('b8c6')!), - position: Chess.initial, - ); - final branch2 = Branch( - sanMove: SanMove('Na6', Move.parse('b8a6')!), - position: Chess.initial, - ); - root.addNodesAt( - UciPath.fromId(UciCharPair.fromUci('e2e4')), - [branch, branch2], - ); + final branch = Branch(sanMove: SanMove('Nc6', Move.parse('b8c6')!), position: Chess.initial); + final branch2 = Branch(sanMove: SanMove('Na6', Move.parse('b8a6')!), position: Chess.initial); + root.addNodesAt(UciPath.fromId(UciCharPair.fromUci('e2e4')), [branch, branch2]); final testNode = root.nodeAt(UciPath.fromId(UciCharPair.fromUci('e2e4'))); expect(testNode.children.length, equals(2)); @@ -341,23 +266,16 @@ void main() { test('addMoveAt', () { final root = Root.fromPgnMoves('e4 e5'); final move = Move.parse('b1c3')!; - final path = UciPath.fromIds( - [UciCharPair.fromUci('e2e4'), UciCharPair.fromUci('e7e5')].lock, - ); + final path = UciPath.fromIds([UciCharPair.fromUci('e2e4'), UciCharPair.fromUci('e7e5')].lock); final currentPath = root.mainlinePath; final (newPath, _) = root.addMoveAt(path, move); - expect( - newPath, - equals(currentPath + UciCharPair.fromMove(move)), - ); + expect(newPath, equals(currentPath + UciCharPair.fromMove(move))); final newNode = root.branchAt(newPath!); expect(newNode?.position.ply, equals(3)); expect(newNode?.sanMove, equals(SanMove('Nc3', move))); expect( newNode?.position.fen, - equals( - 'rnbqkbnr/pppp1ppp/8/4p3/4P3/2N5/PPPP1PPP/R1BQKBNR b KQkq - 1 2', - ), + equals('rnbqkbnr/pppp1ppp/8/4p3/4P3/2N5/PPPP1PPP/R1BQKBNR b KQkq - 1 2'), ); final testNode = root.nodeAt(path); @@ -365,17 +283,13 @@ void main() { expect(testNode.children.first.sanMove, equals(SanMove('Nc3', move))); expect( testNode.children.first.position.fen, - equals( - 'rnbqkbnr/pppp1ppp/8/4p3/4P3/2N5/PPPP1PPP/R1BQKBNR b KQkq - 1 2', - ), + equals('rnbqkbnr/pppp1ppp/8/4p3/4P3/2N5/PPPP1PPP/R1BQKBNR b KQkq - 1 2'), ); }); test('deleteAt', () { final root = Root.fromPgnMoves('e4 e5 Nf3'); - final path = UciPath.fromIds( - [UciCharPair.fromUci('e2e4'), UciCharPair.fromUci('e7e5')], - ); + final path = UciPath.fromIds([UciCharPair.fromUci('e2e4'), UciCharPair.fromUci('e7e5')]); root.deleteAt(path); expect(root.mainline.length, equals(1)); expect(root.mainline.last, equals(root.children.first)); @@ -396,44 +310,27 @@ void main() { root.promoteAt(path, toMainline: false); expect( root.mainline.map((n) => n.sanMove.san).toList(), - equals([ - 'e4', - 'd5', - 'exd5', - 'Nf6', - 'c4', - ]), + equals(['e4', 'd5', 'exd5', 'Nf6', 'c4']), ); expect( root.makePgn(), - equals( - '1. e4 d5 2. exd5 Nf6 ( 2... Qxd5 3. Nc3 ) 3. c4 ( 3. Nc3 ) *\n', - ), + equals('1. e4 d5 2. exd5 Nf6 ( 2... Qxd5 3. Nc3 ) 3. c4 ( 3. Nc3 ) *\n'), ); }); test('promoteAt, to mainline', () { const pgn = '1. e4 d5 2. exd5 Qxd5 (2... Nf6 3. c4 (3. Nc3)) 3. Nc3'; final root = Root.fromPgnGame(PgnGame.parsePgn(pgn)); - final path = - UciPath.fromUciMoves(['e2e4', 'd7d5', 'e4d5', 'g8f6', 'b1c3']); + final path = UciPath.fromUciMoves(['e2e4', 'd7d5', 'e4d5', 'g8f6', 'b1c3']); expect(root.nodeAt(path), isNotNull); root.promoteAt(path, toMainline: true); expect( root.mainline.map((n) => n.sanMove.san).toList(), - equals([ - 'e4', - 'd5', - 'exd5', - 'Nf6', - 'Nc3', - ]), + equals(['e4', 'd5', 'exd5', 'Nf6', 'Nc3']), ); expect( root.makePgn(), - equals( - '1. e4 d5 2. exd5 Nf6 ( 2... Qxd5 3. Nc3 ) 3. Nc3 ( 3. c4 ) *\n', - ), + equals('1. e4 d5 2. exd5 Nf6 ( 2... Qxd5 3. Nc3 ) 3. Nc3 ( 3. c4 ) *\n'), ); }); @@ -443,16 +340,8 @@ void main() { final path = UciPath.fromUciMoves(['d2d4']); expect(root.nodeAt(path), isNotNull); root.promoteAt(path, toMainline: false); - expect( - root.mainline.map((n) => n.sanMove.san).toList(), - equals(['d4']), - ); - expect( - root.makePgn(), - equals( - '1. d4 ( 1. e4 ) *\n', - ), - ); + expect(root.mainline.map((n) => n.sanMove.san).toList(), equals(['d4'])); + expect(root.makePgn(), equals('1. d4 ( 1. e4 ) *\n')); }); group('merge', () { @@ -538,15 +427,9 @@ void main() { expect(node1.clock, equals(node2.clock)); } // one new external eval - expect( - root2.mainline.where((n) => n.externalEval != null).length, - equals(1), - ); + expect(root2.mainline.where((n) => n.externalEval != null).length, equals(1)); // one old client eval preseved - expect( - root2.mainline.where((node) => node.eval != null).length, - equals(1), - ); + expect(root2.mainline.where((node) => node.eval != null).length, equals(1)); expect( root2.mainline.firstWhereOrNull((node) => node.eval != null)?.eval, equals(clientEval), @@ -576,19 +459,11 @@ void main() { } test('e1g1 -> e1h1', () { - makeTestAltCastlingMove( - '1. e4 e5 2. Nf3 Nf6 3. Bc4 Bc5 4. O-O', - 'e1g1', - 'e1h1', - ); + makeTestAltCastlingMove('1. e4 e5 2. Nf3 Nf6 3. Bc4 Bc5 4. O-O', 'e1g1', 'e1h1'); }); test('e8g8 -> e8h8', () { - makeTestAltCastlingMove( - '1. e4 e5 2. Nf3 Nf6 3. Bc4 Bc5 4. O-O O-O', - 'e8g8', - 'e8h8', - ); + makeTestAltCastlingMove('1. e4 e5 2. Nf3 Nf6 3. Bc4 Bc5 4. O-O O-O', 'e8g8', 'e8h8'); }); test('e1c1 -> e1a1', () { @@ -607,8 +482,7 @@ void main() { ); }); test('only convert king moves in altCastlingMove', () { - const pgn = - '1. e4 e5 2. Bc4 Qh4 3. Nf3 Qxh2 4. Ke2 Qxh1 5. Qe1 Qh5 6. Qh1'; + const pgn = '1. e4 e5 2. Bc4 Qh4 3. Nf3 Qxh2 4. Ke2 Qxh1 5. Qe1 Qh5 6. Qh1'; final root = Root.fromPgnGame(PgnGame.parsePgn(pgn)); final initialPng = root.makePgn(); final previousUciPath = root.mainlinePath.penultimate; @@ -617,11 +491,8 @@ void main() { expect(root.makePgn(), isNot(initialPng)); }); - test( - 'do not convert castling move if rook is on the alternative castling square', - () { - const pgn = - '[FEN "rnbqkbnr/pppppppp/8/8/8/2NBQ3/PPPPPPPP/2R1KBNR w KQkq - 0 1"]'; + test('do not convert castling move if rook is on the alternative castling square', () { + const pgn = '[FEN "rnbqkbnr/pppppppp/8/8/8/2NBQ3/PPPPPPPP/2R1KBNR w KQkq - 0 1"]'; final root = Root.fromPgnGame(PgnGame.parsePgn(pgn)); final initialPng = root.makePgn(); final previousUciPath = root.mainlinePath.penultimate; diff --git a/test/model/common/service/fake_sound_service.dart b/test/model/common/service/fake_sound_service.dart index 12f09936a0..d6b8ccafe0 100644 --- a/test/model/common/service/fake_sound_service.dart +++ b/test/model/common/service/fake_sound_service.dart @@ -6,10 +6,7 @@ class FakeSoundService implements SoundService { Future play(Sound sound) async {} @override - Future changeTheme( - SoundTheme theme, { - bool playSound = false, - }) async {} + Future changeTheme(SoundTheme theme, {bool playSound = false}) async {} @override Future release() async {} diff --git a/test/model/common/time_increment_test.dart b/test/model/common/time_increment_test.dart index 84839b3904..022c615a08 100644 --- a/test/model/common/time_increment_test.dart +++ b/test/model/common/time_increment_test.dart @@ -30,10 +30,7 @@ void main() { }); test('Estimated Duration', () { - expect( - const TimeIncrement(300, 5).estimatedDuration, - const Duration(seconds: 300 + 5 * 40), - ); + expect(const TimeIncrement(300, 5).estimatedDuration, const Duration(seconds: 300 + 5 * 40)); expect(const TimeIncrement(0, 0).estimatedDuration, Duration.zero); }); diff --git a/test/model/correspondence/correspondence_game_storage_test.dart b/test/model/correspondence/correspondence_game_storage_test.dart index 58e901f34c..9e5d10a527 100644 --- a/test/model/correspondence/correspondence_game_storage_test.dart +++ b/test/model/correspondence/correspondence_game_storage_test.dart @@ -20,16 +20,10 @@ void main() { test('save and fetch data', () async { final container = await makeContainer(); - final storage = - await container.read(correspondenceGameStorageProvider.future); + final storage = await container.read(correspondenceGameStorageProvider.future); await storage.save(corresGame); - expect( - storage.fetch( - gameId: gameId, - ), - completion(equals(corresGame)), - ); + expect(storage.fetch(gameId: gameId), completion(equals(corresGame))); }); }); } @@ -62,9 +56,7 @@ final corresGame = OfflineCorrespondenceGame( variant: Variant.standard, ), fullId: const GameFullId('g2bzFol8fgty'), - steps: _makeSteps( - 'e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2', - ), + steps: _makeSteps('e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2'), clock: const CorrespondenceClockData( white: Duration(days: 2, hours: 23, minutes: 59), black: Duration(days: 3), @@ -74,20 +66,8 @@ final corresGame = OfflineCorrespondenceGame( variant: Variant.standard, speed: Speed.correspondence, perf: Perf.classical, - white: const Player( - user: LightUser( - id: UserId('whiteId'), - name: 'White', - ), - rating: 1500, - ), - black: const Player( - user: LightUser( - id: UserId('blackId'), - name: 'Black', - ), - rating: 1500, - ), + white: const Player(user: LightUser(id: UserId('whiteId'), name: 'White'), rating: 1500), + black: const Player(user: LightUser(id: UserId('blackId'), name: 'Black'), rating: 1500), youAre: Side.white, daysPerTurn: 3, ); diff --git a/test/model/game/game_repository_test.dart b/test/model/game/game_repository_test.dart index 176bdb07df..5db4f3c474 100644 --- a/test/model/game/game_repository_test.dart +++ b/test/model/game/game_repository_test.dart @@ -46,11 +46,7 @@ void main() { {"id":"9WLmxmiB","rated":true,"variant":"standard","speed":"blitz","perf":"blitz","createdAt":1673553299064,"lastMoveAt":1673553615438,"status":"resign","players":{"white":{"user":{"name":"Dr-Alaakour","id":"dr-alaakour"},"rating":1806,"ratingDiff":5},"black":{"user":{"name":"Thibault","patron":true,"id":"thibault"},"rating":1772,"ratingDiff":-5}},"winner":"white","clock":{"initial":180,"increment":0,"totalTime":180},"lastFen":"2b1Q1k1/p1r4p/1p2p1p1/3pN3/2qP4/P4R2/1P3PPP/4R1K1 b - - 0 1"} '''; - final ids = ISet(const { - GameId('Huk88k3D'), - GameId('g2bzFol8'), - GameId('9WLmxmiB'), - }); + final ids = ISet(const {GameId('Huk88k3D'), GameId('g2bzFol8'), GameId('9WLmxmiB')}); final mockClient = MockClient((request) { if (request.url.path == '/api/games/export/_ids') { diff --git a/test/model/game/game_socket_events_test.dart b/test/model/game/game_socket_events_test.dart index 3bbae4e59b..a61db74930 100644 --- a/test/model/game/game_socket_events_test.dart +++ b/test/model/game/game_socket_events_test.dart @@ -14,19 +14,10 @@ void main() { final fullEvent = GameFullEvent.fromJson(json); final game = fullEvent.game; expect(game.id, const GameId('nV3DaALy')); - expect( - game.clock?.running, - true, - ); - expect( - game.clock?.white, - const Duration(seconds: 149, milliseconds: 50), - ); + expect(game.clock?.running, true); + expect(game.clock?.white, const Duration(seconds: 149, milliseconds: 50)); - expect( - game.clock?.black, - const Duration(seconds: 775, milliseconds: 940), - ); + expect(game.clock?.black, const Duration(seconds: 775, milliseconds: 940)); expect( game.meta, GameMeta( diff --git a/test/model/game/game_socket_example_data.dart b/test/model/game/game_socket_example_data.dart index f841c65cd5..272907e58f 100644 --- a/test/model/game/game_socket_example_data.dart +++ b/test/model/game/game_socket_example_data.dart @@ -1,20 +1,17 @@ import 'package:dartchess/dartchess.dart'; import 'package:lichess_mobile/src/model/common/id.dart'; -typedef FullEventTestClock = ({ - bool running, - Duration initial, - Duration increment, - Duration? emerg, - Duration white, - Duration black, -}); +typedef FullEventTestClock = + ({ + bool running, + Duration initial, + Duration increment, + Duration? emerg, + Duration white, + Duration black, + }); -typedef FullEventTestCorrespondenceClock = ({ - Duration white, - Duration black, - int daysPerTurn, -}); +typedef FullEventTestCorrespondenceClock = ({Duration white, Duration black, int daysPerTurn}); String makeFullEvent( GameId id, @@ -34,8 +31,9 @@ String makeFullEvent( FullEventTestCorrespondenceClock? correspondenceClock, }) { final youAreStr = youAre != null ? '"youAre": "${youAre.name}",' : ''; - final clockStr = clock != null - ? ''' + final clockStr = + clock != null + ? ''' "clock": { "running": ${clock.running}, "initial": ${clock.initial.inSeconds}, @@ -46,17 +44,18 @@ String makeFullEvent( "moretime": 15 }, ''' - : ''; + : ''; - final correspondenceClockStr = correspondenceClock != null - ? ''' + final correspondenceClockStr = + correspondenceClock != null + ? ''' "correspondence": { "daysPerTurn": ${correspondenceClock.daysPerTurn}, "white": ${(correspondenceClock.white.inMilliseconds / 1000).toStringAsFixed(2)}, "black": ${(correspondenceClock.black.inMilliseconds / 1000).toStringAsFixed(2)} }, ''' - : ''; + : ''; return ''' { diff --git a/test/model/game/game_storage_test.dart b/test/model/game/game_storage_test.dart index acf60a92a2..201dd428b1 100644 --- a/test/model/game/game_storage_test.dart +++ b/test/model/game/game_storage_test.dart @@ -23,12 +23,7 @@ void main() { final storage = await container.read(gameStorageProvider.future); await storage.save(game); - expect( - storage.fetch( - gameId: gameId, - ), - completion(equals(game)), - ); + expect(storage.fetch(gameId: gameId), completion(equals(game))); }); test('paginate games', () async { @@ -46,11 +41,7 @@ void main() { expect(page1.length, 10); expect(page1.last.game.id, const GameId('game0090')); - final page2 = await storage.page( - userId: userId, - max: 10, - until: page1.last.lastModified, - ); + final page2 = await storage.page(userId: userId, max: 10, until: page1.last.lastModified); expect(page2.length, 10); expect(page2.last.game.id, const GameId('game0080')); }); @@ -94,43 +85,14 @@ final game = ArchivedGame( speed: Speed.blitz, rated: true, status: GameStatus.started, - white: const Player( - user: LightUser( - id: UserId('whiteId'), - name: 'White', - ), - rating: 1500, - ), - black: const Player( - user: LightUser( - id: UserId('blackId'), - name: 'Black', - ), - rating: 1500, - ), - clock: ( - initial: const Duration(minutes: 2), - increment: const Duration(seconds: 3), - ), - ), - steps: _makeSteps( - 'e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2', + white: const Player(user: LightUser(id: UserId('whiteId'), name: 'White'), rating: 1500), + black: const Player(user: LightUser(id: UserId('blackId'), name: 'Black'), rating: 1500), + clock: (initial: const Duration(minutes: 2), increment: const Duration(seconds: 3)), ), + steps: _makeSteps('e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2'), status: GameStatus.started, - white: const Player( - user: LightUser( - id: UserId('whiteId'), - name: 'White', - ), - rating: 1500, - ), - black: const Player( - user: LightUser( - id: UserId('blackId'), - name: 'Black', - ), - rating: 1500, - ), + white: const Player(user: LightUser(id: UserId('whiteId'), name: 'White'), rating: 1500), + black: const Player(user: LightUser(id: UserId('blackId'), name: 'Black'), rating: 1500), youAre: Side.white, ); @@ -155,43 +117,14 @@ final games = List.generate(100, (index) { speed: Speed.blitz, rated: true, status: GameStatus.started, - white: const Player( - user: LightUser( - id: UserId('whiteId'), - name: 'White', - ), - rating: 1500, - ), - black: const Player( - user: LightUser( - id: UserId('blackId'), - name: 'Black', - ), - rating: 1500, - ), - clock: ( - initial: const Duration(minutes: 2), - increment: const Duration(seconds: 3), - ), - ), - steps: _makeSteps( - 'e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2', + white: const Player(user: LightUser(id: UserId('whiteId'), name: 'White'), rating: 1500), + black: const Player(user: LightUser(id: UserId('blackId'), name: 'Black'), rating: 1500), + clock: (initial: const Duration(minutes: 2), increment: const Duration(seconds: 3)), ), + steps: _makeSteps('e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2'), status: GameStatus.started, - white: const Player( - user: LightUser( - id: UserId('whiteId'), - name: 'White', - ), - rating: 1500, - ), - black: const Player( - user: LightUser( - id: UserId('blackId'), - name: 'Black', - ), - rating: 1500, - ), + white: const Player(user: LightUser(id: UserId('whiteId'), name: 'White'), rating: 1500), + black: const Player(user: LightUser(id: UserId('blackId'), name: 'Black'), rating: 1500), youAre: Side.white, ); }); diff --git a/test/model/game/game_test.dart b/test/model/game/game_test.dart index e67e44c719..6fdd980b27 100644 --- a/test/model/game/game_test.dart +++ b/test/model/game/game_test.dart @@ -11,9 +11,7 @@ void main() { jsonDecode(_unfinishedGameJson) as Map, ); - expect( - game.makePgn(), - ''' + expect(game.makePgn(), ''' [Event "Rated Bullet game"] [Site "https://lichess.dev/Fn9UvVKF"] [Date "2024.01.25"] @@ -26,8 +24,7 @@ void main() { [TimeControl "120+1"] * -''', - ); +'''); }); test('makePgn, finished game', () { @@ -35,9 +32,7 @@ void main() { jsonDecode(_playableGameJson) as Map, ); - expect( - game.makePgn(), - ''' + expect(game.makePgn(), ''' [Event "Rated Bullet game"] [Site "https://lichess.dev/CCW6EEru"] [Date "2024.01.25"] @@ -52,15 +47,12 @@ void main() { [TimeControl "120+1"] 1. e4 e5 2. Nf3 Nc6 3. Bc4 Bc5 4. b4 Bxb4 5. c3 Ba5 6. d4 Bb6 7. Ba3 Nf6 8. Qb3 d6 9. Bxf7+ Kf8 10. O-O Qe7 11. Nxe5 Nxe5 12. dxe5 Be6 13. Bxe6 Nxe4 14. Re1 Nc5 15. Bxc5 Bxc5 16. Qxb7 Re8 17. Bh3 dxe5 18. Qf3+ Kg8 19. Nd2 Rf8 20. Qd5+ Rf7 21. Be6 Qxe6 22. Qxe6 1-0 -''', - ); +'''); }); test('toArchivedGame', () { for (final game in [_playableGameJson, _playable960GameJson]) { - final playableGame = PlayableGame.fromServerJson( - jsonDecode(game) as Map, - ); + final playableGame = PlayableGame.fromServerJson(jsonDecode(game) as Map); final now = DateTime.now(); final archivedGame = playableGame.toArchivedGame(finishedAt: now); @@ -83,16 +75,13 @@ void main() { archivedGame.data.clock, playableGame.meta.clock != null ? ( - initial: playableGame.meta.clock!.initial, - increment: playableGame.meta.clock!.increment, - ) + initial: playableGame.meta.clock!.initial, + increment: playableGame.meta.clock!.increment, + ) : null, ); expect(archivedGame.initialFen, playableGame.initialFen); - expect( - archivedGame.isThreefoldRepetition, - playableGame.isThreefoldRepetition, - ); + expect(archivedGame.isThreefoldRepetition, playableGame.isThreefoldRepetition); expect(archivedGame.status, playableGame.status); expect(archivedGame.winner, playableGame.winner); expect(archivedGame.white, playableGame.white); @@ -110,9 +99,7 @@ void main() { final game = ArchivedGame.fromServerJson( jsonDecode(_archivedGameJsonNoEvals) as Map, ); - expect( - game.makePgn(), - ''' + expect(game.makePgn(), ''' [Event "Rated Bullet game"] [Site "https://lichess.dev/CCW6EEru"] [Date "2024.01.25"] @@ -129,17 +116,14 @@ void main() { [Opening "Italian Game: Evans Gambit, Main Line"] 1. e4 { [%clk 0:02:00.03] } e5 { [%clk 0:02:00.03] } 2. Nf3 { [%emt 0:00:02.2] [%clk 0:01:58.83] } Nc6 { [%emt 0:00:02.92] [%clk 0:01:58.11] } 3. Bc4 { [%emt 0:00:03] [%clk 0:01:56.83] } Bc5 { [%emt 0:00:05.32] [%clk 0:01:53.79] } 4. b4 { [%emt 0:00:04.76] [%clk 0:01:53.07] } Bxb4 { [%emt 0:00:03.16] [%clk 0:01:51.63] } 5. c3 { [%emt 0:00:03.64] [%clk 0:01:50.43] } Ba5 { [%emt 0:00:02.2] [%clk 0:01:50.43] } 6. d4 { [%emt 0:00:02.44] [%clk 0:01:48.99] } Bb6 { [%emt 0:00:04.36] [%clk 0:01:47.07] } 7. Ba3 { [%emt 0:00:08.44] [%clk 0:01:41.55] } Nf6 { [%emt 0:00:03.24] [%clk 0:01:44.83] } 8. Qb3 { [%emt 0:00:02.36] [%clk 0:01:40.19] } d6 { [%emt 0:00:05.88] [%clk 0:01:39.95] } 9. Bxf7+ { [%emt 0:00:04.84] [%clk 0:01:36.35] } Kf8 { [%emt 0:00:01.72] [%clk 0:01:39.23] } 10. O-O { [%emt 0:00:07.72] [%clk 0:01:29.63] } Qe7 { [%emt 0:00:14.2] [%clk 0:01:26.03] } 11. Nxe5 { [%emt 0:00:11.48] [%clk 0:01:19.15] } Nxe5 { [%emt 0:00:04.2] [%clk 0:01:22.83] } 12. dxe5 { [%emt 0:00:02.52] [%clk 0:01:17.63] } Be6 { [%emt 0:00:09.24] [%clk 0:01:14.59] } 13. Bxe6 { [%emt 0:00:04.84] [%clk 0:01:13.79] } Nxe4 { [%emt 0:00:14.76] [%clk 0:01:00.83] } 14. Re1 { [%emt 0:00:08.92] [%clk 0:01:05.87] } Nc5 { [%emt 0:00:03.64] [%clk 0:00:58.19] } 15. Bxc5 { [%emt 0:00:03.24] [%clk 0:01:03.63] } Bxc5 { [%emt 0:00:02.68] [%clk 0:00:56.51] } 16. Qxb7 { [%emt 0:00:03.88] [%clk 0:01:00.75] } Re8 { [%emt 0:00:02.44] [%clk 0:00:55.07] } 17. Bh3 { [%emt 0:00:05] [%clk 0:00:56.75] } dxe5 { [%emt 0:00:08.04] [%clk 0:00:48.03] } 18. Qf3+ { [%emt 0:00:07.16] [%clk 0:00:50.59] } Kg8 { [%emt 0:00:03.88] [%clk 0:00:45.15] } 19. Nd2 { [%emt 0:00:06.12] [%clk 0:00:45.47] } Rf8 { [%emt 0:00:10.6] [%clk 0:00:35.55] } 20. Qd5+ { [%emt 0:00:06.76] [%clk 0:00:39.71] } Rf7 { [%emt 0:00:02.44] [%clk 0:00:34.11] } 21. Be6 { [%emt 0:00:08.36] [%clk 0:00:32.35] } Qxe6 { [%emt 0:00:03.88] [%clk 0:00:31.23] } 22. Qxe6 { [%emt 0:00:02.15] [%clk 0:00:31.2] } 1-0 -''', - ); +'''); }); test('makePgn, with evals and clocks', () { final game = ArchivedGame.fromServerJson( jsonDecode(_archivedGameJson) as Map, ); - expect( - game.makePgn(), - ''' + expect(game.makePgn(), ''' [Event "Rated Bullet game"] [Site "https://lichess.dev/CCW6EEru"] [Date "2024.01.25"] @@ -156,8 +140,7 @@ void main() { [Opening "Italian Game: Evans Gambit, Main Line"] 1. e4 { [%eval 0.32] [%clk 0:02:00.03] } e5 { [%eval 0.41] [%clk 0:02:00.03] } 2. Nf3 { [%eval 0.39] [%emt 0:00:02.2] [%clk 0:01:58.83] } Nc6 { [%eval 0.20] [%emt 0:00:02.92] [%clk 0:01:58.11] } 3. Bc4 { [%eval 0.17] [%emt 0:00:03] [%clk 0:01:56.83] } Bc5 { [%eval 0.21] [%emt 0:00:05.32] [%clk 0:01:53.79] } 4. b4 { [%eval -0.21] [%emt 0:00:04.76] [%clk 0:01:53.07] } Bxb4 { [%eval -0.14] [%emt 0:00:03.16] [%clk 0:01:51.63] } 5. c3 { [%eval -0.23] [%emt 0:00:03.64] [%clk 0:01:50.43] } Ba5 { [%eval -0.24] [%emt 0:00:02.2] [%clk 0:01:50.43] } 6. d4 { [%eval -0.24] [%emt 0:00:02.44] [%clk 0:01:48.99] } Bb6 \$6 { Inaccuracy. d6 was best. [%eval 0.52] [%emt 0:00:04.36] [%clk 0:01:47.07] } ( 6... d6 ) 7. Ba3 \$6 { Inaccuracy. Nxe5 was best. [%eval -0.56] [%emt 0:00:08.44] [%clk 0:01:41.55] } ( 7. Nxe5 ) 7... Nf6 \$4 { Blunder. d6 was best. [%eval 1.77] [%emt 0:00:03.24] [%clk 0:01:44.83] } ( 7... d6 ) 8. Qb3 \$4 { Blunder. dxe5 was best. [%eval -0.19] [%emt 0:00:02.36] [%clk 0:01:40.19] } ( 8. dxe5 Ng4 9. Qd5 Nh6 10. Nbd2 Ne7 11. Qd3 O-O 12. h3 d6 13. g4 Kh8 14. exd6 cxd6 ) 8... d6 { [%eval -0.16] [%emt 0:00:05.88] [%clk 0:01:39.95] } 9. Bxf7+ { [%eval -0.20] [%emt 0:00:04.84] [%clk 0:01:36.35] } Kf8 { [%eval -0.12] [%emt 0:00:01.72] [%clk 0:01:39.23] } 10. O-O \$2 { Mistake. Bd5 was best. [%eval -1.45] [%emt 0:00:07.72] [%clk 0:01:29.63] } ( 10. Bd5 Nxd5 ) 10... Qe7 \$4 { Blunder. Na5 was best. [%eval 0.72] [%emt 0:00:14.2] [%clk 0:01:26.03] } ( 10... Na5 11. Qd1 Kxf7 12. dxe5 dxe5 13. Nxe5+ Ke8 14. Nd2 Be6 15. Qa4+ Bd7 16. Qd1 Nc6 17. Ndc4 ) 11. Nxe5 \$6 { Inaccuracy. Bd5 was best. [%eval -0.36] [%emt 0:00:11.48] [%clk 0:01:19.15] } ( 11. Bd5 Nxd5 12. exd5 Na5 13. Qb4 exd4 14. cxd4 Kg8 15. Re1 Qf7 16. Ng5 Qg6 17. Nc3 h6 ) 11... Nxe5 { [%eval -0.41] [%emt 0:00:04.2] [%clk 0:01:22.83] } 12. dxe5 { [%eval -0.42] [%emt 0:00:02.52] [%clk 0:01:17.63] } Be6 \$4 { Blunder. Qxf7 was best. [%eval 5.93] [%emt 0:00:09.24] [%clk 0:01:14.59] } ( 12... Qxf7 13. exf6 gxf6 14. c4 Rg8 15. Nd2 Qh5 16. c5 Bh3 17. g3 Bxc5 18. Bxc5 dxc5 19. Rfe1 ) 13. Bxe6 { [%eval 5.89] [%emt 0:00:04.84] [%clk 0:01:13.79] } Nxe4 { [%eval 6.30] [%emt 0:00:14.76] [%clk 0:01:00.83] } 14. Re1 \$4 { Blunder. exd6 was best. [%eval -0.32] [%emt 0:00:08.92] [%clk 0:01:05.87] } ( 14. exd6 cxd6 15. Bd5 Nxf2 16. Nd2 g5 17. Nc4 Kg7 18. Nxb6 Qe3 19. Rxf2 Rhf8 20. Bf3 axb6 ) 14... Nc5 \$4 { Blunder. Bxf2+ was best. [%eval 6.02] [%emt 0:00:03.64] [%clk 0:00:58.19] } ( 14... Bxf2+ ) 15. Bxc5 { [%eval 5.81] [%emt 0:00:03.24] [%clk 0:01:03.63] } Bxc5 { [%eval 6.56] [%emt 0:00:02.68] [%clk 0:00:56.51] } 16. Qxb7 { [%eval 6.62] [%emt 0:00:03.88] [%clk 0:01:00.75] } Re8 \$4 { Checkmate is now unavoidable. g6 was best. [%eval #15] [%emt 0:00:02.44] [%clk 0:00:55.07] } ( 16... g6 17. Qxa8+ Kg7 18. Qd5 c6 19. Qb3 Rf8 20. Re2 Qh4 21. Qb7+ Kh6 22. Qb2 dxe5 23. Nd2 ) 17. Bh3 \$4 { Lost forced checkmate sequence. Qf3+ was best. [%eval 5.66] [%emt 0:00:05] [%clk 0:00:56.75] } ( 17. Qf3+ Qf6 18. exf6 g6 19. f7 Kg7 20. fxe8=Q Rxe8 21. Qf7+ Kh6 22. Qxe8 d5 23. g4 c6 ) 17... dxe5 { [%eval 5.74] [%emt 0:00:08.04] [%clk 0:00:48.03] } 18. Qf3+ { [%eval 5.66] [%emt 0:00:07.16] [%clk 0:00:50.59] } Kg8 { [%eval 5.80] [%emt 0:00:03.88] [%clk 0:00:45.15] } 19. Nd2 { [%eval 5.69] [%emt 0:00:06.12] [%clk 0:00:45.47] } Rf8 \$6 { Inaccuracy. g6 was best. [%eval 7.74] [%emt 0:00:10.6] [%clk 0:00:35.55] } ( 19... g6 20. Ne4 Kg7 21. Qe2 Rd8 22. a4 h5 23. Rad1 Rxd1 24. Rxd1 Bb6 25. Rd7 Qxd7 26. Bxd7 ) 20. Qd5+ { [%eval 7.39] [%emt 0:00:06.76] [%clk 0:00:39.71] } Rf7 { [%eval 7.43] [%emt 0:00:02.44] [%clk 0:00:34.11] } 21. Be6 { [%eval 6.15] [%emt 0:00:08.36] [%clk 0:00:32.35] } Qxe6 \$6 { Inaccuracy. Bxf2+ was best. [%eval 9.34] [%emt 0:00:03.88] [%clk 0:00:31.23] } ( 21... Bxf2+ 22. Kh1 Bxe1 23. Rxe1 g6 24. Rf1 Kg7 25. Rxf7+ Qxf7 26. Bxf7 Rf8 27. Be6 e4 28. Qxe4 ) 22. Qxe6 { [%eval 8.61] [%emt 0:00:02.15] [%clk 0:00:31.2] } 1-0 -''', - ); +'''); }); }); } diff --git a/test/model/game/material_diff_test.dart b/test/model/game/material_diff_test.dart index a6391a6b18..264f25b582 100644 --- a/test/model/game/material_diff_test.dart +++ b/test/model/game/material_diff_test.dart @@ -6,8 +6,7 @@ import 'package:lichess_mobile/src/model/game/material_diff.dart'; void main() { group('GameMaterialDiff', () { test('generation from board', () { - final Board board = - Board.parseFen('r5k1/3Q1pp1/2p4p/4P1b1/p3R3/3P4/6PP/R5K1'); + final Board board = Board.parseFen('r5k1/3Q1pp1/2p4p/4P1b1/p3R3/3P4/6PP/R5K1'); final MaterialDiff diff = MaterialDiff.fromBoard(board); expect(diff.bySide(Side.black).score, equals(-10)); diff --git a/test/model/notifications/fake_notification_display.dart b/test/model/notifications/fake_notification_display.dart index 47484acfa6..b4332ca865 100644 --- a/test/model/notifications/fake_notification_display.dart +++ b/test/model/notifications/fake_notification_display.dart @@ -1,8 +1,7 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_test/flutter_test.dart'; -class FakeNotificationDisplay extends Fake - implements FlutterLocalNotificationsPlugin { +class FakeNotificationDisplay extends Fake implements FlutterLocalNotificationsPlugin { final Map _activeNotifications = {}; @override diff --git a/test/model/notifications/notification_service_test.dart b/test/model/notifications/notification_service_test.dart index 472455172a..776d059fbe 100644 --- a/test/model/notifications/notification_service_test.dart +++ b/test/model/notifications/notification_service_test.dart @@ -18,8 +18,7 @@ import '../../test_container.dart'; import '../../test_helpers.dart'; import '../auth/fake_session_storage.dart'; -class NotificationDisplayMock extends Mock - implements FlutterLocalNotificationsPlugin {} +class NotificationDisplayMock extends Mock implements FlutterLocalNotificationsPlugin {} class CorrespondenceServiceMock extends Mock implements CorrespondenceService {} @@ -59,56 +58,49 @@ void main() { await notificationService.start(); - final calls = - testBinding.firebaseMessaging.verifyRequestPermissionCalls(); + final calls = testBinding.firebaseMessaging.verifyRequestPermissionCalls(); expect(calls, hasLength(1)); expect( calls.first, - equals( - ( - alert: true, - badge: true, - sound: true, - announcement: false, - carPlay: false, - criticalAlert: false, - provisional: false, - ), - ), + equals(( + alert: true, + badge: true, + sound: true, + announcement: false, + carPlay: false, + criticalAlert: false, + provisional: false, + )), ); }); test( - 'register device when online, token exists and permissions are granted and a session exists', - () async { - final container = await makeContainer( - userSession: fakeSession, - overrides: [ - lichessClientProvider.overrideWith( - (ref) => LichessClient(registerMockClient, ref), - ), - ], - ); + 'register device when online, token exists and permissions are granted and a session exists', + () async { + final container = await makeContainer( + userSession: fakeSession, + overrides: [ + lichessClientProvider.overrideWith((ref) => LichessClient(registerMockClient, ref)), + ], + ); - final notificationService = container.read(notificationServiceProvider); + final notificationService = container.read(notificationServiceProvider); - FakeAsync().run((async) { - notificationService.start(); + FakeAsync().run((async) { + notificationService.start(); - async.flushMicrotasks(); + async.flushMicrotasks(); - expect(registerDeviceCalls, 1); - }); - }); + expect(registerDeviceCalls, 1); + }); + }, + ); - test("don't try to register device when permissions are not granted", - () async { + test("don't try to register device when permissions are not granted", () async { final container = await makeContainer( userSession: fakeSession, overrides: [ - lichessClientProvider.overrideWith( - (ref) => LichessClient(registerMockClient, ref), - ), + lichessClientProvider.overrideWith((ref) => LichessClient(registerMockClient, ref)), ], ); @@ -128,9 +120,7 @@ void main() { test("don't try to register device when user is not logged in", () async { final container = await makeContainer( overrides: [ - lichessClientProvider.overrideWith( - (ref) => LichessClient(registerMockClient, ref), - ), + lichessClientProvider.overrideWith((ref) => LichessClient(registerMockClient, ref)), ], ); @@ -147,16 +137,12 @@ void main() { }); group('Correspondence game update notifications', () { - test('FCM message with associated notification will show it in foreground', - () async { + test('FCM message with associated notification will show it in foreground', () async { final container = await makeContainer( userSession: fakeSession, overrides: [ - lichessClientProvider.overrideWith( - (ref) => LichessClient(registerMockClient, ref), - ), - notificationDisplayProvider - .overrideWith((_) => notificationDisplayMock), + lichessClientProvider.overrideWith((ref) => LichessClient(registerMockClient, ref)), + notificationDisplayProvider.overrideWith((_) => notificationDisplayMock), ], ); @@ -181,10 +167,7 @@ void main() { testBinding.firebaseMessaging.onMessage.add( const RemoteMessage( - data: { - 'lichess.type': 'gameMove', - 'lichess.fullId': '9wlmxmibr9gh', - }, + data: {'lichess.type': 'gameMove', 'lichess.fullId': '9wlmxmibr9gh'}, notification: RemoteNotification( title: 'It is your turn!', body: 'Dr-Alaakour played a move', @@ -214,16 +197,8 @@ void main() { expect( result.captured[0], isA() - .having( - (d) => d.android?.importance, - 'importance', - Importance.high, - ) - .having( - (d) => d.android?.priority, - 'priority', - Priority.defaultPriority, - ), + .having((d) => d.android?.importance, 'importance', Importance.high) + .having((d) => d.android?.priority, 'priority', Priority.defaultPriority), ); }); }); @@ -232,13 +207,9 @@ void main() { final container = await makeContainer( userSession: fakeSession, overrides: [ - lichessClientProvider.overrideWith( - (ref) => LichessClient(registerMockClient, ref), - ), - notificationDisplayProvider - .overrideWith((_) => notificationDisplayMock), - correspondenceServiceProvider - .overrideWith((_) => correspondenceServiceMock), + lichessClientProvider.overrideWith((ref) => LichessClient(registerMockClient, ref)), + notificationDisplayProvider.overrideWith((_) => notificationDisplayMock), + correspondenceServiceProvider.overrideWith((_) => correspondenceServiceMock), ], ); @@ -310,13 +281,9 @@ void main() { final container = await makeContainer( userSession: fakeSession, overrides: [ - lichessClientProvider.overrideWith( - (ref) => LichessClient(registerMockClient, ref), - ), - notificationDisplayProvider - .overrideWith((_) => notificationDisplayMock), - correspondenceServiceProvider - .overrideWith((_) => correspondenceServiceMock), + lichessClientProvider.overrideWith((ref) => LichessClient(registerMockClient, ref)), + notificationDisplayProvider.overrideWith((_) => notificationDisplayMock), + correspondenceServiceProvider.overrideWith((_) => correspondenceServiceMock), ], ); diff --git a/test/model/puzzle/puzzle_batch_storage_test.dart b/test/model/puzzle/puzzle_batch_storage_test.dart index 0f493f97be..8ee1623183 100644 --- a/test/model/puzzle/puzzle_batch_storage_test.dart +++ b/test/model/puzzle/puzzle_batch_storage_test.dart @@ -21,17 +21,10 @@ void main() { final storage = await container.read(puzzleBatchStorageProvider.future); - await storage.save( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - data: data, - ); + await storage.save(userId: null, angle: const PuzzleTheme(PuzzleThemeKey.mix), data: data); expect( - storage.fetch( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - ), + storage.fetch(userId: null, angle: const PuzzleTheme(PuzzleThemeKey.mix)), completion(equals(data)), ); }); @@ -41,11 +34,7 @@ void main() { final storage = await container.read(puzzleBatchStorageProvider.future); - await storage.save( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - data: data, - ); + await storage.save(userId: null, angle: const PuzzleTheme(PuzzleThemeKey.mix), data: data); await storage.save( userId: null, angle: const PuzzleTheme(PuzzleThemeKey.rookEndgame), @@ -76,27 +65,12 @@ void main() { final storage = await container.read(puzzleBatchStorageProvider.future); - await storage.save( - userId: null, - angle: const PuzzleOpening('test_opening'), - data: data, - ); - await storage.save( - userId: null, - angle: const PuzzleOpening('test_opening2'), - data: data, - ); + await storage.save(userId: null, angle: const PuzzleOpening('test_opening'), data: data); + await storage.save(userId: null, angle: const PuzzleOpening('test_opening2'), data: data); expect( storage.fetchSavedOpenings(userId: null), - completion( - equals( - IMap(const { - 'test_opening': 1, - 'test_opening2': 1, - }), - ), - ), + completion(equals(IMap(const {'test_opening': 1, 'test_opening2': 1}))), ); }); @@ -107,38 +81,18 @@ void main() { final storage = await container.read(puzzleBatchStorageProvider.future); Future save(PuzzleAngle angle, PuzzleBatch data, String timestamp) { - return database.insert( - 'puzzle_batchs', - { - 'userId': '**anon**', - 'angle': angle.key, - 'data': jsonEncode(data.toJson()), - 'lastModified': timestamp, - }, - conflictAlgorithm: ConflictAlgorithm.replace, - ); + return database.insert('puzzle_batchs', { + 'userId': '**anon**', + 'angle': angle.key, + 'data': jsonEncode(data.toJson()), + 'lastModified': timestamp, + }, conflictAlgorithm: ConflictAlgorithm.replace); } - await save( - const PuzzleTheme(PuzzleThemeKey.rookEndgame), - data, - '2021-01-02T00:00:00Z', - ); - await save( - const PuzzleTheme(PuzzleThemeKey.doubleBishopMate), - data, - '2021-01-03T00:00:00Z', - ); - await save( - const PuzzleOpening('test_opening'), - data, - '2021-01-04T00:00:00Z', - ); - await save( - const PuzzleOpening('test_opening2'), - data, - '2021-01-04T80:00:00Z', - ); + await save(const PuzzleTheme(PuzzleThemeKey.rookEndgame), data, '2021-01-02T00:00:00Z'); + await save(const PuzzleTheme(PuzzleThemeKey.doubleBishopMate), data, '2021-01-03T00:00:00Z'); + await save(const PuzzleOpening('test_opening'), data, '2021-01-04T00:00:00Z'); + await save(const PuzzleOpening('test_opening2'), data, '2021-01-04T80:00:00Z'); expect( storage.fetchAll(userId: null), @@ -176,14 +130,8 @@ final data = PuzzleBatch( id: GameId('PrlkCqOv'), perf: Perf.blitz, rated: true, - white: PuzzleGamePlayer( - side: Side.white, - name: 'user1', - ), - black: PuzzleGamePlayer( - side: Side.black, - name: 'user2', - ), + white: PuzzleGamePlayer(side: Side.white, name: 'user1'), + black: PuzzleGamePlayer(side: Side.black, name: 'user2'), pgn: 'e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2', ), ), diff --git a/test/model/puzzle/puzzle_repository_test.dart b/test/model/puzzle/puzzle_repository_test.dart index d053bda7ac..da9a1663ef 100644 --- a/test/model/puzzle/puzzle_repository_test.dart +++ b/test/model/puzzle/puzzle_repository_test.dart @@ -32,12 +32,9 @@ void main() { test('selectBatch with glicko', () async { final mockClient = MockClient((request) { if (request.url.path == '/api/puzzle/batch/mix') { - return mockResponse( - ''' + return mockResponse(''' {"puzzles":[{"game":{"id":"PrlkCqOv","perf":{"key":"rapid","name":"Rapid"},"rated":true,"players":[{"userId":"silverjo","name":"silverjo (1777)","color":"white"},{"userId":"robyarchitetto","name":"Robyarchitetto (1742)","color":"black"}],"pgn":"e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2 Bb7 Bh6 d5 e5 d4 Bxg7 Kxg7 Qf4 Bxf3 Qxf3 dxc3 Nxc3 Nac6 Qf6+ Kg8 Rd1 Nd4 O-O c5 Ne4 Nef5 Rd2 Qxf6 Nxf6+ Kg7 Re1 h5 h3 Rad8 b4 Nh4 Re3 Nhf5 Re1 a5 bxc5 bxc5 Bc4 Ra8 Rb1 Nh4 Rdb2 Nc6 Rb7 Nxe5 Bxe6 Kxf6 Bd5 Nf5 R7b6+ Kg7 Bxa8 Rxa8 R6b3 Nd4 Rb7 Nxd3 Rd1 Ne2+ Kh2 Ndf4 Rdd7 Rf8 Ra7 c4 Rxa5 c3 Rc5 Ne6 Rc4 Ra8 a4 Rb8 a5 Rb2 a6 c2","clock":"5+8"},"puzzle":{"id":"20yWT","rating":1859,"plays":551,"initialPly":93,"solution":["a6a7","b2a2","c4c2","a2a7","d7a7"],"themes":["endgame","long","advantage","advancedPawn"]}}],"glicko":{"rating":1834.54,"deviation":23.45}} -''', - 200, - ); +''', 200); } return mockResponse('', 404); }); @@ -56,12 +53,9 @@ void main() { test('selectBatch with rounds', () async { final mockClient = MockClient((request) { if (request.url.path == '/api/puzzle/batch/mix') { - return mockResponse( - ''' + return mockResponse(''' {"puzzles":[{"game":{"id":"PrlkCqOv","perf":{"key":"rapid","name":"Rapid"},"rated":true,"players":[{"userId":"silverjo","name":"silverjo (1777)","color":"white"},{"userId":"robyarchitetto","name":"Robyarchitetto (1742)","color":"black"}],"pgn":"e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2 Bb7 Bh6 d5 e5 d4 Bxg7 Kxg7 Qf4 Bxf3 Qxf3 dxc3 Nxc3 Nac6 Qf6+ Kg8 Rd1 Nd4 O-O c5 Ne4 Nef5 Rd2 Qxf6 Nxf6+ Kg7 Re1 h5 h3 Rad8 b4 Nh4 Re3 Nhf5 Re1 a5 bxc5 bxc5 Bc4 Ra8 Rb1 Nh4 Rdb2 Nc6 Rb7 Nxe5 Bxe6 Kxf6 Bd5 Nf5 R7b6+ Kg7 Bxa8 Rxa8 R6b3 Nd4 Rb7 Nxd3 Rd1 Ne2+ Kh2 Ndf4 Rdd7 Rf8 Ra7 c4 Rxa5 c3 Rc5 Ne6 Rc4 Ra8 a4 Rb8 a5 Rb2 a6 c2","clock":"5+8"},"puzzle":{"id":"20yWT","rating":1859,"plays":551,"initialPly":93,"solution":["a6a7","b2a2","c4c2","a2a7","d7a7"],"themes":["endgame","long","advantage","advancedPawn"]}}],"glicko":{"rating":1834.54,"deviation":23.45}, "rounds": [{"id": "07jQK", "ratingDiff": 10, "win": true}, {"id": "06jOK", "ratingDiff": -40, "win": false}]} -''', - 200, - ); +''', 200); } return mockResponse('', 404); }); @@ -79,12 +73,9 @@ void main() { test('streak', () async { final mockClient = MockClient((request) { if (request.url.path == '/api/streak') { - return mockResponse( - ''' + return mockResponse(''' {"game":{"id":"3dwjUYP0","perf":{"key":"rapid","name":"Rapid"},"rated":true,"players":[{"userId":"suresh","name":"Suresh (1716)","color":"white"},{"userId":"yulia","name":"Yulia (1765)","color":"black"}],"pgn":"d4 Nf6 Nf3 e6 c4 d5 cxd5 Bb4+ Nc3 Nxd5 Bd2 c6 a3 Bxc3 Bxc3 Nd7 Ne5 Qc7 e3 O-O Bd3 Nxc3 bxc3 Nxe5 dxe5 Qxe5 O-O Qxc3 a4 b6 Rc1 Qf6 Rxc6 Bb7 Rc4 Rad8 Rd4 Qg5 g3 Rxd4 exd4 Qd5 f3 Rd8 Rf2 g6 Be4 Qd7 Bxb7 Qxb7 Kg2 Qd7 Rd2 e5 dxe5","clock":"10+0"},"puzzle":{"id":"9afDa","rating":642,"plays":13675,"initialPly":54,"solution":["d7d2","d1d2","d8d2"],"themes":["endgame","crushing","short"]},"angle":{"key":"mix","name":"Puzzle themes","desc":"A bit of everything. You don't know what to expect, so you remain ready for anything! Just like in real games."},"streak":"9afDa 4V5gW 3mslj 41adQ 2tu7D 9RkvX 0vy7p A4v8U 5ZOBZ 193w0 98fRK CeonU 7yLlT 5RSB1 1tHFC 0Vsh7 7VFdg Dw0Rn EL08H 4dfgu 9ZxSP DUs0d 55MLt 9kmiT 0H0mL 0tBRV 7J6hk 0TjRQ 4G3KC DVlXY 1160r B8UHS 9NmPL 70ujM DJc5M BwkrY 94ynq D9wc6 41QGW 5sDnM 6xRVq 0EkpQ 7nksF 35Umd 0lJjY BrA7Z 8iHjv 5ypqy 4seCY 1bKuj 27svg 6K2S9 5lR21 9WveK DseMX C9m8Q 0K2CK 73mQX Bey7R CFniS 2NMq3 1eKTu 6131w 9m4mG 1H3Oi 9FxX2 4zRod 1C05H 9iEBH 21pIt 95dod 01tg7 47p37 1sK7x 0nSaW BWD8D C6WCD 9h38Q AoWyN CPdp8 ATUTK EFWL2 7GrRe 6W1OR 538Mf CH2cU An8P5 9LrrA 1cIQP B56EI 32pBl 34nq9 1aS2z 3qxyU 4NGY7 9GCq2 C43lx 2W8WA 1bnwL 4I8D1 Dc1u5 BG3VT 3pC4h C5tQJ 3rM5l 6KF3m 6Xnj5 EUX2q 1qiVv 2UTkb 7AtYx CbRCh 5xs9Y BlYuY BGFSj E7AIl 5keIv 1431G 7KYgv 68F2M 16IRi 8cNr9 8g79l BBM7N CmgIo 6zoOr D6Zsx 20mtz"} -''', - 200, - ); +''', 200); } return mockResponse('', 404); }); @@ -100,12 +91,9 @@ void main() { test('puzzle dashboard', () async { final mockClient = MockClient((request) { if (request.url.path == '/api/puzzle/dashboard/30') { - return mockResponse( - ''' + return mockResponse(''' {"days":30,"global":{"nb":196,"firstWins":107,"replayWins":0,"puzzleRatingAvg":1607,"performance":1653},"themes":{"middlegame":{"theme":"Middlegame","results":{"nb":97,"firstWins":51,"replayWins":0,"puzzleRatingAvg":1608,"performance":1634}},"endgame":{"theme":"Endgame","results":{"nb":81,"firstWins":48,"replayWins":0,"puzzleRatingAvg":1604,"performance":1697}}}} - ''', - 200, - ); + ''', 200); } return mockResponse('', 404); }); diff --git a/test/model/puzzle/puzzle_service_test.dart b/test/model/puzzle/puzzle_service_test.dart index 141f851199..4d58402ad1 100644 --- a/test/model/puzzle/puzzle_service_test.dart +++ b/test/model/puzzle/puzzle_service_test.dart @@ -41,13 +41,9 @@ void main() { final container = await makeTestContainer(mockClient); final storage = await container.read(puzzleBatchStorageProvider.future); - final service = await container.read(puzzleServiceFactoryProvider)( - queueLength: 3, - ); + final service = await container.read(puzzleServiceFactoryProvider)(queueLength: 3); - final next = await service.nextPuzzle( - userId: null, - ); + final next = await service.nextPuzzle(userId: null); expect(nbReq, equals(1)); expect(next?.puzzle.puzzle.id, equals(const PuzzleId('20yWT'))); final data = await storage.fetch(userId: null); @@ -67,18 +63,11 @@ void main() { final container = await makeTestContainer(mockClient); final storage = await container.read(puzzleBatchStorageProvider.future); - final service = await container.read(puzzleServiceFactoryProvider)( - queueLength: 1, - ); + final service = await container.read(puzzleServiceFactoryProvider)(queueLength: 1); - await storage.save( - userId: null, - data: _makeUnsolvedPuzzles([const PuzzleId('pId3')]), - ); + await storage.save(userId: null, data: _makeUnsolvedPuzzles([const PuzzleId('pId3')])); - final next = await service.nextPuzzle( - userId: null, - ); + final next = await service.nextPuzzle(userId: null); expect(nbReq, equals(0)); expect(next?.puzzle.puzzle.id, equals(const PuzzleId('pId3'))); final data = await storage.fetch(userId: null); @@ -98,25 +87,17 @@ void main() { final container = await makeTestContainer(mockClient); final storage = await container.read(puzzleBatchStorageProvider.future); - final service = await container.read(puzzleServiceFactoryProvider)( - queueLength: 2, - ); - await storage.save( - userId: null, - data: _makeUnsolvedPuzzles([const PuzzleId('pId3')]), - ); + final service = await container.read(puzzleServiceFactoryProvider)(queueLength: 2); + await storage.save(userId: null, data: _makeUnsolvedPuzzles([const PuzzleId('pId3')])); - final next = await service.nextPuzzle( - userId: null, - ); + final next = await service.nextPuzzle(userId: null); expect(next?.puzzle.puzzle.id, equals(const PuzzleId('pId3'))); expect(nbReq, equals(1)); final data = await storage.fetch(userId: null); expect(data?.unsolved.length, equals(2)); }); - test('nextPuzzle will always get the first puzzle of unsolved queue', - () async { + test('nextPuzzle will always get the first puzzle of unsolved queue', () async { int nbReq = 0; final mockClient = MockClient((request) { nbReq++; @@ -125,44 +106,30 @@ void main() { final container = await makeTestContainer(mockClient); final storage = await container.read(puzzleBatchStorageProvider.future); - final service = await container.read(puzzleServiceFactoryProvider)( - queueLength: 1, - ); - await storage.save( - userId: null, - data: _makeUnsolvedPuzzles([const PuzzleId('pId3')]), - ); + final service = await container.read(puzzleServiceFactoryProvider)(queueLength: 1); + await storage.save(userId: null, data: _makeUnsolvedPuzzles([const PuzzleId('pId3')])); expect(nbReq, equals(0)); - final next = await service.nextPuzzle( - userId: null, - ); + final next = await service.nextPuzzle(userId: null); expect(next?.puzzle.puzzle.id, equals(const PuzzleId('pId3'))); final data = await storage.fetch(userId: null); expect(data?.unsolved.length, equals(1)); - final next2 = await service.nextPuzzle( - userId: null, - ); + final next2 = await service.nextPuzzle(userId: null); expect(next2?.puzzle.puzzle.id, equals(const PuzzleId('pId3'))); final data2 = await storage.fetch(userId: null); expect(data2?.unsolved.length, equals(1)); }); - test('nextPuzzle returns null is unsolved queue is empty and is offline', - () async { + test('nextPuzzle returns null is unsolved queue is empty and is offline', () async { final mockClient = MockClient((request) { throw const SocketException('offline'); }); final container = await makeTestContainer(mockClient); - final service = await container.read(puzzleServiceFactoryProvider)( - queueLength: 1, - ); + final service = await container.read(puzzleServiceFactoryProvider)(queueLength: 1); - final nextPuzzle = await service.nextPuzzle( - userId: null, - ); + final nextPuzzle = await service.nextPuzzle(userId: null); expect(nextPuzzle, isNull); }); @@ -179,25 +146,15 @@ void main() { final container = await makeTestContainer(mockClient); final storage = await container.read(puzzleBatchStorageProvider.future); - final service = await container.read(puzzleServiceFactoryProvider)( - queueLength: 1, - ); - await storage.save( - userId: null, - data: _makeUnsolvedPuzzles([const PuzzleId('pId3')]), - ); + final service = await container.read(puzzleServiceFactoryProvider)(queueLength: 1); + await storage.save(userId: null, data: _makeUnsolvedPuzzles([const PuzzleId('pId3')])); - final next = await service.nextPuzzle( - userId: const UserId('testUserId'), - ); + final next = await service.nextPuzzle(userId: const UserId('testUserId')); expect(next?.puzzle.puzzle.id, equals(const PuzzleId('20yWT'))); expect(nbReq, equals(1)); final data = await storage.fetch(userId: const UserId('testUserId')); - expect( - data?.unsolved.length, - equals(1), - ); + expect(data?.unsolved.length, equals(1)); }); test('different batch is saved per angle', () async { @@ -212,13 +169,8 @@ void main() { final container = await makeTestContainer(mockClient); final storage = await container.read(puzzleBatchStorageProvider.future); - final service = await container.read(puzzleServiceFactoryProvider)( - queueLength: 1, - ); - await storage.save( - userId: null, - data: _makeUnsolvedPuzzles([const PuzzleId('pId3')]), - ); + final service = await container.read(puzzleServiceFactoryProvider)(queueLength: 1); + await storage.save(userId: null, data: _makeUnsolvedPuzzles([const PuzzleId('pId3')])); final next = await service.nextPuzzle( angle: const PuzzleTheme(PuzzleThemeKey.opening), @@ -231,10 +183,7 @@ void main() { userId: null, angle: const PuzzleTheme(PuzzleThemeKey.opening), ); - expect( - data?.unsolved.length, - equals(1), - ); + expect(data?.unsolved.length, equals(1)); }); test('solve puzzle when online, no userId', () async { @@ -249,21 +198,12 @@ void main() { final container = await makeTestContainer(mockClient); final storage = await container.read(puzzleBatchStorageProvider.future); - final service = await container.read(puzzleServiceFactoryProvider)( - queueLength: 1, - ); - await storage.save( - userId: null, - data: _makeUnsolvedPuzzles([const PuzzleId('pId3')]), - ); + final service = await container.read(puzzleServiceFactoryProvider)(queueLength: 1); + await storage.save(userId: null, data: _makeUnsolvedPuzzles([const PuzzleId('pId3')])); final next = await service.solve( puzzle: samplePuzzle, - solution: const PuzzleSolution( - id: PuzzleId('pId3'), - win: true, - rated: true, - ), + solution: const PuzzleSolution(id: PuzzleId('pId3'), win: true, rated: true), userId: null, ); @@ -283,8 +223,7 @@ void main() { nbReq++; if (request.method == 'POST' && request.url.path == '/api/puzzle/batch/mix' && - request.body == - '{"solutions":[{"id":"pId3","win":true,"rated":true}]}') { + request.body == '{"solutions":[{"id":"pId3","win":true,"rated":true}]}') { return mockResponse( '''{"puzzles":[{"game":{"id":"PrlkCqOv","perf":{"key":"rapid","name":"Rapid"},"rated":true,"players":[{"userId":"silverjo","name":"silverjo (1777)","color":"white"},{"userId":"robyarchitetto","name":"Robyarchitetto (1742)","color":"black"}],"pgn":"e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2 Bb7 Bh6 d5 e5 d4 Bxg7 Kxg7 Qf4 Bxf3 Qxf3 dxc3 Nxc3 Nac6 Qf6+ Kg8 Rd1 Nd4 O-O c5 Ne4 Nef5 Rd2 Qxf6 Nxf6+ Kg7 Re1 h5 h3 Rad8 b4 Nh4 Re3 Nhf5 Re1 a5 bxc5 bxc5 Bc4 Ra8 Rb1 Nh4 Rdb2 Nc6 Rb7 Nxe5 Bxe6 Kxf6 Bd5 Nf5 R7b6+ Kg7 Bxa8 Rxa8 R6b3 Nd4 Rb7 Nxd3 Rd1 Ne2+ Kh2 Ndf4 Rdd7 Rf8 Ra7 c4 Rxa5 c3 Rc5 Ne6 Rc4 Ra8 a4 Rb8 a5 Rb2 a6 c2","clock":"5+8"},"puzzle":{"id":"20yWT","rating":1859,"plays":551,"initialPly":93,"solution":["a6a7","b2a2","c4c2","a2a7","d7a7"],"themes":["endgame","long","advantage","advancedPawn"]}}], "glicko":{"rating":1834.54,"deviation":23.45},"rounds":[{"id": "pId3","ratingDiff": 10,"win": true}]}''', 200, @@ -295,9 +234,7 @@ void main() { final container = await makeTestContainer(mockClient); final storage = await container.read(puzzleBatchStorageProvider.future); - final service = await container.read(puzzleServiceFactoryProvider)( - queueLength: 1, - ); + final service = await container.read(puzzleServiceFactoryProvider)(queueLength: 1); await storage.save( userId: const UserId('testUserId'), data: _makeUnsolvedPuzzles([const PuzzleId('pId3')]), @@ -305,11 +242,7 @@ void main() { final next = await service.solve( puzzle: samplePuzzle, - solution: const PuzzleSolution( - id: PuzzleId('pId3'), - win: true, - rated: true, - ), + solution: const PuzzleSolution(id: PuzzleId('pId3'), win: true, rated: true), userId: const UserId('testUserId'), ); @@ -323,15 +256,7 @@ void main() { expect(next?.glicko?.deviation, equals(23.45)); expect( next?.rounds, - equals( - IList(const [ - PuzzleRound( - id: PuzzleId('pId3'), - ratingDiff: 10, - win: true, - ), - ]), - ), + equals(IList(const [PuzzleRound(id: PuzzleId('pId3'), ratingDiff: 10, win: true)])), ); }); @@ -344,19 +269,13 @@ void main() { final container = await makeTestContainer(mockClient); final storage = await container.read(puzzleBatchStorageProvider.future); - final service = await container.read(puzzleServiceFactoryProvider)( - queueLength: 2, - ); + final service = await container.read(puzzleServiceFactoryProvider)(queueLength: 2); await storage.save( userId: const UserId('testUserId'), - data: _makeUnsolvedPuzzles([ - const PuzzleId('pId3'), - const PuzzleId('pId4'), - ]), + data: _makeUnsolvedPuzzles([const PuzzleId('pId3'), const PuzzleId('pId4')]), ); - const solution = - PuzzleSolution(id: PuzzleId('pId3'), win: true, rated: true); + const solution = PuzzleSolution(id: PuzzleId('pId3'), win: true, rated: true); final next = await service.solve( puzzle: samplePuzzle, @@ -384,16 +303,11 @@ void main() { final container = await makeTestContainer(mockClient); final storage = await container.read(puzzleBatchStorageProvider.future); - final service = await container.read(puzzleServiceFactoryProvider)( - queueLength: 2, - ); + final service = await container.read(puzzleServiceFactoryProvider)(queueLength: 2); await storage.save( userId: const UserId('testUserId'), - data: _makeUnsolvedPuzzles([ - const PuzzleId('pId3'), - const PuzzleId('pId4'), - ]), + data: _makeUnsolvedPuzzles([const PuzzleId('pId3'), const PuzzleId('pId4')]), ); final next = await service.resetBatch(userId: const UserId('testUserId')); @@ -434,14 +348,8 @@ final samplePuzzle = Puzzle( id: GameId('PrlkCqOv'), perf: Perf.blitz, rated: true, - white: PuzzleGamePlayer( - side: Side.white, - name: 'user1', - ), - black: PuzzleGamePlayer( - side: Side.black, - name: 'user2', - ), + white: PuzzleGamePlayer(side: Side.white, name: 'user1'), + black: PuzzleGamePlayer(side: Side.black, name: 'user2'), pgn: 'e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2', ), ); @@ -464,16 +372,9 @@ PuzzleBatch _makeUnsolvedPuzzles(List ids) { id: GameId('PrlkCqOv'), perf: Perf.blitz, rated: true, - white: PuzzleGamePlayer( - side: Side.white, - name: 'user1', - ), - black: PuzzleGamePlayer( - side: Side.black, - name: 'user2', - ), - pgn: - 'e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2', + white: PuzzleGamePlayer(side: Side.white, name: 'user1'), + black: PuzzleGamePlayer(side: Side.black, name: 'user2'), + pgn: 'e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2', ), ), ]), diff --git a/test/model/puzzle/puzzle_storage_test.dart b/test/model/puzzle/puzzle_storage_test.dart index 888150b5c0..07bc10bd64 100644 --- a/test/model/puzzle/puzzle_storage_test.dart +++ b/test/model/puzzle/puzzle_storage_test.dart @@ -28,15 +28,8 @@ void main() { final storage = await container.read(puzzleStorageProvider.future); - await storage.save( - puzzle: puzzle, - ); - expect( - storage.fetch( - puzzleId: const PuzzleId('pId3'), - ), - completion(equals(puzzle)), - ); + await storage.save(puzzle: puzzle); + expect(storage.fetch(puzzleId: const PuzzleId('pId3')), completion(equals(puzzle))); }); }); } @@ -54,14 +47,8 @@ final puzzle = Puzzle( id: GameId('PrlkCqOv'), perf: Perf.blitz, rated: true, - white: PuzzleGamePlayer( - side: Side.white, - name: 'user1', - ), - black: PuzzleGamePlayer( - side: Side.black, - name: 'user2', - ), + white: PuzzleGamePlayer(side: Side.white, name: 'user1'), + black: PuzzleGamePlayer(side: Side.black, name: 'user2'), pgn: 'e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2', ), ); diff --git a/test/model/relation/relation_repository_test.dart b/test/model/relation/relation_repository_test.dart index afe1fb9580..98caa64a47 100644 --- a/test/model/relation/relation_repository_test.dart +++ b/test/model/relation/relation_repository_test.dart @@ -17,10 +17,7 @@ void main() { '''; final mockClient = MockClient((request) { if (request.url.path == '/api/rel/following') { - return mockResponse( - testRelationResponseMinimal, - 200, - ); + return mockResponse(testRelationResponseMinimal, 200); } return mockResponse('', 404); }); @@ -39,10 +36,7 @@ void main() { '''; final mockClient = MockClient((request) { if (request.url.path == '/api/rel/following') { - return mockResponse( - testRelationResponse, - 200, - ); + return mockResponse(testRelationResponse, 200); } return mockResponse('', 404); }); diff --git a/test/model/study/study_repository_test.dart b/test/model/study/study_repository_test.dart index 08fbde61c3..2f709fa087 100644 --- a/test/model/study/study_repository_test.dart +++ b/test/model/study/study_repository_test.dart @@ -326,15 +326,9 @@ void main() { final mockClient = MockClient((request) { if (request.url.path == '/study/JbWtuaeK/7OJXp679') { expect(request.url.queryParameters['chapters'], '1'); - return mockResponse( - response, - 200, - ); + return mockResponse(response, 200); } else if (request.url.path == '/api/study/JbWtuaeK/7OJXp679.pgn') { - return mockResponse( - 'pgn', - 200, - ); + return mockResponse('pgn', 200); } return mockResponse('', 404); }); @@ -357,79 +351,61 @@ void main() { liked: false, likes: 29, ownerId: const UserId('kyle-and-jess'), - features: ( - cloneable: false, - chat: true, - sticky: false, - ), + features: (cloneable: false, chat: true, sticky: false), topics: const IList.empty(), - chapters: IList( - const [ - StudyChapterMeta( - id: StudyChapterId('EgqyeQIp'), - name: 'Introduction', - fen: null, - ), - StudyChapterMeta( - id: StudyChapterId('z6tGV47W'), - name: 'Practice Your Thought Process', - fen: - '2k4r/p1p2p2/1p2b2p/1Pqn2r1/2B5/B1PP4/P4PPP/RN2Q1K1 b - - 6 20', - ), - StudyChapterMeta( - id: StudyChapterId('dTfxbccx'), - name: 'Practice Strategic Thinking', - fen: - 'r3r1k1/1b2b2p/pq4pB/1p3pN1/2p5/2P5/PPn1QPPP/3RR1K1 w - - 0 23', - ), - StudyChapterMeta( - id: StudyChapterId('B1U4pFdG'), - name: 'Calculate Fully', - fen: - '3r3r/1Rpk1p2/2p2q1p/Q2pp3/P2PP1n1/2P1B1Pp/5P2/1N3RK1 b - - 2 26', - ), - StudyChapterMeta( - id: StudyChapterId('NJLW7jil'), - name: 'Calculate Freely', - fen: '4k3/8/6p1/R1p1r1n1/P3Pp2/2N2r2/1PP1K1R1/8 b - - 2 39', - ), - StudyChapterMeta( - id: StudyChapterId('7OJXp679'), - name: 'Use a Timer', - fen: - 'r5k1/ppp2ppp/7r/4Nb2/3P4/1QN1PPq1/PP2B1P1/R4RK1 b - - 1 20', - ), - StudyChapterMeta( - id: StudyChapterId('Rgk6UlTP'), - name: 'Understand Your Mistakes', - fen: - 'r4rk1/1R3pb1/pR2N1p1/2q5/4p3/2P1P1Pp/Q2P1P1P/6K1 b - - 1 26', - ), - StudyChapterMeta( - id: StudyChapterId('VsdxmjCf'), - name: 'Adjusting Difficulty', - fen: - '3r4/k1pq1p1r/pp1p2p1/8/3P4/P1P2BP1/1P1N1Pp1/R3R1K1 b - - 0 1', - ), - StudyChapterMeta( - id: StudyChapterId('FHU6xhYs'), - name: 'Using Themes', - fen: - 'r2k3N/pbpp1Bpp/1p6/2b1p3/3n3q/P7/1PPP1RPP/RNB2QK1 b - - 3 12', - ), - StudyChapterMeta( - id: StudyChapterId('8FhO455h'), - name: 'Endurance Training', - fen: '8/1p5k/2qPQ2p/p5p1/5r1n/2B4P/5P2/4R1K1 w - - 3 41', - ), - StudyChapterMeta( - id: StudyChapterId('jWUEWsEf'), - name: 'Final Thoughts', - fen: - '8/1PP2PP1/PppPPppP/Pp1pp1pP/Pp4pP/1Pp2pP1/2PppP2/3PP3 w - - 0 1', - ), - ], - ), + chapters: IList(const [ + StudyChapterMeta(id: StudyChapterId('EgqyeQIp'), name: 'Introduction', fen: null), + StudyChapterMeta( + id: StudyChapterId('z6tGV47W'), + name: 'Practice Your Thought Process', + fen: '2k4r/p1p2p2/1p2b2p/1Pqn2r1/2B5/B1PP4/P4PPP/RN2Q1K1 b - - 6 20', + ), + StudyChapterMeta( + id: StudyChapterId('dTfxbccx'), + name: 'Practice Strategic Thinking', + fen: 'r3r1k1/1b2b2p/pq4pB/1p3pN1/2p5/2P5/PPn1QPPP/3RR1K1 w - - 0 23', + ), + StudyChapterMeta( + id: StudyChapterId('B1U4pFdG'), + name: 'Calculate Fully', + fen: '3r3r/1Rpk1p2/2p2q1p/Q2pp3/P2PP1n1/2P1B1Pp/5P2/1N3RK1 b - - 2 26', + ), + StudyChapterMeta( + id: StudyChapterId('NJLW7jil'), + name: 'Calculate Freely', + fen: '4k3/8/6p1/R1p1r1n1/P3Pp2/2N2r2/1PP1K1R1/8 b - - 2 39', + ), + StudyChapterMeta( + id: StudyChapterId('7OJXp679'), + name: 'Use a Timer', + fen: 'r5k1/ppp2ppp/7r/4Nb2/3P4/1QN1PPq1/PP2B1P1/R4RK1 b - - 1 20', + ), + StudyChapterMeta( + id: StudyChapterId('Rgk6UlTP'), + name: 'Understand Your Mistakes', + fen: 'r4rk1/1R3pb1/pR2N1p1/2q5/4p3/2P1P1Pp/Q2P1P1P/6K1 b - - 1 26', + ), + StudyChapterMeta( + id: StudyChapterId('VsdxmjCf'), + name: 'Adjusting Difficulty', + fen: '3r4/k1pq1p1r/pp1p2p1/8/3P4/P1P2BP1/1P1N1Pp1/R3R1K1 b - - 0 1', + ), + StudyChapterMeta( + id: StudyChapterId('FHU6xhYs'), + name: 'Using Themes', + fen: 'r2k3N/pbpp1Bpp/1p6/2b1p3/3n3q/P7/1PPP1RPP/RNB2QK1 b - - 3 12', + ), + StudyChapterMeta( + id: StudyChapterId('8FhO455h'), + name: 'Endurance Training', + fen: '8/1p5k/2qPQ2p/p5p1/5r1n/2B4P/5P2/4R1K1 w - - 3 41', + ), + StudyChapterMeta( + id: StudyChapterId('jWUEWsEf'), + name: 'Final Thoughts', + fen: '8/1PP2PP1/PppPPppP/Pp1pp1pP/Pp4pP/1Pp2pP1/2PppP2/3PP3 w - - 0 1', + ), + ]), chapter: const StudyChapter( id: StudyChapterId('7OJXp679'), setup: StudyChapterSetup( @@ -441,31 +417,30 @@ void main() { practise: false, conceal: null, gamebook: true, - features: ( - computer: false, - explorer: false, - ), + features: (computer: false, explorer: false), ), - hints: [ - 'The white king is not very safe. Can black increase the pressure on the king?', - null, - null, - null, - null, - null, - null, - null, - ].lock, - deviationComments: [ - null, - "Black has to be quick to jump on the initiative of white's king being vulnerable.", - null, - null, - null, - 'Keep the initiative going! Go for the king!', - null, - null, - ].lock, + hints: + [ + 'The white king is not very safe. Can black increase the pressure on the king?', + null, + null, + null, + null, + null, + null, + null, + ].lock, + deviationComments: + [ + null, + "Black has to be quick to jump on the initiative of white's king being vulnerable.", + null, + null, + null, + 'Keep the initiative going! Go for the king!', + null, + null, + ].lock, ), ); }); diff --git a/test/model/tv/tv_repository_test.dart b/test/model/tv/tv_repository_test.dart index 66c7fe2057..01cdc0dc2e 100644 --- a/test/model/tv/tv_repository_test.dart +++ b/test/model/tv/tv_repository_test.dart @@ -166,10 +166,7 @@ void main() { final mockClient = MockClient((request) { if (request.url.path == '/api/tv/channels') { - return mockResponse( - response, - 200, - ); + return mockResponse(response, 200); } return mockResponse('', 404); }); @@ -183,10 +180,7 @@ void main() { // supported channels only expect(result.length, 13); - expect( - result[TvChannel.best]?.user.name, - 'Chessisnotfair', - ); + expect(result[TvChannel.best]?.user.name, 'Chessisnotfair'); }); }); } diff --git a/test/model/user/user_repository_test.dart b/test/model/user/user_repository_test.dart index 87c1a6cf6a..afae7b35c0 100644 --- a/test/model/user/user_repository_test.dart +++ b/test/model/user/user_repository_test.dart @@ -18,8 +18,7 @@ void main() { test('json read, minimal example', () async { final mockClient = MockClient((request) { if (request.url.path == '/api/user/$testUserId') { - return mockResponse( - ''' + return mockResponse(''' { "id": "$testUserId", "username": "$testUserId", @@ -28,9 +27,7 @@ void main() { "perfs": { } } -''', - 200, - ); +''', 200); } return mockResponse('', 404); }); @@ -48,8 +45,7 @@ void main() { test('json read, full example', () async { final mockClient = MockClient((request) { if (request.url.path == '/api/user/$testUserId') { - return mockResponse( - ''' + return mockResponse(''' { "id": "$testUserId", "username": "$testUserId", @@ -87,9 +83,7 @@ void main() { "links": "http://test.com" } } -''', - 200, - ); +''', 200); } return mockResponse('', 404); }); @@ -113,8 +107,7 @@ void main() { test('json read, minimal example', () async { final mockClient = MockClient((request) { if (request.url.path == path) { - return mockResponse( - ''' + return mockResponse(''' { "user": { "name": "$testUserId" @@ -143,9 +136,7 @@ void main() { } } } -''', - 200, - ); +''', 200); } return mockResponse('', 404); }); @@ -163,8 +154,7 @@ void main() { test('json read, full example', () async { final mockClient = MockClient((request) { if (request.url.path == path) { - return mockResponse( - ''' + return mockResponse(''' { "user": { "name": "testOpponentName" @@ -400,9 +390,7 @@ void main() { } } } -''', - 200, - ); +''', 200); } return mockResponse('', 404); }); @@ -423,16 +411,11 @@ void main() { group('UserRepository.getUsersStatuses', () { test('json read, minimal example', () async { - final ids = ISet( - const {UserId('maia1'), UserId('maia5'), UserId('maia9')}, - ); + final ids = ISet(const {UserId('maia1'), UserId('maia5'), UserId('maia9')}); final mockClient = MockClient((request) { if (request.url.path == '/api/users/status') { - return mockResponse( - '[]', - 200, - ); + return mockResponse('[]', 200); } return mockResponse('', 404); }); @@ -447,13 +430,10 @@ void main() { }); test('json read, full example', () async { - final ids = ISet( - const {UserId('maia1'), UserId('maia5'), UserId('maia9')}, - ); + final ids = ISet(const {UserId('maia1'), UserId('maia5'), UserId('maia9')}); final mockClient = MockClient((request) { if (request.url.path == '/api/users/status') { - return mockResponse( - ''' + return mockResponse(''' [ { "id": "maia1", @@ -472,9 +452,7 @@ void main() { "online": true } ] -''', - 200, - ); +''', 200); } return mockResponse('', 404); }); diff --git a/test/network/fake_websocket_channel.dart b/test/network/fake_websocket_channel.dart index 88e3391d04..edc6b66814 100644 --- a/test/network/fake_websocket_channel.dart +++ b/test/network/fake_websocket_channel.dart @@ -114,25 +114,19 @@ class FakeWebSocketChannel implements WebSocketChannel { void pipe(StreamChannel other) {} @override - StreamChannel transform( - StreamChannelTransformer transformer, - ) { + StreamChannel transform(StreamChannelTransformer transformer) { // TODO: implement transform throw UnimplementedError(); } @override - StreamChannel transformSink( - StreamSinkTransformer transformer, - ) { + StreamChannel transformSink(StreamSinkTransformer transformer) { // TODO: implement transformSink throw UnimplementedError(); } @override - StreamChannel transformStream( - StreamTransformer transformer, - ) { + StreamChannel transformStream(StreamTransformer transformer) { // TODO: implement transformStream throw UnimplementedError(); } @@ -144,17 +138,13 @@ class FakeWebSocketChannel implements WebSocketChannel { } @override - StreamChannel changeSink( - StreamSink Function(StreamSink p1) change, - ) { + StreamChannel changeSink(StreamSink Function(StreamSink p1) change) { // TODO: implement changeSink throw UnimplementedError(); } @override - StreamChannel changeStream( - Stream Function(Stream p1) change, - ) { + StreamChannel changeStream(Stream Function(Stream p1) change) { // TODO: implement changeStream throw UnimplementedError(); } @@ -200,9 +190,7 @@ class FakeWebSocketSink implements WebSocketSink { @override Future close([int? closeCode, String? closeReason]) { - return Future.wait([ - _channel._incomingController.close(), - ]); + return Future.wait([_channel._incomingController.close()]); } @override diff --git a/test/network/http_test.dart b/test/network/http_test.dart index 488eaed8ef..c9b775b574 100644 --- a/test/network/http_test.dart +++ b/test/network/http_test.dart @@ -106,45 +106,27 @@ void main() { ]) { expect( () => method(Uri(path: '/will/return/500')), - throwsA( - isA() - .having((e) => e.statusCode, 'statusCode', 500), - ), + throwsA(isA().having((e) => e.statusCode, 'statusCode', 500)), ); expect( () => method(Uri(path: '/will/return/503')), - throwsA( - isA() - .having((e) => e.statusCode, 'statusCode', 503), - ), + throwsA(isA().having((e) => e.statusCode, 'statusCode', 503)), ); expect( () => method(Uri(path: '/will/return/400')), - throwsA( - isA() - .having((e) => e.statusCode, 'statusCode', 400), - ), + throwsA(isA().having((e) => e.statusCode, 'statusCode', 400)), ); expect( () => method(Uri(path: '/will/return/404')), - throwsA( - isA() - .having((e) => e.statusCode, 'statusCode', 404), - ), + throwsA(isA().having((e) => e.statusCode, 'statusCode', 404)), ); expect( () => method(Uri(path: '/will/return/401')), - throwsA( - isA() - .having((e) => e.statusCode, 'statusCode', 401), - ), + throwsA(isA().having((e) => e.statusCode, 'statusCode', 401)), ); expect( () => method(Uri(path: '/will/return/403')), - throwsA( - isA() - .having((e) => e.statusCode, 'statusCode', 403), - ), + throwsA(isA().having((e) => e.statusCode, 'statusCode', 403)), ); } }); @@ -158,37 +140,13 @@ void main() { ], ); final client = container.read(lichessClientProvider); - for (final method in [ - client.get, - client.post, - client.put, - client.patch, - client.delete, - ]) { - expect( - () => method(Uri(path: '/will/return/500')), - returnsNormally, - ); - expect( - () => method(Uri(path: '/will/return/503')), - returnsNormally, - ); - expect( - () => method(Uri(path: '/will/return/400')), - returnsNormally, - ); - expect( - () => method(Uri(path: '/will/return/404')), - returnsNormally, - ); - expect( - () => method(Uri(path: '/will/return/401')), - returnsNormally, - ); - expect( - () => method(Uri(path: '/will/return/403')), - returnsNormally, - ); + for (final method in [client.get, client.post, client.put, client.patch, client.delete]) { + expect(() => method(Uri(path: '/will/return/500')), returnsNormally); + expect(() => method(Uri(path: '/will/return/503')), returnsNormally); + expect(() => method(Uri(path: '/will/return/400')), returnsNormally); + expect(() => method(Uri(path: '/will/return/404')), returnsNormally); + expect(() => method(Uri(path: '/will/return/401')), returnsNormally); + expect(() => method(Uri(path: '/will/return/403')), returnsNormally); } }); @@ -203,23 +161,11 @@ void main() { final client = container.read(lichessClientProvider); expect( () => client.get(Uri(path: '/will/throw/socket/exception')), - throwsA( - isA().having( - (e) => e.message, - 'message', - 'no internet', - ), - ), + throwsA(isA().having((e) => e.message, 'message', 'no internet')), ); expect( () => client.get(Uri(path: '/will/throw/tls/exception')), - throwsA( - isA().having( - (e) => e.message, - 'message', - 'tls error', - ), - ), + throwsA(isA().having((e) => e.message, 'message', 'tls error')), ); }); @@ -249,8 +195,7 @@ void main() { ); }); - test('adds a signed bearer token when a session is available the request', - () async { + test('adds a signed bearer token when a session is available the request', () async { final container = await makeContainer( overrides: [ httpClientFactoryProvider.overrideWith((ref) { @@ -282,63 +227,62 @@ void main() { }); test( - 'when receiving a 401, will test session token and delete session if not valid anymore', - () async { - int nbTokenTestRequests = 0; - final container = await makeContainer( - overrides: [ - httpClientFactoryProvider.overrideWith((ref) { - return FakeHttpClientFactory(() => FakeClient()); - }), - defaultClientProvider.overrideWith((ref) { - return MockClient((request) async { - if (request.url.path == '/api/token/test') { - nbTokenTestRequests++; - final token = request.body.split(',')[0]; - final response = '{"$token": null}'; - return http.Response(response, 200); - } - return http.Response('', 404); - }); - }), - ], - userSession: const AuthSessionState( - token: 'test-token', - user: LightUser(id: UserId('test-user-id'), name: 'test-username'), - ), - ); + 'when receiving a 401, will test session token and delete session if not valid anymore', + () async { + int nbTokenTestRequests = 0; + final container = await makeContainer( + overrides: [ + httpClientFactoryProvider.overrideWith((ref) { + return FakeHttpClientFactory(() => FakeClient()); + }), + defaultClientProvider.overrideWith((ref) { + return MockClient((request) async { + if (request.url.path == '/api/token/test') { + nbTokenTestRequests++; + final token = request.body.split(',')[0]; + final response = '{"$token": null}'; + return http.Response(response, 200); + } + return http.Response('', 404); + }); + }), + ], + userSession: const AuthSessionState( + token: 'test-token', + user: LightUser(id: UserId('test-user-id'), name: 'test-username'), + ), + ); - fakeAsync((async) { - final session = container.read(authSessionProvider); - expect(session, isNotNull); + fakeAsync((async) { + final session = container.read(authSessionProvider); + expect(session, isNotNull); - final client = container.read(lichessClientProvider); - try { - client.get(Uri(path: '/will/return/401')); - } on ServerException catch (_) {} + final client = container.read(lichessClientProvider); + try { + client.get(Uri(path: '/will/return/401')); + } on ServerException catch (_) {} - async.flushMicrotasks(); + async.flushMicrotasks(); - final requests = FakeClient.verifyRequests(); - expect(requests.length, 1); - expect( - requests.first, - isA().having( - (r) => r.headers['Authorization'], - 'Authorization', - 'Bearer ${signBearerToken('test-token')}', - ), - ); + final requests = FakeClient.verifyRequests(); + expect(requests.length, 1); + expect( + requests.first, + isA().having( + (r) => r.headers['Authorization'], + 'Authorization', + 'Bearer ${signBearerToken('test-token')}', + ), + ); - expect(nbTokenTestRequests, 1); + expect(nbTokenTestRequests, 1); - expect(container.read(authSessionProvider), isNull); - }); - }); + expect(container.read(authSessionProvider), isNull); + }); + }, + ); - test( - 'when receiving a 401, will test session token and keep session if still valid', - () async { + test('when receiving a 401, will test session token and keep session if still valid', () async { int nbTokenTestRequests = 0; final container = await makeContainer( overrides: [ diff --git a/test/network/socket_test.dart b/test/network/socket_test.dart index c5cbe7d5d2..bf9d43595d 100644 --- a/test/network/socket_test.dart +++ b/test/network/socket_test.dart @@ -7,9 +7,7 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'fake_websocket_channel.dart'; -SocketClient makeTestSocketClient( - FakeWebSocketChannelFactory fakeChannelFactory, -) { +SocketClient makeTestSocketClient(FakeWebSocketChannelFactory fakeChannelFactory) { final client = SocketClient( Uri(path: kDefaultSocketRoute), channelFactory: fakeChannelFactory, @@ -44,8 +42,7 @@ void main() { test('handles ping/pong', () async { final fakeChannel = FakeWebSocketChannel(); - final socketClient = - makeTestSocketClient(FakeWebSocketChannelFactory((_) => fakeChannel)); + final socketClient = makeTestSocketClient(FakeWebSocketChannelFactory((_) => fakeChannel)); socketClient.connect(); int sentPingCount = 0; @@ -133,11 +130,9 @@ void main() { }); test('computes average lag', () async { - final fakeChannel = - FakeWebSocketChannel(connectionLag: const Duration(milliseconds: 10)); + final fakeChannel = FakeWebSocketChannel(connectionLag: const Duration(milliseconds: 10)); - final socketClient = - makeTestSocketClient(FakeWebSocketChannelFactory((_) => fakeChannel)); + final socketClient = makeTestSocketClient(FakeWebSocketChannelFactory((_) => fakeChannel)); socketClient.connect(); // before the connection is ready the average lag is zero @@ -153,19 +148,13 @@ void main() { await expectLater(fakeChannel.stream, emits('0')); // after the ping/pong exchange the average lag is computed - expect( - socketClient.averageLag.value.inMilliseconds, - greaterThanOrEqualTo(10), - ); + expect(socketClient.averageLag.value.inMilliseconds, greaterThanOrEqualTo(10)); // wait for more ping/pong exchanges await expectLater(fakeChannel.stream, emitsInOrder(['0', '0', '0', '0'])); // average lag is still the same - expect( - socketClient.averageLag.value.inMilliseconds, - greaterThanOrEqualTo(10), - ); + expect(socketClient.averageLag.value.inMilliseconds, greaterThanOrEqualTo(10)); // increase the lag of the connection fakeChannel.connectionLag = const Duration(milliseconds: 100); @@ -174,10 +163,7 @@ void main() { await expectLater(fakeChannel.stream, emitsInOrder(['0', '0', '0', '0'])); // average lag should be higher - expect( - socketClient.averageLag.value.inMilliseconds, - greaterThanOrEqualTo(40), - ); + expect(socketClient.averageLag.value.inMilliseconds, greaterThanOrEqualTo(40)); await socketClient.close(); @@ -188,8 +174,7 @@ void main() { test('handles ackable messages', () async { final fakeChannel = FakeWebSocketChannel(); - final socketClient = - makeTestSocketClient(FakeWebSocketChannelFactory((_) => fakeChannel)); + final socketClient = makeTestSocketClient(FakeWebSocketChannelFactory((_) => fakeChannel)); socketClient.connect(); await socketClient.firstConnection; @@ -211,10 +196,7 @@ void main() { fakeChannel.addIncomingMessages(['{"t":"ack","d":1}']); // no more messages are expected - await expectLater( - fakeChannel.sentMessagesExceptPing, - emitsInOrder([]), - ); + await expectLater(fakeChannel.sentMessagesExceptPing, emitsInOrder([])); socketClient.close(); }); diff --git a/test/test_container.dart b/test/test_container.dart index 7e1ab64506..435d415720 100644 --- a/test/test_container.dart +++ b/test/test_container.dart @@ -49,9 +49,7 @@ Future makeContainer({ }) async { final binding = TestLichessBinding.ensureInitialized(); - FlutterSecureStorage.setMockInitialValues({ - kSRIStorageKey: 'test', - }); + FlutterSecureStorage.setMockInitialValues({kSRIStorageKey: 'test'}); final container = ProviderContainer( overrides: [ @@ -62,8 +60,7 @@ Future makeContainer({ return FakeNotificationDisplay(); }), databaseProvider.overrideWith((ref) async { - final db = - await openAppDatabase(databaseFactoryFfi, inMemoryDatabasePath); + final db = await openAppDatabase(databaseFactoryFfi, inMemoryDatabasePath); ref.onDispose(db.close); return db; }), diff --git a/test/test_helpers.dart b/test/test_helpers.dart index 3256f9b2ab..5dae185a43 100644 --- a/test/test_helpers.dart +++ b/test/test_helpers.dart @@ -37,19 +37,14 @@ const kTestSurfaces = [ /// iPhone 14 screen size. const kTestSurfaceSize = Size(_kTestScreenWidth, _kTestScreenHeight); -const kPlatformVariant = - TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}); +const kPlatformVariant = TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}); Matcher sameRequest(http.BaseRequest request) => _SameRequest(request); Matcher sameHeaders(Map headers) => _SameHeaders(headers); /// Mocks a surface with a given size. class TestSurface extends StatelessWidget { - const TestSurface({ - required this.child, - required this.size, - super.key, - }); + const TestSurface({required this.child, required this.size, super.key}); final Size size; final Widget child; @@ -58,11 +53,7 @@ class TestSurface extends StatelessWidget { Widget build(BuildContext context) { return MediaQuery( data: MediaQueryData(size: size), - child: SizedBox( - width: size.width, - height: size.height, - child: child, - ), + child: SizedBox(width: size.width, height: size.height, child: child), ); } } @@ -72,23 +63,12 @@ Future mockResponse( String body, int code, { Map headers = const {}, -}) => - Future.value( - http.Response( - body, - code, - headers: headers, - ), - ); +}) => Future.value(http.Response(body, code, headers: headers)); Future mockStreamedResponse(String body, int code) => - Future.value( - http.StreamedResponse(Stream.value(body).map(utf8.encode), code), - ); + Future.value(http.StreamedResponse(Stream.value(body).map(utf8.encode), code)); -Future mockHttpStreamFromIterable( - Iterable events, -) async { +Future mockHttpStreamFromIterable(Iterable events) async { return http.StreamedResponse( _streamFromFutures(events.map((e) => Future.value(utf8.encode(e)))), 200, @@ -115,24 +95,13 @@ Future meetsTapTargetGuideline(WidgetTester tester) async { } /// Returns the offset of a square on a board defined by [Rect]. -Offset squareOffset( - Square square, - Rect boardRect, { - Side orientation = Side.white, -}) { +Offset squareOffset(Square square, Rect boardRect, {Side orientation = Side.white}) { final squareSize = boardRect.width / 8; - final dx = - (orientation == Side.white ? square.file.value : 7 - square.file.value) * - squareSize; - final dy = - (orientation == Side.white ? 7 - square.rank.value : square.rank.value) * - squareSize; + final dx = (orientation == Side.white ? square.file.value : 7 - square.file.value) * squareSize; + final dy = (orientation == Side.white ? 7 - square.rank.value : square.rank.value) * squareSize; - return Offset( - dx + boardRect.left + squareSize / 2, - dy + boardRect.top + squareSize / 2, - ); + return Offset(dx + boardRect.left + squareSize / 2, dy + boardRect.top + squareSize / 2); } /// Plays a move on the board. @@ -144,13 +113,9 @@ Future playMove( Side orientation = Side.white, }) async { final rect = boardRect ?? tester.getRect(find.byType(Chessboard)); - await tester.tapAt( - squareOffset(Square.fromName(from), rect, orientation: orientation), - ); + await tester.tapAt(squareOffset(Square.fromName(from), rect, orientation: orientation)); await tester.pump(); - await tester.tapAt( - squareOffset(Square.fromName(to), rect, orientation: orientation), - ); + await tester.tapAt(squareOffset(Square.fromName(to), rect, orientation: orientation)); await tester.pump(); } diff --git a/test/test_provider_scope.dart b/test/test_provider_scope.dart index 81419fd5a0..05b4b91743 100644 --- a/test/test_provider_scope.dart +++ b/test/test_provider_scope.dart @@ -68,10 +68,7 @@ Future makeTestProviderScopeApp( localizationsDelegates: AppLocalizations.localizationsDelegates, home: home, builder: (context, child) { - return CupertinoTheme( - data: const CupertinoThemeData(), - child: Material(child: child), - ); + return CupertinoTheme(data: const CupertinoThemeData(), child: Material(child: child)); }, ), overrides: overrides, @@ -89,19 +86,18 @@ Future makeOfflineTestProviderScope( List? overrides, AuthSessionState? userSession, Map? defaultPreferences, -}) => - makeTestProviderScope( - tester, - child: child, - overrides: [ - httpClientFactoryProvider.overrideWith((ref) { - return FakeHttpClientFactory(() => offlineClient); - }), - ...overrides ?? [], - ], - userSession: userSession, - defaultPreferences: defaultPreferences, - ); +}) => makeTestProviderScope( + tester, + child: child, + overrides: [ + httpClientFactoryProvider.overrideWith((ref) { + return FakeHttpClientFactory(() => offlineClient); + }), + ...overrides ?? [], + ], + userSession: userSession, + defaultPreferences: defaultPreferences, +); /// Returns a [ProviderScope] and default mocks, ready for testing. /// @@ -134,28 +130,16 @@ Future makeTestProviderScope( // disable piece animation to simplify tests final defaultBoardPref = { - 'preferences.board': jsonEncode( - BoardPrefs.defaults - .copyWith( - pieceAnimation: false, - ) - .toJson(), - ), + 'preferences.board': jsonEncode(BoardPrefs.defaults.copyWith(pieceAnimation: false).toJson()), }; await binding.setInitialSharedPreferencesValues( - defaultPreferences != null - ? { - ...defaultBoardPref, - ...defaultPreferences, - } - : defaultBoardPref, + defaultPreferences != null ? {...defaultBoardPref, ...defaultPreferences} : defaultBoardPref, ); FlutterSecureStorage.setMockInitialValues({ kSRIStorageKey: 'test', - if (userSession != null) - kSessionStorageKey: jsonEncode(userSession.toJson()), + if (userSession != null) kSessionStorageKey: jsonEncode(userSession.toJson()), }); // TODO consider loading true fonts as well @@ -170,10 +154,7 @@ Future makeTestProviderScope( }), // ignore: scoped_providers_should_specify_dependencies databaseProvider.overrideWith((ref) async { - final testDb = await openAppDatabase( - databaseFactoryFfiNoIsolate, - inMemoryDatabasePath, - ); + final testDb = await openAppDatabase(databaseFactoryFfiNoIsolate, inMemoryDatabasePath); ref.onDispose(testDb.close); return testDb; }), @@ -226,23 +207,18 @@ Future makeTestProviderScope( }), ...overrides ?? [], ], - child: TestSurface( - size: surfaceSize, - child: child, - ), + child: TestSurface(size: surfaceSize, child: child), ); } -void _ignoreOverflowErrors( - FlutterErrorDetails details, { - bool forceReport = false, -}) { +void _ignoreOverflowErrors(FlutterErrorDetails details, {bool forceReport = false}) { bool isOverflowError = false; final exception = details.exception; if (exception is FlutterError) { - isOverflowError = exception.diagnostics - .any((e) => e.value.toString().contains('A RenderFlex overflowed by')); + isOverflowError = exception.diagnostics.any( + (e) => e.value.toString().contains('A RenderFlex overflowed by'), + ); } if (isOverflowError) { diff --git a/test/utils/duration_test.dart b/test/utils/duration_test.dart index 7405571d11..cacdc32270 100644 --- a/test/utils/duration_test.dart +++ b/test/utils/duration_test.dart @@ -24,23 +24,11 @@ void main() { group('DurationExtensions.toDaysHoursMinutes()', () { test('all values nonzero, plural', () { - testTimeStr( - mockAppLocalizations, - 2, - 2, - 2, - '2 days, 2 hours and 2 minutes', - ); + testTimeStr(mockAppLocalizations, 2, 2, 2, '2 days, 2 hours and 2 minutes'); }); test('all values nonzero, plural', () { - testTimeStr( - mockAppLocalizations, - 2, - 2, - 2, - '2 days, 2 hours and 2 minutes', - ); + testTimeStr(mockAppLocalizations, 2, 2, 2, '2 days, 2 hours and 2 minutes'); }); test('all values nonzero, single', () { @@ -84,7 +72,10 @@ void testTimeStr( int minutes, String expected, ) { - final timeStr = Duration(days: days, hours: hours, minutes: minutes) - .toDaysHoursMinutes(mockAppLocalizations); + final timeStr = Duration( + days: days, + hours: hours, + minutes: minutes, + ).toDaysHoursMinutes(mockAppLocalizations); expect(timeStr, expected); } diff --git a/test/utils/fake_connectivity.dart b/test/utils/fake_connectivity.dart index 3a920a4bdf..034e048467 100644 --- a/test/utils/fake_connectivity.dart +++ b/test/utils/fake_connectivity.dart @@ -12,10 +12,8 @@ class FakeConnectivity implements Connectivity { /// A broadcast stream controller of connectivity changes. /// /// This is used to simulate connectivity changes in tests. - static StreamController> controller = - StreamController.broadcast(); + static StreamController> controller = StreamController.broadcast(); @override - Stream> get onConnectivityChanged => - controller.stream; + Stream> get onConnectivityChanged => controller.stream; } diff --git a/test/utils/l10n_test.dart b/test/utils/l10n_test.dart index 72f178b3a7..2846a3b785 100644 --- a/test/utils/l10n_test.dart +++ b/test/utils/l10n_test.dart @@ -7,10 +7,7 @@ void main() { group('l10nWithWidget', () { const widget = Text('I am a widget'); test('placeholder in the middle', () { - final text = l10nWithWidget( - (_) => 'foo %s bar', - widget, - ); + final text = l10nWithWidget((_) => 'foo %s bar', widget); final children = (text.textSpan as TextSpan?)?.children; expect(children!.length, 3); expect((children[0] as TextSpan).text, 'foo '); @@ -19,18 +16,12 @@ void main() { }); test('no placeholder', () { - final text = l10nWithWidget( - (_) => 'foo bar', - widget, - ); + final text = l10nWithWidget((_) => 'foo bar', widget); expect(text.data, 'foo bar'); }); test('placeholder at the beginning', () { - final text = l10nWithWidget( - (_) => '%s foo bar', - widget, - ); + final text = l10nWithWidget((_) => '%s foo bar', widget); final children = (text.textSpan as TextSpan?)?.children; expect(children!.length, 2); expect((children[0] as WidgetSpan).child, widget); @@ -38,10 +29,7 @@ void main() { }); test('placeholder at the end', () { - final text = l10nWithWidget( - (_) => 'foo bar %s', - widget, - ); + final text = l10nWithWidget((_) => 'foo bar %s', widget); final children = (text.textSpan as TextSpan?)?.children; expect(children!.length, 2); expect((children[0] as TextSpan).text, 'foo bar '); diff --git a/test/utils/rate_limit_test.dart b/test/utils/rate_limit_test.dart index b652932bd3..78b2a54881 100644 --- a/test/utils/rate_limit_test.dart +++ b/test/utils/rate_limit_test.dart @@ -16,8 +16,7 @@ void main() { expect(called, true); }); - test('should not execute callback more than once if called multiple times', - () async { + test('should not execute callback more than once if called multiple times', () async { final debouncer = Debouncer(const Duration(milliseconds: 100)); var called = 0; debouncer(() { @@ -85,8 +84,7 @@ void main() { expect(called, 1); }); - test('should call the callback multiple times if delay is passed', - () async { + test('should call the callback multiple times if delay is passed', () async { final throttler = Throttler(const Duration(milliseconds: 100)); var called = 0; throttler(() { diff --git a/test/view/analysis/analysis_layout_test.dart b/test/view/analysis/analysis_layout_test.dart index fd0c130feb..e8ce54cb0f 100644 --- a/test/view/analysis/analysis_layout_test.dart +++ b/test/view/analysis/analysis_layout_test.dart @@ -11,136 +11,119 @@ import '../../test_helpers.dart'; import '../../test_provider_scope.dart'; void main() { - testWidgets( - 'board background size should match board size on all surfaces', - (WidgetTester tester) async { - for (final surface in kTestSurfaces) { - final app = await makeTestProviderScope( - key: ValueKey(surface), - tester, - child: MaterialApp( - home: DefaultTabController( - length: 1, - child: AnalysisLayout( - boardBuilder: (context, boardSize, boardRadius) { - return Chessboard.fixed( - size: boardSize, - fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR', - orientation: Side.white, - ); - }, - bottomBar: const SizedBox(height: kBottomBarHeight), - children: const [ - Center(child: Text('Analysis tab')), - ], - ), + testWidgets('board background size should match board size on all surfaces', ( + WidgetTester tester, + ) async { + for (final surface in kTestSurfaces) { + final app = await makeTestProviderScope( + key: ValueKey(surface), + tester, + child: MaterialApp( + home: DefaultTabController( + length: 1, + child: AnalysisLayout( + boardBuilder: (context, boardSize, boardRadius) { + return Chessboard.fixed( + size: boardSize, + fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR', + orientation: Side.white, + ); + }, + bottomBar: const SizedBox(height: kBottomBarHeight), + children: const [Center(child: Text('Analysis tab'))], ), ), - surfaceSize: surface, - ); - await tester.pumpWidget(app); + ), + surfaceSize: surface, + ); + await tester.pumpWidget(app); - final backgroundSize = tester.getSize( - find.byType(SolidColorChessboardBackground), - ); + final backgroundSize = tester.getSize(find.byType(SolidColorChessboardBackground)); - expect( - backgroundSize.width, - backgroundSize.height, - reason: 'Board background size is square on $surface', - ); + expect( + backgroundSize.width, + backgroundSize.height, + reason: 'Board background size is square on $surface', + ); - final boardSize = tester.getSize(find.byType(Chessboard)); + final boardSize = tester.getSize(find.byType(Chessboard)); - expect( - boardSize.width, - boardSize.height, - reason: 'Board size is square on $surface', - ); + expect(boardSize.width, boardSize.height, reason: 'Board size is square on $surface'); - expect( - boardSize, - backgroundSize, - reason: 'Board size should match background size on $surface', - ); - } - }, - variant: kPlatformVariant, - ); + expect( + boardSize, + backgroundSize, + reason: 'Board size should match background size on $surface', + ); + } + }, variant: kPlatformVariant); - testWidgets( - 'board size and table side size should be harmonious on all surfaces', - (WidgetTester tester) async { - for (final surface in kTestSurfaces) { - final app = await makeTestProviderScope( - key: ValueKey(surface), - tester, - child: MaterialApp( - home: DefaultTabController( - length: 1, - child: AnalysisLayout( - boardBuilder: (context, boardSize, boardRadius) { - return Chessboard.fixed( - size: boardSize, - fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR', - orientation: Side.white, - ); - }, - bottomBar: const SizedBox(height: kBottomBarHeight), - children: const [ - Center(child: Text('Analysis tab')), - ], - ), + testWidgets('board size and table side size should be harmonious on all surfaces', ( + WidgetTester tester, + ) async { + for (final surface in kTestSurfaces) { + final app = await makeTestProviderScope( + key: ValueKey(surface), + tester, + child: MaterialApp( + home: DefaultTabController( + length: 1, + child: AnalysisLayout( + boardBuilder: (context, boardSize, boardRadius) { + return Chessboard.fixed( + size: boardSize, + fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR', + orientation: Side.white, + ); + }, + bottomBar: const SizedBox(height: kBottomBarHeight), + children: const [Center(child: Text('Analysis tab'))], ), ), - surfaceSize: surface, - ); - await tester.pumpWidget(app); + ), + surfaceSize: surface, + ); + await tester.pumpWidget(app); - final isPortrait = surface.aspectRatio < 1.0; - final isTablet = surface.shortestSide > 600; - final boardSize = tester.getSize(find.byType(Chessboard)); + final isPortrait = surface.aspectRatio < 1.0; + final isTablet = surface.shortestSide > 600; + final boardSize = tester.getSize(find.byType(Chessboard)); - if (isPortrait) { - final expectedBoardSize = - isTablet ? surface.width - 32.0 : surface.width; - expect( - boardSize, - Size(expectedBoardSize, expectedBoardSize), - reason: 'Board size should match surface width on $surface', - ); - } else { - final tabBarViewSize = tester.getSize(find.byType(TabBarView)); - final goldenBoardSize = (surface.longestSide / kGoldenRatio) - 32.0; - final defaultBoardSize = - surface.shortestSide - kBottomBarHeight - 32.0; - final minBoardSize = min(goldenBoardSize, defaultBoardSize); - final maxBoardSize = max(goldenBoardSize, defaultBoardSize); - // TabBarView is inside a Card so we need to account for its padding - const cardPadding = 8.0; - final minSideWidth = min( - surface.longestSide - goldenBoardSize - 16.0 * 3 - cardPadding, - 250.0, - ); - expect( - boardSize.width, - greaterThanOrEqualTo(minBoardSize), - reason: 'Board size should be at least $minBoardSize on $surface', - ); - expect( - boardSize.width, - lessThanOrEqualTo(maxBoardSize), - reason: 'Board size should be at most $maxBoardSize on $surface', - ); - expect( - tabBarViewSize.width, - greaterThanOrEqualTo(minSideWidth), - reason: - 'Tab bar view width should be at least $minSideWidth on $surface', - ); - } + if (isPortrait) { + final expectedBoardSize = isTablet ? surface.width - 32.0 : surface.width; + expect( + boardSize, + Size(expectedBoardSize, expectedBoardSize), + reason: 'Board size should match surface width on $surface', + ); + } else { + final tabBarViewSize = tester.getSize(find.byType(TabBarView)); + final goldenBoardSize = (surface.longestSide / kGoldenRatio) - 32.0; + final defaultBoardSize = surface.shortestSide - kBottomBarHeight - 32.0; + final minBoardSize = min(goldenBoardSize, defaultBoardSize); + final maxBoardSize = max(goldenBoardSize, defaultBoardSize); + // TabBarView is inside a Card so we need to account for its padding + const cardPadding = 8.0; + final minSideWidth = min( + surface.longestSide - goldenBoardSize - 16.0 * 3 - cardPadding, + 250.0, + ); + expect( + boardSize.width, + greaterThanOrEqualTo(minBoardSize), + reason: 'Board size should be at least $minBoardSize on $surface', + ); + expect( + boardSize.width, + lessThanOrEqualTo(maxBoardSize), + reason: 'Board size should be at most $maxBoardSize on $surface', + ); + expect( + tabBarViewSize.width, + greaterThanOrEqualTo(minSideWidth), + reason: 'Tab bar view width should be at least $minSideWidth on $surface', + ); } - }, - variant: kPlatformVariant, - ); + } + }, variant: kPlatformVariant); } diff --git a/test/view/analysis/analysis_screen_test.dart b/test/view/analysis/analysis_screen_test.dart index 00ea3fcf06..df63b7eb08 100644 --- a/test/view/analysis/analysis_screen_test.dart +++ b/test/view/analysis/analysis_screen_test.dart @@ -44,12 +44,7 @@ void main() { expect(currentMove, findsOneWidget); expect( tester - .widget( - find.ancestor( - of: currentMove, - matching: find.byType(InlineMove), - ), - ) + .widget(find.ancestor(of: currentMove, matching: find.byType(InlineMove))) .isCurrentMove, isTrue, ); @@ -75,18 +70,11 @@ void main() { await tester.pump(const Duration(milliseconds: 1)); // cannot go forward - expect( - tester - .widget(find.byKey(const Key('goto-next'))) - .onTap, - isNull, - ); + expect(tester.widget(find.byKey(const Key('goto-next'))).onTap, isNull); // can go back expect( - tester - .widget(find.byKey(const Key('goto-previous'))) - .onTap, + tester.widget(find.byKey(const Key('goto-previous'))).onTap, isNotNull, ); @@ -98,12 +86,7 @@ void main() { expect(currentMove, findsOneWidget); expect( tester - .widget( - find.ancestor( - of: currentMove, - matching: find.byType(InlineMove), - ), - ) + .widget(find.ancestor(of: currentMove, matching: find.byType(InlineMove))) .isCurrentMove, isTrue, ); @@ -111,29 +94,18 @@ void main() { }); group('Analysis Tree View', () { - Future buildTree( - WidgetTester tester, - String pgn, - ) async { + Future buildTree(WidgetTester tester, String pgn) async { final app = await makeTestProviderScopeApp( tester, defaultPreferences: { PrefCategory.analysis.storageKey: jsonEncode( - AnalysisPrefs.defaults - .copyWith( - enableLocalEvaluation: false, - ) - .toJson(), + AnalysisPrefs.defaults.copyWith(enableLocalEvaluation: false).toJson(), ), }, home: AnalysisScreen( options: AnalysisOptions( orientation: Side.white, - standalone: ( - pgn: pgn, - isComputerAnalysisAllowed: false, - variant: Variant.standard, - ), + standalone: (pgn: pgn, isComputerAnalysisAllowed: false, variant: Variant.standard), ), enableDrawingShapes: false, ), @@ -144,12 +116,7 @@ void main() { } Text parentText(WidgetTester tester, String move) { - return tester.widget( - find.ancestor( - of: find.text(move), - matching: find.byType(Text), - ), - ); + return tester.widget(find.ancestor(of: find.text(move), matching: find.byType(Text))); } void expectSameLine(WidgetTester tester, Iterable moves) { @@ -158,23 +125,14 @@ void main() { for (final move in moves.skip(1)) { final moveText = find.text(move); expect(moveText, findsOneWidget); - expect( - parentText(tester, move), - line, - ); + expect(parentText(tester, move), line); } } - void expectDifferentLines( - WidgetTester tester, - List moves, - ) { + void expectDifferentLines(WidgetTester tester, List moves) { for (int i = 0; i < moves.length; i++) { for (int j = i + 1; j < moves.length; j++) { - expect( - parentText(tester, moves[i]), - isNot(parentText(tester, moves[j])), - ); + expect(parentText(tester, moves[i]), isNot(parentText(tester, moves[j]))); } } } @@ -182,33 +140,23 @@ void main() { testWidgets('displays short sideline as inline', (tester) async { await buildTree(tester, '1. e4 e5 (1... d5 2. exd5) 2. Nf3 *'); - final mainline = find.ancestor( - of: find.text('1. e4'), - matching: find.byType(Text), - ); + final mainline = find.ancestor(of: find.text('1. e4'), matching: find.byType(Text)); expect(mainline, findsOneWidget); expectSameLine(tester, ['1. e4', 'e5', '1… d5', '2. exd5', '2. Nf3']); }); testWidgets('displays long sideline on its own line', (tester) async { - await buildTree( - tester, - '1. e4 e5 (1... d5 2. exd5 Qxd5 3. Nc3 Qd8 4. d4 Nf6) 2. Nc3 *', - ); + await buildTree(tester, '1. e4 e5 (1... d5 2. exd5 Qxd5 3. Nc3 Qd8 4. d4 Nf6) 2. Nc3 *'); expectSameLine(tester, ['1. e4', 'e5']); - expectSameLine( - tester, - ['1… d5', '2. exd5', 'Qxd5', '3. Nc3', 'Qd8', '4. d4', 'Nf6'], - ); + expectSameLine(tester, ['1… d5', '2. exd5', 'Qxd5', '3. Nc3', 'Qd8', '4. d4', 'Nf6']); expectSameLine(tester, ['2. Nc3']); expectDifferentLines(tester, ['1. e4', '1… d5', '2. Nc3']); }); - testWidgets('displays sideline with branching on its own line', - (tester) async { + testWidgets('displays sideline with branching on its own line', (tester) async { await buildTree(tester, '1. e4 e5 (1... d5 2. exd5 (2. Nc3)) *'); expectSameLine(tester, ['1. e4', 'e5']); @@ -220,10 +168,7 @@ void main() { }); testWidgets('multiple sidelines', (tester) async { - await buildTree( - tester, - '1. e4 e5 (1... d5 2. exd5) (1... Nf6 2. e5) 2. Nf3 Nc6 (2... a5) *', - ); + await buildTree(tester, '1. e4 e5 (1... d5 2. exd5) (1... Nf6 2. e5) 2. Nf3 Nc6 (2... a5) *'); expectSameLine(tester, ['1. e4', 'e5']); expectSameLine(tester, ['1… d5', '2. exd5']); @@ -234,10 +179,7 @@ void main() { }); testWidgets('collapses lines with nesting > 2', (tester) async { - await buildTree( - tester, - '1. e4 e5 (1... d5 2. Nc3 (2. h4 h5 (2... Nc6 3. d3) (2... Qd7))) *', - ); + await buildTree(tester, '1. e4 e5 (1... d5 2. Nc3 (2. h4 h5 (2... Nc6 3. d3) (2... Qd7))) *'); expectSameLine(tester, ['1. e4', 'e5']); expectSameLine(tester, ['1… d5']); @@ -277,9 +219,9 @@ void main() { expect(find.text('2… Qd7'), findsNothing); }); - testWidgets( - 'Expanding one line does not expand the following one (regression test)', - (tester) async { + testWidgets('Expanding one line does not expand the following one (regression test)', ( + tester, + ) async { /// Will be rendered as: /// ------------------- /// 1. e4 e5 @@ -287,10 +229,7 @@ void main() { /// 2. Nf3 /// |- 2. a4 d5 (2... f5) /// ------------------- - await buildTree( - tester, - '1. e4 e5 (1... d5 2. Nf3 (2. Nc3)) 2. Nf3 (2. a4 d5 (2... f5))', - ); + await buildTree(tester, '1. e4 e5 (1... d5 2. Nf3 (2. Nc3)) 2. Nf3 (2. a4 d5 (2... f5))'); expect(find.byIcon(Icons.add_box), findsNothing); @@ -323,12 +262,8 @@ void main() { expect(find.text('2. a4'), findsNothing); }); - testWidgets('subtrees not part of the current mainline part are cached', - (tester) async { - await buildTree( - tester, - '1. e4 e5 (1... d5 2. exd5) (1... Nf6 2. e5) 2. Nf3 Nc6 (2... a5) *', - ); + testWidgets('subtrees not part of the current mainline part are cached', (tester) async { + await buildTree(tester, '1. e4 e5 (1... d5 2. exd5) (1... Nf6 2. e5) 2. Nf3 Nc6 (2... a5) *'); // will be rendered as: // ------------------- @@ -346,10 +281,7 @@ void main() { expect( tester .widgetList( - find.ancestor( - of: find.textContaining('Nc6'), - matching: find.byType(InlineMove), - ), + find.ancestor(of: find.textContaining('Nc6'), matching: find.byType(InlineMove)), ) .last .isCurrentMove, @@ -363,10 +295,7 @@ void main() { expect( tester .widgetList( - find.ancestor( - of: find.textContaining('Nf3'), - matching: find.byType(InlineMove), - ), + find.ancestor(of: find.textContaining('Nf3'), matching: find.byType(InlineMove)), ) .last .isCurrentMove, @@ -375,18 +304,12 @@ void main() { // first mainline part has not changed since the current move is // not part of it - expect( - identical(firstMainlinePart, parentText(tester, '1. e4')), - isTrue, - ); + expect(identical(firstMainlinePart, parentText(tester, '1. e4')), isTrue); final secondMainlinePartOnMoveNf3 = parentText(tester, '2. Nf3'); // second mainline part has changed since the current move is part of it - expect( - secondMainlinePart, - isNot(secondMainlinePartOnMoveNf3), - ); + expect(secondMainlinePart, isNot(secondMainlinePartOnMoveNf3)); await tester.tap(find.byKey(const Key('goto-previous'))); // need to wait for current move change debounce delay @@ -395,10 +318,7 @@ void main() { expect( tester .widgetList( - find.ancestor( - of: find.textContaining('e5'), - matching: find.byType(InlineMove), - ), + find.ancestor(of: find.textContaining('e5'), matching: find.byType(InlineMove)), ) .first .isCurrentMove, @@ -409,17 +329,11 @@ void main() { final secondMainlinePartOnMoveE5 = parentText(tester, '2. Nf3'); // first mainline part has changed since the current move is part of it - expect( - firstMainlinePart, - isNot(firstMainlinePartOnMoveE5), - ); + expect(firstMainlinePart, isNot(firstMainlinePartOnMoveE5)); // second mainline part has changed since the current move is not part of it // anymore - expect( - secondMainlinePartOnMoveNf3, - isNot(secondMainlinePartOnMoveE5), - ); + expect(secondMainlinePartOnMoveNf3, isNot(secondMainlinePartOnMoveE5)); await tester.tap(find.byKey(const Key('goto-previous'))); // need to wait for current move change debounce delay @@ -428,10 +342,7 @@ void main() { expect( tester .firstWidget( - find.ancestor( - of: find.textContaining('e4'), - matching: find.byType(InlineMove), - ), + find.ancestor(of: find.textContaining('e4'), matching: find.byType(InlineMove)), ) .isCurrentMove, isTrue, @@ -441,16 +352,10 @@ void main() { final secondMainlinePartOnMoveE4 = parentText(tester, '2. Nf3'); // first mainline part has changed since the current move is part of it - expect( - firstMainlinePartOnMoveE4, - isNot(firstMainlinePartOnMoveE5), - ); + expect(firstMainlinePartOnMoveE4, isNot(firstMainlinePartOnMoveE5)); // second mainline part has not changed since the current move is not part of it - expect( - identical(secondMainlinePartOnMoveE5, secondMainlinePartOnMoveE4), - isTrue, - ); + expect(identical(secondMainlinePartOnMoveE5, secondMainlinePartOnMoveE4), isTrue); }); }); } diff --git a/test/view/board_editor/board_editor_screen_test.dart b/test/view/board_editor/board_editor_screen_test.dart index 2e6b4381d1..320d486d8b 100644 --- a/test/view/board_editor/board_editor_screen_test.dart +++ b/test/view/board_editor/board_editor_screen_test.dart @@ -12,63 +12,42 @@ import '../../test_provider_scope.dart'; void main() { group('Board Editor', () { testWidgets('Displays initial FEN on start', (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const BoardEditorScreen(), - ); + final app = await makeTestProviderScopeApp(tester, home: const BoardEditorScreen()); await tester.pumpWidget(app); - final editor = tester.widget( - find.byType(ChessboardEditor), - ); + final editor = tester.widget(find.byType(ChessboardEditor)); expect(editor.pieces, readFen(kInitialFEN)); expect(editor.orientation, Side.white); expect(editor.pointerMode, EditorPointerMode.drag); // Legal position, so allowed top open analysis board expect( - tester - .widget( - find.byKey(const Key('analysis-board-button')), - ) - .onTap, + tester.widget(find.byKey(const Key('analysis-board-button'))).onTap, isNotNull, ); }); testWidgets('Flip board', (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const BoardEditorScreen(), - ); + final app = await makeTestProviderScopeApp(tester, home: const BoardEditorScreen()); await tester.pumpWidget(app); await tester.tap(find.byKey(const Key('flip-button'))); await tester.pump(); expect( - tester - .widget( - find.byType(ChessboardEditor), - ) - .orientation, + tester.widget(find.byType(ChessboardEditor)).orientation, Side.black, ); }); testWidgets('Side to play and castling rights', (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const BoardEditorScreen(), - ); + final app = await makeTestProviderScopeApp(tester, home: const BoardEditorScreen()); await tester.pumpWidget(app); await tester.tap(find.byKey(const Key('flip-button'))); await tester.pump(); - final container = ProviderScope.containerOf( - tester.element(find.byType(ChessboardEditor)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(ChessboardEditor))); final controllerProvider = boardEditorControllerProvider(null); @@ -78,9 +57,7 @@ void main() { 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1', ); - container - .read(controllerProvider.notifier) - .setCastling(Side.white, CastlingSide.king, false); + container.read(controllerProvider.notifier).setCastling(Side.white, CastlingSide.king, false); expect( container.read(controllerProvider).fen, 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b Qkq - 0 1', @@ -94,9 +71,7 @@ void main() { 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b kq - 0 1', ); - container - .read(controllerProvider.notifier) - .setCastling(Side.black, CastlingSide.king, false); + container.read(controllerProvider.notifier).setCastling(Side.black, CastlingSide.king, false); expect( container.read(controllerProvider).fen, 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b q - 0 1', @@ -110,9 +85,7 @@ void main() { 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b - - 0 1', ); - container - .read(controllerProvider.notifier) - .setCastling(Side.white, CastlingSide.king, true); + container.read(controllerProvider.notifier).setCastling(Side.white, CastlingSide.king, true); expect( container.read(controllerProvider).fen, 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b K - 0 1', @@ -120,15 +93,10 @@ void main() { }); testWidgets('Castling rights ignored when rook is missing', (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const BoardEditorScreen(), - ); + final app = await makeTestProviderScopeApp(tester, home: const BoardEditorScreen()); await tester.pumpWidget(app); - final container = ProviderScope.containerOf( - tester.element(find.byType(ChessboardEditor)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(ChessboardEditor))); final controllerProvider = boardEditorControllerProvider(null); // Starting position, but with all rooks removed @@ -144,15 +112,10 @@ void main() { }); testWidgets('support chess960 castling rights', (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const BoardEditorScreen(), - ); + final app = await makeTestProviderScopeApp(tester, home: const BoardEditorScreen()); await tester.pumpWidget(app); - final container = ProviderScope.containerOf( - tester.element(find.byType(ChessboardEditor)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(ChessboardEditor))); final controllerProvider = boardEditorControllerProvider(null); container @@ -165,17 +128,11 @@ void main() { ); }); - testWidgets('Castling rights ignored when king is not in backrank', - (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const BoardEditorScreen(), - ); + testWidgets('Castling rights ignored when king is not in backrank', (tester) async { + final app = await makeTestProviderScopeApp(tester, home: const BoardEditorScreen()); await tester.pumpWidget(app); - final container = ProviderScope.containerOf( - tester.element(find.byType(ChessboardEditor)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(ChessboardEditor))); final controllerProvider = boardEditorControllerProvider(null); container @@ -188,37 +145,28 @@ void main() { ); }); - testWidgets('Possible en passant squares are calculated correctly', - (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const BoardEditorScreen(), - ); + testWidgets('Possible en passant squares are calculated correctly', (tester) async { + final app = await makeTestProviderScopeApp(tester, home: const BoardEditorScreen()); await tester.pumpWidget(app); - final container = ProviderScope.containerOf( - tester.element(find.byType(ChessboardEditor)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(ChessboardEditor))); final controllerProvider = boardEditorControllerProvider(null); container .read(controllerProvider.notifier) .loadFen('1nbqkbn1/pppppppp/8/8/8/8/PPPPPPPP/1NBQKBN1'); - expect( - container.read(controllerProvider).enPassantOptions, - SquareSet.empty, - ); + expect(container.read(controllerProvider).enPassantOptions, SquareSet.empty); - container.read(controllerProvider.notifier).loadFen( - 'r1bqkbnr/4p1p1/3n4/pPppPppP/8/8/P1PP1P2/RNBQKBNR w KQkq - 0 1', - ); + container + .read(controllerProvider.notifier) + .loadFen('r1bqkbnr/4p1p1/3n4/pPppPppP/8/8/P1PP1P2/RNBQKBNR w KQkq - 0 1'); expect( container.read(controllerProvider).enPassantOptions, SquareSet.fromSquares([Square.a6, Square.c6, Square.f6]), ); - container.read(controllerProvider.notifier).loadFen( - 'rnbqkbnr/pp1p1p1p/8/8/PpPpPQpP/8/NPRP1PP1/2B1KBNR b Kkq - 0 1', - ); + container + .read(controllerProvider.notifier) + .loadFen('rnbqkbnr/pp1p1p1p/8/8/PpPpPQpP/8/NPRP1PP1/2B1KBNR b Kkq - 0 1'); container.read(controllerProvider.notifier).setSideToPlay(Side.black); expect( container.read(controllerProvider).enPassantOptions, @@ -227,15 +175,10 @@ void main() { }); testWidgets('Can drag pieces to new squares', (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const BoardEditorScreen(), - ); + final app = await makeTestProviderScopeApp(tester, home: const BoardEditorScreen()); await tester.pumpWidget(app); - final container = ProviderScope.containerOf( - tester.element(find.byType(ChessboardEditor)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(ChessboardEditor))); final controllerProvider = boardEditorControllerProvider(null); // Two legal moves by white @@ -256,35 +199,23 @@ void main() { }); testWidgets('illegal position cannot be analyzed', (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const BoardEditorScreen(), - ); + final app = await makeTestProviderScopeApp(tester, home: const BoardEditorScreen()); await tester.pumpWidget(app); // White queen "captures" white king => illegal position await dragFromTo(tester, 'd1', 'e1'); expect( - tester - .widget( - find.byKey(const Key('analysis-board-button')), - ) - .onTap, + tester.widget(find.byKey(const Key('analysis-board-button'))).onTap, isNull, ); }); testWidgets('Delete pieces via bin button', (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const BoardEditorScreen(), - ); + final app = await makeTestProviderScopeApp(tester, home: const BoardEditorScreen()); await tester.pumpWidget(app); - final container = ProviderScope.containerOf( - tester.element(find.byType(ChessboardEditor)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(ChessboardEditor))); final controllerProvider = boardEditorControllerProvider(null); await tester.tap(find.byKey(const Key('delete-button-white'))); @@ -318,10 +249,7 @@ void main() { }); testWidgets('Add pieces via tap and pan', (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const BoardEditorScreen(), - ); + final app = await makeTestProviderScopeApp(tester, home: const BoardEditorScreen()); await tester.pumpWidget(app); await tester.tap(find.byKey(const Key('piece-button-white-queen'))); @@ -330,9 +258,7 @@ void main() { await tapSquare(tester, 'h1'); await tapSquare(tester, 'h3'); - final container = ProviderScope.containerOf( - tester.element(find.byType(ChessboardEditor)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(ChessboardEditor))); final controllerProvider = boardEditorControllerProvider(null); expect( @@ -342,37 +268,27 @@ void main() { }); testWidgets('Drag pieces onto the board', (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const BoardEditorScreen(), - ); + final app = await makeTestProviderScopeApp(tester, home: const BoardEditorScreen()); await tester.pumpWidget(app); // Start by pressing bin button, dragging a piece should override this await tester.tap(find.byKey(const Key('delete-button-black'))); await tester.pump(); - final pieceButtonOffset = - tester.getCenter(find.byKey(const Key('piece-button-white-pawn'))); + final pieceButtonOffset = tester.getCenter(find.byKey(const Key('piece-button-white-pawn'))); await tester.dragFrom( pieceButtonOffset, tester.getCenter(find.byKey(const Key('d3-empty'))) - pieceButtonOffset, ); await tester.dragFrom( pieceButtonOffset, - tester.getCenter(find.byKey(const Key('d1-whitequeen'))) - - pieceButtonOffset, + tester.getCenter(find.byKey(const Key('d1-whitequeen'))) - pieceButtonOffset, ); - final container = ProviderScope.containerOf( - tester.element(find.byType(ChessboardEditor)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(ChessboardEditor))); final controllerProvider = boardEditorControllerProvider(null); - expect( - container.read(controllerProvider).editorPointerMode, - EditorPointerMode.drag, - ); + expect(container.read(controllerProvider).editorPointerMode, EditorPointerMode.drag); expect( container.read(controllerProvider).fen, @@ -382,25 +298,14 @@ void main() { }); } -Future dragFromTo( - WidgetTester tester, - String from, - String to, -) async { +Future dragFromTo(WidgetTester tester, String from, String to) async { final fromOffset = squareOffset(tester, Square.fromName(from)); - await tester.dragFrom( - fromOffset, - squareOffset(tester, Square.fromName(to)) - fromOffset, - ); + await tester.dragFrom(fromOffset, squareOffset(tester, Square.fromName(to)) - fromOffset); await tester.pumpAndSettle(); } -Future panFromTo( - WidgetTester tester, - String from, - String to, -) async { +Future panFromTo(WidgetTester tester, String from, String to) async { final fromOffset = squareOffset(tester, Square.fromName(from)); await tester.timedDragFrom( diff --git a/test/view/broadcast/broadcasts_list_screen_test.dart b/test/view/broadcast/broadcasts_list_screen_test.dart index fb845378c6..2a90722cb6 100644 --- a/test/view/broadcast/broadcasts_list_screen_test.dart +++ b/test/view/broadcast/broadcasts_list_screen_test.dart @@ -45,63 +45,53 @@ final client = MockClient((request) { void main() { group('BroadcastListScreen', () { - testWidgets( - 'Displays broadcast tournament screen', - variant: kPlatformVariant, - (tester) async { - mockNetworkImagesFor(() async { - final app = await makeTestProviderScopeApp( - tester, - home: const BroadcastListScreen(), - overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - broadcastImageWorkerFactoryProvider.overrideWith( - (ref) => FakeBroadcastImageWorkerFactory(), - ), - ], - ); - - await tester.pumpWidget(app); - - expect(find.byType(CircularProgressIndicator), findsOneWidget); - - // wait for broadcast tournaments to load - await tester.pump(const Duration(milliseconds: 100)); - - expect(find.byType(BroadcastCard), findsAtLeast(1)); - }); - }, - ); - - testWidgets( - 'Scroll broadcast tournament screen', - variant: kPlatformVariant, - (tester) async { - mockNetworkImagesFor(() async { - final app = await makeTestProviderScopeApp( - tester, - home: const BroadcastListScreen(), - overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - broadcastImageWorkerFactoryProvider.overrideWith( - (ref) => FakeBroadcastImageWorkerFactory(), - ), - ], - ); - - await tester.pumpWidget(app); - - expect(find.byType(CircularProgressIndicator), findsOneWidget); - - // wait for broadcast tournaments to load - await tester.pump(const Duration(milliseconds: 100)); - - await tester.scrollUntilVisible(find.text('Completed'), 200.0); - }); - }, - ); + testWidgets('Displays broadcast tournament screen', variant: kPlatformVariant, (tester) async { + mockNetworkImagesFor(() async { + final app = await makeTestProviderScopeApp( + tester, + home: const BroadcastListScreen(), + overrides: [ + lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)), + broadcastImageWorkerFactoryProvider.overrideWith( + (ref) => FakeBroadcastImageWorkerFactory(), + ), + ], + ); + + await tester.pumpWidget(app); + + expect(find.byType(CircularProgressIndicator), findsOneWidget); + + // wait for broadcast tournaments to load + await tester.pump(const Duration(milliseconds: 100)); + + expect(find.byType(BroadcastCard), findsAtLeast(1)); + }); + }); + + testWidgets('Scroll broadcast tournament screen', variant: kPlatformVariant, (tester) async { + mockNetworkImagesFor(() async { + final app = await makeTestProviderScopeApp( + tester, + home: const BroadcastListScreen(), + overrides: [ + lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)), + broadcastImageWorkerFactoryProvider.overrideWith( + (ref) => FakeBroadcastImageWorkerFactory(), + ), + ], + ); + + await tester.pumpWidget(app); + + expect(find.byType(CircularProgressIndicator), findsOneWidget); + + // wait for broadcast tournaments to load + await tester.pump(const Duration(milliseconds: 100)); + + await tester.scrollUntilVisible(find.text('Completed'), 200.0); + }); + }); }); } diff --git a/test/view/coordinate_training/coordinate_training_screen_test.dart b/test/view/coordinate_training/coordinate_training_screen_test.dart index ab5f65e5b9..687a2b9829 100644 --- a/test/view/coordinate_training/coordinate_training_screen_test.dart +++ b/test/view/coordinate_training/coordinate_training_screen_test.dart @@ -11,25 +11,17 @@ import '../../test_provider_scope.dart'; void main() { group('Coordinate Training', () { - testWidgets('Initial state when started in FindSquare mode', - (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const CoordinateTrainingScreen(), - ); + testWidgets('Initial state when started in FindSquare mode', (tester) async { + final app = await makeTestProviderScopeApp(tester, home: const CoordinateTrainingScreen()); await tester.pumpWidget(app); await tester.tap(find.text('Start Training')); await tester.pumpAndSettle(); - final container = ProviderScope.containerOf( - tester.element(find.byType(ChessboardEditor)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(ChessboardEditor))); final controllerProvider = coordinateTrainingControllerProvider; - final trainingPrefsNotifier = container.read( - coordinateTrainingPreferencesProvider.notifier, - ); + final trainingPrefsNotifier = container.read(coordinateTrainingPreferencesProvider.notifier); trainingPrefsNotifier.setMode(TrainingMode.findSquare); // This way all squares can be found via find.byKey(ValueKey('${square.name}-empty')) trainingPrefsNotifier.setShowPieces(false); @@ -41,34 +33,21 @@ void main() { expect(container.read(controllerProvider).trainingActive, true); // Current and next coordinate prompt should be displayed - expect( - find.text(container.read(controllerProvider).currentCoord!.name), - findsOneWidget, - ); - expect( - find.text(container.read(controllerProvider).nextCoord!.name), - findsOneWidget, - ); + expect(find.text(container.read(controllerProvider).currentCoord!.name), findsOneWidget); + expect(find.text(container.read(controllerProvider).nextCoord!.name), findsOneWidget); }); testWidgets('Tap wrong square', (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const CoordinateTrainingScreen(), - ); + final app = await makeTestProviderScopeApp(tester, home: const CoordinateTrainingScreen()); await tester.pumpWidget(app); await tester.tap(find.text('Start Training')); await tester.pumpAndSettle(); - final container = ProviderScope.containerOf( - tester.element(find.byType(ChessboardEditor)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(ChessboardEditor))); final controllerProvider = coordinateTrainingControllerProvider; - final trainingPrefsNotifier = container.read( - coordinateTrainingPreferencesProvider.notifier, - ); + final trainingPrefsNotifier = container.read(coordinateTrainingPreferencesProvider.notifier); trainingPrefsNotifier.setMode(TrainingMode.findSquare); // This way all squares can be found via find.byKey(ValueKey('${square.name}-empty')) trainingPrefsNotifier.setShowPieces(false); @@ -77,8 +56,7 @@ void main() { final currentCoord = container.read(controllerProvider).currentCoord; final nextCoord = container.read(controllerProvider).nextCoord; - final wrongCoord = - Square.values[(currentCoord! + 1) % Square.values.length]; + final wrongCoord = Square.values[(currentCoord! + 1) % Square.values.length]; await tester.tap(find.byKey(ValueKey('${wrongCoord.name}-empty'))); await tester.pump(); @@ -88,36 +66,23 @@ void main() { expect(container.read(controllerProvider).nextCoord, nextCoord); expect(container.read(controllerProvider).trainingActive, true); - expect( - find.byKey(ValueKey('${wrongCoord.name}-highlight')), - findsOneWidget, - ); + expect(find.byKey(ValueKey('${wrongCoord.name}-highlight')), findsOneWidget); await tester.pump(const Duration(milliseconds: 300)); - expect( - find.byKey(ValueKey('${wrongCoord.name}-highlight')), - findsNothing, - ); + expect(find.byKey(ValueKey('${wrongCoord.name}-highlight')), findsNothing); }); testWidgets('Tap correct square', (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const CoordinateTrainingScreen(), - ); + final app = await makeTestProviderScopeApp(tester, home: const CoordinateTrainingScreen()); await tester.pumpWidget(app); await tester.tap(find.text('Start Training')); await tester.pumpAndSettle(); - final container = ProviderScope.containerOf( - tester.element(find.byType(ChessboardEditor)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(ChessboardEditor))); final controllerProvider = coordinateTrainingControllerProvider; - final trainingPrefsNotifier = container.read( - coordinateTrainingPreferencesProvider.notifier, - ); + final trainingPrefsNotifier = container.read(coordinateTrainingPreferencesProvider.notifier); trainingPrefsNotifier.setMode(TrainingMode.findSquare); // This way all squares can be found via find.byKey(ValueKey('${square.name}-empty')) trainingPrefsNotifier.setShowPieces(false); @@ -129,29 +94,17 @@ void main() { await tester.tap(find.byKey(ValueKey('${currentCoord!.name}-empty'))); await tester.pump(); - expect( - find.byKey(ValueKey('${currentCoord.name}-highlight')), - findsOneWidget, - ); + expect(find.byKey(ValueKey('${currentCoord.name}-highlight')), findsOneWidget); expect(container.read(controllerProvider).score, 1); expect(container.read(controllerProvider).currentCoord, nextCoord); expect(container.read(controllerProvider).trainingActive, true); await tester.pumpAndSettle(const Duration(milliseconds: 300)); - expect( - find.byKey(ValueKey('${currentCoord.name}-highlight')), - findsNothing, - ); - - expect( - find.text(container.read(controllerProvider).currentCoord!.name), - findsOneWidget, - ); - expect( - find.text(container.read(controllerProvider).nextCoord!.name), - findsOneWidget, - ); + expect(find.byKey(ValueKey('${currentCoord.name}-highlight')), findsNothing); + + expect(find.text(container.read(controllerProvider).currentCoord!.name), findsOneWidget); + expect(find.text(container.read(controllerProvider).nextCoord!.name), findsOneWidget); }); }); } diff --git a/test/view/game/archived_game_screen_test.dart b/test/view/game/archived_game_screen_test.dart index cc09d0f7ed..8350079209 100644 --- a/test/view/game/archived_game_screen_test.dart +++ b/test/view/game/archived_game_screen_test.dart @@ -29,121 +29,78 @@ final client = MockClient((request) { void main() { group('ArchivedGameScreen', () { - testWidgets( - 'loads game data if only game id is provided', - (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const ArchivedGameScreen( - gameId: GameId('qVChCOTc'), - orientation: Side.white, - ), - overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - ], - ); - - await tester.pumpWidget(app); - - expect(find.byType(PieceWidget), findsNothing); - expect(find.byType(CircularProgressIndicator), findsOneWidget); - - // wait for game data loading - await tester.pump(const Duration(milliseconds: 100)); - - expect(find.byType(PieceWidget), findsNWidgets(25)); - expect(find.widgetWithText(GamePlayer, 'veloce'), findsOneWidget); - expect( - find.widgetWithText(GamePlayer, 'Stockfish level 1'), - findsOneWidget, - ); - }, - variant: kPlatformVariant, - ); - - testWidgets( - 'displays game data and last fen immediately, then moves', - (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: ArchivedGameScreen( - gameData: gameData, - orientation: Side.white, - ), - overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - ], - ); - - await tester.pumpWidget(app); - - // data shown immediately - expect(find.byType(Chessboard), findsOneWidget); - expect(find.byType(PieceWidget), findsNWidgets(25)); - expect(find.widgetWithText(GamePlayer, 'veloce'), findsOneWidget); - expect( - find.widgetWithText(GamePlayer, 'Stockfish level 1'), - findsOneWidget, - ); - - // cannot interact with board - expect( - tester.widget(find.byType(Chessboard)).game, - null, - ); - - // moves are not loaded - expect(find.byType(MoveList), findsNothing); - expect( - tester - .widget( - find.byKey(const ValueKey('cursor-back')), - ) - .onTap, - isNull, - ); - - // wait for game steps loading - await tester.pump(const Duration(milliseconds: 100)); - // wait for move list ensureVisible animation to finish - await tester.pumpAndSettle(); + testWidgets('loads game data if only game id is provided', (tester) async { + final app = await makeTestProviderScopeApp( + tester, + home: const ArchivedGameScreen(gameId: GameId('qVChCOTc'), orientation: Side.white), + overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))], + ); + + await tester.pumpWidget(app); + + expect(find.byType(PieceWidget), findsNothing); + expect(find.byType(CircularProgressIndicator), findsOneWidget); + + // wait for game data loading + await tester.pump(const Duration(milliseconds: 100)); + + expect(find.byType(PieceWidget), findsNWidgets(25)); + expect(find.widgetWithText(GamePlayer, 'veloce'), findsOneWidget); + expect(find.widgetWithText(GamePlayer, 'Stockfish level 1'), findsOneWidget); + }, variant: kPlatformVariant); + + testWidgets('displays game data and last fen immediately, then moves', (tester) async { + final app = await makeTestProviderScopeApp( + tester, + home: ArchivedGameScreen(gameData: gameData, orientation: Side.white), + overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))], + ); + + await tester.pumpWidget(app); + + // data shown immediately + expect(find.byType(Chessboard), findsOneWidget); + expect(find.byType(PieceWidget), findsNWidgets(25)); + expect(find.widgetWithText(GamePlayer, 'veloce'), findsOneWidget); + expect(find.widgetWithText(GamePlayer, 'Stockfish level 1'), findsOneWidget); + + // cannot interact with board + expect(tester.widget(find.byType(Chessboard)).game, null); + + // moves are not loaded + expect(find.byType(MoveList), findsNothing); + expect( + tester.widget(find.byKey(const ValueKey('cursor-back'))).onTap, + isNull, + ); + + // wait for game steps loading + await tester.pump(const Duration(milliseconds: 100)); + // wait for move list ensureVisible animation to finish + await tester.pumpAndSettle(); - // same info still displayed - expect(find.byType(Chessboard), findsOneWidget); - expect(find.byType(PieceWidget), findsNWidgets(25)); - expect(find.widgetWithText(GamePlayer, 'veloce'), findsOneWidget); - expect( - find.widgetWithText(GamePlayer, 'Stockfish level 1'), - findsOneWidget, - ); - - // now with the clocks - expect(find.text('1:46', findRichText: true), findsNWidgets(1)); - expect(find.text('0:46', findRichText: true), findsNWidgets(1)); - - // moves are loaded - expect(find.byType(MoveList), findsOneWidget); - expect( - tester - .widget( - find.byKey(const ValueKey('cursor-back')), - ) - .onTap, - isNotNull, - ); - }, - variant: kPlatformVariant, - ); + // same info still displayed + expect(find.byType(Chessboard), findsOneWidget); + expect(find.byType(PieceWidget), findsNWidgets(25)); + expect(find.widgetWithText(GamePlayer, 'veloce'), findsOneWidget); + expect(find.widgetWithText(GamePlayer, 'Stockfish level 1'), findsOneWidget); + + // now with the clocks + expect(find.text('1:46', findRichText: true), findsNWidgets(1)); + expect(find.text('0:46', findRichText: true), findsNWidgets(1)); + + // moves are loaded + expect(find.byType(MoveList), findsOneWidget); + expect( + tester.widget(find.byKey(const ValueKey('cursor-back'))).onTap, + isNotNull, + ); + }, variant: kPlatformVariant); testWidgets('navigate game positions', (tester) async { final app = await makeTestProviderScopeApp( tester, - home: ArchivedGameScreen( - gameData: gameData, - orientation: Side.white, - ), + home: ArchivedGameScreen(gameData: gameData, orientation: Side.white), overrides: [ lichessClientProvider.overrideWith((ref) { return LichessClient(client, ref); @@ -168,23 +125,12 @@ void main() { .toList(); expect( - tester - .widget( - find.widgetWithText(InlineMoveItem, 'Qe1#'), - ) - .current, + tester.widget(find.widgetWithText(InlineMoveItem, 'Qe1#')).current, isTrue, ); // cannot go forward - expect( - tester - .widget( - find.byKey(const Key('cursor-forward')), - ) - .onTap, - isNull, - ); + expect(tester.widget(find.byKey(const Key('cursor-forward'))).onTap, isNull); for (var i = 0; i <= movesAfterE4.length; i++) { // go back in history @@ -195,25 +141,17 @@ void main() { // move list is updated final prevMoveIndex = i + 1; if (prevMoveIndex < movesAfterE4.length) { - final prevMove = - find.widgetWithText(InlineMoveItem, movesAfterE4[prevMoveIndex]); + final prevMove = find.widgetWithText(InlineMoveItem, movesAfterE4[prevMoveIndex]); expect(prevMove, findsAtLeastNWidgets(1)); expect( - tester - .widgetList(prevMove) - .any((e) => e.current ?? false), + tester.widgetList(prevMove).any((e) => e.current ?? false), isTrue, ); } } // cannot go backward anymore - expect( - tester - .widget(find.byKey(const Key('cursor-back'))) - .onTap, - isNull, - ); + expect(tester.widget(find.byKey(const Key('cursor-back'))).onTap, isNull); }); }); } @@ -234,11 +172,7 @@ final gameData = LightArchivedGame( status: GameStatus.mate, white: const Player(aiLevel: 1), black: const Player( - user: LightUser( - id: UserId('veloce'), - name: 'veloce', - isPatron: true, - ), + user: LightUser(id: UserId('veloce'), name: 'veloce', isPatron: true), rating: 1435, ), variant: Variant.standard, diff --git a/test/view/game/game_screen_test.dart b/test/view/game/game_screen_test.dart index 03dd851370..fb8ede009a 100644 --- a/test/view/game/game_screen_test.dart +++ b/test/view/game/game_screen_test.dart @@ -35,18 +35,14 @@ class MockSoundService extends Mock implements SoundService {} void main() { group('Loading', () { - testWidgets('a game directly with initialGameId', - (WidgetTester tester) async { + testWidgets('a game directly with initialGameId', (WidgetTester tester) async { final fakeSocket = FakeWebSocketChannel(); final app = await makeTestProviderScopeApp( tester, - home: const GameScreen( - initialGameId: GameFullId('qVChCOTcHSeW'), - ), + home: const GameScreen(initialGameId: GameFullId('qVChCOTcHSeW')), overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), + lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)), webSocketChannelFactoryProvider.overrideWith((ref) { return FakeWebSocketChannelFactory((_) => fakeSocket); }), @@ -81,26 +77,20 @@ void main() { expect(find.text('Steven'), findsOneWidget); }); - testWidgets('a game from the pool with a seek', - (WidgetTester tester) async { + testWidgets('a game from the pool with a seek', (WidgetTester tester) async { final fakeLobbySocket = FakeWebSocketChannel(); final fakeGameSocket = FakeWebSocketChannel(); final app = await makeTestProviderScopeApp( tester, home: const GameScreen( - seek: GameSeek( - clock: (Duration(minutes: 3), Duration(seconds: 2)), - rated: true, - ), + seek: GameSeek(clock: (Duration(minutes: 3), Duration(seconds: 2)), rated: true), ), overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), + lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)), webSocketChannelFactoryProvider.overrideWith((ref) { return FakeWebSocketChannelFactory( - (String url) => - url.contains('lobby') ? fakeLobbySocket : fakeGameSocket, + (String url) => url.contains('lobby') ? fakeLobbySocket : fakeGameSocket, ); }), ], @@ -197,8 +187,7 @@ void main() { expect(findClockWithTime('2:58'), findsOneWidget); }); - testWidgets('ticks immediately when resuming game', - (WidgetTester tester) async { + testWidgets('ticks immediately when resuming game', (WidgetTester tester) async { final fakeSocket = FakeWebSocketChannel(); await createTestGame( fakeSocket, @@ -340,9 +329,7 @@ void main() { black: Duration(minutes: 3), emerg: Duration(seconds: 30), ), - overrides: [ - soundServiceProvider.overrideWith((_) => mockSoundService), - ], + overrides: [soundServiceProvider.overrideWith((_) => mockSoundService)], ); expect( tester.widget(findClockWithTime('0:40')).emergencyThreshold, @@ -394,10 +381,7 @@ void main() { // flag messages are sent expectLater( fakeSocket.sentMessagesExceptPing, - emitsInOrder([ - '{"t":"flag","d":"black"}', - '{"t":"flag","d":"black"}', - ]), + emitsInOrder(['{"t":"flag","d":"black"}', '{"t":"flag","d":"black"}']), ); await tester.pump(const Duration(seconds: 1)); fakeSocket.addIncomingMessages([ @@ -422,14 +406,12 @@ void main() { }); group('Opening analysis', () { - testWidgets('is not possible for an unfinished real time game', - (WidgetTester tester) async { + testWidgets('is not possible for an unfinished real time game', (WidgetTester tester) async { final fakeSocket = FakeWebSocketChannel(); await createTestGame( fakeSocket, tester, - pgn: - 'e4 e5 Nf3 Nc6 Bc4 Nf6 Ng5 d5 exd5 Na5 Bb5+ c6 dxc6 bxc6 Qf3 Rb8 Bd3', + pgn: 'e4 e5 Nf3 Nc6 Bc4 Nf6 Ng5 d5 exd5 Na5 Bb5+ c6 dxc6 bxc6 Qf3 Rb8 Bd3', socketVersion: 0, ); expect(find.byType(Chessboard), findsOneWidget); @@ -439,14 +421,12 @@ void main() { expect(find.text('Analysis board'), findsNothing); }); - testWidgets('for an unfinished correspondence game', - (WidgetTester tester) async { + testWidgets('for an unfinished correspondence game', (WidgetTester tester) async { final fakeSocket = FakeWebSocketChannel(); await createTestGame( fakeSocket, tester, - pgn: - 'e4 e5 Nf3 Nc6 Bc4 Nf6 Ng5 d5 exd5 Na5 Bb5+ c6 dxc6 bxc6 Qf3 Rb8 Bd3', + pgn: 'e4 e5 Nf3 Nc6 Bc4 Nf6 Ng5 d5 exd5 Na5 Bb5+ c6 dxc6 bxc6 Qf3 Rb8 Bd3', clock: null, correspondenceClock: ( daysPerTurn: 3, @@ -499,10 +479,7 @@ void main() { await tester.tap(find.byIcon(LichessIcons.flow_cascade)); await tester.pumpAndSettle(); // wait for the moves tab menu to open expect(find.text('Moves played'), findsOneWidget); - expect( - find.text('Computer analysis'), - findsOneWidget, - ); // computer analysis is available + expect(find.text('Computer analysis'), findsOneWidget); // computer analysis is available }); }); } @@ -529,9 +506,8 @@ Future playMoveWithServerAck( }) async { await playMove(tester, from, to, orientation: orientation); final uci = '$from$to'; - final lagStr = clockAck.lag != null - ? ', "lag": ${(clockAck.lag!.inMilliseconds / 10).round()}' - : ''; + final lagStr = + clockAck.lag != null ? ', "lag": ${(clockAck.lag!.inMilliseconds / 10).round()}' : ''; await tester.pump(elapsedTime - const Duration(milliseconds: 1)); socket.addIncomingMessages([ '{"t": "move", "v": $socketVersion, "d": {"ply": $ply, "uci": "$uci", "san": "$san", "clock": {"white": ${(clockAck.white.inMilliseconds / 1000).toStringAsFixed(2)}, "black": ${(clockAck.black.inMilliseconds / 1000).toStringAsFixed(2)}$lagStr}}}', @@ -559,9 +535,7 @@ Future createTestGame( }) async { final app = await makeTestProviderScopeApp( tester, - home: const GameScreen( - initialGameId: GameFullId('qVChCOTcHSeW'), - ), + home: const GameScreen(initialGameId: GameFullId('qVChCOTcHSeW')), overrides: [ lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)), webSocketChannelFactoryProvider.overrideWith((ref) { @@ -596,13 +570,10 @@ Future loadFinishedTestGame( List? overrides, }) async { final json = jsonDecode(serverFullEvent) as Map; - final gameId = - GameFullEvent.fromJson(json['d'] as Map).game.id; + final gameId = GameFullEvent.fromJson(json['d'] as Map).game.id; final app = await makeTestProviderScopeApp( tester, - home: GameScreen( - initialGameId: GameFullId('${gameId.value}test'), - ), + home: GameScreen(initialGameId: GameFullId('${gameId.value}test')), overrides: [ lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)), webSocketChannelFactoryProvider.overrideWith((ref) { diff --git a/test/view/home/home_tab_screen_test.dart b/test/view/home/home_tab_screen_test.dart index aa9a6a7ad2..0546253d87 100644 --- a/test/view/home/home_tab_screen_test.dart +++ b/test/view/home/home_tab_screen_test.dart @@ -21,10 +21,7 @@ import '../../test_provider_scope.dart'; void main() { group('Home online', () { testWidgets('shows Play button', (tester) async { - final app = await makeTestProviderScope( - tester, - child: const Application(), - ); + final app = await makeTestProviderScope(tester, child: const Application()); await tester.pumpWidget(app); // wait for connectivity @@ -35,10 +32,7 @@ void main() { }); testWidgets('shows players button', (tester) async { - final app = await makeTestProviderScope( - tester, - child: const Application(), - ); + final app = await makeTestProviderScope(tester, child: const Application()); await tester.pumpWidget(app); // wait for connectivity @@ -84,10 +78,7 @@ void main() { }); testWidgets('shows quick pairing matrix', (tester) async { - final app = await makeTestProviderScope( - tester, - child: const Application(), - ); + final app = await makeTestProviderScope(tester, child: const Application()); await tester.pumpWidget(app); // wait for connectivity @@ -97,31 +88,24 @@ void main() { expect(find.byType(QuickGameMatrix), findsOneWidget); }); - testWidgets('no session, no stored game: shows welcome screen ', - (tester) async { - final app = await makeTestProviderScope( - tester, - child: const Application(), - ); + testWidgets('no session, no stored game: shows welcome screen ', (tester) async { + final app = await makeTestProviderScope(tester, child: const Application()); await tester.pumpWidget(app); // wait for connectivity expect(find.byType(CircularProgressIndicator), findsOneWidget); await tester.pumpAndSettle(); expect( - find.textContaining( - 'libre, no-ads, open source chess server.', - findRichText: true, - ), + find.textContaining('libre, no-ads, open source chess server.', findRichText: true), findsOneWidget, ); expect(find.text('Sign in'), findsOneWidget); expect(find.text('About Lichess...'), findsOneWidget); }); - testWidgets( - 'session, no played game: shows welcome screen but no sign in button', - (tester) async { + testWidgets('session, no played game: shows welcome screen but no sign in button', ( + tester, + ) async { int nbUserGamesRequests = 0; final mockClient = MockClient((request) async { if (request.url.path == '/api/games/user/testuser') { @@ -135,8 +119,7 @@ void main() { child: const Application(), userSession: fakeSession, overrides: [ - httpClientFactoryProvider - .overrideWith((ref) => FakeHttpClientFactory(() => mockClient)), + httpClientFactoryProvider.overrideWith((ref) => FakeHttpClientFactory(() => mockClient)), ], ); await tester.pumpWidget(app); @@ -146,26 +129,17 @@ void main() { expect(nbUserGamesRequests, 1); expect( - find.textContaining( - 'libre, no-ads, open source chess server.', - findRichText: true, - ), + find.textContaining('libre, no-ads, open source chess server.', findRichText: true), findsOneWidget, ); expect(find.text('About Lichess...'), findsOneWidget); }); - testWidgets('no session, with stored games: shows list of recent games', - (tester) async { - final app = await makeTestProviderScope( - tester, - child: const Application(), - ); + testWidgets('no session, with stored games: shows list of recent games', (tester) async { + final app = await makeTestProviderScope(tester, child: const Application()); await tester.pumpWidget(app); - final container = ProviderScope.containerOf( - tester.element(find.byType(Application)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(Application))); final storage = await container.read(gameStorageProvider.future); final games = generateArchivedGames(count: 3); for (final game in games) { @@ -181,8 +155,7 @@ void main() { expect(find.text('Anonymous'), findsNWidgets(3)); }); - testWidgets('session, with played games: shows recent games', - (tester) async { + testWidgets('session, with played games: shows recent games', (tester) async { int nbUserGamesRequests = 0; final mockClient = MockClient((request) async { if (request.url.path == '/api/games/user/testuser') { @@ -196,8 +169,7 @@ void main() { child: const Application(), userSession: fakeSession, overrides: [ - httpClientFactoryProvider - .overrideWith((ref) => FakeHttpClientFactory(() => mockClient)), + httpClientFactoryProvider.overrideWith((ref) => FakeHttpClientFactory(() => mockClient)), ], ); await tester.pumpWidget(app); @@ -215,10 +187,7 @@ void main() { group('Home offline', () { testWidgets('shows offline banner', (tester) async { - final app = await makeOfflineTestProviderScope( - tester, - child: const Application(), - ); + final app = await makeOfflineTestProviderScope(tester, child: const Application()); await tester.pumpWidget(app); // wait for connectivity @@ -229,10 +198,7 @@ void main() { }); testWidgets('shows Play button', (tester) async { - final app = await makeOfflineTestProviderScope( - tester, - child: const Application(), - ); + final app = await makeOfflineTestProviderScope(tester, child: const Application()); await tester.pumpWidget(app); @@ -244,10 +210,7 @@ void main() { }); testWidgets('shows disabled players button', (tester) async { - final app = await makeOfflineTestProviderScope( - tester, - child: const Application(), - ); + final app = await makeOfflineTestProviderScope(tester, child: const Application()); await tester.pumpWidget(app); @@ -268,39 +231,26 @@ void main() { ); }); - testWidgets('no session, no stored game: shows welcome screen ', - (tester) async { - final app = await makeTestProviderScope( - tester, - child: const Application(), - ); + testWidgets('no session, no stored game: shows welcome screen ', (tester) async { + final app = await makeTestProviderScope(tester, child: const Application()); await tester.pumpWidget(app); // wait for connectivity expect(find.byType(CircularProgressIndicator), findsOneWidget); await tester.pumpAndSettle(); expect( - find.textContaining( - 'libre, no-ads, open source chess server.', - findRichText: true, - ), + find.textContaining('libre, no-ads, open source chess server.', findRichText: true), findsOneWidget, ); expect(find.text('Sign in'), findsOneWidget); expect(find.text('About Lichess...'), findsOneWidget); }); - testWidgets('no session, with stored games: shows list of recent games', - (tester) async { - final app = await makeOfflineTestProviderScope( - tester, - child: const Application(), - ); + testWidgets('no session, with stored games: shows list of recent games', (tester) async { + final app = await makeOfflineTestProviderScope(tester, child: const Application()); await tester.pumpWidget(app); - final container = ProviderScope.containerOf( - tester.element(find.byType(Application)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(Application))); final storage = await container.read(gameStorageProvider.future); final games = generateArchivedGames(count: 3); for (final game in games) { @@ -316,8 +266,7 @@ void main() { expect(find.text('Anonymous'), findsNWidgets(3)); }); - testWidgets('session, with stored games: shows list of recent games', - (tester) async { + testWidgets('session, with stored games: shows list of recent games', (tester) async { final app = await makeOfflineTestProviderScope( tester, child: const Application(), @@ -325,9 +274,7 @@ void main() { ); await tester.pumpWidget(app); - final container = ProviderScope.containerOf( - tester.element(find.byType(Application)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(Application))); final storage = await container.read(gameStorageProvider.future); final games = generateArchivedGames(count: 3, username: 'testUser'); for (final game in games) { diff --git a/test/view/opening_explorer/opening_explorer_screen_test.dart b/test/view/opening_explorer/opening_explorer_screen_test.dart index 0ff43f2ff4..bfc9ca9015 100644 --- a/test/view/opening_explorer/opening_explorer_screen_test.dart +++ b/test/view/opening_explorer/opening_explorer_screen_test.dart @@ -41,179 +41,137 @@ void main() { const options = AnalysisOptions( orientation: Side.white, - standalone: ( - pgn: '', - isComputerAnalysisAllowed: false, - variant: Variant.standard, - ), + standalone: (pgn: '', isComputerAnalysisAllowed: false, variant: Variant.standard), ); const name = 'John'; - final user = LightUser( - id: UserId.fromUserName(name), - name: name, - ); + final user = LightUser(id: UserId.fromUserName(name), name: name); - final session = AuthSessionState( - user: user, - token: 'test-token', - ); + final session = AuthSessionState(user: user, token: 'test-token'); group('OpeningExplorerScreen', () { - testWidgets( - 'master opening explorer loads', - (WidgetTester tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const OpeningExplorerScreen(options: options), - overrides: [ - defaultClientProvider.overrideWithValue(mockClient), - ], - ); - await tester.pumpWidget(app); - // wait for analysis controller to load - expect(find.byType(CircularProgressIndicator), findsOneWidget); - await tester.pump(const Duration(milliseconds: 10)); + testWidgets('master opening explorer loads', (WidgetTester tester) async { + final app = await makeTestProviderScopeApp( + tester, + home: const OpeningExplorerScreen(options: options), + overrides: [defaultClientProvider.overrideWithValue(mockClient)], + ); + await tester.pumpWidget(app); + // wait for analysis controller to load + expect(find.byType(CircularProgressIndicator), findsOneWidget); + await tester.pump(const Duration(milliseconds: 10)); - // wait for opening explorer data to load (taking debounce delay into account) - await tester.pump(const Duration(milliseconds: 350)); + // wait for opening explorer data to load (taking debounce delay into account) + await tester.pump(const Duration(milliseconds: 350)); - final moves = [ - 'e4', - 'd4', - ]; - expect(find.byType(Table), findsOneWidget); - for (final move in moves) { - expect(find.widgetWithText(TableRowInkWell, move), findsOneWidget); - } + final moves = ['e4', 'd4']; + expect(find.byType(Table), findsOneWidget); + for (final move in moves) { + expect(find.widgetWithText(TableRowInkWell, move), findsOneWidget); + } - expect(find.widgetWithText(Container, 'Top games'), findsOneWidget); - expect(find.widgetWithText(Container, 'Recent games'), findsNothing); + expect(find.widgetWithText(Container, 'Top games'), findsOneWidget); + expect(find.widgetWithText(Container, 'Recent games'), findsNothing); - // TODO: make a custom scrollUntilVisible that works with the non-scrollable - // board widget + // TODO: make a custom scrollUntilVisible that works with the non-scrollable + // board widget - // await tester.scrollUntilVisible( - // find.text('Firouzja, A.'), - // 200, - // scrollable: explorerViewFinder, - // ); + // await tester.scrollUntilVisible( + // find.text('Firouzja, A.'), + // 200, + // scrollable: explorerViewFinder, + // ); - // expect( - // find.byType(OpeningExplorerGameTile), - // findsNWidgets(2), - // ); - }, - variant: kPlatformVariant, - ); + // expect( + // find.byType(OpeningExplorerGameTile), + // findsNWidgets(2), + // ); + }, variant: kPlatformVariant); - testWidgets( - 'lichess opening explorer loads', - (WidgetTester tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const OpeningExplorerScreen(options: options), - overrides: [ - defaultClientProvider.overrideWithValue(mockClient), - ], - defaultPreferences: { - SessionPreferencesStorage.key( - PrefCategory.openingExplorer.storageKey, - null, - ): jsonEncode( - OpeningExplorerPrefs.defaults() - .copyWith(db: OpeningDatabase.lichess) - .toJson(), - ), - }, - ); - await tester.pumpWidget(app); - // wait for analysis controller to load - expect(find.byType(CircularProgressIndicator), findsOneWidget); - await tester.pump(const Duration(milliseconds: 10)); + testWidgets('lichess opening explorer loads', (WidgetTester tester) async { + final app = await makeTestProviderScopeApp( + tester, + home: const OpeningExplorerScreen(options: options), + overrides: [defaultClientProvider.overrideWithValue(mockClient)], + defaultPreferences: { + SessionPreferencesStorage.key(PrefCategory.openingExplorer.storageKey, null): jsonEncode( + OpeningExplorerPrefs.defaults().copyWith(db: OpeningDatabase.lichess).toJson(), + ), + }, + ); + await tester.pumpWidget(app); + // wait for analysis controller to load + expect(find.byType(CircularProgressIndicator), findsOneWidget); + await tester.pump(const Duration(milliseconds: 10)); - // wait for opening explorer data to load (taking debounce delay into account) - await tester.pump(const Duration(milliseconds: 350)); + // wait for opening explorer data to load (taking debounce delay into account) + await tester.pump(const Duration(milliseconds: 350)); - final moves = [ - 'd4', - ]; - expect(find.byType(Table), findsOneWidget); - for (final move in moves) { - expect(find.widgetWithText(TableRowInkWell, move), findsOneWidget); - } + final moves = ['d4']; + expect(find.byType(Table), findsOneWidget); + for (final move in moves) { + expect(find.widgetWithText(TableRowInkWell, move), findsOneWidget); + } - expect(find.widgetWithText(Container, 'Top games'), findsNothing); - expect(find.widgetWithText(Container, 'Recent games'), findsOneWidget); + expect(find.widgetWithText(Container, 'Top games'), findsNothing); + expect(find.widgetWithText(Container, 'Recent games'), findsOneWidget); - // await tester.scrollUntilVisible( - // find.byType(OpeningExplorerGameTile), - // 200, - // scrollable: explorerViewFinder, - // ); + // await tester.scrollUntilVisible( + // find.byType(OpeningExplorerGameTile), + // 200, + // scrollable: explorerViewFinder, + // ); - // expect( - // find.byType(OpeningExplorerGameTile), - // findsOneWidget, - // ); - }, - variant: kPlatformVariant, - ); + // expect( + // find.byType(OpeningExplorerGameTile), + // findsOneWidget, + // ); + }, variant: kPlatformVariant); - testWidgets( - 'player opening explorer loads', - (WidgetTester tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const OpeningExplorerScreen(options: options), - overrides: [ - defaultClientProvider.overrideWithValue(mockClient), - ], - userSession: session, - defaultPreferences: { - SessionPreferencesStorage.key( - PrefCategory.openingExplorer.storageKey, - session, - ): jsonEncode( - OpeningExplorerPrefs.defaults(user: user) - .copyWith(db: OpeningDatabase.player) - .toJson(), - ), - }, - ); - await tester.pumpWidget(app); - // wait for analysis controller to load - expect(find.byType(CircularProgressIndicator), findsOneWidget); - await tester.pump(const Duration(milliseconds: 10)); + testWidgets('player opening explorer loads', (WidgetTester tester) async { + final app = await makeTestProviderScopeApp( + tester, + home: const OpeningExplorerScreen(options: options), + overrides: [defaultClientProvider.overrideWithValue(mockClient)], + userSession: session, + defaultPreferences: { + SessionPreferencesStorage.key( + PrefCategory.openingExplorer.storageKey, + session, + ): jsonEncode( + OpeningExplorerPrefs.defaults(user: user).copyWith(db: OpeningDatabase.player).toJson(), + ), + }, + ); + await tester.pumpWidget(app); + // wait for analysis controller to load + expect(find.byType(CircularProgressIndicator), findsOneWidget); + await tester.pump(const Duration(milliseconds: 10)); - // wait for opening explorer data to load (taking debounce delay into account) - await tester.pump(const Duration(milliseconds: 350)); + // wait for opening explorer data to load (taking debounce delay into account) + await tester.pump(const Duration(milliseconds: 350)); - final moves = [ - 'c4', - ]; - expect(find.byType(Table), findsOneWidget); - for (final move in moves) { - expect(find.widgetWithText(TableRowInkWell, move), findsOneWidget); - } + final moves = ['c4']; + expect(find.byType(Table), findsOneWidget); + for (final move in moves) { + expect(find.widgetWithText(TableRowInkWell, move), findsOneWidget); + } - expect(find.widgetWithText(Container, 'Top games'), findsNothing); - expect(find.widgetWithText(Container, 'Recent games'), findsOneWidget); + expect(find.widgetWithText(Container, 'Top games'), findsNothing); + expect(find.widgetWithText(Container, 'Recent games'), findsOneWidget); - // await tester.scrollUntilVisible( - // find.byType(OpeningExplorerGameTile), - // 200, - // scrollable: explorerViewFinder, - // ); + // await tester.scrollUntilVisible( + // find.byType(OpeningExplorerGameTile), + // 200, + // scrollable: explorerViewFinder, + // ); - // expect( - // find.byType(OpeningExplorerGameTile), - // findsOneWidget, - // ); - }, - variant: kPlatformVariant, - ); + // expect( + // find.byType(OpeningExplorerGameTile), + // findsOneWidget, + // ); + }, variant: kPlatformVariant); }); } diff --git a/test/view/over_the_board/over_the_board_screen_test.dart b/test/view/over_the_board/over_the_board_screen_test.dart index 720ccfba09..206f017347 100644 --- a/test/view/over_the_board/over_the_board_screen_test.dart +++ b/test/view/over_the_board/over_the_board_screen_test.dart @@ -17,10 +17,7 @@ import '../../test_provider_scope.dart'; void main() { group('Playing over the board (offline)', () { testWidgets('Checkmate and Rematch', (tester) async { - final boardRect = await initOverTheBoardGame( - tester, - const TimeIncrement(60, 5), - ); + final boardRect = await initOverTheBoardGame(tester, const TimeIncrement(60, 5)); // Default orientation is white at the bottom expect( @@ -40,28 +37,20 @@ void main() { await tester.tap(find.text('Rematch')); await tester.pumpAndSettle(); - final container = ProviderScope.containerOf( - tester.element(find.byType(Chessboard)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(Chessboard))); final gameState = container.read(overTheBoardGameControllerProvider); expect(gameState.game.steps.length, 1); expect(gameState.game.steps.first.position, Chess.initial); // Rematch should flip orientation - expect( - tester.getTopRight(find.byKey(const ValueKey('a1-whiterook'))), - boardRect.topRight, - ); + expect(tester.getTopRight(find.byKey(const ValueKey('a1-whiterook'))), boardRect.topRight); expect(activeClock(tester), null); }); testWidgets('Game ends when out of time', (tester) async { const time = Duration(seconds: 1); - await initOverTheBoardGame( - tester, - TimeIncrement(time.inSeconds, 0), - ); + await initOverTheBoardGame(tester, TimeIncrement(time.inSeconds, 0)); await playMove(tester, 'e2', 'e4'); await playMove(tester, 'e7', 'e5'); @@ -81,10 +70,7 @@ void main() { testWidgets('Pausing the clock', (tester) async { const time = Duration(seconds: 10); - await initOverTheBoardGame( - tester, - TimeIncrement(time.inSeconds, 0), - ); + await initOverTheBoardGame(tester, TimeIncrement(time.inSeconds, 0)); await playMove(tester, 'e2', 'e4'); await playMove(tester, 'e7', 'e5'); @@ -116,10 +102,7 @@ void main() { testWidgets('Go back and Forward', (tester) async { const time = Duration(seconds: 10); - await initOverTheBoardGame( - tester, - TimeIncrement(time.inSeconds, 0), - ); + await initOverTheBoardGame(tester, TimeIncrement(time.inSeconds, 0)); await playMove(tester, 'e2', 'e4'); await playMove(tester, 'e7', 'e5'); @@ -155,10 +138,7 @@ void main() { }); testWidgets('No clock if time is infinite', (tester) async { - await initOverTheBoardGame( - tester, - const TimeIncrement(0, 0), - ); + await initOverTheBoardGame(tester, const TimeIncrement(0, 0)); expect(find.byType(Clock), findsNothing); }); @@ -166,10 +146,7 @@ void main() { testWidgets('Clock logic', (tester) async { const time = Duration(minutes: 5); - await initOverTheBoardGame( - tester, - TimeIncrement(time.inSeconds, 3), - ); + await initOverTheBoardGame(tester, TimeIncrement(time.inSeconds, 3)); expect(activeClock(tester), null); @@ -199,23 +176,15 @@ void main() { }); } -Future initOverTheBoardGame( - WidgetTester tester, - TimeIncrement timeIncrement, -) async { - final app = await makeTestProviderScopeApp( - tester, - home: const OverTheBoardScreen(), - ); +Future initOverTheBoardGame(WidgetTester tester, TimeIncrement timeIncrement) async { + final app = await makeTestProviderScopeApp(tester, home: const OverTheBoardScreen()); await tester.pumpWidget(app); await tester.pumpAndSettle(); await tester.tap(find.text('Play')); await tester.pumpAndSettle(); - final container = ProviderScope.containerOf( - tester.element(find.byType(Chessboard)), - ); + final container = ProviderScope.containerOf(tester.element(find.byType(Chessboard))); container.read(overTheBoardClockProvider.notifier).setupClock(timeIncrement); await tester.pumpAndSettle(); @@ -241,16 +210,12 @@ Side? activeClock(WidgetTester tester, {Side orientation = Side.white}) { Clock findWhiteClock(WidgetTester tester, {Side orientation = Side.white}) { return tester.widget( - find.byKey( - ValueKey(orientation == Side.white ? 'bottomClock' : 'topClock'), - ), + find.byKey(ValueKey(orientation == Side.white ? 'bottomClock' : 'topClock')), ); } Clock findBlackClock(WidgetTester tester, {Side orientation = Side.white}) { return tester.widget( - find.byKey( - ValueKey(orientation == Side.white ? 'topClock' : 'bottomClock'), - ), + find.byKey(ValueKey(orientation == Side.white ? 'topClock' : 'bottomClock')), ); } diff --git a/test/view/puzzle/example_data.dart b/test/view/puzzle/example_data.dart index 8a7b6a0b8c..9bf19cde6b 100644 --- a/test/view/puzzle/example_data.dart +++ b/test/view/puzzle/example_data.dart @@ -11,21 +11,8 @@ final puzzle = Puzzle( initialPly: 40, plays: 68176, rating: 1984, - solution: IList(const [ - 'h4h2', - 'h1h2', - 'e5f3', - 'h2h3', - 'b4h4', - ]), - themes: ISet(const [ - 'middlegame', - 'attraction', - 'long', - 'mateIn3', - 'sacrifice', - 'doubleCheck', - ]), + solution: IList(const ['h4h2', 'h1h2', 'e5f3', 'h2h3', 'b4h4']), + themes: ISet(const ['middlegame', 'attraction', 'long', 'mateIn3', 'sacrifice', 'doubleCheck']), ), game: const PuzzleGame( rated: true, @@ -33,23 +20,12 @@ final puzzle = Puzzle( perf: Perf.blitz, pgn: 'e4 c5 Nf3 e6 c4 Nc6 d4 cxd4 Nxd4 Bc5 Nxc6 bxc6 Be2 Ne7 O-O Ng6 Nc3 Rb8 Kh1 Bb7 f4 d5 f5 Ne5 fxe6 fxe6 cxd5 cxd5 exd5 Bxd5 Qa4+ Bc6 Qf4 Bd6 Ne4 Bxe4 Qxe4 Rb4 Qe3 Qh4 Qxa7', - black: PuzzleGamePlayer( - side: Side.black, - name: 'CAMBIADOR', - ), - white: PuzzleGamePlayer( - side: Side.white, - name: 'arroyoM10', - ), + black: PuzzleGamePlayer(side: Side.black, name: 'CAMBIADOR'), + white: PuzzleGamePlayer(side: Side.white, name: 'arroyoM10'), ), ); -final batch = PuzzleBatch( - solved: IList(const []), - unsolved: IList([ - puzzle, - ]), -); +final batch = PuzzleBatch(solved: IList(const []), unsolved: IList([puzzle])); final puzzle2 = Puzzle( puzzle: PuzzleData( @@ -58,28 +34,14 @@ final puzzle2 = Puzzle( plays: 23890, initialPly: 88, solution: IList(const ['g4h4', 'h8h4', 'b4h4']), - themes: ISet(const { - 'endgame', - 'short', - 'crushing', - 'fork', - 'queenRookEndgame', - }), + themes: ISet(const {'endgame', 'short', 'crushing', 'fork', 'queenRookEndgame'}), ), game: const PuzzleGame( id: GameId('w32JTzEf'), perf: Perf.blitz, rated: true, - white: PuzzleGamePlayer( - side: Side.white, - name: 'Li', - title: null, - ), - black: PuzzleGamePlayer( - side: Side.black, - name: 'Gabriela', - title: null, - ), + white: PuzzleGamePlayer(side: Side.white, name: 'Li', title: null), + black: PuzzleGamePlayer(side: Side.black, name: 'Gabriela', title: null), pgn: 'e4 e5 Nf3 Nc6 Bb5 a6 Ba4 b5 Bb3 Nf6 c3 Nxe4 d4 exd4 cxd4 Qe7 O-O Qd8 Bd5 Nf6 Bb3 Bd6 Nc3 O-O Bg5 h6 Bh4 g5 Nxg5 hxg5 Bxg5 Kg7 Ne4 Be7 Bxf6+ Bxf6 Qg4+ Kh8 Qh5+ Kg8 Qg6+ Kh8 Qxf6+ Qxf6 Nxf6 Nxd4 Rfd1 Ne2+ Kh1 d6 Rd5 Kg7 Nh5+ Kh6 Rad1 Be6 R5d2 Bxb3 axb3 Kxh5 Rxe2 Rfe8 Red2 Re5 h3 Rae8 Kh2 Re2 Rd5+ Kg6 f4 Rxb2 R1d3 Ree2 Rg3+ Kf6 h4 Re4 Rg4 Rxb3 h5 Rbb4 h6 Rxf4 h7 Rxg4 h8=Q+ Ke7 Rd3', ), diff --git a/test/view/puzzle/puzzle_history_screen_test.dart b/test/view/puzzle/puzzle_history_screen_test.dart index caa034b73d..2f93dcaf4a 100644 --- a/test/view/puzzle/puzzle_history_screen_test.dart +++ b/test/view/puzzle/puzzle_history_screen_test.dart @@ -20,45 +20,36 @@ void main() { }); MockClient makeClient(int totalNumberOfPuzzles) => MockClient((request) { - if (request.url.path == '/api/puzzle/activity') { - final query = request.url.queryParameters; - final max = int.parse(query['max']!); - final beforeDateParam = query['before']; - final beforeDate = beforeDateParam != null + if (request.url.path == '/api/puzzle/activity') { + final query = request.url.queryParameters; + final max = int.parse(query['max']!); + final beforeDateParam = query['before']; + final beforeDate = + beforeDateParam != null ? DateTime.fromMillisecondsSinceEpoch(int.parse(beforeDateParam)) : null; - final totalAlreadyRequested = - mockActivityRequestsCount.values.fold(0, (p, e) => p + e); - - if (totalAlreadyRequested >= totalNumberOfPuzzles) { - return mockResponse('', 200); - } - - final key = - beforeDate != null ? DateFormat.yMd().format(beforeDate) : null; - - final nbPuzzles = math.min(max, totalNumberOfPuzzles); - mockActivityRequestsCount[key] = - (mockActivityRequestsCount[key] ?? 0) + nbPuzzles; - return mockResponse( - generateHistory(nbPuzzles, beforeDate), - 200, - ); - } else if (request.url.path == '/api/puzzle/batch/mix') { - return mockResponse(mockMixBatchResponse, 200); - } else if (request.url.path.startsWith('/api/puzzle')) { - return mockResponse( - ''' + final totalAlreadyRequested = mockActivityRequestsCount.values.fold(0, (p, e) => p + e); + + if (totalAlreadyRequested >= totalNumberOfPuzzles) { + return mockResponse('', 200); + } + + final key = beforeDate != null ? DateFormat.yMd().format(beforeDate) : null; + + final nbPuzzles = math.min(max, totalNumberOfPuzzles); + mockActivityRequestsCount[key] = (mockActivityRequestsCount[key] ?? 0) + nbPuzzles; + return mockResponse(generateHistory(nbPuzzles, beforeDate), 200); + } else if (request.url.path == '/api/puzzle/batch/mix') { + return mockResponse(mockMixBatchResponse, 200); + } else if (request.url.path.startsWith('/api/puzzle')) { + return mockResponse(''' {"game":{"id":"MNMYnEjm","perf":{"key":"classical","name":"Classical"},"rated":true,"players":[{"name":"Igor76","id":"igor76","color":"white","rating":2211},{"name":"dmitriy_duyun","id":"dmitriy_duyun","color":"black","rating":2180}],"pgn":"e4 c6 d4 d5 Nc3 g6 Nf3 Bg7 h3 dxe4 Nxe4 Nf6 Bd3 Nxe4 Bxe4 Nd7 O-O Nf6 Bd3 O-O Re1 Bf5 Bxf5 gxf5 c3 e6 Bg5 Qb6 Qc2 Rac8 Ne5 Qc7 Rad1 Nd7 Bf4 Nxe5 Bxe5 Bxe5 Rxe5 Rcd8 Qd2 Kh8 Rde1 Rg8 Qf4","clock":"20+15"},"puzzle":{"id":"0XqV2","rating":1929,"plays":93270,"solution":["f7f6","e5f5","c7g7","g2g3","e6f5"],"themes":["clearance","endgame","advantage","intermezzo","long"],"initialPly":44}} -''', - 200, - ); - } - return mockResponse('', 404); - }); - - testWidgets('Displays an initial list of puzzles', - (WidgetTester tester) async { +''', 200); + } + return mockResponse('', 404); + }); + + testWidgets('Displays an initial list of puzzles', (WidgetTester tester) async { final app = await makeTestProviderScopeApp( tester, home: const PuzzleHistoryScreen(), @@ -111,46 +102,30 @@ void main() { await tester.scrollUntilVisible( find.byWidgetPredicate( - (widget) => - widget is PuzzleHistoryBoard && widget.puzzle.id.value == 'Bnull20', + (widget) => widget is PuzzleHistoryBoard && widget.puzzle.id.value == 'Bnull20', description: 'last item of 1st page', ), 400, ); // next pages have 50 puzzles - expect( - mockActivityRequestsCount, - equals({ - null: 20, - '1/31/2024': 50, - }), - ); + expect(mockActivityRequestsCount, equals({null: 20, '1/31/2024': 50})); // by the time we've scrolled to the end the next puzzles are already here await tester.scrollUntilVisible( find.byWidgetPredicate( - (widget) => - widget is PuzzleHistoryBoard && widget.puzzle.id.value == 'B3150', + (widget) => widget is PuzzleHistoryBoard && widget.puzzle.id.value == 'B3150', description: 'last item of 2nd page', ), 1000, ); // one more page - expect( - mockActivityRequestsCount, - equals({ - null: 20, - '1/31/2024': 50, - '1/30/2024': 50, - }), - ); + expect(mockActivityRequestsCount, equals({null: 20, '1/31/2024': 50, '1/30/2024': 50})); await tester.scrollUntilVisible( find.byWidgetPredicate( - (widget) => - widget is PuzzleHistoryBoard && widget.puzzle.id.value == 'B3010', + (widget) => widget is PuzzleHistoryBoard && widget.puzzle.id.value == 'B3010', description: 'last item of 3rd page', ), 400, @@ -164,8 +139,7 @@ void main() { await tester.tap( find.byWidgetPredicate( - (widget) => - widget is PuzzleHistoryBoard && widget.puzzle.id.value == 'B3010', + (widget) => widget is PuzzleHistoryBoard && widget.puzzle.id.value == 'B3010', ), ); @@ -179,8 +153,7 @@ void main() { expect(find.byType(PuzzleHistoryScreen), findsOneWidget); expect( find.byWidgetPredicate( - (widget) => - widget is PuzzleHistoryBoard && widget.puzzle.id.value == 'B3010', + (widget) => widget is PuzzleHistoryBoard && widget.puzzle.id.value == 'B3010', ), findsOneWidget, ); diff --git a/test/view/puzzle/puzzle_screen_test.dart b/test/view/puzzle/puzzle_screen_test.dart index 8845f25da3..5789fcb318 100644 --- a/test/view/puzzle/puzzle_screen_test.dart +++ b/test/view/puzzle/puzzle_screen_test.dart @@ -26,12 +26,7 @@ class MockPuzzleStorage extends Mock implements PuzzleStorage {} void main() { setUpAll(() { - registerFallbackValue( - PuzzleBatch( - solved: IList(const []), - unsolved: IList([puzzle]), - ), - ); + registerFallbackValue(PuzzleBatch(solved: IList(const []), unsolved: IList([puzzle]))); registerFallbackValue(puzzle); }); @@ -39,74 +34,70 @@ void main() { final mockHistoryStorage = MockPuzzleStorage(); group('PuzzleScreen', () { - testWidgets( - 'meets accessibility guidelines', - variant: kPlatformVariant, - (WidgetTester tester) async { - final SemanticsHandle handle = tester.ensureSemantics(); + testWidgets('meets accessibility guidelines', variant: kPlatformVariant, ( + WidgetTester tester, + ) async { + final SemanticsHandle handle = tester.ensureSemantics(); - final app = await makeTestProviderScopeApp( - tester, - home: PuzzleScreen( - angle: const PuzzleTheme(PuzzleThemeKey.mix), - puzzleId: puzzle.puzzle.id, - ), - overrides: [ - puzzleBatchStorageProvider.overrideWith((ref) => mockBatchStorage), - puzzleStorageProvider.overrideWith((ref) => mockHistoryStorage), - ], - ); + final app = await makeTestProviderScopeApp( + tester, + home: PuzzleScreen( + angle: const PuzzleTheme(PuzzleThemeKey.mix), + puzzleId: puzzle.puzzle.id, + ), + overrides: [ + puzzleBatchStorageProvider.overrideWith((ref) => mockBatchStorage), + puzzleStorageProvider.overrideWith((ref) => mockHistoryStorage), + ], + ); - when(() => mockHistoryStorage.fetch(puzzleId: puzzle.puzzle.id)) - .thenAnswer((_) async => puzzle); + when( + () => mockHistoryStorage.fetch(puzzleId: puzzle.puzzle.id), + ).thenAnswer((_) async => puzzle); - await tester.pumpWidget(app); + await tester.pumpWidget(app); - // wait for the puzzle to load - await tester.pump(const Duration(milliseconds: 200)); + // wait for the puzzle to load + await tester.pump(const Duration(milliseconds: 200)); - await meetsTapTargetGuideline(tester); + await meetsTapTargetGuideline(tester); - await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); - handle.dispose(); - }, - ); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + handle.dispose(); + }); - testWidgets( - 'Loads puzzle directly by passing a puzzleId', - variant: kPlatformVariant, - (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: PuzzleScreen( - angle: const PuzzleTheme(PuzzleThemeKey.mix), - puzzleId: puzzle.puzzle.id, - ), - overrides: [ - puzzleBatchStorageProvider.overrideWith((ref) => mockBatchStorage), - puzzleStorageProvider.overrideWith((ref) => mockHistoryStorage), - ], - ); + testWidgets('Loads puzzle directly by passing a puzzleId', variant: kPlatformVariant, ( + tester, + ) async { + final app = await makeTestProviderScopeApp( + tester, + home: PuzzleScreen( + angle: const PuzzleTheme(PuzzleThemeKey.mix), + puzzleId: puzzle.puzzle.id, + ), + overrides: [ + puzzleBatchStorageProvider.overrideWith((ref) => mockBatchStorage), + puzzleStorageProvider.overrideWith((ref) => mockHistoryStorage), + ], + ); - when(() => mockHistoryStorage.fetch(puzzleId: puzzle.puzzle.id)) - .thenAnswer((_) async => puzzle); + when( + () => mockHistoryStorage.fetch(puzzleId: puzzle.puzzle.id), + ).thenAnswer((_) async => puzzle); - await tester.pumpWidget(app); + await tester.pumpWidget(app); - // wait for the puzzle to load - await tester.pump(const Duration(milliseconds: 200)); + // wait for the puzzle to load + await tester.pump(const Duration(milliseconds: 200)); - expect(find.byType(Chessboard), findsOneWidget); - expect(find.text('Your turn'), findsOneWidget); - }, - ); + expect(find.byType(Chessboard), findsOneWidget); + expect(find.text('Your turn'), findsOneWidget); + }); testWidgets('Loads next puzzle when no puzzleId is passed', (tester) async { final app = await makeTestProviderScopeApp( tester, - home: const PuzzleScreen( - angle: PuzzleTheme(PuzzleThemeKey.mix), - ), + home: const PuzzleScreen(angle: PuzzleTheme(PuzzleThemeKey.mix)), overrides: [ puzzleBatchStorageProvider.overrideWith((ref) => mockBatchStorage), puzzleStorageProvider.overrideWith((ref) => mockHistoryStorage), @@ -114,14 +105,10 @@ void main() { ); when( - () => mockBatchStorage.fetch( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - ), + () => mockBatchStorage.fetch(userId: null, angle: const PuzzleTheme(PuzzleThemeKey.mix)), ).thenAnswer((_) async => batch); - when(() => mockHistoryStorage.save(puzzle: any(named: 'puzzle'))) - .thenAnswer((_) async {}); + when(() => mockHistoryStorage.save(puzzle: any(named: 'puzzle'))).thenAnswer((_) async {}); await tester.pumpWidget(app); @@ -135,121 +122,117 @@ void main() { expect(find.text('Your turn'), findsOneWidget); }); - testWidgets( - 'solves a puzzle and loads the next one', - variant: kPlatformVariant, - (tester) async { - final mockClient = MockClient((request) { - if (request.url.path == '/api/puzzle/batch/mix') { - return mockResponse(batchOf1, 200); - } - return mockResponse('', 404); - }); + testWidgets('solves a puzzle and loads the next one', variant: kPlatformVariant, ( + tester, + ) async { + final mockClient = MockClient((request) { + if (request.url.path == '/api/puzzle/batch/mix') { + return mockResponse(batchOf1, 200); + } + return mockResponse('', 404); + }); - when(() => mockHistoryStorage.fetch(puzzleId: puzzle2.puzzle.id)) - .thenAnswer((_) async => puzzle2); + when( + () => mockHistoryStorage.fetch(puzzleId: puzzle2.puzzle.id), + ).thenAnswer((_) async => puzzle2); - final app = await makeTestProviderScopeApp( - tester, - home: PuzzleScreen( - angle: const PuzzleTheme(PuzzleThemeKey.mix), - puzzleId: puzzle2.puzzle.id, - ), - overrides: [ - lichessClientProvider.overrideWith((ref) { - return LichessClient(mockClient, ref); - }), - puzzleBatchStorageProvider.overrideWith((ref) { - return mockBatchStorage; - }), - puzzleStorageProvider.overrideWith((ref) => mockHistoryStorage), - ], - ); + final app = await makeTestProviderScopeApp( + tester, + home: PuzzleScreen( + angle: const PuzzleTheme(PuzzleThemeKey.mix), + puzzleId: puzzle2.puzzle.id, + ), + overrides: [ + lichessClientProvider.overrideWith((ref) { + return LichessClient(mockClient, ref); + }), + puzzleBatchStorageProvider.overrideWith((ref) { + return mockBatchStorage; + }), + puzzleStorageProvider.overrideWith((ref) => mockHistoryStorage), + ], + ); - Future saveDBReq() => mockBatchStorage.save( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - data: any(named: 'data'), - ); - when(saveDBReq).thenAnswer((_) async {}); - when( - () => mockBatchStorage.fetch( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - ), - ).thenAnswer((_) async => batch); + Future saveDBReq() => mockBatchStorage.save( + userId: null, + angle: const PuzzleTheme(PuzzleThemeKey.mix), + data: any(named: 'data'), + ); + when(saveDBReq).thenAnswer((_) async {}); + when( + () => mockBatchStorage.fetch(userId: null, angle: const PuzzleTheme(PuzzleThemeKey.mix)), + ).thenAnswer((_) async => batch); - when(() => mockHistoryStorage.save(puzzle: any(named: 'puzzle'))) - .thenAnswer((_) async {}); + when(() => mockHistoryStorage.save(puzzle: any(named: 'puzzle'))).thenAnswer((_) async {}); - await tester.pumpWidget(app); + await tester.pumpWidget(app); - // wait for the puzzle to load - await tester.pump(const Duration(milliseconds: 200)); + // wait for the puzzle to load + await tester.pump(const Duration(milliseconds: 200)); - expect(find.byType(Chessboard), findsOneWidget); - expect(find.text('Your turn'), findsOneWidget); + expect(find.byType(Chessboard), findsOneWidget); + expect(find.text('Your turn'), findsOneWidget); - // before the first move is played, puzzle is not interactable - expect(find.byKey(const Key('g4-blackrook')), findsOneWidget); - await tester.tap(find.byKey(const Key('g4-blackrook'))); - await tester.pump(); - expect(find.byKey(const Key('g4-selected')), findsNothing); + // before the first move is played, puzzle is not interactable + expect(find.byKey(const Key('g4-blackrook')), findsOneWidget); + await tester.tap(find.byKey(const Key('g4-blackrook'))); + await tester.pump(); + expect(find.byKey(const Key('g4-selected')), findsNothing); - const orientation = Side.black; + const orientation = Side.black; - // await for first move to be played - await tester.pump(const Duration(milliseconds: 1500)); + // await for first move to be played + await tester.pump(const Duration(milliseconds: 1500)); - // in play mode we don't see the continue button - expect(find.byIcon(CupertinoIcons.play_arrow_solid), findsNothing); - // in play mode we see the solution button - expect(find.byIcon(Icons.help), findsOneWidget); + // in play mode we don't see the continue button + expect(find.byIcon(CupertinoIcons.play_arrow_solid), findsNothing); + // in play mode we see the solution button + expect(find.byIcon(Icons.help), findsOneWidget); - expect(find.byKey(const Key('g4-blackrook')), findsOneWidget); - expect(find.byKey(const Key('h8-whitequeen')), findsOneWidget); + expect(find.byKey(const Key('g4-blackrook')), findsOneWidget); + expect(find.byKey(const Key('h8-whitequeen')), findsOneWidget); - await playMove(tester, 'g4', 'h4', orientation: orientation); + await playMove(tester, 'g4', 'h4', orientation: orientation); - expect(find.byKey(const Key('h4-blackrook')), findsOneWidget); - expect(find.text('Best move!'), findsOneWidget); + expect(find.byKey(const Key('h4-blackrook')), findsOneWidget); + expect(find.text('Best move!'), findsOneWidget); - // wait for line reply and move animation - await tester.pump(const Duration(milliseconds: 500)); - await tester.pumpAndSettle(); + // wait for line reply and move animation + await tester.pump(const Duration(milliseconds: 500)); + await tester.pumpAndSettle(); - expect(find.byKey(const Key('h4-whitequeen')), findsOneWidget); + expect(find.byKey(const Key('h4-whitequeen')), findsOneWidget); - await playMove(tester, 'b4', 'h4', orientation: orientation); + await playMove(tester, 'b4', 'h4', orientation: orientation); - expect(find.byKey(const Key('h4-blackrook')), findsOneWidget); - expect(find.text('Success!'), findsOneWidget); + expect(find.byKey(const Key('h4-blackrook')), findsOneWidget); + expect(find.text('Success!'), findsOneWidget); - // wait for move animation - await tester.pumpAndSettle(); + // wait for move animation + await tester.pumpAndSettle(); - // called once to save solution and once after fetching a new puzzle - verify(saveDBReq).called(2); + // called once to save solution and once after fetching a new puzzle + verify(saveDBReq).called(2); - expect(find.byIcon(CupertinoIcons.play_arrow_solid), findsOneWidget); - expect(find.byIcon(Icons.help), findsNothing); + expect(find.byIcon(CupertinoIcons.play_arrow_solid), findsOneWidget); + expect(find.byIcon(Icons.help), findsNothing); - await tester.tap(find.byIcon(CupertinoIcons.play_arrow_solid)); + await tester.tap(find.byIcon(CupertinoIcons.play_arrow_solid)); - // await for new puzzle load - await tester.pump(const Duration(milliseconds: 500)); + // await for new puzzle load + await tester.pump(const Duration(milliseconds: 500)); - expect(find.text('Success!'), findsNothing); - expect(find.text('Your turn'), findsOneWidget); + expect(find.text('Success!'), findsNothing); + expect(find.text('Your turn'), findsOneWidget); - // await for view solution timer - await tester.pump(const Duration(seconds: 4)); - }, - ); + // await for view solution timer + await tester.pump(const Duration(seconds: 4)); + }); for (final showRatings in [true, false]) { - testWidgets('fails a puzzle, (showRatings: $showRatings)', - variant: kPlatformVariant, (tester) async { + testWidgets('fails a puzzle, (showRatings: $showRatings)', variant: kPlatformVariant, ( + tester, + ) async { final mockClient = MockClient((request) { if (request.url.path == '/api/puzzle/batch/mix') { return mockResponse(batchOf1, 200); @@ -257,8 +240,9 @@ void main() { return mockResponse('', 404); }); - when(() => mockHistoryStorage.fetch(puzzleId: puzzle2.puzzle.id)) - .thenAnswer((_) async => puzzle2); + when( + () => mockHistoryStorage.fetch(puzzleId: puzzle2.puzzle.id), + ).thenAnswer((_) async => puzzle2); final app = await makeTestProviderScopeApp( tester, @@ -280,20 +264,16 @@ void main() { ], ); - when(() => mockHistoryStorage.save(puzzle: any(named: 'puzzle'))) - .thenAnswer((_) async {}); + when(() => mockHistoryStorage.save(puzzle: any(named: 'puzzle'))).thenAnswer((_) async {}); Future saveDBReq() => mockBatchStorage.save( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - data: any(named: 'data'), - ); + userId: null, + angle: const PuzzleTheme(PuzzleThemeKey.mix), + data: any(named: 'data'), + ); when(saveDBReq).thenAnswer((_) async {}); when( - () => mockBatchStorage.fetch( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - ), + () => mockBatchStorage.fetch(userId: null, angle: const PuzzleTheme(PuzzleThemeKey.mix)), ).thenAnswer((_) async => batch); await tester.pumpWidget(app); @@ -313,10 +293,7 @@ void main() { await playMove(tester, 'g4', 'f4', orientation: orientation); - expect( - find.text("That's not the move!"), - findsOneWidget, - ); + expect(find.text("That's not the move!"), findsOneWidget); // wait for move cancel and animation await tester.pump(const Duration(milliseconds: 500)); @@ -337,10 +314,7 @@ void main() { await playMove(tester, 'b4', 'h4', orientation: orientation); expect(find.byKey(const Key('h4-blackrook')), findsOneWidget); - expect( - find.text('Puzzle complete!'), - findsOneWidget, - ); + expect(find.text('Puzzle complete!'), findsOneWidget); final expectedPlayedXTimes = 'Played ${puzzle2.puzzle.plays.toString().localizeNumbers()} times.'; expect( @@ -360,94 +334,84 @@ void main() { }); } - testWidgets( - 'view solution', - variant: kPlatformVariant, - (tester) async { - final mockClient = MockClient((request) { - if (request.url.path == '/api/puzzle/batch/mix') { - return mockResponse(batchOf1, 200); - } - return mockResponse('', 404); - }); + testWidgets('view solution', variant: kPlatformVariant, (tester) async { + final mockClient = MockClient((request) { + if (request.url.path == '/api/puzzle/batch/mix') { + return mockResponse(batchOf1, 200); + } + return mockResponse('', 404); + }); - final app = await makeTestProviderScopeApp( - tester, - home: PuzzleScreen( - angle: const PuzzleTheme(PuzzleThemeKey.mix), - puzzleId: puzzle2.puzzle.id, - ), - overrides: [ - lichessClientProvider.overrideWith((ref) { - return LichessClient(mockClient, ref); - }), - puzzleBatchStorageProvider.overrideWith((ref) { - return mockBatchStorage; - }), - puzzleStorageProvider.overrideWith((ref) => mockHistoryStorage), - ], - ); + final app = await makeTestProviderScopeApp( + tester, + home: PuzzleScreen( + angle: const PuzzleTheme(PuzzleThemeKey.mix), + puzzleId: puzzle2.puzzle.id, + ), + overrides: [ + lichessClientProvider.overrideWith((ref) { + return LichessClient(mockClient, ref); + }), + puzzleBatchStorageProvider.overrideWith((ref) { + return mockBatchStorage; + }), + puzzleStorageProvider.overrideWith((ref) => mockHistoryStorage), + ], + ); - when(() => mockHistoryStorage.fetch(puzzleId: puzzle2.puzzle.id)) - .thenAnswer((_) async => puzzle2); + when( + () => mockHistoryStorage.fetch(puzzleId: puzzle2.puzzle.id), + ).thenAnswer((_) async => puzzle2); - when(() => mockHistoryStorage.save(puzzle: any(named: 'puzzle'))) - .thenAnswer((_) async {}); + when(() => mockHistoryStorage.save(puzzle: any(named: 'puzzle'))).thenAnswer((_) async {}); - Future saveDBReq() => mockBatchStorage.save( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - data: any(named: 'data'), - ); - when(saveDBReq).thenAnswer((_) async {}); - when( - () => mockBatchStorage.fetch( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - ), - ).thenAnswer((_) async => batch); + Future saveDBReq() => mockBatchStorage.save( + userId: null, + angle: const PuzzleTheme(PuzzleThemeKey.mix), + data: any(named: 'data'), + ); + when(saveDBReq).thenAnswer((_) async {}); + when( + () => mockBatchStorage.fetch(userId: null, angle: const PuzzleTheme(PuzzleThemeKey.mix)), + ).thenAnswer((_) async => batch); - await tester.pumpWidget(app); + await tester.pumpWidget(app); - // wait for the puzzle to load - await tester.pump(const Duration(milliseconds: 200)); + // wait for the puzzle to load + await tester.pump(const Duration(milliseconds: 200)); - expect(find.byType(Chessboard), findsOneWidget); - expect(find.text('Your turn'), findsOneWidget); + expect(find.byType(Chessboard), findsOneWidget); + expect(find.text('Your turn'), findsOneWidget); - // await for first move to be played and view solution button to appear - await tester.pump(const Duration(seconds: 5)); + // await for first move to be played and view solution button to appear + await tester.pump(const Duration(seconds: 5)); - expect(find.byKey(const Key('g4-blackrook')), findsOneWidget); + expect(find.byKey(const Key('g4-blackrook')), findsOneWidget); - expect(find.byIcon(Icons.help), findsOneWidget); - await tester.tap(find.byIcon(Icons.help)); + expect(find.byIcon(Icons.help), findsOneWidget); + await tester.tap(find.byIcon(Icons.help)); - // wait for solution replay animation to finish - await tester.pump(const Duration(seconds: 1)); - await tester.pumpAndSettle(); + // wait for solution replay animation to finish + await tester.pump(const Duration(seconds: 1)); + await tester.pumpAndSettle(); - expect(find.byKey(const Key('h4-blackrook')), findsOneWidget); - expect(find.byKey(const Key('h8-whitequeen')), findsOneWidget); - expect( - find.text('Puzzle complete!'), - findsOneWidget, - ); + expect(find.byKey(const Key('h4-blackrook')), findsOneWidget); + expect(find.byKey(const Key('h8-whitequeen')), findsOneWidget); + expect(find.text('Puzzle complete!'), findsOneWidget); - final nextMoveBtnEnabled = find.byWidgetPredicate( - (widget) => - widget is BottomBarButton && - widget.icon == CupertinoIcons.chevron_forward && - widget.enabled, - ); - expect(nextMoveBtnEnabled, findsOneWidget); + final nextMoveBtnEnabled = find.byWidgetPredicate( + (widget) => + widget is BottomBarButton && + widget.icon == CupertinoIcons.chevron_forward && + widget.enabled, + ); + expect(nextMoveBtnEnabled, findsOneWidget); - expect(find.byIcon(CupertinoIcons.play_arrow_solid), findsOneWidget); + expect(find.byIcon(CupertinoIcons.play_arrow_solid), findsOneWidget); - // called once to save solution and once after fetching a new puzzle - verify(saveDBReq).called(2); - }, - ); + // called once to save solution and once after fetching a new puzzle + verify(saveDBReq).called(2); + }); }); } diff --git a/test/view/puzzle/puzzle_tab_screen_test.dart b/test/view/puzzle/puzzle_tab_screen_test.dart index 051a41fd81..188a18c0db 100644 --- a/test/view/puzzle/puzzle_tab_screen_test.dart +++ b/test/view/puzzle/puzzle_tab_screen_test.dart @@ -35,12 +35,7 @@ class MockPuzzleBatchStorage extends Mock implements PuzzleBatchStorage {} void main() { setUpAll(() { - registerFallbackValue( - PuzzleBatch( - solved: IList(const []), - unsolved: IList([puzzle]), - ), - ); + registerFallbackValue(PuzzleBatch(solved: IList(const []), unsolved: IList([puzzle]))); }); final mockBatchStorage = MockPuzzleBatchStorage(); @@ -49,23 +44,14 @@ void main() { final SemanticsHandle handle = tester.ensureSemantics(); when( - () => mockBatchStorage.fetch( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - ), + () => mockBatchStorage.fetch(userId: null, angle: const PuzzleTheme(PuzzleThemeKey.mix)), ).thenAnswer((_) async => batch); - when( - () => mockBatchStorage.fetchAll( - userId: null, - ), - ).thenAnswer((_) async => IList(const [])); + when(() => mockBatchStorage.fetchAll(userId: null)).thenAnswer((_) async => IList(const [])); final app = await makeTestProviderScopeApp( tester, home: const PuzzleTabScreen(), - overrides: [ - puzzleBatchStorageProvider.overrideWith((ref) => mockBatchStorage), - ], + overrides: [puzzleBatchStorageProvider.overrideWith((ref) => mockBatchStorage)], ); await tester.pumpWidget(app); @@ -87,16 +73,9 @@ void main() { testWidgets('shows puzzle menu', (WidgetTester tester) async { when( - () => mockBatchStorage.fetch( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - ), + () => mockBatchStorage.fetch(userId: null, angle: const PuzzleTheme(PuzzleThemeKey.mix)), ).thenAnswer((_) async => batch); - when( - () => mockBatchStorage.fetchAll( - userId: null, - ), - ).thenAnswer((_) async => IList(const [])); + when(() => mockBatchStorage.fetchAll(userId: null)).thenAnswer((_) async => IList(const [])); final app = await makeTestProviderScopeApp( tester, home: const PuzzleTabScreen(), @@ -120,16 +99,9 @@ void main() { testWidgets('shows daily puzzle', (WidgetTester tester) async { when( - () => mockBatchStorage.fetch( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - ), + () => mockBatchStorage.fetch(userId: null, angle: const PuzzleTheme(PuzzleThemeKey.mix)), ).thenAnswer((_) async => batch); - when( - () => mockBatchStorage.fetchAll( - userId: null, - ), - ).thenAnswer((_) async => IList(const [])); + when(() => mockBatchStorage.fetchAll(userId: null)).thenAnswer((_) async => IList(const [])); final app = await makeTestProviderScopeApp( tester, home: const PuzzleTabScreen(), @@ -150,31 +122,17 @@ void main() { await tester.pump(const Duration(milliseconds: 100)); expect(find.byType(DailyPuzzle), findsOneWidget); - expect( - find.widgetWithText(DailyPuzzle, 'Puzzle of the day'), - findsOneWidget, - ); - expect( - find.widgetWithText(DailyPuzzle, 'Played 93,270 times'), - findsOneWidget, - ); + expect(find.widgetWithText(DailyPuzzle, 'Puzzle of the day'), findsOneWidget); + expect(find.widgetWithText(DailyPuzzle, 'Played 93,270 times'), findsOneWidget); expect(find.widgetWithText(DailyPuzzle, 'Black to play'), findsOneWidget); }); group('tactical training preview', () { - testWidgets('shows first puzzle from unsolved batch', - (WidgetTester tester) async { + testWidgets('shows first puzzle from unsolved batch', (WidgetTester tester) async { when( - () => mockBatchStorage.fetch( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - ), + () => mockBatchStorage.fetch(userId: null, angle: const PuzzleTheme(PuzzleThemeKey.mix)), ).thenAnswer((_) async => batch); - when( - () => mockBatchStorage.fetchAll( - userId: null, - ), - ).thenAnswer((_) async => IList(const [])); + when(() => mockBatchStorage.fetchAll(userId: null)).thenAnswer((_) async => IList(const [])); final app = await makeTestProviderScopeApp( tester, @@ -196,31 +154,24 @@ void main() { await tester.pump(const Duration(milliseconds: 100)); expect(find.byType(PuzzleAnglePreview), findsOneWidget); - expect( - find.widgetWithText(PuzzleAnglePreview, 'Healthy mix'), - findsOneWidget, - ); - final chessboard = find - .descendant( - of: find.byType(PuzzleAnglePreview), - matching: find.byType(Chessboard), - ) - .evaluate() - .first - .widget as Chessboard; - - expect( - chessboard.fen, - equals('4k2r/Q5pp/3bp3/4n3/1r5q/8/PP2B1PP/R1B2R1K b k - 0 21'), - ); + expect(find.widgetWithText(PuzzleAnglePreview, 'Healthy mix'), findsOneWidget); + final chessboard = + find + .descendant( + of: find.byType(PuzzleAnglePreview), + matching: find.byType(Chessboard), + ) + .evaluate() + .first + .widget + as Chessboard; + + expect(chessboard.fen, equals('4k2r/Q5pp/3bp3/4n3/1r5q/8/PP2B1PP/R1B2R1K b k - 0 21')); }); testWidgets('shows saved puzzle batches', (WidgetTester tester) async { when( - () => mockBatchStorage.fetch( - userId: null, - angle: const PuzzleTheme(PuzzleThemeKey.mix), - ), + () => mockBatchStorage.fetch(userId: null, angle: const PuzzleTheme(PuzzleThemeKey.mix)), ).thenAnswer((_) async => batch); when( () => mockBatchStorage.fetch( @@ -229,16 +180,9 @@ void main() { ), ).thenAnswer((_) async => batch); when( - () => mockBatchStorage.fetch( - userId: null, - angle: const PuzzleOpening('A00'), - ), + () => mockBatchStorage.fetch(userId: null, angle: const PuzzleOpening('A00')), ).thenAnswer((_) async => batch); - when( - () => mockBatchStorage.fetchAll( - userId: null, - ), - ).thenAnswer( + when(() => mockBatchStorage.fetchAll(userId: null)).thenAnswer( (_) async => IList(const [ (PuzzleTheme(PuzzleThemeKey.advancedPawn), 50), (PuzzleOpening('A00'), 50), @@ -264,49 +208,27 @@ void main() { // wait for the puzzles to load await tester.pump(const Duration(milliseconds: 100)); - await tester.scrollUntilVisible( - find.widgetWithText(PuzzleAnglePreview, 'A00'), - 200, - ); + await tester.scrollUntilVisible(find.widgetWithText(PuzzleAnglePreview, 'A00'), 200); expect(find.byType(PuzzleAnglePreview), findsNWidgets(3)); - expect( - find.widgetWithText(PuzzleAnglePreview, 'Healthy mix'), - findsOneWidget, - ); - expect( - find.widgetWithText(PuzzleAnglePreview, 'Advanced pawn'), - findsOneWidget, - ); - expect( - find.widgetWithText(PuzzleAnglePreview, 'A00'), - findsOneWidget, - ); + expect(find.widgetWithText(PuzzleAnglePreview, 'Healthy mix'), findsOneWidget); + expect(find.widgetWithText(PuzzleAnglePreview, 'Advanced pawn'), findsOneWidget); + expect(find.widgetWithText(PuzzleAnglePreview, 'A00'), findsOneWidget); }); testWidgets('delete a saved puzzle batch', (WidgetTester tester) async { - final testDb = await openAppDatabase( - databaseFactoryFfiNoIsolate, - inMemoryDatabasePath, - ); + final testDb = await openAppDatabase(databaseFactoryFfiNoIsolate, inMemoryDatabasePath); for (final (angle, timestamp) in [ (const PuzzleTheme(PuzzleThemeKey.mix), '2021-01-01T00:00:00Z'), - ( - const PuzzleTheme(PuzzleThemeKey.advancedPawn), - '2021-01-01T00:00:00Z' - ), + (const PuzzleTheme(PuzzleThemeKey.advancedPawn), '2021-01-01T00:00:00Z'), (const PuzzleOpening('A00'), '2021-01-02T00:00:00Z'), ]) { - await testDb.insert( - 'puzzle_batchs', - { - 'userId': '**anon**', - 'angle': angle.key, - 'data': jsonEncode(onePuzzleBatch.toJson()), - 'lastModified': timestamp, - }, - conflictAlgorithm: ConflictAlgorithm.replace, - ); + await testDb.insert('puzzle_batchs', { + 'userId': '**anon**', + 'angle': angle.key, + 'data': jsonEncode(onePuzzleBatch.toJson()), + 'lastModified': timestamp, + }, conflictAlgorithm: ConflictAlgorithm.replace); } final app = await makeTestProviderScopeApp( @@ -347,19 +269,13 @@ void main() { ); await tester.pumpAndSettle(); - expect( - find.widgetWithText(SlidableAction, 'Delete'), - findsOneWidget, - ); + expect(find.widgetWithText(SlidableAction, 'Delete'), findsOneWidget); await tester.tap(find.widgetWithText(SlidableAction, 'Delete')); await tester.pumpAndSettle(); - expect( - find.widgetWithText(PuzzleAnglePreview, 'A00'), - findsNothing, - ); + expect(find.widgetWithText(PuzzleAnglePreview, 'A00'), findsNothing); }); }); } @@ -383,14 +299,8 @@ final onePuzzleBatch = PuzzleBatch( id: GameId('PrlkCqOv'), perf: Perf.blitz, rated: true, - white: PuzzleGamePlayer( - side: Side.white, - name: 'user1', - ), - black: PuzzleGamePlayer( - side: Side.black, - name: 'user2', - ), + white: PuzzleGamePlayer(side: Side.white, name: 'user1'), + black: PuzzleGamePlayer(side: Side.black, name: 'user2'), pgn: 'e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2', ), ), diff --git a/test/view/puzzle/storm_screen_test.dart b/test/view/puzzle/storm_screen_test.dart index 7f6e38996a..679eb5ddf1 100644 --- a/test/view/puzzle/storm_screen_test.dart +++ b/test/view/puzzle/storm_screen_test.dart @@ -23,137 +23,98 @@ final client = MockClient((request) { void main() { group('StormScreen', () { - testWidgets( - 'meets accessibility guidelines', - (tester) async { - final SemanticsHandle handle = tester.ensureSemantics(); - - final app = await makeTestProviderScopeApp( - tester, - home: const StormScreen(), - overrides: [ - stormProvider.overrideWith((ref) => mockStromRun), - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - ], - ); - - await tester.pumpWidget(app); - - await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); - handle.dispose(); - }, - variant: kPlatformVariant, - ); - - testWidgets( - 'Load puzzle and play white pieces', - (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const StormScreen(), - overrides: [ - stormProvider.overrideWith((ref) => mockStromRun), - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - ], - ); - - await tester.pumpWidget(app); - - expect(find.byType(Chessboard), findsOneWidget); - expect( - find.text('You play the white pieces in all puzzles'), - findsWidgets, - ); - }, - variant: kPlatformVariant, - ); - - testWidgets( - 'Play one puzzle', - (tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const StormScreen(), - overrides: [ - stormProvider.overrideWith((ref) => mockStromRun), - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - ], - ); - - await tester.pumpWidget(app); - - // before the first move is played, puzzle is not interactable - expect(find.byKey(const Key('h5-whiterook')), findsOneWidget); - await tester.tap(find.byKey(const Key('h5-whiterook'))); - await tester.pump(); - expect(find.byKey(const Key('h5-selected')), findsNothing); - - // wait for first move to be played - await tester.pump(const Duration(seconds: 1)); - - expect(find.byKey(const Key('g8-blackking')), findsOneWidget); - - await playMove( - tester, - 'h5', - 'h7', - orientation: Side.white, - ); - - await tester.pump(const Duration(milliseconds: 500)); - await tester.pumpAndSettle(); - expect(find.byKey(const Key('h7-whiterook')), findsOneWidget); - expect(find.byKey(const Key('d1-blackqueen')), findsOneWidget); - - await playMove( - tester, - 'e3', - 'g1', - orientation: Side.white, - ); - - await tester.pump(const Duration(milliseconds: 500)); - - // should have loaded next puzzle - expect(find.byKey(const Key('h6-blackking')), findsOneWidget); - }, - variant: kPlatformVariant, - ); + testWidgets('meets accessibility guidelines', (tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); - testWidgets('shows end run result', (tester) async { final app = await makeTestProviderScopeApp( tester, home: const StormScreen(), overrides: [ stormProvider.overrideWith((ref) => mockStromRun), - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), + lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)), ], ); await tester.pumpWidget(app); - // wait for first move to be played - await tester.pump(const Duration(seconds: 1)); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + handle.dispose(); + }, variant: kPlatformVariant); - await playMove( + testWidgets('Load puzzle and play white pieces', (tester) async { + final app = await makeTestProviderScopeApp( tester, - 'h5', - 'h7', - orientation: Side.white, + home: const StormScreen(), + overrides: [ + stormProvider.overrideWith((ref) => mockStromRun), + lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)), + ], ); + await tester.pumpWidget(app); + + expect(find.byType(Chessboard), findsOneWidget); + expect(find.text('You play the white pieces in all puzzles'), findsWidgets); + }, variant: kPlatformVariant); + + testWidgets('Play one puzzle', (tester) async { + final app = await makeTestProviderScopeApp( + tester, + home: const StormScreen(), + overrides: [ + stormProvider.overrideWith((ref) => mockStromRun), + lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)), + ], + ); + + await tester.pumpWidget(app); + + // before the first move is played, puzzle is not interactable + expect(find.byKey(const Key('h5-whiterook')), findsOneWidget); + await tester.tap(find.byKey(const Key('h5-whiterook'))); + await tester.pump(); + expect(find.byKey(const Key('h5-selected')), findsNothing); + + // wait for first move to be played + await tester.pump(const Duration(seconds: 1)); + + expect(find.byKey(const Key('g8-blackking')), findsOneWidget); + + await playMove(tester, 'h5', 'h7', orientation: Side.white); + + await tester.pump(const Duration(milliseconds: 500)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('h7-whiterook')), findsOneWidget); + expect(find.byKey(const Key('d1-blackqueen')), findsOneWidget); + + await playMove(tester, 'e3', 'g1', orientation: Side.white); + await tester.pump(const Duration(milliseconds: 500)); - await playMove( + + // should have loaded next puzzle + expect(find.byKey(const Key('h6-blackking')), findsOneWidget); + }, variant: kPlatformVariant); + + testWidgets('shows end run result', (tester) async { + final app = await makeTestProviderScopeApp( tester, - 'e3', - 'g1', - orientation: Side.white, + home: const StormScreen(), + overrides: [ + stormProvider.overrideWith((ref) => mockStromRun), + lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)), + ], ); + await tester.pumpWidget(app); + + // wait for first move to be played + await tester.pump(const Duration(seconds: 1)); + + await playMove(tester, 'h5', 'h7', orientation: Side.white); + + await tester.pump(const Duration(milliseconds: 500)); + await playMove(tester, 'e3', 'g1', orientation: Side.white); + await tester.pump(const Duration(milliseconds: 500)); // should have loaded next puzzle expect(find.byKey(const Key('h6-blackking')), findsOneWidget); @@ -170,8 +131,7 @@ void main() { home: const StormScreen(), overrides: [ stormProvider.overrideWith((ref) => mockStromRun), - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), + lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)), ], ); diff --git a/test/view/puzzle/streak_screen_test.dart b/test/view/puzzle/streak_screen_test.dart index efdd87166e..866b3659cb 100644 --- a/test/view/puzzle/streak_screen_test.dart +++ b/test/view/puzzle/streak_screen_test.dart @@ -13,8 +13,7 @@ import '../../test_provider_scope.dart'; final client = MockClient((request) { if (request.url.path == '/api/streak') { - return mockResponse( - ''' + return mockResponse(''' { "game": { "id": "Xndtxsoa", @@ -65,12 +64,9 @@ final client = MockClient((request) { }, "streak": "MptxK 4CZxz kcN3a 1I9Ly kOx90 eTrkO G0tpf iwTxQ tg2IU TovLC 0miTI Jpmkf 8VqjS XftoM 70UGG lm8O8 R4Y49 76Llk XZOyq QUgzo dACnQ qFjLp ytKo4 6JIj1 SYz3x kEkib dkvMp Dk0Ln Ok3qk zbRCc fQSVb vmDLx VJw06 3up01 X9aHm EicvD 5lhwD fTJE0 08LZy XAsVO TVB8s VCLTk KH6zc CaByR E2dUi JOxJg Agtzu KwbY9 Rmcf7 k9jGo 0zTgd 5YCx8 BtqDp DQdRO ytwPd sHqWB 1WunB Fovke mmMDN UNcwu isI02 3sIJB mnuzi 4aaRt Jvkvj UsXO2 kLfmz gsC1H TADGH a0Jz6 oUPR2 1IOBO 9PUdj haSH3 wn5by 22fL0 CR3Wu FaBtd DorJu unTls qeu0r xo40H DssQ9 D6s6S hkWx4 GF7s5 rzREu vhsbo s1haw j9ckI ekJnL TvcVB a7T4o 1olwh pydoy rGs3G k5ljZ gowEl UNXOV XkaUw 10lYO 6Ufqg Q45go KxGe3 vgwIt lqoaX nBtOq uAo3e jsbpu JLtdz TGUcX PobG5 ScDAL YPEfv o52sU FV0lM evQzq qAny0 dkDJi 0AUNz uzI6q kh13r Rubxa ecY6Q T9EL2 TmBka DPT5t qmzEf dyo0g MsGbE hPkmk 3wZBI 7kpeT 6EKGn kozHL Vnaiz 6DzDP HQ5RQ 7Ilyn 9n7Pz PwtXo kgMG2 J7gat gXcxs 4YVfC e8jGb m71Kb 9OrKY z530i" } - ''', - 200, - ); + ''', 200); } else if (request.url.path == '/api/puzzle/4CZxz') { - return mockResponse( - ''' + return mockResponse(''' { "game": { "id": "MQOxq7Jl", @@ -114,12 +110,9 @@ final client = MockClient((request) { "initialPly": 87 } } - ''', - 200, - ); + ''', 200); } else if (request.url.path == '/api/puzzle/kcN3a') { - return mockResponse( - ''' + return mockResponse(''' { "game": { "id": "bEuHKQSa", @@ -161,12 +154,9 @@ final client = MockClient((request) { "initialPly": 36 } } - ''', - 200, - ); + ''', 200); } else if (request.url.path == '/api/puzzle/1I9Ly') { - return mockResponse( - ''' + return mockResponse(''' { "game": { "id": "DTmg6BsX", @@ -210,58 +200,48 @@ final client = MockClient((request) { "initialPly": 22 } } - ''', - 200, - ); + ''', 200); } return mockResponse('', 404); }); void main() { group('StreakScreen', () { - testWidgets( - 'meets accessibility guidelines', - (tester) async { - final SemanticsHandle handle = tester.ensureSemantics(); + testWidgets('meets accessibility guidelines', (tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); - final app = await makeTestProviderScopeApp( - tester, - home: const StreakScreen(), - overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - ], - ); + final app = await makeTestProviderScopeApp( + tester, + home: const StreakScreen(), + overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))], + ); - await tester.pumpWidget(app); + await tester.pumpWidget(app); - await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); - handle.dispose(); - }, - variant: kPlatformVariant, - ); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + handle.dispose(); + }, variant: kPlatformVariant); testWidgets('Score is saved when exiting screen', (tester) async { final app = await makeTestProviderScopeApp( tester, home: Builder( - builder: (context) => PlatformScaffold( - appBar: const PlatformAppBar(title: Text('Test Streak Screen')), - body: FatButton( - semanticsLabel: 'Start Streak', - child: const Text('Start Streak'), - onPressed: () => pushPlatformRoute( - context, - rootNavigator: true, - builder: (context) => const StreakScreen(), + builder: + (context) => PlatformScaffold( + appBar: const PlatformAppBar(title: Text('Test Streak Screen')), + body: FatButton( + semanticsLabel: 'Start Streak', + child: const Text('Start Streak'), + onPressed: + () => pushPlatformRoute( + context, + rootNavigator: true, + builder: (context) => const StreakScreen(), + ), + ), ), - ), - ), ), - overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - ], + overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))], ); await tester.pumpWidget(app); diff --git a/test/view/settings/settings_tab_screen_test.dart b/test/view/settings/settings_tab_screen_test.dart index b907dc94a0..2c4fdb627e 100644 --- a/test/view/settings/settings_tab_screen_test.dart +++ b/test/view/settings/settings_tab_screen_test.dart @@ -21,83 +21,60 @@ final client = MockClient((request) { void main() { group('SettingsTabScreen', () { - testWidgets( - 'meets accessibility guidelines', - (WidgetTester tester) async { - final SemanticsHandle handle = tester.ensureSemantics(); - - final app = await makeTestProviderScopeApp( - tester, - home: const SettingsTabScreen(), - ); - - await tester.pumpWidget(app); - - await meetsTapTargetGuideline(tester); - - await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); - - await expectLater(tester, meetsGuideline(textContrastGuideline)); - handle.dispose(); - }, - variant: kPlatformVariant, - ); - - testWidgets( - "don't show signOut if no session", - (WidgetTester tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const SettingsTabScreen(), - ); - - await tester.pumpWidget(app); - - expect(find.text('Sign out'), findsNothing); - }, - variant: kPlatformVariant, - ); - - testWidgets( - 'signout', - (WidgetTester tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const SettingsTabScreen(), - userSession: fakeSession, - overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - getDbSizeInBytesProvider.overrideWith((_) => 1000), - ], - ); - - await tester.pumpWidget(app); - - expect(find.text('Sign out'), findsOneWidget); - - await tester.tap( - find.widgetWithText(PlatformListTile, 'Sign out'), - warnIfMissed: false, - ); - await tester.pumpAndSettle(); - - // confirm - if (debugDefaultTargetPlatformOverride == TargetPlatform.iOS) { - await tester - .tap(find.widgetWithText(CupertinoActionSheetAction, 'Sign out')); - } else { - await tester.tap(find.text('OK')); - } - await tester.pump(); - - expect(find.byType(CircularProgressIndicator), findsOneWidget); - // wait for sign out future - await tester.pump(const Duration(seconds: 1)); - - expect(find.text('Sign out'), findsNothing); - }, - variant: kPlatformVariant, - ); + testWidgets('meets accessibility guidelines', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); + + final app = await makeTestProviderScopeApp(tester, home: const SettingsTabScreen()); + + await tester.pumpWidget(app); + + await meetsTapTargetGuideline(tester); + + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + + await expectLater(tester, meetsGuideline(textContrastGuideline)); + handle.dispose(); + }, variant: kPlatformVariant); + + testWidgets("don't show signOut if no session", (WidgetTester tester) async { + final app = await makeTestProviderScopeApp(tester, home: const SettingsTabScreen()); + + await tester.pumpWidget(app); + + expect(find.text('Sign out'), findsNothing); + }, variant: kPlatformVariant); + + testWidgets('signout', (WidgetTester tester) async { + final app = await makeTestProviderScopeApp( + tester, + home: const SettingsTabScreen(), + userSession: fakeSession, + overrides: [ + lichessClientProvider.overrideWith((ref) => LichessClient(client, ref)), + getDbSizeInBytesProvider.overrideWith((_) => 1000), + ], + ); + + await tester.pumpWidget(app); + + expect(find.text('Sign out'), findsOneWidget); + + await tester.tap(find.widgetWithText(PlatformListTile, 'Sign out'), warnIfMissed: false); + await tester.pumpAndSettle(); + + // confirm + if (debugDefaultTargetPlatformOverride == TargetPlatform.iOS) { + await tester.tap(find.widgetWithText(CupertinoActionSheetAction, 'Sign out')); + } else { + await tester.tap(find.text('OK')); + } + await tester.pump(); + + expect(find.byType(CircularProgressIndicator), findsOneWidget); + // wait for sign out future + await tester.pump(const Duration(seconds: 1)); + + expect(find.text('Sign out'), findsNothing); + }, variant: kPlatformVariant); }); } diff --git a/test/view/study/study_list_screen_test.dart b/test/view/study/study_list_screen_test.dart index 0cf063d7a9..d0dbd37d22 100644 --- a/test/view/study/study_list_screen_test.dart +++ b/test/view/study/study_list_screen_test.dart @@ -55,13 +55,11 @@ void main() { final requestedUrls = []; final mockClient = MockClient((request) { requestedUrls.add(request.url.toString()); - if (request.url.path == '/study/all/hot' && - request.url.queryParameters['page'] == '1') { + if (request.url.path == '/study/all/hot' && request.url.queryParameters['page'] == '1') { return mockResponse(kStudyAllHotPage1Response, 200); } else if (request.url.path == '/study/search') { if (request.url.queryParameters['q'] == 'Magnus') { - return mockResponse( - ''' + return mockResponse(''' { "paginator": { "currentPage": 1, @@ -91,9 +89,7 @@ void main() { "nbPages": 1 } } - ''', - 200, - ); + ''', 200); } } return mockResponse('', 404); diff --git a/test/view/study/study_screen_test.dart b/test/view/study/study_screen_test.dart index 5073bfd240..11b3e63f84 100644 --- a/test/view/study/study_screen_test.dart +++ b/test/view/study/study_screen_test.dart @@ -51,8 +51,7 @@ Study makeStudy({ ownerId: null, features: (cloneable: false, chat: false, sticky: false), topics: const IList.empty(), - chapters: chapters ?? - IList([StudyChapterMeta(id: chapter.id, name: '', fen: null)]), + chapters: chapters ?? IList([StudyChapterMeta(id: chapter.id, name: '', fen: null)]), chapter: chapter, hints: hints, deviationComments: deviationComments, @@ -64,19 +63,14 @@ void main() { testWidgets('Displays PGN moves and comments', (WidgetTester tester) async { final mockRepository = MockStudyRepository(); - when(() => mockRepository.getStudy(id: testId)).thenAnswer( - (_) async => - (makeStudy(), '{root comment} 1. e4 {wow} e5 {such chess}'), - ); + when( + () => mockRepository.getStudy(id: testId), + ).thenAnswer((_) async => (makeStudy(), '{root comment} 1. e4 {wow} e5 {such chess}')); final app = await makeTestProviderScopeApp( tester, home: const StudyScreen(id: testId), - overrides: [ - studyRepositoryProvider.overrideWith( - (ref) => mockRepository, - ), - ], + overrides: [studyRepositoryProvider.overrideWith((ref) => mockRepository)], ); await tester.pumpWidget(app); @@ -96,16 +90,8 @@ void main() { final studyChapter1 = makeStudy( chapter: makeChapter(id: const StudyChapterId('1')), chapters: IList(const [ - StudyChapterMeta( - id: StudyChapterId('1'), - name: 'Chapter 1', - fen: null, - ), - StudyChapterMeta( - id: StudyChapterId('2'), - name: 'Chapter 2', - fen: null, - ), + StudyChapterMeta(id: StudyChapterId('1'), name: 'Chapter 1', fen: null), + StudyChapterMeta(id: StudyChapterId('2'), name: 'Chapter 2', fen: null), ]), ); @@ -113,34 +99,20 @@ void main() { chapter: makeChapter(id: const StudyChapterId('2')), ); - when(() => mockRepository.getStudy(id: testId)).thenAnswer( - (_) async => (studyChapter1, '{pgn 1}'), - ); when( - () => mockRepository.getStudy( - id: testId, - chapterId: const StudyChapterId('1'), - ), - ).thenAnswer( - (_) async => (studyChapter1, '{pgn 1}'), - ); + () => mockRepository.getStudy(id: testId), + ).thenAnswer((_) async => (studyChapter1, '{pgn 1}')); when( - () => mockRepository.getStudy( - id: testId, - chapterId: const StudyChapterId('2'), - ), - ).thenAnswer( - (_) async => (studyChapter2, '{pgn 2}'), - ); + () => mockRepository.getStudy(id: testId, chapterId: const StudyChapterId('1')), + ).thenAnswer((_) async => (studyChapter1, '{pgn 1}')); + when( + () => mockRepository.getStudy(id: testId, chapterId: const StudyChapterId('2')), + ).thenAnswer((_) async => (studyChapter2, '{pgn 2}')); final app = await makeTestProviderScopeApp( tester, home: const StudyScreen(id: testId), - overrides: [ - studyRepositoryProvider.overrideWith( - (ref) => mockRepository, - ), - ], + overrides: [studyRepositoryProvider.overrideWith((ref) => mockRepository)], ); await tester.pumpWidget(app); // Wait for study to load @@ -172,17 +144,11 @@ void main() { await tester.pumpAndSettle(); expect( - find.descendant( - of: find.byType(Scrollable), - matching: find.text('Chapter 1'), - ), + find.descendant(of: find.byType(Scrollable), matching: find.text('Chapter 1')), findsOneWidget, ); expect( - find.descendant( - of: find.byType(Scrollable), - matching: find.text('Chapter 2'), - ), + find.descendant(of: find.byType(Scrollable), matching: find.text('Chapter 2')), findsOneWidget, ); @@ -201,24 +167,15 @@ void main() { final mockRepository = MockStudyRepository(); when(() => mockRepository.getStudy(id: testId)).thenAnswer( (_) async => ( - makeStudy( - chapter: makeChapter( - id: const StudyChapterId('1'), - orientation: Side.black, - ), - ), - '' + makeStudy(chapter: makeChapter(id: const StudyChapterId('1'), orientation: Side.black)), + '', ), ); final app = await makeTestProviderScopeApp( tester, home: const StudyScreen(id: testId), - overrides: [ - studyRepositoryProvider.overrideWith( - (ref) => mockRepository, - ), - ], + overrides: [studyRepositoryProvider.overrideWith((ref) => mockRepository)], ); await tester.pumpWidget(app); // Wait for study to load @@ -265,18 +222,14 @@ void main() { { We begin our lecture with an 'easy but not easy' example. White to play and win. } 1. Nd5!! { Brilliant! You noticed that the queen on c4 was kinda smothered. } (1. Ne2? { Not much to say after ...Qc7. }) 1... exd5 2. Rc3 Qa4 3. Rg3! { A fork, threatening Rg7 & b3. } { [%csl Gg7][%cal Gg3g7,Gd4g7,Gb2b3] } (3. Rxc8?? { Uh-oh! After Rc8, b3, there is the counter-sac Rxc2, which is winning for black!! } 3... Raxc8 4. b3 Rxc2!! 5. Qxc2 Qxd4 \$19) 3... g6 4. b3 \$18 { ...and the queen is trapped. GGs. If this was too hard for you, don't worry, there will be easier examples. } * - ''' + ''', ), ); final app = await makeTestProviderScopeApp( tester, home: const StudyScreen(id: testId), - overrides: [ - studyRepositoryProvider.overrideWith( - (ref) => mockRepository, - ), - ], + overrides: [studyRepositoryProvider.overrideWith((ref) => mockRepository)], ); await tester.pumpWidget(app); // Wait for study to load @@ -288,9 +241,7 @@ void main() { expect(find.text(introText), findsOneWidget); expect( - find.text( - 'Brilliant! You noticed that the queen on c4 was kinda smothered.', - ), + find.text('Brilliant! You noticed that the queen on c4 was kinda smothered.'), findsNothing, ); @@ -323,9 +274,7 @@ void main() { await playMove(tester, 'c3', 'd5'); expect( - find.text( - 'Brilliant! You noticed that the queen on c4 was kinda smothered.', - ), + find.text('Brilliant! You noticed that the queen on c4 was kinda smothered.'), findsOneWidget, ); @@ -333,19 +282,14 @@ void main() { await tester.pump(const Duration(seconds: 1)); expect( - find.text( - 'Brilliant! You noticed that the queen on c4 was kinda smothered.', - ), + find.text('Brilliant! You noticed that the queen on c4 was kinda smothered.'), findsOneWidget, ); await tester.tap(find.byTooltip('Next')); await tester.pump(); // Wait for opponent move to be played - expect( - find.text('What would you play in this position?'), - findsOneWidget, - ); + expect(find.text('What would you play in this position?'), findsOneWidget); await playMove(tester, 'f3', 'c3'); expect(find.text('Good move'), findsOneWidget); @@ -353,10 +297,7 @@ void main() { // No explicit feedback, so opponent move should be played automatically after delay await tester.pump(const Duration(seconds: 1)); - expect( - find.text('What would you play in this position?'), - findsOneWidget, - ); + expect(find.text('What would you play in this position?'), findsOneWidget); await playMove(tester, 'c3', 'g3'); expect(find.text('A fork, threatening Rg7 & b3.'), findsOneWidget); @@ -364,10 +305,7 @@ void main() { await tester.tap(find.byTooltip('Next')); await tester.pump(); // Wait for opponent move to be played - expect( - find.text('What would you play in this position?'), - findsOneWidget, - ); + expect(find.text('What would you play in this position?'), findsOneWidget); await playMove(tester, 'b2', 'b3'); @@ -383,8 +321,7 @@ void main() { expect(find.byTooltip('Analysis board'), findsOneWidget); }); - testWidgets('Interactive study hints and deviation comments', - (WidgetTester tester) async { + testWidgets('Interactive study hints and deviation comments', (WidgetTester tester) async { final mockRepository = MockStudyRepository(); when(() => mockRepository.getStudy(id: testId)).thenAnswer( (_) async => ( @@ -394,31 +331,17 @@ void main() { orientation: Side.white, gamebook: true, ), - hints: [ - 'Hint 1', - null, - null, - null, - ].lock, - deviationComments: [ - null, - 'Shown if any move other than d4 is played', - null, - null, - ].lock, + hints: ['Hint 1', null, null, null].lock, + deviationComments: [null, 'Shown if any move other than d4 is played', null, null].lock, ), - '1. e4 (1. d4 {Shown if d4 is played}) e5 2. Nf3' + '1. e4 (1. d4 {Shown if d4 is played}) e5 2. Nf3', ), ); final app = await makeTestProviderScopeApp( tester, home: const StudyScreen(id: testId), - overrides: [ - studyRepositoryProvider.overrideWith( - (ref) => mockRepository, - ), - ], + overrides: [studyRepositoryProvider.overrideWith((ref) => mockRepository)], ); await tester.pumpWidget(app); // Wait for study to load @@ -433,10 +356,7 @@ void main() { expect(find.text('Get a hint'), findsNothing); await playMove(tester, 'e2', 'e3'); - expect( - find.text('Shown if any move other than d4 is played'), - findsOneWidget, - ); + expect(find.text('Shown if any move other than d4 is played'), findsOneWidget); await tester.tap(find.byTooltip('Retry')); await tester.pump(); // Wait for move to be taken back @@ -458,10 +378,7 @@ void main() { // Wait for wrong move to be taken back await tester.pump(const Duration(seconds: 1)); - expect( - find.text('What would you play in this position?'), - findsOneWidget, - ); + expect(find.text('What would you play in this position?'), findsOneWidget); expect(find.text("That's not the move!"), findsNothing); }); }); diff --git a/test/view/user/leaderboard_screen_test.dart b/test/view/user/leaderboard_screen_test.dart index 40575d6b6d..70e18efd3a 100644 --- a/test/view/user/leaderboard_screen_test.dart +++ b/test/view/user/leaderboard_screen_test.dart @@ -16,36 +16,29 @@ final client = MockClient((request) { void main() { group('LeaderboardScreen', () { - testWidgets( - 'meets accessibility guidelines', - (WidgetTester tester) async { - final SemanticsHandle handle = tester.ensureSemantics(); + testWidgets('meets accessibility guidelines', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); - final app = await makeTestProviderScopeApp( - tester, - overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - ], - home: const LeaderboardScreen(), - ); + final app = await makeTestProviderScopeApp( + tester, + overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))], + home: const LeaderboardScreen(), + ); - await tester.pumpWidget(app); + await tester.pumpWidget(app); - await tester.pump(const Duration(milliseconds: 200)); + await tester.pump(const Duration(milliseconds: 200)); - // TODO find why it fails on android - // await meetsTapTargetGuideline(tester); + // TODO find why it fails on android + // await meetsTapTargetGuideline(tester); - await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); - if (debugDefaultTargetPlatformOverride == TargetPlatform.android) { - await expectLater(tester, meetsGuideline(textContrastGuideline)); - } - handle.dispose(); - }, - variant: kPlatformVariant, - ); + if (debugDefaultTargetPlatformOverride == TargetPlatform.android) { + await expectLater(tester, meetsGuideline(textContrastGuideline)); + } + handle.dispose(); + }, variant: kPlatformVariant); }); } diff --git a/test/view/user/leaderboard_widget_test.dart b/test/view/user/leaderboard_widget_test.dart index a34e0ebd5d..17611b545a 100644 --- a/test/view/user/leaderboard_widget_test.dart +++ b/test/view/user/leaderboard_widget_test.dart @@ -17,47 +17,31 @@ final client = MockClient((request) { void main() { group('LeaderboardWidget', () { - testWidgets( - 'accessibility and basic info showing test', - (WidgetTester tester) async { - final SemanticsHandle handle = tester.ensureSemantics(); - final app = await makeTestProviderScopeApp( - tester, - home: Column(children: [LeaderboardWidget()]), - overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - ], - ); + testWidgets('accessibility and basic info showing test', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); + final app = await makeTestProviderScopeApp( + tester, + home: Column(children: [LeaderboardWidget()]), + overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))], + ); - await tester.pumpWidget(app); + await tester.pumpWidget(app); - await tester.pump(const Duration(milliseconds: 50)); + await tester.pump(const Duration(milliseconds: 50)); - for (final name in [ - 'Svetlana', - 'Marcel', - 'Anthony', - 'Patoulatchi', - 'Cerdan', - ]) { - expect( - find.widgetWithText(LeaderboardListTile, name), - findsOneWidget, - ); - } + for (final name in ['Svetlana', 'Marcel', 'Anthony', 'Patoulatchi', 'Cerdan']) { + expect(find.widgetWithText(LeaderboardListTile, name), findsOneWidget); + } - // await meetsTapTargetGuideline(tester); + // await meetsTapTargetGuideline(tester); - // await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + // await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); - // if (debugDefaultTargetPlatformOverride == TargetPlatform.android) { - // await expectLater(tester, meetsGuideline(textContrastGuideline)); - // } - handle.dispose(); - }, - variant: kPlatformVariant, - ); + // if (debugDefaultTargetPlatformOverride == TargetPlatform.android) { + // await expectLater(tester, meetsGuideline(textContrastGuideline)); + // } + handle.dispose(); + }, variant: kPlatformVariant); }); } diff --git a/test/view/user/perf_stats_screen_test.dart b/test/view/user/perf_stats_screen_test.dart index 33013947fd..95a8a02562 100644 --- a/test/view/user/perf_stats_screen_test.dart +++ b/test/view/user/perf_stats_screen_test.dart @@ -22,85 +22,68 @@ final client = MockClient((request) { void main() { group('PerfStatsScreen', () { - testWidgets( - 'meets accessibility guidelines', - (WidgetTester tester) async { - final SemanticsHandle handle = tester.ensureSemantics(); + testWidgets('meets accessibility guidelines', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); - final app = await makeTestProviderScopeApp( - tester, - home: PerfStatsScreen( - user: fakeUser, - perf: testPerf, - ), - overrides: [ - lichessClientProvider.overrideWith((ref) { - return LichessClient(client, ref); - }), - ], - ); + final app = await makeTestProviderScopeApp( + tester, + home: PerfStatsScreen(user: fakeUser, perf: testPerf), + overrides: [ + lichessClientProvider.overrideWith((ref) { + return LichessClient(client, ref); + }), + ], + ); - await tester.pumpWidget(app); + await tester.pumpWidget(app); - // wait for auth state and perf stats - await tester.pump(const Duration(milliseconds: 50)); + // wait for auth state and perf stats + await tester.pump(const Duration(milliseconds: 50)); - await meetsTapTargetGuideline(tester); - await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + await meetsTapTargetGuideline(tester); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); - if (debugDefaultTargetPlatformOverride == TargetPlatform.android) { - await expectLater(tester, meetsGuideline(textContrastGuideline)); - } - handle.dispose(); - }, - variant: kPlatformVariant, - ); + if (debugDefaultTargetPlatformOverride == TargetPlatform.android) { + await expectLater(tester, meetsGuideline(textContrastGuideline)); + } + handle.dispose(); + }, variant: kPlatformVariant); - testWidgets( - 'screen loads, required stats are shown', - (WidgetTester tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: PerfStatsScreen( - user: fakeUser, - perf: testPerf, - ), - overrides: [ - lichessClientProvider.overrideWith((ref) { - return LichessClient(client, ref); - }), - ], - ); + testWidgets('screen loads, required stats are shown', (WidgetTester tester) async { + final app = await makeTestProviderScopeApp( + tester, + home: PerfStatsScreen(user: fakeUser, perf: testPerf), + overrides: [ + lichessClientProvider.overrideWith((ref) { + return LichessClient(client, ref); + }), + ], + ); - await tester.pumpWidget(app); + await tester.pumpWidget(app); - // wait for auth state and perf stats - await tester.pump(const Duration(milliseconds: 50)); + // wait for auth state and perf stats + await tester.pump(const Duration(milliseconds: 50)); - final requiredStatsValues = [ - '50.24', // Deviation - '20', // Progression in last 12 games - '0', // Berserked games - '0', // Tournament games - '3', // Rated games - '2', // Won games - '2', // Lost games - '1', // Drawn games - '1', // Disconnections - ]; + final requiredStatsValues = [ + '50.24', // Deviation + '20', // Progression in last 12 games + '0', // Berserked games + '0', // Tournament games + '3', // Rated games + '2', // Won games + '2', // Lost games + '1', // Drawn games + '1', // Disconnections + ]; - // rating - expect(find.text('1500'), findsOneWidget); + // rating + expect(find.text('1500'), findsOneWidget); - for (final val in requiredStatsValues) { - expect( - find.widgetWithText(PlatformCard, val), - findsAtLeastNWidgets(1), - ); - } - }, - variant: kPlatformVariant, - ); + for (final val in requiredStatsValues) { + expect(find.widgetWithText(PlatformCard, val), findsAtLeastNWidgets(1)); + } + }, variant: kPlatformVariant); }); } diff --git a/test/view/user/search_screen_test.dart b/test/view/user/search_screen_test.dart index 93579a2efa..eefe7e3c89 100644 --- a/test/view/user/search_screen_test.dart +++ b/test/view/user/search_screen_test.dart @@ -22,83 +22,69 @@ final client = MockClient((request) { void main() { group('SearchScreen', () { - testWidgets( - 'should see search results', - (WidgetTester tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const SearchScreen(), - overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - ], - ); - - await tester.pumpWidget(app); - - final textFieldFinder = - debugDefaultTargetPlatformOverride == TargetPlatform.iOS - ? find.byType(CupertinoSearchTextField) - : find.byType(SearchBar); - - await tester.enterText(textFieldFinder, 'joh'); - - // await debouce call - await tester.pump(const Duration(milliseconds: 300)); - - expect(find.byType(CircularProgressIndicator), findsOneWidget); - - // await response - await tester.pumpAndSettle(const Duration(milliseconds: 100)); - - expect(find.byType(CircularProgressIndicator), findsNothing); - expect(find.text('Players with "joh"'), findsOneWidget); - expect(find.byType(UserListTile), findsNWidgets(2)); - expect(find.text('John Doe'), findsOneWidget); - expect(find.text('John Doe 2'), findsOneWidget); - - // await debouce call for saving search history - await tester.pump(const Duration(seconds: 2)); - }, - variant: kPlatformVariant, - ); - - testWidgets( - 'should see "no result" when search finds nothing', - (WidgetTester tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const SearchScreen(), - overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - ], - ); - - await tester.pumpWidget(app); - - final textFieldFinder = - debugDefaultTargetPlatformOverride == TargetPlatform.iOS - ? find.byType(CupertinoSearchTextField) - : find.byType(SearchBar); - - await tester.enterText(textFieldFinder, 'johnny'); - // await debouce call - await tester.pump(const Duration(milliseconds: 300)); - - expect(find.byType(CircularProgressIndicator), findsOneWidget); - - // await response - await tester.pumpAndSettle(const Duration(milliseconds: 100)); - - expect(find.text('Players with "johnny"'), findsNothing); - expect(find.text('No results'), findsOneWidget); - - // await debouce call for saving search history - await tester.pump(const Duration(seconds: 2)); - }, - variant: kPlatformVariant, - ); + testWidgets('should see search results', (WidgetTester tester) async { + final app = await makeTestProviderScopeApp( + tester, + home: const SearchScreen(), + overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))], + ); + + await tester.pumpWidget(app); + + final textFieldFinder = + debugDefaultTargetPlatformOverride == TargetPlatform.iOS + ? find.byType(CupertinoSearchTextField) + : find.byType(SearchBar); + + await tester.enterText(textFieldFinder, 'joh'); + + // await debouce call + await tester.pump(const Duration(milliseconds: 300)); + + expect(find.byType(CircularProgressIndicator), findsOneWidget); + + // await response + await tester.pumpAndSettle(const Duration(milliseconds: 100)); + + expect(find.byType(CircularProgressIndicator), findsNothing); + expect(find.text('Players with "joh"'), findsOneWidget); + expect(find.byType(UserListTile), findsNWidgets(2)); + expect(find.text('John Doe'), findsOneWidget); + expect(find.text('John Doe 2'), findsOneWidget); + + // await debouce call for saving search history + await tester.pump(const Duration(seconds: 2)); + }, variant: kPlatformVariant); + + testWidgets('should see "no result" when search finds nothing', (WidgetTester tester) async { + final app = await makeTestProviderScopeApp( + tester, + home: const SearchScreen(), + overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))], + ); + + await tester.pumpWidget(app); + + final textFieldFinder = + debugDefaultTargetPlatformOverride == TargetPlatform.iOS + ? find.byType(CupertinoSearchTextField) + : find.byType(SearchBar); + + await tester.enterText(textFieldFinder, 'johnny'); + // await debouce call + await tester.pump(const Duration(milliseconds: 300)); + + expect(find.byType(CircularProgressIndicator), findsOneWidget); + + // await response + await tester.pumpAndSettle(const Duration(milliseconds: 100)); + + expect(find.text('Players with "johnny"'), findsNothing); + expect(find.text('No results'), findsOneWidget); + + // await debouce call for saving search history + await tester.pump(const Duration(seconds: 2)); + }, variant: kPlatformVariant); }); } diff --git a/test/view/user/user_screen_test.dart b/test/view/user/user_screen_test.dart index 91648e6db8..fdc11214e4 100644 --- a/test/view/user/user_screen_test.dart +++ b/test/view/user/user_screen_test.dart @@ -15,8 +15,7 @@ final client = MockClient((request) { } else if (request.url.path == '/api/user/$testUserId') { return mockResponse(testUserResponse, 200); } else if (request.url.path == '/api/users/status') { - return mockResponse( - ''' + return mockResponse(''' [ { "id": "$testUserId", @@ -24,9 +23,7 @@ final client = MockClient((request) { "online": true } ] -''', - 200, - ); +''', 200); } else if (request.url.path == '/api/user/$testUserId/activity') { return mockResponse(userActivityResponse, 200); } @@ -35,36 +32,26 @@ final client = MockClient((request) { void main() { group('UserScreen', () { - testWidgets( - 'should see activity and recent games', - (WidgetTester tester) async { - final app = await makeTestProviderScopeApp( - tester, - home: const UserScreen(user: testUser), - overrides: [ - lichessClientProvider - .overrideWith((ref) => LichessClient(client, ref)), - ], - ); + testWidgets('should see activity and recent games', (WidgetTester tester) async { + final app = await makeTestProviderScopeApp( + tester, + home: const UserScreen(user: testUser), + overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))], + ); - await tester.pumpWidget(app); + await tester.pumpWidget(app); - // wait for user request - await tester.pump(const Duration(milliseconds: 50)); + // wait for user request + await tester.pump(const Duration(milliseconds: 50)); - // full name at the top - expect( - find.text('John Doe'), - findsOneWidget, - ); + // full name at the top + expect(find.text('John Doe'), findsOneWidget); - // wait for recent games and activity - await tester.pump(const Duration(milliseconds: 50)); + // wait for recent games and activity + await tester.pump(const Duration(milliseconds: 50)); - expect(find.text('Activity'), findsOneWidget); - }, - variant: kPlatformVariant, - ); + expect(find.text('Activity'), findsOneWidget); + }, variant: kPlatformVariant); }); } diff --git a/test/widgets/adaptive_choice_picker_test.dart b/test/widgets/adaptive_choice_picker_test.dart index dfba25df4e..5cbaed910d 100644 --- a/test/widgets/adaptive_choice_picker_test.dart +++ b/test/widgets/adaptive_choice_picker_test.dart @@ -6,119 +6,103 @@ import 'package:lichess_mobile/src/widgets/adaptive_choice_picker.dart'; import '../test_helpers.dart'; -enum TestEnumLarge { - one, - two, - three, - four, - five, - six, - seven, - eight, - nine, - ten, - eleven -} +enum TestEnumLarge { one, two, three, four, five, six, seven, eight, nine, ten, eleven } enum TestEnumSmall { one, two, three } void main() { - testWidgets( - 'showChoicePicker call onSelectedItemChanged (large choices)', - (WidgetTester tester) async { - final List selectedItems = []; + testWidgets('showChoicePicker call onSelectedItemChanged (large choices)', ( + WidgetTester tester, + ) async { + final List selectedItems = []; - await tester.pumpWidget( - MaterialApp( - localizationsDelegates: AppLocalizations.localizationsDelegates, - home: Scaffold( - body: Builder( - builder: (context) { - return Center( - child: ElevatedButton( - child: const Text('Show picker'), - onPressed: () { - showChoicePicker( - context, - choices: TestEnumLarge.values, - selectedItem: TestEnumLarge.one, - labelBuilder: (choice) => Text(choice.name), - onSelectedItemChanged: (choice) { - selectedItems.add(choice); - }, - ); - }, - ), - ); - }, - ), + await tester.pumpWidget( + MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + home: Scaffold( + body: Builder( + builder: (context) { + return Center( + child: ElevatedButton( + child: const Text('Show picker'), + onPressed: () { + showChoicePicker( + context, + choices: TestEnumLarge.values, + selectedItem: TestEnumLarge.one, + labelBuilder: (choice) => Text(choice.name), + onSelectedItemChanged: (choice) { + selectedItems.add(choice); + }, + ); + }, + ), + ); + }, ), ), - ); + ), + ); - await tester.tap(find.text('Show picker')); - await tester.pumpAndSettle(); + await tester.tap(find.text('Show picker')); + await tester.pumpAndSettle(); - // with large choices (>= 6), on iOS the picker scrolls - if (debugDefaultTargetPlatformOverride == TargetPlatform.iOS) { - // scroll 2 items (2 * 40 height) - await tester.drag( - find.text('one'), - const Offset(0.0, -80.0), - warnIfMissed: false, - ); // has an IgnorePointer - expect(selectedItems, []); - await tester.pumpAndSettle(); // await for scroll ends - // only third item is selected as the scroll ends - expect(selectedItems, [TestEnumLarge.three]); - } else { - await tester.tap(find.text('three')); - expect(selectedItems, [TestEnumLarge.three]); - } - }, - variant: kPlatformVariant, - ); + // with large choices (>= 6), on iOS the picker scrolls + if (debugDefaultTargetPlatformOverride == TargetPlatform.iOS) { + // scroll 2 items (2 * 40 height) + await tester.drag( + find.text('one'), + const Offset(0.0, -80.0), + warnIfMissed: false, + ); // has an IgnorePointer + expect(selectedItems, []); + await tester.pumpAndSettle(); // await for scroll ends + // only third item is selected as the scroll ends + expect(selectedItems, [TestEnumLarge.three]); + } else { + await tester.tap(find.text('three')); + expect(selectedItems, [TestEnumLarge.three]); + } + }, variant: kPlatformVariant); - testWidgets( - 'showChoicePicker call onSelectedItemChanged (small choices)', - (WidgetTester tester) async { - final List selectedItems = []; + testWidgets('showChoicePicker call onSelectedItemChanged (small choices)', ( + WidgetTester tester, + ) async { + final List selectedItems = []; - await tester.pumpWidget( - MaterialApp( - localizationsDelegates: AppLocalizations.localizationsDelegates, - home: Scaffold( - body: Builder( - builder: (context) { - return Center( - child: ElevatedButton( - child: const Text('Show picker'), - onPressed: () { - showChoicePicker( - context, - choices: TestEnumSmall.values, - selectedItem: TestEnumSmall.one, - labelBuilder: (choice) => Text(choice.name), - onSelectedItemChanged: (choice) { - selectedItems.add(choice); - }, - ); - }, - ), - ); - }, - ), + await tester.pumpWidget( + MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + home: Scaffold( + body: Builder( + builder: (context) { + return Center( + child: ElevatedButton( + child: const Text('Show picker'), + onPressed: () { + showChoicePicker( + context, + choices: TestEnumSmall.values, + selectedItem: TestEnumSmall.one, + labelBuilder: (choice) => Text(choice.name), + onSelectedItemChanged: (choice) { + selectedItems.add(choice); + }, + ); + }, + ), + ); + }, ), ), - ); + ), + ); - await tester.tap(find.text('Show picker')); - await tester.pumpAndSettle(); + await tester.tap(find.text('Show picker')); + await tester.pumpAndSettle(); - // With small choices, on iOS the picker is an action sheet - await tester.tap(find.text('three')); - expect(selectedItems, [TestEnumSmall.three]); - }, - variant: kPlatformVariant, - ); + // With small choices, on iOS the picker is an action sheet + await tester.tap(find.text('three')); + expect(selectedItems, [TestEnumSmall.three]); + }, variant: kPlatformVariant); } diff --git a/test/widgets/board_table_test.dart b/test/widgets/board_table_test.dart index f49bf98090..3d260ede55 100644 --- a/test/widgets/board_table_test.dart +++ b/test/widgets/board_table_test.dart @@ -11,144 +11,120 @@ import '../test_helpers.dart'; import '../test_provider_scope.dart'; void main() { - testWidgets( - 'board background size should match board size on all surfaces', - (WidgetTester tester) async { - for (final surface in kTestSurfaces) { - final app = await makeTestProviderScope( - key: ValueKey(surface), - tester, - child: const MaterialApp( - home: BoardTable( - orientation: Side.white, - fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR', - topTable: Row( - mainAxisSize: MainAxisSize.max, - key: ValueKey('top_table'), - children: [ - Text('Top table'), - ], - ), - bottomTable: Row( - mainAxisSize: MainAxisSize.max, - key: ValueKey('bottom_table'), - children: [ - Text('Bottom table'), - ], - ), + testWidgets('board background size should match board size on all surfaces', ( + WidgetTester tester, + ) async { + for (final surface in kTestSurfaces) { + final app = await makeTestProviderScope( + key: ValueKey(surface), + tester, + child: const MaterialApp( + home: BoardTable( + orientation: Side.white, + fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR', + topTable: Row( + mainAxisSize: MainAxisSize.max, + key: ValueKey('top_table'), + children: [Text('Top table')], + ), + bottomTable: Row( + mainAxisSize: MainAxisSize.max, + key: ValueKey('bottom_table'), + children: [Text('Bottom table')], ), ), - surfaceSize: surface, - ); - await tester.pumpWidget(app); + ), + surfaceSize: surface, + ); + await tester.pumpWidget(app); - final backgroundSize = tester.getSize( - find.byType(SolidColorChessboardBackground), - ); + final backgroundSize = tester.getSize(find.byType(SolidColorChessboardBackground)); - expect( - backgroundSize.width, - backgroundSize.height, - reason: 'Board background size is square on $surface', - ); + expect( + backgroundSize.width, + backgroundSize.height, + reason: 'Board background size is square on $surface', + ); - final boardSize = tester.getSize(find.byType(Chessboard)); + final boardSize = tester.getSize(find.byType(Chessboard)); - expect( - boardSize.width, - boardSize.height, - reason: 'Board size is square on $surface', - ); + expect(boardSize.width, boardSize.height, reason: 'Board size is square on $surface'); - expect( - boardSize, - backgroundSize, - reason: 'Board size should match background size on $surface', - ); - } - }, - variant: kPlatformVariant, - ); + expect( + boardSize, + backgroundSize, + reason: 'Board size should match background size on $surface', + ); + } + }, variant: kPlatformVariant); - testWidgets( - 'board size and table side size should be harmonious on all surfaces', - (WidgetTester tester) async { - for (final surface in kTestSurfaces) { - final app = await makeTestProviderScope( - key: ValueKey(surface), - tester, - child: const MaterialApp( - home: BoardTable( - orientation: Side.white, - fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR', - topTable: Row( - mainAxisSize: MainAxisSize.max, - key: ValueKey('top_table'), - children: [ - Text('Top table'), - ], - ), - bottomTable: Row( - mainAxisSize: MainAxisSize.max, - key: ValueKey('bottom_table'), - children: [ - Text('Bottom table'), - ], - ), + testWidgets('board size and table side size should be harmonious on all surfaces', ( + WidgetTester tester, + ) async { + for (final surface in kTestSurfaces) { + final app = await makeTestProviderScope( + key: ValueKey(surface), + tester, + child: const MaterialApp( + home: BoardTable( + orientation: Side.white, + fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR', + topTable: Row( + mainAxisSize: MainAxisSize.max, + key: ValueKey('top_table'), + children: [Text('Top table')], + ), + bottomTable: Row( + mainAxisSize: MainAxisSize.max, + key: ValueKey('bottom_table'), + children: [Text('Bottom table')], ), ), - surfaceSize: surface, - ); - await tester.pumpWidget(app); + ), + surfaceSize: surface, + ); + await tester.pumpWidget(app); - final isPortrait = surface.aspectRatio < 1.0; - final isTablet = surface.shortestSide > 600; - final boardSize = tester.getSize(find.byType(Chessboard)); + final isPortrait = surface.aspectRatio < 1.0; + final isTablet = surface.shortestSide > 600; + final boardSize = tester.getSize(find.byType(Chessboard)); - if (isPortrait) { - final expectedBoardSize = - isTablet ? surface.width - 32.0 : surface.width; - expect( - boardSize, - Size(expectedBoardSize, expectedBoardSize), - reason: 'Board size should match surface width on $surface', - ); - } else { - final topTableSize = - tester.getSize(find.byKey(const ValueKey('top_table'))); - final bottomTableSize = - tester.getSize(find.byKey(const ValueKey('bottom_table'))); - final goldenBoardSize = (surface.longestSide / kGoldenRatio) - 32.0; - final defaultBoardSize = surface.shortestSide - 32.0; - final minBoardSize = min(goldenBoardSize, defaultBoardSize); - final maxBoardSize = max(goldenBoardSize, defaultBoardSize); - final minSideWidth = - min(surface.longestSide - goldenBoardSize - 16.0 * 3, 250.0); - expect( - boardSize.width, - greaterThanOrEqualTo(minBoardSize), - reason: 'Board size should be at least $minBoardSize on $surface', - ); - expect( - boardSize.width, - lessThanOrEqualTo(maxBoardSize), - reason: 'Board size should be at most $maxBoardSize on $surface', - ); - expect( - bottomTableSize.width, - greaterThanOrEqualTo(minSideWidth), - reason: - 'Bottom table width should be at least $minSideWidth on $surface', - ); - expect( - topTableSize.width, - greaterThanOrEqualTo(minSideWidth), - reason: - 'Top table width should be at least $minSideWidth on $surface', - ); - } + if (isPortrait) { + final expectedBoardSize = isTablet ? surface.width - 32.0 : surface.width; + expect( + boardSize, + Size(expectedBoardSize, expectedBoardSize), + reason: 'Board size should match surface width on $surface', + ); + } else { + final topTableSize = tester.getSize(find.byKey(const ValueKey('top_table'))); + final bottomTableSize = tester.getSize(find.byKey(const ValueKey('bottom_table'))); + final goldenBoardSize = (surface.longestSide / kGoldenRatio) - 32.0; + final defaultBoardSize = surface.shortestSide - 32.0; + final minBoardSize = min(goldenBoardSize, defaultBoardSize); + final maxBoardSize = max(goldenBoardSize, defaultBoardSize); + final minSideWidth = min(surface.longestSide - goldenBoardSize - 16.0 * 3, 250.0); + expect( + boardSize.width, + greaterThanOrEqualTo(minBoardSize), + reason: 'Board size should be at least $minBoardSize on $surface', + ); + expect( + boardSize.width, + lessThanOrEqualTo(maxBoardSize), + reason: 'Board size should be at most $maxBoardSize on $surface', + ); + expect( + bottomTableSize.width, + greaterThanOrEqualTo(minSideWidth), + reason: 'Bottom table width should be at least $minSideWidth on $surface', + ); + expect( + topTableSize.width, + greaterThanOrEqualTo(minSideWidth), + reason: 'Top table width should be at least $minSideWidth on $surface', + ); } - }, - variant: kPlatformVariant, - ); + } + }, variant: kPlatformVariant); } diff --git a/test/widgets/clock_test.dart b/test/widgets/clock_test.dart index 73e20078f6..555ca56d0d 100644 --- a/test/widgets/clock_test.dart +++ b/test/widgets/clock_test.dart @@ -5,26 +5,17 @@ import 'package:lichess_mobile/src/widgets/clock.dart'; void main() { group('Clock', () { - testWidgets('shows milliseconds when time < 1s and active is false', - (WidgetTester tester) async { + testWidgets('shows milliseconds when time < 1s and active is false', ( + WidgetTester tester, + ) async { await tester.pumpWidget( - const MaterialApp( - home: Clock( - timeLeft: Duration(seconds: 1), - active: true, - ), - ), + const MaterialApp(home: Clock(timeLeft: Duration(seconds: 1), active: true)), ); expect(find.text('0:01.0', findRichText: true), findsOneWidget); await tester.pumpWidget( - const MaterialApp( - home: Clock( - timeLeft: Duration(milliseconds: 988), - active: false, - ), - ), + const MaterialApp(home: Clock(timeLeft: Duration(milliseconds: 988), active: false)), duration: const Duration(milliseconds: 1000), ); @@ -77,8 +68,7 @@ void main() { expect(find.text('0:00.0'), findsOneWidget); }); - testWidgets('update time by changing widget configuration', - (WidgetTester tester) async { + testWidgets('update time by changing widget configuration', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: CountdownClockBuilder( @@ -115,8 +105,7 @@ void main() { expect(find.text('0:00.0'), findsOneWidget); }); - testWidgets('do not update if clockUpdatedAt is same', - (WidgetTester tester) async { + testWidgets('do not update if clockUpdatedAt is same', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: CountdownClockBuilder(