From c4e77798100add354496e735b03a353d1f94a6a9 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 23 Dec 2024 10:48:42 +0100 Subject: [PATCH 01/41] Try out new theme --- lib/src/app.dart | 154 +++++++++++------- lib/src/styles/styles.dart | 8 - .../view/broadcast/broadcast_game_screen.dart | 6 +- .../view/broadcast/broadcast_list_screen.dart | 5 +- lib/src/view/home/home_tab_screen.dart | 3 +- .../opening_explorer_widgets.dart | 13 +- lib/src/view/play/quick_game_matrix.dart | 5 +- lib/src/widgets/list.dart | 2 +- lib/src/widgets/platform.dart | 6 +- lib/src/widgets/settings.dart | 2 +- pubspec.lock | 16 ++ pubspec.yaml | 1 + 12 files changed, 124 insertions(+), 97 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index c7be85f24b..43d3d42dd2 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,4 +1,5 @@ import 'package:dynamic_color/dynamic_color.dart'; +import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; @@ -121,80 +122,72 @@ class _AppState extends ConsumerState { final generalPrefs = ref.watch(generalPreferencesProvider); final brightness = ref.watch(currentBrightnessProvider); - final boardTheme = ref.watch(boardPreferencesProvider.select((state) => state.boardTheme)); final remainingHeight = estimateRemainingHeightLeftBoard(context); return DynamicColorBuilder( 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 isTablet = isTabletOrLarger(context); final isIOS = Theme.of(context).platform == TargetPlatform.iOS; - final dynamicColorScheme = - brightness == Brightness.light ? fixedLightScheme : fixedDarkScheme; + final colorScheme = + brightness == Brightness.light ? AppTheme.light.colorScheme : AppTheme.dark.colorScheme; - final ColorScheme colorScheme = switch (generalPrefs.appThemeSeed) { - AppThemeSeed.board => ColorScheme.fromSeed( - seedColor: boardTheme.colors.darkSquare, - brightness: brightness, - ), - AppThemeSeed.system => - dynamicColorScheme ?? - ColorScheme.fromSeed( - seedColor: boardTheme.colors.darkSquare, - brightness: brightness, - ), - }; + // final appTheme = brightness == Brightness.light ? AppTheme.light : AppTheme.dark; - 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), - ), - scaffoldBackgroundColor: Styles.cupertinoScaffoldColor, - barBackgroundColor: - isTablet ? Styles.cupertinoTabletAppBarColor : Styles.cupertinoAppBarColor, - ); + // 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: colorScheme.onSurface), + // navTitleTextStyle: CupertinoTheme.of( + // context, + // ).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + // navLargeTitleTextStyle: CupertinoTheme.of( + // context, + // ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + // ), + // scaffoldBackgroundColor: colorScheme.surface, + // barBackgroundColor: appTheme.appBarTheme.backgroundColor?.withValues( + // alpha: isTablet ? 1.0 : 0.9, + // ), + // applyThemeToAll: true, + // ); return MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: kSupportedLocales, onGenerateTitle: (BuildContext context) => 'lichess.org', locale: generalPrefs.locale, - theme: ThemeData.from( - colorScheme: colorScheme, + theme: AppTheme.light.copyWith( textTheme: - isIOS - ? brightness == Brightness.light - ? Typography.blackCupertino - : Styles.whiteCupertinoTextTheme - : null, - ).copyWith( - splashFactory: isIOS ? NoSplash.splashFactory : null, - cupertinoOverrideTheme: cupertinoThemeData, + Theme.of(context).platform == TargetPlatform.iOS ? Typography.blackCupertino : null, navigationBarTheme: NavigationBarTheme.of(context).copyWith( height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null, ), extensions: [lichessCustomColors.harmonized(colorScheme)], ), + darkTheme: AppTheme.dark + .copyWith( + textTheme: + isIOS + ? brightness == Brightness.light + ? Typography.blackCupertino + : Styles.whiteCupertinoTextTheme + : null, + ) + .copyWith( + splashFactory: isIOS ? NoSplash.splashFactory : null, + // cupertinoOverrideTheme: cupertinoThemeData, + navigationBarTheme: NavigationBarTheme.of(context).copyWith( + height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null, + ), + extensions: [lichessCustomColors.harmonized(colorScheme)], + ), themeMode: switch (generalPrefs.themeMode) { BackgroundThemeMode.light => ThemeMode.light, BackgroundThemeMode.dark => ThemeMode.dark, @@ -203,15 +196,16 @@ class _AppState extends ConsumerState { 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), - ), - ); + // return CupertinoTheme( + // data: cupertinoThemeData, + // child: IconTheme.merge( + // data: IconThemeData( + // color: CupertinoTheme.of(context).textTheme.textStyle.color, + // ), + // child: Material(child: child), + // ), + // ); + return Material(child: child); } : null, home: const BottomNavScaffold(), @@ -222,6 +216,44 @@ class _AppState extends ConsumerState { } } +/// The [AppTheme] defines light and dark themes for the app. +/// +/// Theme setup for FlexColorScheme package v8. +/// Use same major flex_color_scheme package version. If you use a +/// lower minor version, some properties may not be supported. +/// In that case, remove them after copying this theme to your +/// app or upgrade package to version 8.0.2. +/// +/// Use in [MaterialApp] like this: +/// +/// MaterialApp( +/// theme: AppTheme.light, +/// darkTheme: AppTheme.dark, +/// : +/// ); +sealed class AppTheme { + // The defined light theme. + static ThemeData light = FlexThemeData.light( + scheme: FlexScheme.espresso, + surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold, + blendLevel: 1, + appBarStyle: FlexAppBarStyle.background, + bottomAppBarElevation: 2.0, + visualDensity: FlexColorScheme.comfortablePlatformDensity, + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + ); + // The defined dark theme. + static ThemeData dark = FlexThemeData.dark( + scheme: FlexScheme.espresso, + surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold, + blendLevel: 2, + appBarStyle: FlexAppBarStyle.background, + bottomAppBarElevation: 2.0, + visualDensity: FlexColorScheme.comfortablePlatformDensity, + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + ); +} + // -- (ColorScheme light, ColorScheme dark) _generateDynamicColourSchemes( diff --git a/lib/src/styles/styles.dart b/lib/src/styles/styles.dart index 6959840530..b6da83a141 100644 --- a/lib/src/styles/styles.dart +++ b/lib/src/styles/styles.dart @@ -58,10 +58,6 @@ abstract class Styles { color: Color(0xFFF9F9F9), darkColor: Color.fromARGB(255, 36, 36, 36), ); - static const cupertinoScaffoldColor = CupertinoDynamicColor.withBrightness( - color: Color.fromARGB(255, 242, 242, 247), - darkColor: Color.fromARGB(255, 23, 23, 23), - ); static const _cupertinoDarkLabelColor = Color(0xFFDCDCDC); static const cupertinoLabelColor = CupertinoDynamicColor.withBrightness( @@ -72,10 +68,6 @@ abstract class Styles { color: Color(0xFF000000), darkColor: Color(0xFFF5F5F5), ); - static const cupertinoCardColor = CupertinoDynamicColor.withBrightness( - color: Color(0xFFFFFFFF), - darkColor: Color.fromARGB(255, 44, 44, 46), - ); static const cupertinoSeparatorColor = CupertinoDynamicColor.withBrightness( debugLabel: 'separator', color: Color.fromARGB(73, 60, 60, 67), diff --git a/lib/src/view/broadcast/broadcast_game_screen.dart b/lib/src/view/broadcast/broadcast_game_screen.dart index 6381f7ab91..458fca148f 100644 --- a/lib/src/view/broadcast/broadcast_game_screen.dart +++ b/lib/src/view/broadcast/broadcast_game_screen.dart @@ -12,7 +12,6 @@ import 'package:lichess_mobile/src/model/common/eval.dart'; import 'package:lichess_mobile/src/model/common/id.dart'; import 'package:lichess_mobile/src/model/engine/evaluation_service.dart'; import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; -import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/duration.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/navigation.dart'; @@ -401,10 +400,7 @@ class _PlayerWidget extends ConsumerWidget { ); }, child: Container( - color: - Theme.of(context).platform == TargetPlatform.iOS - ? Styles.cupertinoCardColor.resolveFrom(context) - : Theme.of(context).colorScheme.surfaceContainer, + color: Theme.of(context).colorScheme.surfaceContainer, padding: const EdgeInsets.only(left: 8.0), child: Row( children: [ diff --git a/lib/src/view/broadcast/broadcast_list_screen.dart b/lib/src/view/broadcast/broadcast_list_screen.dart index c20e559db2..4e999f8b2e 100644 --- a/lib/src/view/broadcast/broadcast_list_screen.dart +++ b/lib/src/view/broadcast/broadcast_list_screen.dart @@ -373,10 +373,7 @@ class _BroadcastCartState extends State { @override Widget build(BuildContext context) { - final defaultBackgroundColor = - Theme.of(context).platform == TargetPlatform.iOS - ? Styles.cupertinoCardColor.resolveFrom(context) - : Theme.of(context).colorScheme.surfaceContainer; + final defaultBackgroundColor = Theme.of(context).colorScheme.surfaceContainer; final backgroundColor = _cardColors?.primaryContainer ?? defaultBackgroundColor; final titleColor = _cardColors?.onPrimaryContainer; final subTitleColor = diff --git a/lib/src/view/home/home_tab_screen.dart b/lib/src/view/home/home_tab_screen.dart index 07f8145720..7a04ca40b1 100644 --- a/lib/src/view/home/home_tab_screen.dart +++ b/lib/src/view/home/home_tab_screen.dart @@ -146,8 +146,7 @@ class _HomeScreenState extends ConsumerState with RouteAware { bottom: MediaQuery.paddingOf(context).bottom + 16.0, right: 8.0, child: FloatingActionButton.extended( - backgroundColor: CupertinoTheme.of(context).primaryColor, - foregroundColor: CupertinoTheme.of(context).primaryContrastingColor, + splashColor: Colors.transparent, onPressed: () { pushPlatformRoute( context, diff --git a/lib/src/view/opening_explorer/opening_explorer_widgets.dart b/lib/src/view/opening_explorer/opening_explorer_widgets.dart index c953f2d83c..c544bcd88d 100644 --- a/lib/src/view/opening_explorer/opening_explorer_widgets.dart +++ b/lib/src/view/opening_explorer/opening_explorer_widgets.dart @@ -38,7 +38,7 @@ 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.surfaceDim), child: GestureDetector( onTap: opening.name == context.l10n.startPosition @@ -47,17 +47,14 @@ class OpeningNameHeader extends StatelessWidget { child: Row( children: [ if (opening.name != context.l10n.startPosition) ...[ - Icon( - Icons.open_in_browser_outlined, - color: Theme.of(context).colorScheme.onSecondaryContainer, - ), + Icon(Icons.open_in_browser_outlined, color: Theme.of(context).colorScheme.onSurface), const SizedBox(width: 6.0), ], Expanded( child: Text( opening.name, style: TextStyle( - color: Theme.of(context).colorScheme.onSecondaryContainer, + color: Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.bold, ), maxLines: 1, @@ -119,7 +116,7 @@ class OpeningExplorerMoveTable extends ConsumerWidget { columnWidths: columnWidths, children: [ TableRow( - decoration: BoxDecoration(color: Theme.of(context).colorScheme.secondaryContainer), + decoration: BoxDecoration(color: Theme.of(context).colorScheme.surfaceDim), children: [ Padding( padding: _kTableRowPadding, @@ -324,7 +321,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.surfaceDim), child: child, ); } diff --git a/lib/src/view/play/quick_game_matrix.dart b/lib/src/view/play/quick_game_matrix.dart index d7aaff84ab..90ed0a6b0c 100644 --- a/lib/src/view/play/quick_game_matrix.dart +++ b/lib/src/view/play/quick_game_matrix.dart @@ -142,10 +142,7 @@ 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).colorScheme.surfaceContainer.withValues(alpha: 0.7); return Opacity( opacity: widget.onTap != null ? 1.0 : 0.5, diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index 4c4443e729..87e02b4980 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -182,7 +182,7 @@ class ListSection extends StatelessWidget { decoration: BoxDecoration( color: cupertinoBackgroundColor ?? - Styles.cupertinoCardColor.resolveFrom(context), + Theme.of(context).colorScheme.surfaceContainer, borderRadius: cupertinoBorderRadius ?? const BorderRadius.all(Radius.circular(10.0)), ), diff --git a/lib/src/widgets/platform.dart b/lib/src/widgets/platform.dart index 87fffb58b6..c8f8b92b14 100644 --- a/lib/src/widgets/platform.dart +++ b/lib/src/widgets/platform.dart @@ -89,10 +89,10 @@ class PlatformCard extends StatelessWidget { maxScaleFactor: kCardTextScaleFactor, child: Theme.of(context).platform == TargetPlatform.iOS - ? Card( + ? Card.filled( margin: margin ?? EdgeInsets.zero, - elevation: elevation ?? 0, - color: color ?? Styles.cupertinoCardColor.resolveFrom(context), + // elevation: elevation ?? 0, + color: color, shadowColor: shadowColor, shape: borderRadius != null diff --git a/lib/src/widgets/settings.dart b/lib/src/widgets/settings.dart index b553c49b03..2c05abf236 100644 --- a/lib/src/widgets/settings.dart +++ b/lib/src/widgets/settings.dart @@ -278,7 +278,7 @@ class ChoicePicker extends StatelessWidget { child: CupertinoListSection.insetGrouped( backgroundColor: CupertinoTheme.of(context).scaffoldBackgroundColor, decoration: BoxDecoration( - color: Styles.cupertinoCardColor.resolveFrom(context), + color: Theme.of(context).colorScheme.surfaceContainer, borderRadius: const BorderRadius.all(Radius.circular(10.0)), ), separatorColor: Styles.cupertinoSeparatorColor.resolveFrom(context), diff --git a/pubspec.lock b/pubspec.lock index 93d069ec5e..4ece265b72 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -526,6 +526,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.70.2" + flex_color_scheme: + dependency: "direct main" + description: + name: flex_color_scheme + sha256: "09bea5d776f694c5a67f2229f2aa500cc7cce369322dc6500ab01cf9ad1b4e1a" + url: "https://pub.dev" + source: hosted + version: "8.1.0" + flex_seed_scheme: + dependency: transitive + description: + name: flex_seed_scheme + sha256: d3ba3c5c92d2d79d45e94b4c6c71d01fac3c15017da1545880c53864da5dfeb0 + url: "https://pub.dev" + source: hosted + version: "3.5.0" flutter: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index b6cf71bfbd..c62da5ce0b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: firebase_crashlytics: ^4.0.0 firebase_messaging: ^15.0.0 fl_chart: ^0.70.0 + flex_color_scheme: ^8.0.2 flutter: sdk: flutter flutter_appauth: ^8.0.0+1 From 252820e3a28f25c814642ff1b8f0f22a74c9a30b Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Tue, 14 Jan 2025 17:37:38 +0800 Subject: [PATCH 02/41] Cupertino theme --- lib/src/app.dart | 51 ++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 43d3d42dd2..1777732a5f 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -133,30 +133,30 @@ class _AppState extends ConsumerState { final colorScheme = brightness == Brightness.light ? AppTheme.light.colorScheme : AppTheme.dark.colorScheme; - // final appTheme = brightness == Brightness.light ? AppTheme.light : AppTheme.dark; + final appTheme = brightness == Brightness.light ? AppTheme.light : AppTheme.dark; - // 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: colorScheme.onSurface), - // navTitleTextStyle: CupertinoTheme.of( - // context, - // ).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), - // navLargeTitleTextStyle: CupertinoTheme.of( - // context, - // ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), - // ), - // scaffoldBackgroundColor: colorScheme.surface, - // barBackgroundColor: appTheme.appBarTheme.backgroundColor?.withValues( - // alpha: isTablet ? 1.0 : 0.9, - // ), - // applyThemeToAll: true, - // ); + 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: colorScheme.onSurface), + navTitleTextStyle: CupertinoTheme.of( + context, + ).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + navLargeTitleTextStyle: CupertinoTheme.of( + context, + ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + ), + scaffoldBackgroundColor: colorScheme.surface, + barBackgroundColor: appTheme.appBarTheme.backgroundColor?.withValues( + alpha: isTablet ? 1.0 : 0.9, + ), + applyThemeToAll: true, + ); return MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -164,6 +164,7 @@ class _AppState extends ConsumerState { onGenerateTitle: (BuildContext context) => 'lichess.org', locale: generalPrefs.locale, theme: AppTheme.light.copyWith( + cupertinoOverrideTheme: cupertinoThemeData, textTheme: Theme.of(context).platform == TargetPlatform.iOS ? Typography.blackCupertino : null, navigationBarTheme: NavigationBarTheme.of(context).copyWith( @@ -182,7 +183,7 @@ class _AppState extends ConsumerState { ) .copyWith( splashFactory: isIOS ? NoSplash.splashFactory : null, - // cupertinoOverrideTheme: cupertinoThemeData, + cupertinoOverrideTheme: cupertinoThemeData, navigationBarTheme: NavigationBarTheme.of(context).copyWith( height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null, ), @@ -246,7 +247,7 @@ sealed class AppTheme { static ThemeData dark = FlexThemeData.dark( scheme: FlexScheme.espresso, surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold, - blendLevel: 2, + blendLevel: 16, appBarStyle: FlexAppBarStyle.background, bottomAppBarElevation: 2.0, visualDensity: FlexColorScheme.comfortablePlatformDensity, From 298c1372570f62c295857c1457d9df307421a24d Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Wed, 15 Jan 2025 15:26:48 +0800 Subject: [PATCH 03/41] More work on flex color scheme --- lib/src/app.dart | 291 ++++++++---------- .../model/settings/general_preferences.dart | 75 +++++ lib/src/utils/color_palette.dart | 44 ++- lib/src/view/play/quick_game_matrix.dart | 2 +- lib/src/view/settings/app_theme_screen.dart | 93 ++++++ lib/src/view/settings/theme_screen.dart | 87 +++--- lib/src/view/study/study_gamebook.dart | 4 +- lib/src/widgets/list.dart | 2 +- pubspec.yaml | 2 +- 9 files changed, 388 insertions(+), 212 deletions(-) create mode 100644 lib/src/view/settings/app_theme_screen.dart diff --git a/lib/src/app.dart b/lib/src/app.dart index 1777732a5f..e169cfa3ab 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -120,181 +120,134 @@ class _AppState extends ConsumerState { @override Widget build(BuildContext context) { final generalPrefs = ref.watch(generalPreferencesProvider); - - final brightness = ref.watch(currentBrightnessProvider); - + // final boardTheme = ref.watch(boardPreferencesProvider.select((state) => state.boardTheme)); + final isTablet = isTabletOrLarger(context); + final isIOS = Theme.of(context).platform == TargetPlatform.iOS; final remainingHeight = estimateRemainingHeightLeftBoard(context); - return DynamicColorBuilder( - builder: (lightColorScheme, darkColorScheme) { - final isTablet = isTabletOrLarger(context); - final isIOS = Theme.of(context).platform == TargetPlatform.iOS; - - final colorScheme = - brightness == Brightness.light ? AppTheme.light.colorScheme : AppTheme.dark.colorScheme; + final flexSchemeLightColors = generalPrefs.appTheme.flexScheme.light; + final flexSchemeDarkColors = generalPrefs.appTheme.flexScheme.dark; + + final lightTheme = FlexThemeData.light( + colors: flexSchemeLightColors, + surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold, + blendLevel: 1, + appBarStyle: FlexAppBarStyle.background, + bottomAppBarElevation: 2.0, + visualDensity: FlexColorScheme.comfortablePlatformDensity, + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + ); + final darkTheme = FlexThemeData.dark( + colors: flexSchemeDarkColors, + surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold, + blendLevel: 28, + appBarStyle: FlexAppBarStyle.background, + bottomAppBarElevation: 2.0, + visualDensity: FlexColorScheme.comfortablePlatformDensity, + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + ); - final appTheme = brightness == Brightness.light ? AppTheme.light : AppTheme.dark; + final lightCupertinoTheme = CupertinoThemeData( + primaryColor: lightTheme.colorScheme.primary, + primaryContrastingColor: lightTheme.colorScheme.onPrimary, + brightness: Brightness.light, + textTheme: CupertinoTheme.of(context).textTheme.copyWith( + primaryColor: lightTheme.colorScheme.primary, + textStyle: CupertinoTheme.of( + context, + ).textTheme.textStyle.copyWith(color: lightTheme.colorScheme.onSurface), + navTitleTextStyle: CupertinoTheme.of( + context, + ).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + navLargeTitleTextStyle: CupertinoTheme.of( + context, + ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + ), + scaffoldBackgroundColor: lightTheme.colorScheme.surface, + barBackgroundColor: lightTheme.appBarTheme.backgroundColor?.withValues( + alpha: isTablet ? 1.0 : 0.9, + ), + applyThemeToAll: true, + ); - 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: colorScheme.onSurface), - navTitleTextStyle: CupertinoTheme.of( - context, - ).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), - navLargeTitleTextStyle: CupertinoTheme.of( - context, - ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), - ), - scaffoldBackgroundColor: colorScheme.surface, - barBackgroundColor: appTheme.appBarTheme.backgroundColor?.withValues( - alpha: isTablet ? 1.0 : 0.9, - ), - applyThemeToAll: true, - ); + final darkCupertinoTheme = CupertinoThemeData( + primaryColor: darkTheme.colorScheme.primary, + primaryContrastingColor: darkTheme.colorScheme.onPrimary, + brightness: Brightness.dark, + textTheme: CupertinoTheme.of(context).textTheme.copyWith( + primaryColor: darkTheme.colorScheme.primary, + textStyle: CupertinoTheme.of( + context, + ).textTheme.textStyle.copyWith(color: darkTheme.colorScheme.onSurface), + navTitleTextStyle: CupertinoTheme.of( + context, + ).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + navLargeTitleTextStyle: CupertinoTheme.of( + context, + ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + ), + scaffoldBackgroundColor: darkTheme.colorScheme.surface, + barBackgroundColor: darkTheme.appBarTheme.backgroundColor?.withValues( + alpha: isTablet ? 1.0 : 0.9, + ), + applyThemeToAll: true, + ); - return MaterialApp( - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: kSupportedLocales, - onGenerateTitle: (BuildContext context) => 'lichess.org', - locale: generalPrefs.locale, - theme: AppTheme.light.copyWith( - cupertinoOverrideTheme: cupertinoThemeData, - textTheme: - Theme.of(context).platform == TargetPlatform.iOS ? Typography.blackCupertino : null, - navigationBarTheme: NavigationBarTheme.of(context).copyWith( - height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null, - ), - extensions: [lichessCustomColors.harmonized(colorScheme)], - ), - darkTheme: AppTheme.dark - .copyWith( - textTheme: - isIOS - ? brightness == Brightness.light - ? Typography.blackCupertino - : Styles.whiteCupertinoTextTheme - : null, - ) - .copyWith( - splashFactory: isIOS ? NoSplash.splashFactory : null, - cupertinoOverrideTheme: cupertinoThemeData, - navigationBarTheme: NavigationBarTheme.of(context).copyWith( - height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null, - ), - 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, - // ), - // child: Material(child: child), - // ), - // ); - return Material(child: child); - } - : null, - home: const BottomNavScaffold(), - navigatorObservers: [rootNavPageRouteObserver], - ); + return MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: kSupportedLocales, + onGenerateTitle: (BuildContext context) => 'lichess.org', + locale: generalPrefs.locale, + theme: lightTheme.copyWith( + cupertinoOverrideTheme: lightCupertinoTheme, + splashFactory: isIOS ? NoSplash.splashFactory : null, + textTheme: isIOS ? Typography.blackCupertino : null, + listTileTheme: ListTileTheme.of(context).copyWith( + titleTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, + subtitleTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, + leadingAndTrailingTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, + ), + navigationBarTheme: NavigationBarTheme.of( + context, + ).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null), + extensions: [lichessCustomColors.harmonized(lightTheme.colorScheme)], + ), + darkTheme: darkTheme.copyWith( + cupertinoOverrideTheme: darkCupertinoTheme, + splashFactory: isIOS ? NoSplash.splashFactory : null, + textTheme: isIOS ? Typography.whiteCupertino : null, + listTileTheme: ListTileTheme.of(context).copyWith( + titleTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, + subtitleTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, + leadingAndTrailingTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, + ), + navigationBarTheme: NavigationBarTheme.of( + context, + ).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null), + extensions: [lichessCustomColors.harmonized(darkTheme.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, + // ), + // child: Material(child: child), + // ), + // ); + return Material(child: child); + } + : null, + home: const BottomNavScaffold(), + navigatorObservers: [rootNavPageRouteObserver], ); } } - -/// The [AppTheme] defines light and dark themes for the app. -/// -/// Theme setup for FlexColorScheme package v8. -/// Use same major flex_color_scheme package version. If you use a -/// lower minor version, some properties may not be supported. -/// In that case, remove them after copying this theme to your -/// app or upgrade package to version 8.0.2. -/// -/// Use in [MaterialApp] like this: -/// -/// MaterialApp( -/// theme: AppTheme.light, -/// darkTheme: AppTheme.dark, -/// : -/// ); -sealed class AppTheme { - // The defined light theme. - static ThemeData light = FlexThemeData.light( - scheme: FlexScheme.espresso, - surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold, - blendLevel: 1, - appBarStyle: FlexAppBarStyle.background, - bottomAppBarElevation: 2.0, - visualDensity: FlexColorScheme.comfortablePlatformDensity, - cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), - ); - // The defined dark theme. - static ThemeData dark = FlexThemeData.dark( - scheme: FlexScheme.espresso, - surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold, - blendLevel: 16, - appBarStyle: FlexAppBarStyle.background, - bottomAppBarElevation: 2.0, - visualDensity: FlexColorScheme.comfortablePlatformDensity, - cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), - ); -} - -// -- - -(ColorScheme light, ColorScheme dark) _generateDynamicColourSchemes( - ColorScheme lightDynamic, - ColorScheme darkDynamic, -) { - final lightBase = ColorScheme.fromSeed(seedColor: lightDynamic.primary); - final darkBase = ColorScheme.fromSeed( - seedColor: darkDynamic.primary, - brightness: Brightness.dark, - ); - - final lightAdditionalColours = _extractAdditionalColours(lightBase); - final darkAdditionalColours = _extractAdditionalColours(darkBase); - - 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, -]; - -ColorScheme _insertAdditionalColours(ColorScheme scheme, List additionalColours) => - scheme.copyWith( - surface: additionalColours[0], - surfaceDim: additionalColours[1], - surfaceBright: additionalColours[2], - surfaceContainerLowest: additionalColours[3], - surfaceContainerLow: additionalColours[4], - surfaceContainer: additionalColours[5], - surfaceContainerHigh: additionalColours[6], - surfaceContainerHighest: additionalColours[7], - ); diff --git a/lib/src/model/settings/general_preferences.dart b/lib/src/model/settings/general_preferences.dart index 7fa2643f2d..e02b8797f1 100644 --- a/lib/src/model/settings/general_preferences.dart +++ b/lib/src/model/settings/general_preferences.dart @@ -1,7 +1,9 @@ import 'dart:ui' show Locale; +import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:lichess_mobile/src/model/settings/preferences_storage.dart'; +import 'package:lichess_mobile/src/utils/color_palette.dart'; import 'package:lichess_mobile/src/utils/json.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -49,6 +51,10 @@ class GeneralPreferences extends _$GeneralPreferences with PreferencesStorage setAppThemeSeed(AppThemeSeed seed) { return save(state.copyWith(appThemeSeed: seed)); } + + Future setAppTheme(AppTheme appTheme) { + return save(state.copyWith(appTheme: appTheme)); + } } @Freezed(fromJson: true, toJson: true) @@ -62,6 +68,9 @@ class GeneralPrefs with _$GeneralPrefs implements Serializable { @Deprecated('Use appThemeSeed instead') bool? systemColors, + @JsonKey(unknownEnumValue: AppTheme.gold, defaultValue: AppTheme.gold) + required AppTheme appTheme, + /// App theme seed @JsonKey(unknownEnumValue: AppThemeSeed.board, defaultValue: AppThemeSeed.board) required AppThemeSeed appThemeSeed, @@ -76,6 +85,7 @@ class GeneralPrefs with _$GeneralPrefs implements Serializable { soundTheme: SoundTheme.standard, masterVolume: 0.8, appThemeSeed: AppThemeSeed.board, + appTheme: AppTheme.gold, ); factory GeneralPrefs.fromJson(Map json) { @@ -91,6 +101,71 @@ enum AppThemeSeed { board, } +enum AppTheme { + /// The app theme is based on the user's system theme (only available on Android 10+). + system, + + /// Below values from [FlexScheme] + blue, + indigo, + hippieBlue, + aquaBlue, + brandBlue, + deepBlue, + sakura, + mandyRed, + red, + redWine, + purpleBrown, + green, + money, + jungle, + greyLaw, + wasabi, + gold, + mango, + amber, + vesuviusBurn, + deepPurple, + ebonyClay, + barossa, + shark, + bigStone, + damask, + bahamaBlue, + mallardGreen, + espresso, + outerSpace, + blueWhale, + sanJuanBlue, + rosewood, + blumineBlue, + flutterDash, + materialBaseline, + verdunHemlock, + dellGenoa, + redM3, + pinkM3, + purpleM3, + indigoM3, + blueM3, + cyanM3, + tealM3, + greenM3, + limeM3, + yellowM3, + orangeM3, + deepOrangeM3, + blackWhite, + greys, + sepia; + + static final _flexSchemesNameMap = FlexScheme.values.asNameMap(); + + FlexSchemeData get flexScheme => + this == AppTheme.system ? getSystemScheme()! : _flexSchemesNameMap[name]!.data; +} + /// Describes the background theme of the app. enum BackgroundThemeMode { /// Use either the light or dark theme based on what the user has selected in diff --git a/lib/src/utils/color_palette.dart b/lib/src/utils/color_palette.dart index 6ecdd18d82..8e80820fac 100644 --- a/lib/src/utils/color_palette.dart +++ b/lib/src/utils/color_palette.dart @@ -2,23 +2,58 @@ import 'dart:ui'; import 'package:chessground/chessground.dart'; import 'package:dartchess/dartchess.dart'; +import 'package:dynamic_color/dynamic_color.dart'; +import 'package:flex_color_scheme/flex_color_scheme.dart' show FlexSchemeColor, FlexSchemeData; import 'package:material_color_utilities/material_color_utilities.dart'; CorePalette? _corePalette; +FlexSchemeData? _systemScheme; + ChessboardColorScheme? _boardColorScheme; /// Set the system core palette if available (android 12+ only). /// /// It also defines the system board colors based on the core palette. void setCorePalette(CorePalette? palette) { - _corePalette = palette; + _corePalette ??= palette; if (palette != null) { + final lightScheme = palette.toColorScheme(); + final darkScheme = palette.toColorScheme(brightness: Brightness.dark); + + _systemScheme ??= FlexSchemeData( + name: 'System', + description: 'System core palette on Android 12+', + light: FlexSchemeColor( + primary: lightScheme.primary, + primaryContainer: lightScheme.primaryContainer, + secondary: lightScheme.secondary, + secondaryContainer: lightScheme.secondaryContainer, + tertiary: lightScheme.tertiary, + tertiaryContainer: lightScheme.tertiaryContainer, + error: lightScheme.error, + errorContainer: lightScheme.errorContainer, + ), + dark: FlexSchemeColor( + primary: darkScheme.primary, + primaryContainer: darkScheme.primaryContainer, + primaryLightRef: lightScheme.primary, + secondary: darkScheme.secondary, + secondaryContainer: darkScheme.secondaryContainer, + secondaryLightRef: lightScheme.secondary, + tertiary: darkScheme.tertiary, + tertiaryContainer: darkScheme.tertiaryContainer, + tertiaryLightRef: lightScheme.tertiary, + error: darkScheme.error, + errorContainer: darkScheme.errorContainer, + ), + ); + final darkSquare = Color(palette.secondary.get(60)); final lightSquare = Color(palette.primary.get(95)); - _boardColorScheme = ChessboardColorScheme( + _boardColorScheme ??= ChessboardColorScheme( darkSquare: darkSquare, lightSquare: lightSquare, background: SolidColorChessboardBackground(lightSquare: lightSquare, darkSquare: darkSquare), @@ -50,6 +85,11 @@ CorePalette? getCorePalette() { return _corePalette; } +/// Get the system [FlexSchemeData] if available (android 12+ only). +FlexSchemeData? getSystemScheme() { + return _systemScheme; +} + /// Get the board colors based on the core palette, if available (android 12+). ChessboardColorScheme? getBoardColorScheme() { return _boardColorScheme; diff --git a/lib/src/view/play/quick_game_matrix.dart b/lib/src/view/play/quick_game_matrix.dart index 90ed0a6b0c..e657403579 100644 --- a/lib/src/view/play/quick_game_matrix.dart +++ b/lib/src/view/play/quick_game_matrix.dart @@ -142,7 +142,7 @@ class _ChoiceChip extends StatefulWidget { class _ChoiceChipState extends State<_ChoiceChip> { @override Widget build(BuildContext context) { - final cardColor = Theme.of(context).colorScheme.surfaceContainer.withValues(alpha: 0.7); + final cardColor = Theme.of(context).colorScheme.surfaceContainerHigh.withValues(alpha: 0.7); return Opacity( opacity: widget.onTap != null ? 1.0 : 0.5, diff --git a/lib/src/view/settings/app_theme_screen.dart b/lib/src/view/settings/app_theme_screen.dart new file mode 100644 index 0000000000..15baeec9fa --- /dev/null +++ b/lib/src/view/settings/app_theme_screen.dart @@ -0,0 +1,93 @@ +import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; +import 'package:lichess_mobile/src/model/settings/general_preferences.dart'; +import 'package:lichess_mobile/src/utils/color_palette.dart'; +import 'package:lichess_mobile/src/utils/l10n_context.dart'; +import 'package:lichess_mobile/src/widgets/list.dart'; +import 'package:lichess_mobile/src/widgets/platform.dart'; + +class AppThemeScreen extends StatelessWidget { + const AppThemeScreen({super.key}); + + @override + Widget build(BuildContext context) { + return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); + } + + Widget _androidBuilder(BuildContext context) { + return Scaffold(appBar: AppBar(title: Text('Theme')), body: _Body()); + } + + Widget _iosBuilder(BuildContext context) { + return CupertinoPageScaffold(navigationBar: const CupertinoNavigationBar(), child: _Body()); + } +} + +class _Body extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final appTheme = ref.watch(generalPreferencesProvider.select((p) => p.appTheme)); + + final hasSystemColors = getCorePalette() != null; + + final choices = AppTheme.values.where((t) => t != AppTheme.system || hasSystemColors).toList(); + + void onChanged(AppTheme? value) => + ref.read(generalPreferencesProvider.notifier).setAppTheme(value ?? AppTheme.gold); + + final checkedIcon = + Theme.of(context).platform == TargetPlatform.android + ? const Icon(Icons.check) + : Icon( + CupertinoIcons.check_mark_circled_solid, + color: CupertinoTheme.of(context).primaryColor, + ); + + final brightness = Theme.of(context).brightness; + + return SafeArea( + child: ListView.separated( + itemBuilder: (context, index) { + final t = choices[index]; + final fsd = t.flexScheme; + + return AdaptiveListTile( + // selected: t == appTheme, + leading: SizedBox( + width: 52, + height: 36, + child: FlexThemeModeOptionButton( + flexSchemeColor: brightness == Brightness.light ? fsd.light : fsd.dark, + selected: false, + unselectedBorder: BorderSide.none, + selectedBorder: BorderSide(color: Theme.of(context).colorScheme.outline, width: 3), + backgroundColor: Theme.of(context).colorScheme.surface, + width: 26, + height: 18, + padding: EdgeInsets.zero, + borderRadius: 0, + optionButtonPadding: EdgeInsets.zero, + optionButtonMargin: EdgeInsets.zero, + optionButtonBorderRadius: 4, + ), + ), + trailing: t == appTheme ? checkedIcon : null, + title: Text(fsd.name), + onTap: () => onChanged(t), + ); + }, + separatorBuilder: + (_, __) => PlatformDivider( + height: 1, + // on iOS: 52 (leading) + 14 (default indent) + 16 (padding) + indent: Theme.of(context).platform == TargetPlatform.iOS ? 52 + 14 + 16 : null, + color: Theme.of(context).platform == TargetPlatform.iOS ? null : Colors.transparent, + ), + itemCount: choices.length, + ), + ); + } +} diff --git a/lib/src/view/settings/theme_screen.dart b/lib/src/view/settings/theme_screen.dart index 8fe386ed46..88e8392f37 100644 --- a/lib/src/view/settings/theme_screen.dart +++ b/lib/src/view/settings/theme_screen.dart @@ -14,6 +14,7 @@ import 'package:lichess_mobile/src/utils/color_palette.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/navigation.dart'; import 'package:lichess_mobile/src/utils/screen.dart'; +import 'package:lichess_mobile/src/view/settings/app_theme_screen.dart'; import 'package:lichess_mobile/src/view/settings/board_theme_screen.dart'; import 'package:lichess_mobile/src/view/settings/piece_set_screen.dart'; import 'package:lichess_mobile/src/widgets/adaptive_action_sheet.dart'; @@ -170,43 +171,55 @@ class _BodyState extends ConsumerState<_Body> { ListSection( hasLeading: true, children: [ - if (getCorePalette() != null) - SettingsListTile( - icon: const Icon(Icons.colorize_outlined), - settingsLabel: const Text('Color scheme'), - settingsValue: switch (generalPrefs.appThemeSeed) { - AppThemeSeed.board => context.l10n.board, - AppThemeSeed.system => context.l10n.mobileSystemColors, - }, - onTap: () { - showAdaptiveActionSheet( - context: context, - actions: - AppThemeSeed.values - .where( - (t) => t != AppThemeSeed.system || getCorePalette() != null, - ) - .map( - (t) => BottomSheetAction( - makeLabel: - (context) => switch (t) { - AppThemeSeed.board => Text(context.l10n.board), - AppThemeSeed.system => Text( - context.l10n.mobileSystemColors, - ), - }, - onPressed: (context) { - ref - .read(generalPreferencesProvider.notifier) - .setAppThemeSeed(t); - }, - dismissOnPress: true, - ), - ) - .toList(), - ); - }, - ), + // if (getCorePalette() != null) + // SettingsListTile( + // icon: const Icon(Icons.colorize_outlined), + // settingsLabel: const Text('Color scheme'), + // settingsValue: switch (generalPrefs.appThemeSeed) { + // AppThemeSeed.board => context.l10n.board, + // AppThemeSeed.system => context.l10n.mobileSystemColors, + // }, + // onTap: () { + // showAdaptiveActionSheet( + // context: context, + // actions: + // AppThemeSeed.values + // .where( + // (t) => t != AppThemeSeed.system || getCorePalette() != null, + // ) + // .map( + // (t) => BottomSheetAction( + // makeLabel: + // (context) => switch (t) { + // AppThemeSeed.board => Text(context.l10n.board), + // AppThemeSeed.system => Text( + // context.l10n.mobileSystemColors, + // ), + // }, + // onPressed: (context) { + // ref + // .read(generalPreferencesProvider.notifier) + // .setAppThemeSeed(t); + // }, + // dismissOnPress: true, + // ), + // ) + // .toList(), + // ); + // }, + // ), + SettingsListTile( + icon: const Icon(Icons.palette), + settingsLabel: Text('Theme'), + settingsValue: generalPrefs.appTheme.flexScheme.name, + onTap: () { + pushPlatformRoute( + context, + title: 'Theme', + builder: (context) => const AppThemeScreen(), + ); + }, + ), SettingsListTile( icon: const Icon(LichessIcons.chess_board), settingsLabel: Text(context.l10n.board), diff --git a/lib/src/view/study/study_gamebook.dart b/lib/src/view/study/study_gamebook.dart index c12c09ed5e..716a393771 100644 --- a/lib/src/view/study/study_gamebook.dart +++ b/lib/src/view/study/study_gamebook.dart @@ -5,6 +5,7 @@ import 'package:lichess_mobile/src/model/common/id.dart'; import 'package:lichess_mobile/src/model/study/study_controller.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/widgets/buttons.dart'; +import 'package:lichess_mobile/src/widgets/platform.dart'; import 'package:url_launcher/url_launcher.dart'; class StudyGamebook extends StatelessWidget { @@ -19,7 +20,8 @@ class StudyGamebook extends StatelessWidget { child: Column( children: [ Expanded( - child: Card( + child: PlatformCard( + margin: const EdgeInsets.all(8.0), child: Padding( padding: const EdgeInsets.all(10), child: Column( diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index 87e02b4980..fdc880f6d9 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -182,7 +182,7 @@ class ListSection extends StatelessWidget { decoration: BoxDecoration( color: cupertinoBackgroundColor ?? - Theme.of(context).colorScheme.surfaceContainer, + Theme.of(context).colorScheme.surfaceContainerHigh, borderRadius: cupertinoBorderRadius ?? const BorderRadius.all(Radius.circular(10.0)), ), diff --git a/pubspec.yaml b/pubspec.yaml index c62da5ce0b..dd638a22ae 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,7 +31,7 @@ dependencies: firebase_crashlytics: ^4.0.0 firebase_messaging: ^15.0.0 fl_chart: ^0.70.0 - flex_color_scheme: ^8.0.2 + flex_color_scheme: ^8.1.0 flutter: sdk: flutter flutter_appauth: ^8.0.0+1 From d3b5ae9def63c1bee204ccebf68bad6e15cc314a Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Wed, 15 Jan 2025 16:59:47 +0800 Subject: [PATCH 04/41] More wip on flex color scheme --- lib/src/app.dart | 128 +++++++++--------- .../view/broadcast/broadcast_list_screen.dart | 4 +- .../opening_explorer_screen.dart | 2 +- .../view/play/create_custom_game_screen.dart | 6 +- lib/src/view/settings/app_theme_screen.dart | 78 +++++------ lib/src/view/settings/theme_screen.dart | 49 +------ lib/src/widgets/list.dart | 2 +- 7 files changed, 112 insertions(+), 157 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index e169cfa3ab..04adac0eb2 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -2,6 +2,7 @@ import 'package:dynamic_color/dynamic_color.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show SystemUiOverlayStyle; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lichess_mobile/l10n/l10n.dart'; @@ -130,21 +131,16 @@ class _AppState extends ConsumerState { final lightTheme = FlexThemeData.light( colors: flexSchemeLightColors, - surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold, - blendLevel: 1, - appBarStyle: FlexAppBarStyle.background, - bottomAppBarElevation: 2.0, - visualDensity: FlexColorScheme.comfortablePlatformDensity, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + surfaceMode: FlexSurfaceMode.highSurfaceLowScaffold, + blendLevel: 2, ); final darkTheme = FlexThemeData.dark( colors: flexSchemeDarkColors, surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold, - blendLevel: 28, - appBarStyle: FlexAppBarStyle.background, - bottomAppBarElevation: 2.0, - visualDensity: FlexColorScheme.comfortablePlatformDensity, + blendLevel: 8, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, ); final lightCupertinoTheme = CupertinoThemeData( @@ -163,7 +159,7 @@ class _AppState extends ConsumerState { context, ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), ), - scaffoldBackgroundColor: lightTheme.colorScheme.surface, + scaffoldBackgroundColor: lightTheme.scaffoldBackgroundColor, barBackgroundColor: lightTheme.appBarTheme.backgroundColor?.withValues( alpha: isTablet ? 1.0 : 0.9, ), @@ -186,68 +182,74 @@ class _AppState extends ConsumerState { context, ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), ), - scaffoldBackgroundColor: darkTheme.colorScheme.surface, + scaffoldBackgroundColor: darkTheme.scaffoldBackgroundColor, barBackgroundColor: darkTheme.appBarTheme.backgroundColor?.withValues( alpha: isTablet ? 1.0 : 0.9, ), applyThemeToAll: true, ); - return MaterialApp( - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: kSupportedLocales, - onGenerateTitle: (BuildContext context) => 'lichess.org', - locale: generalPrefs.locale, - theme: lightTheme.copyWith( - cupertinoOverrideTheme: lightCupertinoTheme, - splashFactory: isIOS ? NoSplash.splashFactory : null, - textTheme: isIOS ? Typography.blackCupertino : null, - listTileTheme: ListTileTheme.of(context).copyWith( - titleTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, - subtitleTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, - leadingAndTrailingTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, - ), - navigationBarTheme: NavigationBarTheme.of( - context, - ).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null), - extensions: [lichessCustomColors.harmonized(lightTheme.colorScheme)], + return AnnotatedRegion( + value: FlexColorScheme.themedSystemNavigationBar( + context, + systemNavBarStyle: FlexSystemNavBarStyle.transparent, ), - darkTheme: darkTheme.copyWith( - cupertinoOverrideTheme: darkCupertinoTheme, - splashFactory: isIOS ? NoSplash.splashFactory : null, - textTheme: isIOS ? Typography.whiteCupertino : null, - listTileTheme: ListTileTheme.of(context).copyWith( - titleTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, - subtitleTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, - leadingAndTrailingTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, + child: MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: kSupportedLocales, + onGenerateTitle: (BuildContext context) => 'lichess.org', + locale: generalPrefs.locale, + theme: lightTheme.copyWith( + cupertinoOverrideTheme: lightCupertinoTheme, + splashFactory: isIOS ? NoSplash.splashFactory : null, + textTheme: isIOS ? Typography.blackCupertino : null, + listTileTheme: ListTileTheme.of(context).copyWith( + titleTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, + subtitleTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, + leadingAndTrailingTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, + ), + navigationBarTheme: NavigationBarTheme.of( + context, + ).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null), + extensions: [lichessCustomColors.harmonized(lightTheme.colorScheme)], ), - navigationBarTheme: NavigationBarTheme.of( - context, - ).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null), - extensions: [lichessCustomColors.harmonized(darkTheme.colorScheme)], + darkTheme: darkTheme.copyWith( + cupertinoOverrideTheme: darkCupertinoTheme, + splashFactory: isIOS ? NoSplash.splashFactory : null, + textTheme: isIOS ? Typography.whiteCupertino : null, + listTileTheme: ListTileTheme.of(context).copyWith( + titleTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, + subtitleTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, + leadingAndTrailingTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, + ), + navigationBarTheme: NavigationBarTheme.of( + context, + ).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null), + extensions: [lichessCustomColors.harmonized(darkTheme.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, + // ), + // child: Material(child: child), + // ), + // ); + return Material(child: child); + } + : null, + home: const BottomNavScaffold(), + navigatorObservers: [rootNavPageRouteObserver], ), - 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, - // ), - // child: Material(child: child), - // ), - // ); - return Material(child: child); - } - : null, - home: const BottomNavScaffold(), - navigatorObservers: [rootNavPageRouteObserver], ); } } diff --git a/lib/src/view/broadcast/broadcast_list_screen.dart b/lib/src/view/broadcast/broadcast_list_screen.dart index 4e999f8b2e..18ede9cb17 100644 --- a/lib/src/view/broadcast/broadcast_list_screen.dart +++ b/lib/src/view/broadcast/broadcast_list_screen.dart @@ -44,9 +44,7 @@ class BroadcastListScreen extends StatelessWidget { navigationBar: CupertinoNavigationBar( middle: title, automaticBackgroundVisibility: false, - backgroundColor: Styles.cupertinoAppBarColor - .resolveFrom(context) - .withValues(alpha: 0.0), + backgroundColor: CupertinoTheme.of(context).barBackgroundColor.withValues(alpha: 0.0), border: null, ), child: const _Body(), diff --git a/lib/src/view/opening_explorer/opening_explorer_screen.dart b/lib/src/view/opening_explorer/opening_explorer_screen.dart index e8bd2fc5a4..b53e16a673 100644 --- a/lib/src/view/opening_explorer/opening_explorer_screen.dart +++ b/lib/src/view/opening_explorer/opening_explorer_screen.dart @@ -217,7 +217,7 @@ class _MoveList extends ConsumerWidget implements PreferredSizeWidget { inlineDecoration: Theme.of(context).platform == TargetPlatform.iOS ? BoxDecoration( - color: Styles.cupertinoAppBarColor.resolveFrom(context), + color: CupertinoTheme.of(context).barBackgroundColor, border: const Border(bottom: BorderSide(color: Color(0x4D000000), width: 0.0)), ) : null, diff --git a/lib/src/view/play/create_custom_game_screen.dart b/lib/src/view/play/create_custom_game_screen.dart index be799c53e1..e5e8ccb632 100644 --- a/lib/src/view/play/create_custom_game_screen.dart +++ b/lib/src/view/play/create_custom_game_screen.dart @@ -48,7 +48,7 @@ class CreateCustomGameScreen extends StatelessWidget { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( automaticBackgroundVisibility: false, - backgroundColor: Styles.cupertinoAppBarColor.resolveFrom(context).withValues(alpha: 0.0), + backgroundColor: CupertinoTheme.of(context).barBackgroundColor.withValues(alpha: 0.0), border: null, ), child: const _CupertinoBody(), @@ -210,14 +210,14 @@ class _TabView extends StatelessWidget { ? EdgeInsets.only(top: MediaQuery.paddingOf(context).top) : EdgeInsets.zero) + Styles.verticalBodyPadding; - final backgroundColor = Styles.cupertinoAppBarColor.resolveFrom(context); + final backgroundColor = CupertinoTheme.of(context).barBackgroundColor; return CustomScrollView( slivers: [ if (cupertinoTabSwitcher != null) PinnedHeaderSliver( child: ClipRect( child: BackdropFilter( - enabled: backgroundColor.alpha != 0xFF, + enabled: backgroundColor.a != 1, filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), child: AnimatedContainer( duration: const Duration(milliseconds: 200), diff --git a/lib/src/view/settings/app_theme_screen.dart b/lib/src/view/settings/app_theme_screen.dart index 15baeec9fa..cf6f0f72b4 100644 --- a/lib/src/view/settings/app_theme_screen.dart +++ b/lib/src/view/settings/app_theme_screen.dart @@ -2,10 +2,8 @@ import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; import 'package:lichess_mobile/src/model/settings/general_preferences.dart'; import 'package:lichess_mobile/src/utils/color_palette.dart'; -import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; @@ -18,7 +16,7 @@ class AppThemeScreen extends StatelessWidget { } Widget _androidBuilder(BuildContext context) { - return Scaffold(appBar: AppBar(title: Text('Theme')), body: _Body()); + return Scaffold(appBar: AppBar(title: const Text('Theme')), body: _Body()); } Widget _iosBuilder(BuildContext context) { @@ -48,46 +46,44 @@ class _Body extends ConsumerWidget { final brightness = Theme.of(context).brightness; - return SafeArea( - child: ListView.separated( - itemBuilder: (context, index) { - final t = choices[index]; - final fsd = t.flexScheme; + return ListView.separated( + itemBuilder: (context, index) { + final t = choices[index]; + final fsd = t.flexScheme; - return AdaptiveListTile( - // selected: t == appTheme, - leading: SizedBox( - width: 52, - height: 36, - child: FlexThemeModeOptionButton( - flexSchemeColor: brightness == Brightness.light ? fsd.light : fsd.dark, - selected: false, - unselectedBorder: BorderSide.none, - selectedBorder: BorderSide(color: Theme.of(context).colorScheme.outline, width: 3), - backgroundColor: Theme.of(context).colorScheme.surface, - width: 26, - height: 18, - padding: EdgeInsets.zero, - borderRadius: 0, - optionButtonPadding: EdgeInsets.zero, - optionButtonMargin: EdgeInsets.zero, - optionButtonBorderRadius: 4, - ), + return AdaptiveListTile( + // selected: t == appTheme, + leading: SizedBox( + width: 52, + height: 36, + child: FlexThemeModeOptionButton( + flexSchemeColor: brightness == Brightness.light ? fsd.light : fsd.dark, + selected: false, + unselectedBorder: BorderSide.none, + selectedBorder: BorderSide(color: Theme.of(context).colorScheme.outline, width: 3), + backgroundColor: Theme.of(context).colorScheme.surface, + width: 26, + height: 18, + padding: EdgeInsets.zero, + borderRadius: 0, + optionButtonPadding: EdgeInsets.zero, + optionButtonMargin: EdgeInsets.zero, + optionButtonBorderRadius: 4, ), - trailing: t == appTheme ? checkedIcon : null, - title: Text(fsd.name), - onTap: () => onChanged(t), - ); - }, - separatorBuilder: - (_, __) => PlatformDivider( - height: 1, - // on iOS: 52 (leading) + 14 (default indent) + 16 (padding) - indent: Theme.of(context).platform == TargetPlatform.iOS ? 52 + 14 + 16 : null, - color: Theme.of(context).platform == TargetPlatform.iOS ? null : Colors.transparent, - ), - itemCount: choices.length, - ), + ), + trailing: t == appTheme ? checkedIcon : null, + title: Text(fsd.name), + onTap: () => onChanged(t), + ); + }, + separatorBuilder: + (_, __) => PlatformDivider( + height: 1, + // on iOS: 52 (leading) + 14 (default indent) + 16 (padding) + indent: Theme.of(context).platform == TargetPlatform.iOS ? 52 + 14 + 16 : null, + color: Theme.of(context).platform == TargetPlatform.iOS ? null : Colors.transparent, + ), + itemCount: choices.length, ); } } diff --git a/lib/src/view/settings/theme_screen.dart b/lib/src/view/settings/theme_screen.dart index 88e8392f37..253f3526b6 100644 --- a/lib/src/view/settings/theme_screen.dart +++ b/lib/src/view/settings/theme_screen.dart @@ -10,14 +10,12 @@ import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; import 'package:lichess_mobile/src/model/settings/general_preferences.dart'; import 'package:lichess_mobile/src/styles/lichess_icons.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; -import 'package:lichess_mobile/src/utils/color_palette.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/navigation.dart'; import 'package:lichess_mobile/src/utils/screen.dart'; import 'package:lichess_mobile/src/view/settings/app_theme_screen.dart'; import 'package:lichess_mobile/src/view/settings/board_theme_screen.dart'; import 'package:lichess_mobile/src/view/settings/piece_set_screen.dart'; -import 'package:lichess_mobile/src/widgets/adaptive_action_sheet.dart'; import 'package:lichess_mobile/src/widgets/adaptive_choice_picker.dart'; import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; @@ -34,9 +32,7 @@ class ThemeScreen extends StatelessWidget { (context) => CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( automaticBackgroundVisibility: false, - backgroundColor: Styles.cupertinoAppBarColor - .resolveFrom(context) - .withValues(alpha: 0.0), + backgroundColor: CupertinoTheme.of(context).barBackgroundColor.withValues(alpha: 0.0), border: null, ), child: const _Body(), @@ -112,7 +108,7 @@ class _BodyState extends ConsumerState<_Body> { final boardSize = isTabletOrLarger(context) ? 350.0 : 200.0; - final backgroundColor = Styles.cupertinoAppBarColor.resolveFrom(context); + final backgroundColor = CupertinoTheme.of(context).barBackgroundColor; return NotificationListener( onNotification: handleScrollNotification, @@ -122,7 +118,7 @@ class _BodyState extends ConsumerState<_Body> { PinnedHeaderSliver( child: ClipRect( child: BackdropFilter( - enabled: backgroundColor.alpha != 0xFF, + enabled: backgroundColor.a != 1, filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), child: AnimatedContainer( duration: const Duration(milliseconds: 200), @@ -171,46 +167,9 @@ class _BodyState extends ConsumerState<_Body> { ListSection( hasLeading: true, children: [ - // if (getCorePalette() != null) - // SettingsListTile( - // icon: const Icon(Icons.colorize_outlined), - // settingsLabel: const Text('Color scheme'), - // settingsValue: switch (generalPrefs.appThemeSeed) { - // AppThemeSeed.board => context.l10n.board, - // AppThemeSeed.system => context.l10n.mobileSystemColors, - // }, - // onTap: () { - // showAdaptiveActionSheet( - // context: context, - // actions: - // AppThemeSeed.values - // .where( - // (t) => t != AppThemeSeed.system || getCorePalette() != null, - // ) - // .map( - // (t) => BottomSheetAction( - // makeLabel: - // (context) => switch (t) { - // AppThemeSeed.board => Text(context.l10n.board), - // AppThemeSeed.system => Text( - // context.l10n.mobileSystemColors, - // ), - // }, - // onPressed: (context) { - // ref - // .read(generalPreferencesProvider.notifier) - // .setAppThemeSeed(t); - // }, - // dismissOnPress: true, - // ), - // ) - // .toList(), - // ); - // }, - // ), SettingsListTile( icon: const Icon(Icons.palette), - settingsLabel: Text('Theme'), + settingsLabel: const Text('Theme'), settingsValue: generalPrefs.appTheme.flexScheme.name, onTap: () { pushPlatformRoute( diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index fdc880f6d9..87e02b4980 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -182,7 +182,7 @@ class ListSection extends StatelessWidget { decoration: BoxDecoration( color: cupertinoBackgroundColor ?? - Theme.of(context).colorScheme.surfaceContainerHigh, + Theme.of(context).colorScheme.surfaceContainer, borderRadius: cupertinoBorderRadius ?? const BorderRadius.all(Radius.circular(10.0)), ), From b7152fd3de59843e16a71f6e451fd9ac6a7a6564 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Wed, 15 Jan 2025 23:14:50 +0800 Subject: [PATCH 05/41] More work on flex color scheme --- lib/src/app.dart | 18 ++++---- lib/src/model/settings/board_preferences.dart | 42 ++++++++++++++++++- .../model/settings/general_preferences.dart | 12 +++++- lib/src/view/play/create_game_options.dart | 9 +++- lib/src/view/play/quick_game_button.dart | 6 +-- lib/src/view/play/quick_game_matrix.dart | 2 +- lib/src/view/play/time_control_modal.dart | 5 +-- lib/src/view/puzzle/dashboard_screen.dart | 2 +- lib/src/view/settings/app_theme_screen.dart | 4 +- lib/src/view/settings/theme_screen.dart | 2 +- lib/src/view/study/study_gamebook.dart | 14 +++---- lib/src/view/user/perf_stats_screen.dart | 6 +-- lib/src/widgets/adaptive_bottom_sheet.dart | 10 ----- lib/src/widgets/list.dart | 10 +++-- lib/src/widgets/platform.dart | 4 +- lib/src/widgets/settings.dart | 6 ++- 16 files changed, 100 insertions(+), 52 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 04adac0eb2..6b1fb17f55 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,4 +1,3 @@ -import 'package:dynamic_color/dynamic_color.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -13,7 +12,6 @@ import 'package:lichess_mobile/src/model/common/preloaded_data.dart'; import 'package:lichess_mobile/src/model/correspondence/correspondence_service.dart'; import 'package:lichess_mobile/src/model/notifications/notification_service.dart'; import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; -import 'package:lichess_mobile/src/model/settings/brightness.dart'; import 'package:lichess_mobile/src/model/settings/general_preferences.dart'; import 'package:lichess_mobile/src/navigation.dart'; import 'package:lichess_mobile/src/network/connectivity.dart'; @@ -121,24 +119,26 @@ class _AppState extends ConsumerState { @override Widget build(BuildContext context) { final generalPrefs = ref.watch(generalPreferencesProvider); - // final boardTheme = ref.watch(boardPreferencesProvider.select((state) => state.boardTheme)); + final boardTheme = ref.watch(boardPreferencesProvider.select((state) => state.boardTheme)); final isTablet = isTabletOrLarger(context); final isIOS = Theme.of(context).platform == TargetPlatform.iOS; final remainingHeight = estimateRemainingHeightLeftBoard(context); - final flexSchemeLightColors = generalPrefs.appTheme.flexScheme.light; - final flexSchemeDarkColors = generalPrefs.appTheme.flexScheme.dark; + final flexScheme = generalPrefs.appTheme.getFlexScheme(boardTheme); + final flexSchemeLightColors = flexScheme.light; + final flexSchemeDarkColors = flexScheme.dark; final lightTheme = FlexThemeData.light( colors: flexSchemeLightColors, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), - surfaceMode: FlexSurfaceMode.highSurfaceLowScaffold, - blendLevel: 2, + surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, + appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, + blendLevel: 6, ); final darkTheme = FlexThemeData.dark( colors: flexSchemeDarkColors, - surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold, - blendLevel: 8, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + blendLevel: 12, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, ); diff --git a/lib/src/model/settings/board_preferences.dart b/lib/src/model/settings/board_preferences.dart index 9e84bd3dd7..9c5cc98f48 100644 --- a/lib/src/model/settings/board_preferences.dart +++ b/lib/src/model/settings/board_preferences.dart @@ -1,6 +1,6 @@ import 'package:chessground/chessground.dart'; +import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:lichess_mobile/l10n/l10n.dart'; import 'package:lichess_mobile/src/model/settings/preferences_storage.dart'; @@ -293,6 +293,46 @@ enum BoardTheme { } } + FlexSchemeData get flexScheme { + final lightScheme = SeedColorScheme.fromSeeds( + primaryKey: colors.darkSquare, + secondaryKey: colors.lightSquare, + brightness: Brightness.light, + ); + final darkScheme = SeedColorScheme.fromSeeds( + primaryKey: colors.darkSquare, + secondaryKey: colors.lightSquare, + brightness: Brightness.dark, + ); + return FlexSchemeData( + name: 'Chessboard', + description: 'Chessboard based theme: $label', + light: FlexSchemeColor( + primary: lightScheme.primary, + primaryContainer: lightScheme.primaryContainer, + secondary: lightScheme.secondary, + secondaryContainer: lightScheme.secondaryContainer, + tertiary: lightScheme.tertiary, + tertiaryContainer: lightScheme.tertiaryContainer, + error: lightScheme.error, + errorContainer: lightScheme.errorContainer, + ), + dark: FlexSchemeColor( + primary: darkScheme.primary, + primaryContainer: darkScheme.primaryContainer, + primaryLightRef: lightScheme.primary, + secondary: darkScheme.secondary, + secondaryContainer: darkScheme.secondaryContainer, + secondaryLightRef: lightScheme.secondary, + tertiary: darkScheme.tertiary, + tertiaryContainer: darkScheme.tertiaryContainer, + tertiaryLightRef: lightScheme.tertiary, + error: darkScheme.error, + errorContainer: darkScheme.errorContainer, + ), + ); + } + Widget get thumbnail => this == BoardTheme.system ? SizedBox( diff --git a/lib/src/model/settings/general_preferences.dart b/lib/src/model/settings/general_preferences.dart index e02b8797f1..690c3c770f 100644 --- a/lib/src/model/settings/general_preferences.dart +++ b/lib/src/model/settings/general_preferences.dart @@ -2,6 +2,7 @@ import 'dart:ui' show Locale; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; import 'package:lichess_mobile/src/model/settings/preferences_storage.dart'; import 'package:lichess_mobile/src/utils/color_palette.dart'; import 'package:lichess_mobile/src/utils/json.dart'; @@ -105,6 +106,9 @@ enum AppTheme { /// The app theme is based on the user's system theme (only available on Android 10+). system, + /// The app theme is based on the chess board + board, + /// Below values from [FlexScheme] blue, indigo, @@ -162,8 +166,12 @@ enum AppTheme { static final _flexSchemesNameMap = FlexScheme.values.asNameMap(); - FlexSchemeData get flexScheme => - this == AppTheme.system ? getSystemScheme()! : _flexSchemesNameMap[name]!.data; + FlexSchemeData getFlexScheme(BoardTheme boardTheme) => + this == AppTheme.system + ? getSystemScheme()! + : this == AppTheme.board + ? boardTheme.flexScheme + : _flexSchemesNameMap[name]!.data; } /// Describes the background theme of the app. diff --git a/lib/src/view/play/create_game_options.dart b/lib/src/view/play/create_game_options.dart index c5c1bdb528..33a495e1b9 100644 --- a/lib/src/view/play/create_game_options.dart +++ b/lib/src/view/play/create_game_options.dart @@ -110,6 +110,13 @@ class _CreateGamePlatformButton extends StatelessWidget { title: Text(label, style: Styles.mainListTileTitle), onTap: onTap, ) - : ElevatedButton.icon(onPressed: onTap, icon: Icon(icon), label: Text(label)); + : OutlinedButton.icon( + onPressed: onTap, + icon: Icon(icon), + label: Text(label, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500)), + style: const ButtonStyle( + padding: WidgetStatePropertyAll(EdgeInsets.symmetric(vertical: 8.0)), + ), + ); } } diff --git a/lib/src/view/play/quick_game_button.dart b/lib/src/view/play/quick_game_button.dart index 7c20db1ecf..3573fe59b1 100644 --- a/lib/src/view/play/quick_game_button.dart +++ b/lib/src/view/play/quick_game_button.dart @@ -6,7 +6,6 @@ import 'package:lichess_mobile/src/model/auth/auth_session.dart'; import 'package:lichess_mobile/src/model/lobby/game_seek.dart'; import 'package:lichess_mobile/src/model/lobby/game_setup_preferences.dart'; import 'package:lichess_mobile/src/network/connectivity.dart'; -import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/navigation.dart'; import 'package:lichess_mobile/src/view/game/game_screen.dart'; @@ -22,6 +21,7 @@ class QuickGameButton extends ConsumerWidget { final playPrefs = ref.watch(gameSetupPreferencesProvider); final session = ref.watch(authSessionProvider); final isOnline = ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? false; + const buttonTextStyle = TextStyle(fontSize: 18, fontWeight: FontWeight.bold); return Row( children: [ @@ -88,7 +88,7 @@ class QuickGameButton extends ConsumerWidget { ); } : null, - child: Text(context.l10n.play, style: Styles.bold), + child: Text(context.l10n.play, style: buttonTextStyle), ) : FilledButton( onPressed: @@ -109,7 +109,7 @@ class QuickGameButton extends ConsumerWidget { : null, child: Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Text(context.l10n.play, style: Styles.bold), + child: Text(context.l10n.play, style: buttonTextStyle), ), ), ), diff --git a/lib/src/view/play/quick_game_matrix.dart b/lib/src/view/play/quick_game_matrix.dart index e657403579..daef3d668c 100644 --- a/lib/src/view/play/quick_game_matrix.dart +++ b/lib/src/view/play/quick_game_matrix.dart @@ -142,7 +142,7 @@ class _ChoiceChip extends StatefulWidget { class _ChoiceChipState extends State<_ChoiceChip> { @override Widget build(BuildContext context) { - final cardColor = Theme.of(context).colorScheme.surfaceContainerHigh.withValues(alpha: 0.7); + final cardColor = Theme.of(context).colorScheme.surfaceContainerHighest.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 5ff39f8fa2..a53e20213a 100644 --- a/lib/src/view/play/time_control_modal.dart +++ b/lib/src/view/play/time_control_modal.dart @@ -240,10 +240,7 @@ 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).colorScheme.surfaceContainerHighest, borderRadius: const BorderRadius.all(Radius.circular(5.0)), border: selected diff --git a/lib/src/view/puzzle/dashboard_screen.dart b/lib/src/view/puzzle/dashboard_screen.dart index 49a09e9067..9518935df6 100644 --- a/lib/src/view/puzzle/dashboard_screen.dart +++ b/lib/src/view/puzzle/dashboard_screen.dart @@ -164,7 +164,7 @@ class PuzzleChart extends StatelessWidget { @override Widget build(BuildContext context) { final radarColor = Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5); - final chartColor = Theme.of(context).colorScheme.tertiary; + final chartColor = Theme.of(context).colorScheme.secondary; return RadarChart( RadarChartData( radarBorderData: BorderSide(width: 0.5, color: radarColor), diff --git a/lib/src/view/settings/app_theme_screen.dart b/lib/src/view/settings/app_theme_screen.dart index cf6f0f72b4..44bfcf7ad0 100644 --- a/lib/src/view/settings/app_theme_screen.dart +++ b/lib/src/view/settings/app_theme_screen.dart @@ -2,6 +2,7 @@ import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; import 'package:lichess_mobile/src/model/settings/general_preferences.dart'; import 'package:lichess_mobile/src/utils/color_palette.dart'; import 'package:lichess_mobile/src/widgets/list.dart'; @@ -28,6 +29,7 @@ class _Body extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final appTheme = ref.watch(generalPreferencesProvider.select((p) => p.appTheme)); + final boardPrefs = ref.watch(boardPreferencesProvider); final hasSystemColors = getCorePalette() != null; @@ -49,7 +51,7 @@ class _Body extends ConsumerWidget { return ListView.separated( itemBuilder: (context, index) { final t = choices[index]; - final fsd = t.flexScheme; + final fsd = t.getFlexScheme(boardPrefs.boardTheme); return AdaptiveListTile( // selected: t == appTheme, diff --git a/lib/src/view/settings/theme_screen.dart b/lib/src/view/settings/theme_screen.dart index 253f3526b6..1d4e872aa6 100644 --- a/lib/src/view/settings/theme_screen.dart +++ b/lib/src/view/settings/theme_screen.dart @@ -170,7 +170,7 @@ class _BodyState extends ConsumerState<_Body> { SettingsListTile( icon: const Icon(Icons.palette), settingsLabel: const Text('Theme'), - settingsValue: generalPrefs.appTheme.flexScheme.name, + settingsValue: generalPrefs.appTheme.getFlexScheme(boardPrefs.boardTheme).name, onTap: () { pushPlatformRoute( context, diff --git a/lib/src/view/study/study_gamebook.dart b/lib/src/view/study/study_gamebook.dart index 716a393771..44e34a0e83 100644 --- a/lib/src/view/study/study_gamebook.dart +++ b/lib/src/view/study/study_gamebook.dart @@ -5,7 +5,6 @@ import 'package:lichess_mobile/src/model/common/id.dart'; import 'package:lichess_mobile/src/model/study/study_controller.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/widgets/buttons.dart'; -import 'package:lichess_mobile/src/widgets/platform.dart'; import 'package:url_launcher/url_launcher.dart'; class StudyGamebook extends StatelessWidget { @@ -20,14 +19,11 @@ class StudyGamebook extends StatelessWidget { child: Column( children: [ Expanded( - child: PlatformCard( - margin: const EdgeInsets.all(8.0), - child: Padding( - padding: const EdgeInsets.all(10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [_Comment(id: id), _Hint(id: id)], - ), + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [_Comment(id: id), _Hint(id: id)], ), ), ), diff --git a/lib/src/view/user/perf_stats_screen.dart b/lib/src/view/user/perf_stats_screen.dart index 78bc51d3cb..2b75cdb596 100644 --- a/lib/src/view/user/perf_stats_screen.dart +++ b/lib/src/view/user/perf_stats_screen.dart @@ -397,7 +397,7 @@ class _UserGameWidget extends StatelessWidget { // (Return a button? Wrap with InkWell?) const defaultDateFontSize = 16.0; final defaultDateStyle = TextStyle( - color: Theme.of(context).colorScheme.tertiary, + color: Theme.of(context).colorScheme.secondary, fontSize: defaultDateFontSize, ); @@ -720,7 +720,7 @@ class _EloChartState extends State<_EloChart> { @override Widget build(BuildContext context) { final borderColor = Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5); - final chartColor = Theme.of(context).colorScheme.tertiary; + final chartColor = Theme.of(context).colorScheme.secondary; final chartDateFormatter = switch (_selectedRange) { DateRange.oneWeek => DateFormat.MMMd(), DateRange.oneMonth => DateFormat.MMMd(), @@ -883,7 +883,7 @@ class _RangeButton extends StatelessWidget { @override Widget build(BuildContext context) { - final chartColor = Theme.of(context).colorScheme.tertiary; + final chartColor = Theme.of(context).colorScheme.secondary; return PlatformCard( color: selected ? chartColor.withValues(alpha: 0.2) : null, diff --git a/lib/src/widgets/adaptive_bottom_sheet.dart b/lib/src/widgets/adaptive_bottom_sheet.dart index 16e964563a..d0435e44a9 100644 --- a/lib/src/widgets/adaptive_bottom_sheet.dart +++ b/lib/src/widgets/adaptive_bottom_sheet.dart @@ -28,13 +28,6 @@ Future showAdaptiveBottomSheet({ ) : null, constraints: constraints, - backgroundColor: - Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoDynamicColor.resolve( - CupertinoColors.tertiarySystemGroupedBackground, - context, - ) - : null, elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0 : null, builder: builder, ); @@ -85,9 +78,6 @@ class BottomSheetContextMenuAction extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformListTile( - cupertinoBackgroundColor: CupertinoColors.tertiarySystemGroupedBackground.resolveFrom( - context, - ), leading: Icon(icon), title: child, onTap: () { diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index 87e02b4980..5875718ada 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -72,7 +72,8 @@ class ListSection extends StatelessWidget { @override Widget build(BuildContext context) { - switch (Theme.of(context).platform) { + final theme = Theme.of(context); + switch (theme.platform) { case TargetPlatform.android: return _isLoading ? Padding( @@ -182,7 +183,9 @@ class ListSection extends StatelessWidget { decoration: BoxDecoration( color: cupertinoBackgroundColor ?? - Theme.of(context).colorScheme.surfaceContainer, + (theme.brightness == Brightness.light + ? theme.colorScheme.surfaceContainerLowest + : theme.colorScheme.surfaceContainerHighest), borderRadius: cupertinoBorderRadius ?? const BorderRadius.all(Radius.circular(10.0)), ), @@ -196,7 +199,7 @@ class ListSection extends StatelessWidget { ), ); default: - assert(false, 'Unexpected platform ${Theme.of(context).platform}'); + assert(false, 'Unexpected platform ${theme.platform}'); return const SizedBox.shrink(); } } @@ -335,6 +338,7 @@ class PlatformListTile extends StatelessWidget { selected == true ? CupertinoColors.systemGrey4.resolveFrom(context) : cupertinoBackgroundColor, + // backgroundColorActivated: Theme.of(context).colorScheme.surfaceContainerHighest, leading: leading, title: harmonizeCupertinoTitleStyle diff --git a/lib/src/widgets/platform.dart b/lib/src/widgets/platform.dart index c8f8b92b14..ad64bfcb7d 100644 --- a/lib/src/widgets/platform.dart +++ b/lib/src/widgets/platform.dart @@ -102,7 +102,7 @@ class PlatformCard extends StatelessWidget { clipBehavior: clipBehavior, child: child, ) - : Card( + : Card.filled( shape: borderRadius != null ? RoundedRectangleBorder(borderRadius: borderRadius!) @@ -111,7 +111,7 @@ class PlatformCard extends StatelessWidget { shadowColor: shadowColor, semanticContainer: semanticContainer, elevation: elevation, - margin: margin, + margin: margin ?? EdgeInsets.zero, clipBehavior: clipBehavior, child: child, ), diff --git a/lib/src/widgets/settings.dart b/lib/src/widgets/settings.dart index 2c05abf236..32cde5a72a 100644 --- a/lib/src/widgets/settings.dart +++ b/lib/src/widgets/settings.dart @@ -271,6 +271,7 @@ class ChoicePicker extends StatelessWidget { ); case TargetPlatform.iOS: final tileConstructor = notchedTile ? CupertinoListTile.notched : CupertinoListTile.new; + final theme = Theme.of(context); return Padding( padding: margin ?? Styles.bodySectionPadding, child: Opacity( @@ -278,7 +279,10 @@ class ChoicePicker extends StatelessWidget { child: CupertinoListSection.insetGrouped( backgroundColor: CupertinoTheme.of(context).scaffoldBackgroundColor, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainer, + color: + theme.brightness == Brightness.light + ? theme.colorScheme.surfaceContainerLowest + : theme.colorScheme.surfaceContainerHighest, borderRadius: const BorderRadius.all(Radius.circular(10.0)), ), separatorColor: Styles.cupertinoSeparatorColor.resolveFrom(context), From 1e82ccf5ec53dfd700b432f832361451d716a965 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 16 Jan 2025 14:30:03 +0800 Subject: [PATCH 06/41] Flex theme fixes --- lib/src/app.dart | 2 +- lib/src/init.dart | 6 +++--- lib/src/model/settings/general_preferences.dart | 7 +------ .../view/opening_explorer/opening_explorer_screen.dart | 1 - lib/src/view/puzzle/dashboard_screen.dart | 8 +++++++- lib/src/view/settings/app_theme_screen.dart | 3 ++- lib/src/view/settings/settings_tab_screen.dart | 6 +++++- lib/src/view/settings/theme_screen.dart | 6 +++--- lib/src/view/user/perf_stats_screen.dart | 7 ++----- lib/src/widgets/adaptive_bottom_sheet.dart | 1 - lib/src/widgets/platform.dart | 1 - lib/src/widgets/stat_card.dart | 3 +++ 12 files changed, 27 insertions(+), 24 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 6b1fb17f55..bd78392fbb 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -133,7 +133,7 @@ class _AppState extends ConsumerState { cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, - blendLevel: 6, + blendLevel: 7, ); final darkTheme = FlexThemeData.dark( colors: flexSchemeDarkColors, diff --git a/lib/src/init.dart b/lib/src/init.dart index 655a4a5d31..b5a78f20fe 100644 --- a/lib/src/init.dart +++ b/lib/src/init.dart @@ -33,7 +33,7 @@ Future setupFirstLaunch() async { final installedVersion = prefs.getString('installed_version'); // TODO remove this migration code after a few releases - if (installedVersion != null && Version.parse(installedVersion) <= Version(0, 13, 9)) { + if (installedVersion != null && Version.parse(installedVersion) <= Version(0, 14, 0)) { _migrateThemeSettings(); } @@ -68,9 +68,9 @@ Future _migrateThemeSettings() async { } final generalPrefs = GeneralPrefs.fromJson(jsonDecode(stored) as Map); final migrated = generalPrefs.copyWith( - appThemeSeed: + appTheme: // ignore: deprecated_member_use_from_same_package - generalPrefs.systemColors == true ? AppThemeSeed.system : AppThemeSeed.board, + generalPrefs.appThemeSeed == AppThemeSeed.system ? AppTheme.system : AppTheme.gold, ); await prefs.setString(PrefCategory.general.storageKey, jsonEncode(migrated.toJson())); } catch (e) { diff --git a/lib/src/model/settings/general_preferences.dart b/lib/src/model/settings/general_preferences.dart index 690c3c770f..a692cf3dad 100644 --- a/lib/src/model/settings/general_preferences.dart +++ b/lib/src/model/settings/general_preferences.dart @@ -49,10 +49,6 @@ class GeneralPreferences extends _$GeneralPreferences with PreferencesStorage setAppThemeSeed(AppThemeSeed seed) { - return save(state.copyWith(appThemeSeed: seed)); - } - Future setAppTheme(AppTheme appTheme) { return save(state.copyWith(appTheme: appTheme)); } @@ -67,12 +63,11 @@ class GeneralPrefs with _$GeneralPrefs implements Serializable { @JsonKey(unknownEnumValue: SoundTheme.standard) required SoundTheme soundTheme, @JsonKey(defaultValue: 0.8) required double masterVolume, - @Deprecated('Use appThemeSeed instead') bool? systemColors, - @JsonKey(unknownEnumValue: AppTheme.gold, defaultValue: AppTheme.gold) required AppTheme appTheme, /// App theme seed + @Deprecated('Use appTheme instead') @JsonKey(unknownEnumValue: AppThemeSeed.board, defaultValue: AppThemeSeed.board) required AppThemeSeed appThemeSeed, diff --git a/lib/src/view/opening_explorer/opening_explorer_screen.dart b/lib/src/view/opening_explorer/opening_explorer_screen.dart index b53e16a673..ba0a6b7320 100644 --- a/lib/src/view/opening_explorer/opening_explorer_screen.dart +++ b/lib/src/view/opening_explorer/opening_explorer_screen.dart @@ -7,7 +7,6 @@ import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart'; import 'package:lichess_mobile/src/model/common/chess.dart'; import 'package:lichess_mobile/src/model/opening_explorer/opening_explorer.dart'; import 'package:lichess_mobile/src/model/opening_explorer/opening_explorer_preferences.dart'; -import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/screen.dart'; import 'package:lichess_mobile/src/view/analysis/analysis_board.dart'; diff --git a/lib/src/view/puzzle/dashboard_screen.dart b/lib/src/view/puzzle/dashboard_screen.dart index 9518935df6..923c43f83d 100644 --- a/lib/src/view/puzzle/dashboard_screen.dart +++ b/lib/src/view/puzzle/dashboard_screen.dart @@ -71,7 +71,11 @@ class PuzzleDashboardWidget extends ConsumerWidget { ? EdgeInsets.zero : Styles.horizontalBodyPadding, child: StatCardRow([ - StatCard(context.l10n.performance, value: dashboard.global.performance.toString()), + StatCard( + context.l10n.performance, + value: dashboard.global.performance.toString(), + backgroundColor: Colors.transparent, + ), StatCard( context.l10n .puzzleNbPlayed(dashboard.global.nb) @@ -79,10 +83,12 @@ class PuzzleDashboardWidget extends ConsumerWidget { .trim() .capitalize(), value: dashboard.global.nb.toString().localizeNumbers(), + backgroundColor: Colors.transparent, ), StatCard( context.l10n.puzzleSolved.capitalize(), value: '${((dashboard.global.firstWins / dashboard.global.nb) * 100).round()}%', + backgroundColor: Colors.transparent, ), ]), ), diff --git a/lib/src/view/settings/app_theme_screen.dart b/lib/src/view/settings/app_theme_screen.dart index 44bfcf7ad0..220c513e7a 100644 --- a/lib/src/view/settings/app_theme_screen.dart +++ b/lib/src/view/settings/app_theme_screen.dart @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; import 'package:lichess_mobile/src/model/settings/general_preferences.dart'; import 'package:lichess_mobile/src/utils/color_palette.dart'; +import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; @@ -17,7 +18,7 @@ class AppThemeScreen extends StatelessWidget { } Widget _androidBuilder(BuildContext context) { - return Scaffold(appBar: AppBar(title: const Text('Theme')), body: _Body()); + return Scaffold(appBar: AppBar(title: Text(context.l10n.mobileTheme)), body: _Body()); } Widget _iosBuilder(BuildContext context) { diff --git a/lib/src/view/settings/settings_tab_screen.dart b/lib/src/view/settings/settings_tab_screen.dart index 89aa1d117a..b36ff480af 100644 --- a/lib/src/view/settings/settings_tab_screen.dart +++ b/lib/src/view/settings/settings_tab_screen.dart @@ -227,7 +227,11 @@ class _Body extends ConsumerWidget { ? const CupertinoListTileChevron() : null, onTap: () { - pushPlatformRoute(context, title: 'Theme', builder: (context) => const ThemeScreen()); + pushPlatformRoute( + context, + title: context.l10n.mobileTheme, + builder: (context) => const ThemeScreen(), + ); }, ), PlatformListTile( diff --git a/lib/src/view/settings/theme_screen.dart b/lib/src/view/settings/theme_screen.dart index 1d4e872aa6..9dd9b0b531 100644 --- a/lib/src/view/settings/theme_screen.dart +++ b/lib/src/view/settings/theme_screen.dart @@ -148,7 +148,7 @@ class _BodyState extends ConsumerState<_Body> { else SliverAppBar( pinned: true, - title: const Text('Theme'), + title: Text(context.l10n.mobileTheme), bottom: PreferredSize( preferredSize: Size.fromHeight(boardSize + 16.0), child: Padding( @@ -169,12 +169,12 @@ class _BodyState extends ConsumerState<_Body> { children: [ SettingsListTile( icon: const Icon(Icons.palette), - settingsLabel: const Text('Theme'), + settingsLabel: Text(context.l10n.mobileTheme), settingsValue: generalPrefs.appTheme.getFlexScheme(boardPrefs.boardTheme).name, onTap: () { pushPlatformRoute( context, - title: 'Theme', + title: context.l10n.mobileTheme, builder: (context) => const AppThemeScreen(), ); }, diff --git a/lib/src/view/user/perf_stats_screen.dart b/lib/src/view/user/perf_stats_screen.dart index 2b75cdb596..56e0451563 100644 --- a/lib/src/view/user/perf_stats_screen.dart +++ b/lib/src/view/user/perf_stats_screen.dart @@ -396,13 +396,10 @@ class _UserGameWidget extends StatelessWidget { // TODO: Implement functionality to view game on tap. // (Return a button? Wrap with InkWell?) const defaultDateFontSize = 16.0; - final defaultDateStyle = TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontSize: defaultDateFontSize, - ); + const defaultDateStyle = TextStyle(fontSize: defaultDateFontSize); return game == null - ? Text('?', style: defaultDateStyle) + ? const Text('?', style: defaultDateStyle) : Text(_dateFormatter.format(game!.finishedAt), style: defaultDateStyle); } } diff --git a/lib/src/widgets/adaptive_bottom_sheet.dart b/lib/src/widgets/adaptive_bottom_sheet.dart index d0435e44a9..e10c22490e 100644 --- a/lib/src/widgets/adaptive_bottom_sheet.dart +++ b/lib/src/widgets/adaptive_bottom_sheet.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:lichess_mobile/src/widgets/list.dart'; diff --git a/lib/src/widgets/platform.dart b/lib/src/widgets/platform.dart index ad64bfcb7d..b39514f9d2 100644 --- a/lib/src/widgets/platform.dart +++ b/lib/src/widgets/platform.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lichess_mobile/src/constants.dart'; -import 'package:lichess_mobile/src/styles/styles.dart'; /// A simple widget that builds different things on different platforms. class PlatformWidget extends StatelessWidget { diff --git a/lib/src/widgets/stat_card.dart b/lib/src/widgets/stat_card.dart index 92c5e3fb18..616718553d 100644 --- a/lib/src/widgets/stat_card.dart +++ b/lib/src/widgets/stat_card.dart @@ -16,6 +16,7 @@ class StatCard extends StatelessWidget { this.opacity, this.statFontSize, this.valueFontSize, + this.backgroundColor, }); final String stat; @@ -25,6 +26,7 @@ class StatCard extends StatelessWidget { final double? opacity; final double? statFontSize; final double? valueFontSize; + final Color? backgroundColor; @override Widget build(BuildContext context) { @@ -38,6 +40,7 @@ class StatCard extends StatelessWidget { return Padding( padding: padding ?? EdgeInsets.zero, child: PlatformCard( + color: backgroundColor, margin: const EdgeInsets.symmetric(vertical: 6.0), child: Padding( padding: const EdgeInsets.all(8.0), From 4d34101f8714486b714a71462f70ee0876e48b9e Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 16 Jan 2025 14:57:52 +0800 Subject: [PATCH 07/41] More theme tweaks --- lib/src/app.dart | 4 ++-- lib/src/view/puzzle/dashboard_screen.dart | 7 ++++--- lib/src/widgets/platform.dart | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index bd78392fbb..b9de5e83a4 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -131,9 +131,9 @@ class _AppState extends ConsumerState { final lightTheme = FlexThemeData.light( colors: flexSchemeLightColors, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), - surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, - blendLevel: 7, + blendLevel: 8, ); final darkTheme = FlexThemeData.dark( colors: flexSchemeDarkColors, diff --git a/lib/src/view/puzzle/dashboard_screen.dart b/lib/src/view/puzzle/dashboard_screen.dart index 923c43f83d..f8231c65ea 100644 --- a/lib/src/view/puzzle/dashboard_screen.dart +++ b/lib/src/view/puzzle/dashboard_screen.dart @@ -44,6 +44,7 @@ class PuzzleDashboardWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final puzzleDashboard = ref.watch(puzzleDashboardProvider(ref.watch(daysProvider).days)); + final cardColor = Theme.of(context).platform == TargetPlatform.iOS ? Colors.transparent : null; return puzzleDashboard.when( data: (dashboard) { @@ -74,7 +75,7 @@ class PuzzleDashboardWidget extends ConsumerWidget { StatCard( context.l10n.performance, value: dashboard.global.performance.toString(), - backgroundColor: Colors.transparent, + backgroundColor: cardColor, ), StatCard( context.l10n @@ -83,12 +84,12 @@ class PuzzleDashboardWidget extends ConsumerWidget { .trim() .capitalize(), value: dashboard.global.nb.toString().localizeNumbers(), - backgroundColor: Colors.transparent, + backgroundColor: cardColor, ), StatCard( context.l10n.puzzleSolved.capitalize(), value: '${((dashboard.global.firstWins / dashboard.global.nb) * 100).round()}%', - backgroundColor: Colors.transparent, + backgroundColor: cardColor, ), ]), ), diff --git a/lib/src/widgets/platform.dart b/lib/src/widgets/platform.dart index b39514f9d2..65971e5b24 100644 --- a/lib/src/widgets/platform.dart +++ b/lib/src/widgets/platform.dart @@ -101,7 +101,7 @@ class PlatformCard extends StatelessWidget { clipBehavior: clipBehavior, child: child, ) - : Card.filled( + : (Theme.of(context).brightness == Brightness.dark ? Card.filled : Card.new)( shape: borderRadius != null ? RoundedRectangleBorder(borderRadius: borderRadius!) From d97add018cc5e7143ed4d005017ae66fd21f84df Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Sun, 19 Jan 2025 16:33:19 +0800 Subject: [PATCH 08/41] More flex scheme tweaks --- lib/src/styles/styles.dart | 17 ----------------- lib/src/view/home/home_tab_screen.dart | 1 - lib/src/view/settings/app_theme_screen.dart | 1 + 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/lib/src/styles/styles.dart b/lib/src/styles/styles.dart index b6da83a141..8df41c78a7 100644 --- a/lib/src/styles/styles.dart +++ b/lib/src/styles/styles.dart @@ -50,20 +50,8 @@ abstract class Styles { defaultTargetPlatform == TargetPlatform.iOS ? CupertinoColors.secondaryLabel.resolveFrom(context) : null; - static const cupertinoAppBarColor = CupertinoDynamicColor.withBrightness( - color: Color(0xE6F9F9F9), - darkColor: Color.fromARGB(210, 36, 36, 38), - ); - static const cupertinoTabletAppBarColor = CupertinoDynamicColor.withBrightness( - color: Color(0xFFF9F9F9), - darkColor: Color.fromARGB(255, 36, 36, 36), - ); static const _cupertinoDarkLabelColor = Color(0xFFDCDCDC); - static const cupertinoLabelColor = CupertinoDynamicColor.withBrightness( - color: Color(0xFF000000), - darkColor: _cupertinoDarkLabelColor, - ); static const cupertinoTitleColor = CupertinoDynamicColor.withBrightness( color: Color(0xFF000000), darkColor: Color(0xFFF5F5F5), @@ -171,11 +159,6 @@ abstract class Styles { decoration: TextDecoration.none, ), ); - - // 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)); } /// Retrieve the default text color and apply an opacity to it. diff --git a/lib/src/view/home/home_tab_screen.dart b/lib/src/view/home/home_tab_screen.dart index 7a04ca40b1..8e595b9512 100644 --- a/lib/src/view/home/home_tab_screen.dart +++ b/lib/src/view/home/home_tab_screen.dart @@ -146,7 +146,6 @@ class _HomeScreenState extends ConsumerState with RouteAware { bottom: MediaQuery.paddingOf(context).bottom + 16.0, right: 8.0, child: FloatingActionButton.extended( - splashColor: Colors.transparent, onPressed: () { pushPlatformRoute( context, diff --git a/lib/src/view/settings/app_theme_screen.dart b/lib/src/view/settings/app_theme_screen.dart index 220c513e7a..88637d5c68 100644 --- a/lib/src/view/settings/app_theme_screen.dart +++ b/lib/src/view/settings/app_theme_screen.dart @@ -72,6 +72,7 @@ class _Body extends ConsumerWidget { optionButtonPadding: EdgeInsets.zero, optionButtonMargin: EdgeInsets.zero, optionButtonBorderRadius: 4, + onSelect: () => onChanged(t), ), ), trailing: t == appTheme ? checkedIcon : null, From 09cdd4b4bbe54c7c06e30dcc581f63953bfe6241 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 20 Jan 2025 11:59:30 +0800 Subject: [PATCH 09/41] More work on flex theme --- lib/src/app.dart | 15 +------ lib/src/init.dart | 4 +- .../model/settings/general_preferences.dart | 6 ++- lib/src/view/analysis/analysis_screen.dart | 2 +- .../broadcast/broadcast_game_bottom_bar.dart | 2 +- .../broadcast/broadcast_round_screen.dart | 6 +-- .../opening_explorer_screen.dart | 2 +- lib/src/view/play/quick_game_matrix.dart | 8 +++- lib/src/view/settings/app_theme_screen.dart | 2 +- lib/src/view/study/study_bottom_bar.dart | 2 +- lib/src/widgets/bottom_bar.dart | 9 ++-- lib/src/widgets/platform.dart | 5 ++- lib/src/widgets/shimmer.dart | 42 +++++++++++-------- pubspec.yaml | 2 +- 14 files changed, 55 insertions(+), 52 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index b9de5e83a4..5e771a1d9f 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -133,7 +133,7 @@ class _AppState extends ConsumerState { cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, - blendLevel: 8, + blendLevel: isIOS ? 10 : 8, ); final darkTheme = FlexThemeData.dark( colors: flexSchemeDarkColors, @@ -234,18 +234,7 @@ class _AppState extends ConsumerState { }, 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), - // ), - // ); - return Material(child: child); - } + ? (context, child) => Material(child: child) : null, home: const BottomNavScaffold(), navigatorObservers: [rootNavPageRouteObserver], diff --git a/lib/src/init.dart b/lib/src/init.dart index b5a78f20fe..fecd053802 100644 --- a/lib/src/init.dart +++ b/lib/src/init.dart @@ -33,7 +33,7 @@ Future setupFirstLaunch() async { final installedVersion = prefs.getString('installed_version'); // TODO remove this migration code after a few releases - if (installedVersion != null && Version.parse(installedVersion) <= Version(0, 14, 0)) { + if (installedVersion != null && Version.parse(installedVersion) < Version(0, 13, 14)) { _migrateThemeSettings(); } @@ -70,7 +70,7 @@ Future _migrateThemeSettings() async { final migrated = generalPrefs.copyWith( appTheme: // ignore: deprecated_member_use_from_same_package - generalPrefs.appThemeSeed == AppThemeSeed.system ? AppTheme.system : AppTheme.gold, + generalPrefs.appThemeSeed == AppThemeSeed.system ? AppTheme.system : defaultAppTheme, ); await prefs.setString(PrefCategory.general.storageKey, jsonEncode(migrated.toJson())); } catch (e) { diff --git a/lib/src/model/settings/general_preferences.dart b/lib/src/model/settings/general_preferences.dart index a692cf3dad..a3e1af760b 100644 --- a/lib/src/model/settings/general_preferences.dart +++ b/lib/src/model/settings/general_preferences.dart @@ -54,6 +54,8 @@ class GeneralPreferences extends _$GeneralPreferences with PreferencesStorage json) { diff --git a/lib/src/view/analysis/analysis_screen.dart b/lib/src/view/analysis/analysis_screen.dart index 1e71fc02b1..8d68a1d2c7 100644 --- a/lib/src/view/analysis/analysis_screen.dart +++ b/lib/src/view/analysis/analysis_screen.dart @@ -233,7 +233,7 @@ class _BottomBar extends ConsumerWidget { final analysisState = ref.watch(ctrlProvider).requireValue; return PlatformBottomBar( - transparentCupertinoBar: false, + transparentBackground: false, children: [ BottomBarButton( label: context.l10n.menu, diff --git a/lib/src/view/broadcast/broadcast_game_bottom_bar.dart b/lib/src/view/broadcast/broadcast_game_bottom_bar.dart index 004885f8f0..4f479d896e 100644 --- a/lib/src/view/broadcast/broadcast_game_bottom_bar.dart +++ b/lib/src/view/broadcast/broadcast_game_bottom_bar.dart @@ -33,7 +33,7 @@ class BroadcastGameBottomBar extends ConsumerWidget { final broadcastAnalysisState = ref.watch(ctrlProvider).requireValue; return PlatformBottomBar( - transparentCupertinoBar: false, + transparentBackground: false, children: [ BottomBarButton( label: context.l10n.menu, diff --git a/lib/src/view/broadcast/broadcast_round_screen.dart b/lib/src/view/broadcast/broadcast_round_screen.dart index fafef0af74..01682c03ac 100644 --- a/lib/src/view/broadcast/broadcast_round_screen.dart +++ b/lib/src/view/broadcast/broadcast_round_screen.dart @@ -138,7 +138,7 @@ class _BroadcastRoundScreenState extends ConsumerState setTournamentId: setTournamentId, setRoundId: setRoundId, ), - _ => const PlatformBottomBar.empty(transparentCupertinoBar: false), + _ => const PlatformBottomBar.empty(transparentBackground: false), }, ], ), @@ -199,7 +199,7 @@ class _BroadcastRoundScreenState extends ConsumerState setTournamentId: setTournamentId, setRoundId: setRoundId, ), - _ => const PlatformBottomBar.empty(transparentCupertinoBar: false), + _ => const PlatformBottomBar.empty(transparentBackground: false), }, ); } @@ -285,7 +285,7 @@ class _BottomBar extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return PlatformBottomBar( - transparentCupertinoBar: false, + transparentBackground: false, children: [ if (tournament.group != null) AdaptiveTextButton( diff --git a/lib/src/view/opening_explorer/opening_explorer_screen.dart b/lib/src/view/opening_explorer/opening_explorer_screen.dart index ba0a6b7320..9563dc511d 100644 --- a/lib/src/view/opening_explorer/opening_explorer_screen.dart +++ b/lib/src/view/opening_explorer/opening_explorer_screen.dart @@ -252,7 +252,7 @@ class _BottomBar extends ConsumerWidget { }; return PlatformBottomBar( - transparentCupertinoBar: false, + transparentBackground: false, children: [ BottomBarButton( label: dbLabel, diff --git a/lib/src/view/play/quick_game_matrix.dart b/lib/src/view/play/quick_game_matrix.dart index daef3d668c..190c233613 100644 --- a/lib/src/view/play/quick_game_matrix.dart +++ b/lib/src/view/play/quick_game_matrix.dart @@ -142,13 +142,17 @@ class _ChoiceChip extends StatefulWidget { class _ChoiceChipState extends State<_ChoiceChip> { @override Widget build(BuildContext context) { - final cardColor = Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.7); + final theme = Theme.of(context); + final bgColor = + theme.brightness == Brightness.light + ? theme.colorScheme.surfaceContainerLowest + : theme.colorScheme.surfaceContainerHighest; return Opacity( opacity: widget.onTap != null ? 1.0 : 0.5, child: Container( decoration: BoxDecoration( - color: cardColor, + color: bgColor.withValues(alpha: 0.7), borderRadius: const BorderRadius.all(Radius.circular(6.0)), ), child: AdaptiveInkWell( diff --git a/lib/src/view/settings/app_theme_screen.dart b/lib/src/view/settings/app_theme_screen.dart index 88637d5c68..c145714b19 100644 --- a/lib/src/view/settings/app_theme_screen.dart +++ b/lib/src/view/settings/app_theme_screen.dart @@ -37,7 +37,7 @@ class _Body extends ConsumerWidget { final choices = AppTheme.values.where((t) => t != AppTheme.system || hasSystemColors).toList(); void onChanged(AppTheme? value) => - ref.read(generalPreferencesProvider.notifier).setAppTheme(value ?? AppTheme.gold); + ref.read(generalPreferencesProvider.notifier).setAppTheme(value ?? defaultAppTheme); final checkedIcon = Theme.of(context).platform == TargetPlatform.android diff --git a/lib/src/view/study/study_bottom_bar.dart b/lib/src/view/study/study_bottom_bar.dart index 0ba1c782fa..3295585d94 100644 --- a/lib/src/view/study/study_bottom_bar.dart +++ b/lib/src/view/study/study_bottom_bar.dart @@ -46,7 +46,7 @@ class _AnalysisBottomBar extends ConsumerWidget { state.canGoBack ? ref.read(studyControllerProvider(id).notifier).userPrevious : null; return PlatformBottomBar( - transparentCupertinoBar: false, + transparentBackground: false, children: [ _ChapterButton(state: state), _NextChapterButton( diff --git a/lib/src/widgets/bottom_bar.dart b/lib/src/widgets/bottom_bar.dart index 4c2cd216fa..d5dac5ce9a 100644 --- a/lib/src/widgets/bottom_bar.dart +++ b/lib/src/widgets/bottom_bar.dart @@ -12,10 +12,10 @@ class PlatformBottomBar extends StatelessWidget { required this.children, this.mainAxisAlignment = MainAxisAlignment.spaceAround, this.expandChildren = true, - this.transparentCupertinoBar = true, + this.transparentBackground = true, }); - const PlatformBottomBar.empty({this.transparentCupertinoBar = true}) + const PlatformBottomBar.empty({this.transparentBackground = true}) : children = const [], expandChildren = true, mainAxisAlignment = MainAxisAlignment.spaceAround; @@ -30,7 +30,7 @@ class PlatformBottomBar extends StatelessWidget { final bool expandChildren; /// Whether to make the Cupertino bar transparent. Defaults to true. - final bool transparentCupertinoBar; + final bool transparentBackground; @override Widget build(BuildContext context) { @@ -38,7 +38,7 @@ class PlatformBottomBar extends StatelessWidget { return ColoredBox( color: CupertinoTheme.of( context, - ).barBackgroundColor.withValues(alpha: transparentCupertinoBar ? 0.0 : null), + ).barBackgroundColor.withValues(alpha: transparentBackground ? 0.0 : null), child: SizedBox( height: kBottomBarHeight + MediaQuery.paddingOf(context).bottom, child: SafeArea( @@ -57,6 +57,7 @@ class PlatformBottomBar extends StatelessWidget { } return BottomAppBar( + color: transparentBackground ? Colors.transparent : null, height: kBottomBarHeight, padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0), child: Row( diff --git a/lib/src/widgets/platform.dart b/lib/src/widgets/platform.dart index 65971e5b24..fc97f3bb34 100644 --- a/lib/src/widgets/platform.dart +++ b/lib/src/widgets/platform.dart @@ -84,13 +84,14 @@ class PlatformCard extends StatelessWidget { @override Widget build(BuildContext context) { + final brightness = Theme.of(context).brightness; return MediaQuery.withClampedTextScaling( maxScaleFactor: kCardTextScaleFactor, child: Theme.of(context).platform == TargetPlatform.iOS ? Card.filled( margin: margin ?? EdgeInsets.zero, - // elevation: elevation ?? 0, + elevation: elevation ?? 0, color: color, shadowColor: shadowColor, shape: @@ -101,7 +102,7 @@ class PlatformCard extends StatelessWidget { clipBehavior: clipBehavior, child: child, ) - : (Theme.of(context).brightness == Brightness.dark ? Card.filled : Card.new)( + : (brightness == Brightness.dark ? Card.filled : Card.new)( shape: borderRadius != null ? RoundedRectangleBorder(borderRadius: borderRadius!) diff --git a/lib/src/widgets/shimmer.dart b/lib/src/widgets/shimmer.dart index 7853ebcb44..6e5364913a 100644 --- a/lib/src/widgets/shimmer.dart +++ b/lib/src/widgets/shimmer.dart @@ -18,11 +18,33 @@ class ShimmerState extends State with SingleTickerProviderStateMixin { LinearGradient get _defaultGradient { final brightness = Theme.of(context).brightness; + final colorScheme = Theme.of(context).colorScheme; switch (brightness) { case Brightness.light: - return lightShimmerGradient; + return LinearGradient( + colors: [ + colorScheme.surfaceContainer, + colorScheme.surfaceContainerLow, + colorScheme.surfaceContainerLowest, + ], + stops: const [0.1, 0.3, 0.4], + begin: const Alignment(-1.0, -0.3), + end: const Alignment(1.0, 0.3), + tileMode: TileMode.clamp, + ); + case Brightness.dark: - return darkShimmerGradient; + return LinearGradient( + colors: [ + colorScheme.surfaceContainer, + colorScheme.surfaceContainerHigh, + colorScheme.surfaceContainerHighest, + ], + stops: const [0.1, 0.3, 0.4], + begin: const Alignment(-1.0, -0.3), + end: const Alignment(1.0, 0.3), + tileMode: TileMode.clamp, + ); } } @@ -140,22 +162,6 @@ class _ShimmerLoadingState extends State { } } -const lightShimmerGradient = LinearGradient( - 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], - begin: Alignment(-1.0, -0.3), - end: Alignment(1.0, 0.3), - tileMode: TileMode.clamp, -); - class _SlidingGradientTransform extends GradientTransform { const _SlidingGradientTransform({required this.slidePercent}); diff --git a/pubspec.yaml b/pubspec.yaml index dd638a22ae..0a718b708e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: lichess_mobile description: Lichess mobile app V2 publish_to: "none" -version: 0.13.13+001313 # See README.md for details about versioning +version: 0.13.14+001314 # See README.md for details about versioning environment: sdk: '^3.7.0-209.1.beta' From 048263adbdcb102c2f4556a707b2a91e4bb97685 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Tue, 21 Jan 2025 14:14:01 +0800 Subject: [PATCH 10/41] More work on flex theme --- lib/src/app.dart | 6 +- .../model/settings/general_preferences.dart | 2 +- lib/src/view/user/leaderboard_widget.dart | 2 +- lib/src/view/user/recent_games.dart | 9 +- lib/src/view/user/user_activity.dart | 2 +- lib/src/view/watch/watch_tab_screen.dart | 14 +- lib/src/widgets/board_preview.dart | 187 ++++++++---------- lib/src/widgets/list.dart | 78 +++++--- lib/src/widgets/settings.dart | 4 +- 9 files changed, 157 insertions(+), 147 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 5e771a1d9f..1d6c529e23 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -133,12 +133,12 @@ class _AppState extends ConsumerState { cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, - blendLevel: isIOS ? 10 : 8, + blendLevel: 10, ); final darkTheme = FlexThemeData.dark( colors: flexSchemeDarkColors, - surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: 12, + surfaceMode: FlexSurfaceMode.level, + blendLevel: 40, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, ); diff --git a/lib/src/model/settings/general_preferences.dart b/lib/src/model/settings/general_preferences.dart index a3e1af760b..5e48354cfe 100644 --- a/lib/src/model/settings/general_preferences.dart +++ b/lib/src/model/settings/general_preferences.dart @@ -54,7 +54,7 @@ class GeneralPreferences extends _$GeneralPreferences with PreferencesStorage Shimmer( child: ShimmerLoading( isLoading: true, - child: ListSection.loading(itemsNumber: 5, header: true), + child: ListSection.loading(itemsNumber: 5, header: true, hasLeading: true), ), ), ); diff --git a/lib/src/view/user/recent_games.dart b/lib/src/view/user/recent_games.dart index 1dca4861f4..c1db22c01b 100644 --- a/lib/src/view/user/recent_games.dart +++ b/lib/src/view/user/recent_games.dart @@ -22,12 +22,14 @@ class RecentGamesWidget extends ConsumerWidget { required this.recentGames, required this.user, required this.nbOfGames, + this.maxGamesToShow = 10, super.key, }); final LightUser? user; final AsyncValue> recentGames; final int nbOfGames; + final int maxGamesToShow; @override Widget build(BuildContext context, WidgetRef ref) { @@ -38,11 +40,12 @@ class RecentGamesWidget extends ConsumerWidget { if (data.isEmpty) { return const SizedBox.shrink(); } + final list = data.take(maxGamesToShow); return ListSection( header: Text(context.l10n.recentGames), hasLeading: true, headerTrailing: - nbOfGames > data.length + nbOfGames > list.length ? NoPaddingTextButton( onPressed: () { pushPlatformRoute( @@ -58,7 +61,7 @@ class RecentGamesWidget extends ConsumerWidget { ) : null, children: - data.map((item) { + list.map((item) { return ExtendedGameListTile(item: item); }).toList(), ); @@ -74,7 +77,7 @@ class RecentGamesWidget extends ConsumerWidget { () => Shimmer( child: ShimmerLoading( isLoading: true, - child: ListSection.loading(itemsNumber: 10, header: true), + child: ListSection.loading(itemsNumber: 10, header: true, hasLeading: true), ), ), ); diff --git a/lib/src/view/user/user_activity.dart b/lib/src/view/user/user_activity.dart index ab57c5c966..d4268c7517 100644 --- a/lib/src/view/user/user_activity.dart +++ b/lib/src/view/user/user_activity.dart @@ -48,7 +48,7 @@ class UserActivityWidget extends ConsumerWidget { () => Shimmer( child: ShimmerLoading( isLoading: true, - child: ListSection.loading(itemsNumber: 10, header: true), + child: ListSection.loading(itemsNumber: 10, header: true, hasLeading: true), ), ), ); diff --git a/lib/src/view/watch/watch_tab_screen.dart b/lib/src/view/watch/watch_tab_screen.dart index 77b80fa7ba..d852d1f505 100644 --- a/lib/src/view/watch/watch_tab_screen.dart +++ b/lib/src/view/watch/watch_tab_screen.dart @@ -251,7 +251,11 @@ class _BroadcastWidget extends ConsumerWidget { () => Shimmer( child: ShimmerLoading( isLoading: true, - child: ListSection.loading(itemsNumber: numberOfItems, header: true), + child: ListSection.loading( + itemsNumber: numberOfItems, + header: true, + hasLeading: true, + ), ), ), ); @@ -365,7 +369,7 @@ class _WatchTvWidget extends ConsumerWidget { () => Shimmer( child: ShimmerLoading( isLoading: true, - child: ListSection.loading(itemsNumber: 4, header: true), + child: ListSection.loading(itemsNumber: 4, header: true, hasLeading: true), ), ), ); @@ -415,7 +419,11 @@ class _StreamerWidget extends ConsumerWidget { () => Shimmer( child: ShimmerLoading( isLoading: true, - child: ListSection.loading(itemsNumber: numberOfItems, header: true), + child: ListSection.loading( + itemsNumber: numberOfItems, + header: true, + hasLeading: true, + ), ), ), ); diff --git a/lib/src/widgets/board_preview.dart b/lib/src/widgets/board_preview.dart index 003eaf94a4..ae7629e007 100644 --- a/lib/src/widgets/board_preview.dart +++ b/lib/src/widgets/board_preview.dart @@ -1,14 +1,14 @@ import 'package:chessground/chessground.dart'; import 'package:dartchess/dartchess.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lichess_mobile/src/constants.dart'; import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; +import 'package:lichess_mobile/src/widgets/buttons.dart'; /// A board preview with a description. -class SmallBoardPreview extends ConsumerStatefulWidget { +class SmallBoardPreview extends ConsumerWidget { const SmallBoardPreview({ required this.orientation, required this.fen, @@ -44,127 +44,102 @@ class SmallBoardPreview extends ConsumerStatefulWidget { final bool _showLoadingPlaceholder; @override - ConsumerState createState() => _SmallBoardPreviewState(); -} - -class _SmallBoardPreviewState extends ConsumerState { - bool _isPressed = false; - - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final boardPrefs = ref.watch(boardPreferencesProvider); final content = LayoutBuilder( builder: (context, constraints) { final boardSize = constraints.biggest.shortestSide - (constraints.biggest.shortestSide / 1.618); - return Container( - decoration: BoxDecoration( - color: - _isPressed - ? CupertinoDynamicColor.resolve(CupertinoColors.systemGrey5, context) - : null, - ), - child: Padding( - padding: - widget.padding ?? - Styles.horizontalBodyPadding.add(const EdgeInsets.symmetric(vertical: 8.0)), - child: SizedBox( - height: boardSize, - child: Row( - children: [ - if (widget._showLoadingPlaceholder) - Container( - width: boardSize, - height: boardSize, - decoration: const BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.all(Radius.circular(4.0)), - ), - ) - else - StaticChessboard( - size: boardSize, - fen: widget.fen, - orientation: widget.orientation, - lastMove: widget.lastMove as NormalMove?, - pieceAssets: boardPrefs.pieceSet.assets, - colorScheme: boardPrefs.boardTheme.colors, - brightness: boardPrefs.brightness, - hue: boardPrefs.hue, - enableCoordinates: false, - borderRadius: const BorderRadius.all(Radius.circular(4.0)), - boxShadow: boardShadows, - animationDuration: const Duration(milliseconds: 150), + return Padding( + padding: + padding ?? + Styles.horizontalBodyPadding.add(const EdgeInsets.symmetric(vertical: 8.0)), + child: SizedBox( + height: boardSize, + child: Row( + children: [ + if (_showLoadingPlaceholder) + Container( + width: boardSize, + height: boardSize, + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(4.0)), ), - const SizedBox(width: 10.0), - if (widget._showLoadingPlaceholder) - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 16.0, - width: double.infinity, - decoration: const BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.all(Radius.circular(4.0)), - ), + ) + else + StaticChessboard( + size: boardSize, + fen: fen, + orientation: orientation, + lastMove: lastMove as NormalMove?, + pieceAssets: boardPrefs.pieceSet.assets, + colorScheme: boardPrefs.boardTheme.colors, + brightness: boardPrefs.brightness, + hue: boardPrefs.hue, + enableCoordinates: false, + borderRadius: const BorderRadius.all(Radius.circular(4.0)), + boxShadow: boardShadows, + animationDuration: const Duration(milliseconds: 150), + ), + const SizedBox(width: 10.0), + if (_showLoadingPlaceholder) + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 16.0, + width: double.infinity, + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(4.0)), ), - const SizedBox(height: 4.0), - Container( - height: 16.0, - width: MediaQuery.sizeOf(context).width / 3, - decoration: const BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.all(Radius.circular(4.0)), - ), + ), + const SizedBox(height: 4.0), + Container( + height: 16.0, + width: MediaQuery.sizeOf(context).width / 3, + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(4.0)), ), - ], - ), - Container( - height: 44.0, - width: 44.0, - decoration: const BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.all(Radius.circular(4.0)), ), + ], + ), + Container( + height: 44.0, + width: 44.0, + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(4.0)), ), - Container( - height: 16.0, - width: double.infinity, - decoration: const BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.all(Radius.circular(4.0)), - ), + ), + Container( + height: 16.0, + width: double.infinity, + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(4.0)), ), - ], - ), - ) - else - Expanded(child: widget.description), - ], - ), + ), + ], + ), + ) + else + Expanded(child: description), + ], ), ), ); }, ); - 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) - : content; + return onTap != null ? AdaptiveInkWell(onTap: onTap, child: content) : content; } } diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index 5875718ada..d71518b3a9 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -22,19 +22,22 @@ 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, + this.hasLeading = false, + }) : children = [for (int i = 0; i < itemsNumber; i++) const SizedBox.shrink()], + headerTrailing = null, + header = header ? const SizedBox.shrink() : null, + 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; @@ -139,7 +142,7 @@ class ListSection extends StatelessWidget { if (header != null) // ignore: avoid-wrapping-in-padding Padding( - padding: const EdgeInsets.only(top: 10.0, bottom: 16.0), + padding: const EdgeInsets.only(top: 10.0, bottom: 10.0), child: Container( width: double.infinity, height: 24, @@ -149,14 +152,34 @@ class ListSection extends StatelessWidget { ), ), ), - Container( - width: double.infinity, - height: children.length * 54, - 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: 4.0), + child: Row( + children: [ + if (hasLeading) ...[ + Container( + width: 46, + height: 46, + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + ), + const SizedBox(width: 10), + ], + Expanded( + child: Container( + height: 46, + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + ), + ), + ], + ), ), - ), ], ), ) @@ -185,7 +208,7 @@ class ListSection extends StatelessWidget { cupertinoBackgroundColor ?? (theme.brightness == Brightness.light ? theme.colorScheme.surfaceContainerLowest - : theme.colorScheme.surfaceContainerHighest), + : theme.colorScheme.surfaceContainerHigh), borderRadius: cupertinoBorderRadius ?? const BorderRadius.all(Radius.circular(10.0)), ), @@ -306,12 +329,13 @@ class PlatformListTile extends StatelessWidget { @override Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; switch (Theme.of(context).platform) { case TargetPlatform.android: return ListTile( leading: leading, title: title, - iconColor: Theme.of(context).colorScheme.outline, + iconColor: colorScheme.outline, subtitle: subtitle != null ? DefaultTextStyle.merge( @@ -329,16 +353,14 @@ class PlatformListTile extends StatelessWidget { contentPadding: padding, ); case TargetPlatform.iOS: + final activatedColor = colorScheme.surfaceContainerHighest; return IconTheme( data: CupertinoIconThemeData(color: CupertinoColors.systemGrey.resolveFrom(context)), child: GestureDetector( onLongPress: onLongPress, child: CupertinoListTile.notched( - backgroundColor: - selected == true - ? CupertinoColors.systemGrey4.resolveFrom(context) - : cupertinoBackgroundColor, - // backgroundColorActivated: Theme.of(context).colorScheme.surfaceContainerHighest, + backgroundColor: selected == true ? activatedColor : cupertinoBackgroundColor, + backgroundColorActivated: activatedColor, leading: leading, title: harmonizeCupertinoTitleStyle diff --git a/lib/src/widgets/settings.dart b/lib/src/widgets/settings.dart index 32cde5a72a..d920f967ed 100644 --- a/lib/src/widgets/settings.dart +++ b/lib/src/widgets/settings.dart @@ -272,6 +272,7 @@ class ChoicePicker extends StatelessWidget { case TargetPlatform.iOS: final tileConstructor = notchedTile ? CupertinoListTile.notched : CupertinoListTile.new; final theme = Theme.of(context); + final colorScheme = theme.colorScheme; return Padding( padding: margin ?? Styles.bodySectionPadding, child: Opacity( @@ -282,7 +283,7 @@ class ChoicePicker extends StatelessWidget { color: theme.brightness == Brightness.light ? theme.colorScheme.surfaceContainerLowest - : theme.colorScheme.surfaceContainerHighest, + : theme.colorScheme.surfaceContainerHigh, borderRadius: const BorderRadius.all(Radius.circular(10.0)), ), separatorColor: Styles.cupertinoSeparatorColor.resolveFrom(context), @@ -299,6 +300,7 @@ class ChoicePicker extends StatelessWidget { title: titleBuilder(value), subtitle: subtitleBuilder?.call(value), leading: leadingBuilder?.call(value), + backgroundColorActivated: colorScheme.surfaceContainerHighest, onTap: onSelectedItemChanged != null ? () => onSelectedItemChanged!(value) From 3e67c3b05de503f56bdbef1237126677fd1bd86e Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 23 Jan 2025 09:23:38 +0800 Subject: [PATCH 11/41] More flex them tweaks --- lib/src/view/game/message_screen.dart | 12 ++---------- lib/src/view/play/time_control_modal.dart | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/src/view/game/message_screen.dart b/lib/src/view/game/message_screen.dart index 2e21301209..97abefb9ab 100644 --- a/lib/src/view/game/message_screen.dart +++ b/lib/src/view/game/message_screen.dart @@ -112,22 +112,14 @@ class _MessageBubble extends ConsumerWidget { const _MessageBubble({required this.you, required this.message}); Color _bubbleColor(BuildContext context, Brightness brightness) => - Theme.of(context).platform == TargetPlatform.iOS - ? you - ? Theme.of(context).colorScheme.primaryContainer - : CupertinoColors.systemGrey4.resolveFrom(context) - : you + you ? 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 - ? you - ? Theme.of(context).colorScheme.onPrimaryContainer - : CupertinoColors.label.resolveFrom(context) - : you + you ? Theme.of(context).colorScheme.onPrimaryContainer : brightness == Brightness.light ? Colors.black diff --git a/lib/src/view/play/time_control_modal.dart b/lib/src/view/play/time_control_modal.dart index a53e20213a..04f57eaa07 100644 --- a/lib/src/view/play/time_control_modal.dart +++ b/lib/src/view/play/time_control_modal.dart @@ -245,7 +245,7 @@ class _ChoiceChip extends StatelessWidget { border: selected ? Border.fromBorderSide( - BorderSide(color: Theme.of(context).colorScheme.primary, width: 2.0), + BorderSide(color: Theme.of(context).colorScheme.secondary, width: 2.0), ) : const Border.fromBorderSide(BorderSide(color: Colors.transparent, width: 2.0)), ), From 657a6fc7287edaaa26ee36995fadad0aa8c19fa8 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 23 Jan 2025 09:23:59 +0800 Subject: [PATCH 12/41] Use a grid theme selector --- lib/src/view/settings/app_theme_screen.dart | 52 +++++++++------------ 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/lib/src/view/settings/app_theme_screen.dart b/lib/src/view/settings/app_theme_screen.dart index c145714b19..588408d26c 100644 --- a/lib/src/view/settings/app_theme_screen.dart +++ b/lib/src/view/settings/app_theme_screen.dart @@ -4,9 +4,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; import 'package:lichess_mobile/src/model/settings/general_preferences.dart'; +import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/color_palette.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; -import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; class AppThemeScreen extends StatelessWidget { @@ -31,7 +31,6 @@ class _Body extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final appTheme = ref.watch(generalPreferencesProvider.select((p) => p.appTheme)); final boardPrefs = ref.watch(boardPreferencesProvider); - final hasSystemColors = getCorePalette() != null; final choices = AppTheme.values.where((t) => t != AppTheme.system || hasSystemColors).toList(); @@ -39,35 +38,36 @@ class _Body extends ConsumerWidget { void onChanged(AppTheme? value) => ref.read(generalPreferencesProvider.notifier).setAppTheme(value ?? defaultAppTheme); - final checkedIcon = - Theme.of(context).platform == TargetPlatform.android - ? const Icon(Icons.check) - : Icon( - CupertinoIcons.check_mark_circled_solid, - color: CupertinoTheme.of(context).primaryColor, - ); - final brightness = Theme.of(context).brightness; - return ListView.separated( + const itemsByRow = 4; + final width = MediaQuery.sizeOf(context).width; + final size = (width - 16 * (itemsByRow + 2)) / itemsByRow; + + return GridView.builder( + padding: MediaQuery.paddingOf(context) + Styles.bodyPadding, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: itemsByRow, + crossAxisSpacing: 16.0, + mainAxisSpacing: 16.0, + ), itemBuilder: (context, index) { final t = choices[index]; final fsd = t.getFlexScheme(boardPrefs.boardTheme); - return AdaptiveListTile( - // selected: t == appTheme, - leading: SizedBox( - width: 52, - height: 36, + return Center( + child: SizedBox( + width: size, + height: size, child: FlexThemeModeOptionButton( flexSchemeColor: brightness == Brightness.light ? fsd.light : fsd.dark, - selected: false, - unselectedBorder: BorderSide.none, - selectedBorder: BorderSide(color: Theme.of(context).colorScheme.outline, width: 3), + selected: t == appTheme, + // unselectedBorder: BorderSide.none, + // selectedBorder: BorderSide(color: Theme.of(context).colorScheme.outline, width: 3), backgroundColor: Theme.of(context).colorScheme.surface, - width: 26, - height: 18, padding: EdgeInsets.zero, + width: size / 2, + height: size / 2, borderRadius: 0, optionButtonPadding: EdgeInsets.zero, optionButtonMargin: EdgeInsets.zero, @@ -75,18 +75,8 @@ class _Body extends ConsumerWidget { onSelect: () => onChanged(t), ), ), - trailing: t == appTheme ? checkedIcon : null, - title: Text(fsd.name), - onTap: () => onChanged(t), ); }, - separatorBuilder: - (_, __) => PlatformDivider( - height: 1, - // on iOS: 52 (leading) + 14 (default indent) + 16 (padding) - indent: Theme.of(context).platform == TargetPlatform.iOS ? 52 + 14 + 16 : null, - color: Theme.of(context).platform == TargetPlatform.iOS ? null : Colors.transparent, - ), itemCount: choices.length, ); } From 96060a6c7b1ff1e3636f9473e093765eac88927b Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 23 Jan 2025 13:20:31 +0800 Subject: [PATCH 13/41] WIP on custom board background themes --- lib/src/app.dart | 23 ++- lib/src/model/settings/board_preferences.dart | 70 ++++++++ .../model/settings/general_preferences.dart | 68 ++++---- lib/src/view/analysis/analysis_screen.dart | 4 +- .../board_editor/board_editor_screen.dart | 2 +- .../view/broadcast/broadcast_game_screen.dart | 2 +- .../coordinate_training_screen.dart | 2 +- .../offline_correspondence_game_screen.dart | 2 +- lib/src/view/game/archived_game_screen.dart | 2 +- lib/src/view/game/game_result_dialog.dart | 23 +-- lib/src/view/game/game_screen.dart | 6 +- lib/src/view/game/message_screen.dart | 2 +- .../opening_explorer_screen.dart | 35 ++-- lib/src/view/puzzle/puzzle_screen.dart | 2 +- lib/src/view/puzzle/storm_screen.dart | 4 +- lib/src/view/settings/app_theme_screen.dart | 83 ---------- .../board_background_theme_screen.dart | 155 ++++++++++++++++++ lib/src/view/settings/theme_screen.dart | 53 +++--- lib/src/view/study/study_screen.dart | 6 +- lib/src/view/watch/tv_screen.dart | 2 +- lib/src/widgets/board_theme.dart | 127 ++++++++++++++ lib/src/widgets/list.dart | 3 + lib/src/widgets/platform_scaffold.dart | 32 ++++ lib/src/widgets/shimmer.dart | 15 +- 24 files changed, 527 insertions(+), 196 deletions(-) delete mode 100644 lib/src/view/settings/app_theme_screen.dart create mode 100644 lib/src/view/settings/board_background_theme_screen.dart create mode 100644 lib/src/widgets/board_theme.dart diff --git a/lib/src/app.dart b/lib/src/app.dart index 1d6c529e23..ea4a848faf 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -11,12 +11,12 @@ import 'package:lichess_mobile/src/model/challenge/challenge_service.dart'; import 'package:lichess_mobile/src/model/common/preloaded_data.dart'; import 'package:lichess_mobile/src/model/correspondence/correspondence_service.dart'; import 'package:lichess_mobile/src/model/notifications/notification_service.dart'; -import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; import 'package:lichess_mobile/src/model/settings/general_preferences.dart'; import 'package:lichess_mobile/src/navigation.dart'; import 'package:lichess_mobile/src/network/connectivity.dart'; import 'package:lichess_mobile/src/network/http.dart'; import 'package:lichess_mobile/src/network/socket.dart'; +import 'package:lichess_mobile/src/styles/lichess_colors.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/screen.dart'; @@ -119,12 +119,11 @@ class _AppState extends ConsumerState { @override Widget build(BuildContext context) { final generalPrefs = ref.watch(generalPreferencesProvider); - final boardTheme = ref.watch(boardPreferencesProvider.select((state) => state.boardTheme)); final isTablet = isTabletOrLarger(context); final isIOS = Theme.of(context).platform == TargetPlatform.iOS; final remainingHeight = estimateRemainingHeightLeftBoard(context); - final flexScheme = generalPrefs.appTheme.getFlexScheme(boardTheme); + final flexScheme = FlexScheme.espresso.data; final flexSchemeLightColors = flexScheme.light; final flexSchemeDarkColors = flexScheme.dark; @@ -137,8 +136,8 @@ class _AppState extends ConsumerState { ); final darkTheme = FlexThemeData.dark( colors: flexSchemeDarkColors, - surfaceMode: FlexSurfaceMode.level, - blendLevel: 40, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + blendLevel: 10, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, ); @@ -167,11 +166,11 @@ class _AppState extends ConsumerState { ); final darkCupertinoTheme = CupertinoThemeData( - primaryColor: darkTheme.colorScheme.primary, - primaryContrastingColor: darkTheme.colorScheme.onPrimary, + primaryColor: darkTheme.colorScheme.primaryFixedDim, + primaryContrastingColor: darkTheme.colorScheme.onPrimaryFixed, brightness: Brightness.dark, textTheme: CupertinoTheme.of(context).textTheme.copyWith( - primaryColor: darkTheme.colorScheme.primary, + primaryColor: darkTheme.colorScheme.primaryFixedDim, textStyle: CupertinoTheme.of( context, ).textTheme.textStyle.copyWith(color: darkTheme.colorScheme.onSurface), @@ -208,6 +207,10 @@ class _AppState extends ConsumerState { subtitleTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, leadingAndTrailingTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, ), + floatingActionButtonTheme: const FloatingActionButtonThemeData( + backgroundColor: LichessColors.brag, + foregroundColor: Colors.white, + ), navigationBarTheme: NavigationBarTheme.of( context, ).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null), @@ -222,6 +225,10 @@ class _AppState extends ConsumerState { subtitleTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, leadingAndTrailingTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, ), + floatingActionButtonTheme: const FloatingActionButtonThemeData( + backgroundColor: LichessColors.brag, + foregroundColor: Colors.white, + ), navigationBarTheme: NavigationBarTheme.of( context, ).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null), diff --git a/lib/src/model/settings/board_preferences.dart b/lib/src/model/settings/board_preferences.dart index 9c5cc98f48..4484d6510f 100644 --- a/lib/src/model/settings/board_preferences.dart +++ b/lib/src/model/settings/board_preferences.dart @@ -101,6 +101,10 @@ class BoardPreferences extends _$BoardPreferences with PreferencesStorage adjustColors({double? brightness, double? hue}) { return save(state.copyWith(brightness: brightness ?? state.brightness, hue: hue ?? state.hue)); } + + Future setBackgroundTheme(BoardBackgroundTheme? backgroundTheme) { + return save(state.copyWith(backgroundTheme: backgroundTheme)); + } } @Freezed(fromJson: true, toJson: true) @@ -137,6 +141,7 @@ class BoardPrefs with _$BoardPrefs implements Serializable { @JsonKey(defaultValue: false) required bool showBorder, @JsonKey(defaultValue: kBoardDefaultBrightnessFilter) required double brightness, @JsonKey(defaultValue: kBoardDefaultHueFilter) required double hue, + BoardBackgroundTheme? backgroundTheme, }) = _BoardPrefs; static const defaults = BoardPrefs( @@ -393,3 +398,68 @@ String dragTargetKindLabel(DragTargetKind kind) => switch (kind) { DragTargetKind.square => 'Square', DragTargetKind.none => 'None', }; + +enum BoardBackgroundTheme { + /// The app theme is based on the chess board + board, + + /// Below values from [FlexScheme] + // blue, + // indigo, + // hippieBlue, + // sakura, + // mandyRed, + // green, + // money, + // jungle, + // greyLaw, + // wasabi, + // mango, + // amber, + // vesuviusBurn, + // deepPurple, + // ebonyClay, + // barossa, + // shark, + // bigStone, + // damask, + // bahamaBlue, + // mallardGreen, + // espresso, + // outerSpace, + // blueWhale, + // sanJuanBlue, + // rosewood, + // blumineBlue, + // verdunHemlock, + // dellGenoa, + redM3, + red, + redWine, + pinkM3, + purpleBrown, + purpleM3, + indigoM3, + blueM3, + aquaBlue, + // brandBlue, + // deepBlue, + cyanM3, + tealM3, + greenM3, + limeM3, + yellowM3, + orangeM3, + deepOrangeM3, + gold, + greys, + sepia; + + static final _flexSchemesNameMap = FlexScheme.values.asNameMap(); + + String get label => + this == BoardBackgroundTheme.board ? 'Chessboard' : _flexSchemesNameMap[name]!.data.name; + + FlexSchemeData getFlexScheme(BoardTheme boardTheme) => + this == BoardBackgroundTheme.board ? boardTheme.flexScheme : _flexSchemesNameMap[name]!.data; +} diff --git a/lib/src/model/settings/general_preferences.dart b/lib/src/model/settings/general_preferences.dart index 5e48354cfe..7a668a1a4a 100644 --- a/lib/src/model/settings/general_preferences.dart +++ b/lib/src/model/settings/general_preferences.dart @@ -54,7 +54,7 @@ class GeneralPreferences extends _$GeneralPreferences with PreferencesStorage + this == AppTheme.system + ? 'System' + : this == defaultAppTheme + ? 'Default' + : this == AppTheme.board + ? 'Chessboard' + : _flexSchemesNameMap[name]!.data.name; + FlexSchemeData getFlexScheme(BoardTheme boardTheme) => this == AppTheme.system ? getSystemScheme()! diff --git a/lib/src/view/analysis/analysis_screen.dart b/lib/src/view/analysis/analysis_screen.dart index 8d68a1d2c7..3dd9fee064 100644 --- a/lib/src/view/analysis/analysis_screen.dart +++ b/lib/src/view/analysis/analysis_screen.dart @@ -92,7 +92,7 @@ class _AnalysisScreenState extends ConsumerState switch (asyncState) { case AsyncData(:final value): - return PlatformScaffold( + return PlatformBoardThemeScaffold( resizeToAvoidBottomInset: false, appBar: PlatformAppBar(title: _Title(variant: value.variant), actions: appBarActions), body: _Body( @@ -109,7 +109,7 @@ class _AnalysisScreenState extends ConsumerState }, ); case _: - return PlatformScaffold( + return PlatformBoardThemeScaffold( resizeToAvoidBottomInset: false, appBar: PlatformAppBar( title: const _Title(variant: Variant.standard), diff --git a/lib/src/view/board_editor/board_editor_screen.dart b/lib/src/view/board_editor/board_editor_screen.dart index 12ca17468e..f6c59ed6f4 100644 --- a/lib/src/view/board_editor/board_editor_screen.dart +++ b/lib/src/view/board_editor/board_editor_screen.dart @@ -27,7 +27,7 @@ class BoardEditorScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return PlatformScaffold( + return PlatformBoardThemeScaffold( appBar: PlatformAppBar(title: Text(context.l10n.boardEditor)), body: _Body(initialFen), ); diff --git a/lib/src/view/broadcast/broadcast_game_screen.dart b/lib/src/view/broadcast/broadcast_game_screen.dart index 458fca148f..9300996ac8 100644 --- a/lib/src/view/broadcast/broadcast_game_screen.dart +++ b/lib/src/view/broadcast/broadcast_game_screen.dart @@ -84,7 +84,7 @@ class _BroadcastGameScreenState extends ConsumerState _ => const SizedBox.shrink(), }; - return PlatformScaffold( + return PlatformBoardThemeScaffold( appBar: PlatformAppBar( title: title, actions: [ diff --git a/lib/src/view/coordinate_training/coordinate_training_screen.dart b/lib/src/view/coordinate_training/coordinate_training_screen.dart index f4be76be47..151c97be7a 100644 --- a/lib/src/view/coordinate_training/coordinate_training_screen.dart +++ b/lib/src/view/coordinate_training/coordinate_training_screen.dart @@ -29,7 +29,7 @@ class CoordinateTrainingScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return PlatformScaffold( + return PlatformBoardThemeScaffold( appBar: PlatformAppBar( title: const Text('Coordinate Training'), // TODO l10n once script works actions: [ diff --git a/lib/src/view/correspondence/offline_correspondence_game_screen.dart b/lib/src/view/correspondence/offline_correspondence_game_screen.dart index 1942f93c7e..024a77f609 100644 --- a/lib/src/view/correspondence/offline_correspondence_game_screen.dart +++ b/lib/src/view/correspondence/offline_correspondence_game_screen.dart @@ -53,7 +53,7 @@ class _OfflineCorrespondenceGameScreenState extends State with RouteAware { : ChallengeDeclineReason.generic.label(context.l10n), ) : const LoadGameError('Could not create the game.'); - return PlatformScaffold( + return PlatformBoardThemeScaffold( resizeToAvoidBottomInset: false, appBar: GameAppBar(id: gameId, lastMoveAt: widget.lastMoveAt), body: body, @@ -170,7 +170,7 @@ class _GameScreenState extends ConsumerState with RouteAware { ) : const StandaloneGameLoadingBoard(); - return PlatformScaffold( + return PlatformBoardThemeScaffold( resizeToAvoidBottomInset: false, appBar: GameAppBar(seek: widget.seek, lastMoveAt: widget.lastMoveAt), body: PopScope(canPop: false, child: loadingBoard), @@ -191,7 +191,7 @@ class _GameScreenState extends ConsumerState with RouteAware { final body = PopScope(child: message); - return PlatformScaffold( + return PlatformBoardThemeScaffold( appBar: GameAppBar(seek: widget.seek, lastMoveAt: widget.lastMoveAt), body: body, ); diff --git a/lib/src/view/game/message_screen.dart b/lib/src/view/game/message_screen.dart index 97abefb9ab..b84b3ad2a7 100644 --- a/lib/src/view/game/message_screen.dart +++ b/lib/src/view/game/message_screen.dart @@ -49,7 +49,7 @@ class _MessageScreenState extends ConsumerState with RouteAware { @override Widget build(BuildContext context) { - return PlatformScaffold( + return PlatformBoardThemeScaffold( appBar: PlatformAppBar(title: widget.title, centerTitle: true), body: _Body(me: widget.me, id: widget.id), ); diff --git a/lib/src/view/opening_explorer/opening_explorer_screen.dart b/lib/src/view/opening_explorer/opening_explorer_screen.dart index 9563dc511d..237190fda1 100644 --- a/lib/src/view/opening_explorer/opening_explorer_screen.dart +++ b/lib/src/view/opening_explorer/opening_explorer_screen.dart @@ -13,6 +13,7 @@ import 'package:lichess_mobile/src/view/analysis/analysis_board.dart'; import 'package:lichess_mobile/src/view/opening_explorer/opening_explorer_settings.dart'; import 'package:lichess_mobile/src/view/opening_explorer/opening_explorer_view.dart'; import 'package:lichess_mobile/src/widgets/adaptive_bottom_sheet.dart'; +import 'package:lichess_mobile/src/widgets/board_theme.dart'; import 'package:lichess_mobile/src/widgets/bottom_bar.dart'; import 'package:lichess_mobile/src/widgets/bottom_bar_button.dart'; import 'package:lichess_mobile/src/widgets/buttons.dart'; @@ -39,24 +40,26 @@ class OpeningExplorerScreen extends ConsumerWidget { _ => const CenterLoadingIndicator(), }; - return PlatformWidget( - androidBuilder: - (_) => Scaffold( - body: body, - appBar: AppBar( - title: Text(context.l10n.openingExplorer), - bottom: _MoveList(options: options), + return BoardTheme( + child: 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, + iosBuilder: + (_) => CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(context.l10n.openingExplorer), + automaticBackgroundVisibility: false, + border: null, + ), + child: body, ), - child: body, - ), + ), ); } } diff --git a/lib/src/view/puzzle/puzzle_screen.dart b/lib/src/view/puzzle/puzzle_screen.dart index d03764ee38..a1be2e41a4 100644 --- a/lib/src/view/puzzle/puzzle_screen.dart +++ b/lib/src/view/puzzle/puzzle_screen.dart @@ -84,7 +84,7 @@ class _PuzzleScreenState extends ConsumerState with RouteAware { @override Widget build(BuildContext context) { return WakelockWidget( - child: PlatformScaffold( + child: PlatformBoardThemeScaffold( appBar: PlatformAppBar( actions: const [ToggleSoundButton(), _PuzzleSettingsButton()], title: _Title(angle: widget.angle), diff --git a/lib/src/view/puzzle/storm_screen.dart b/lib/src/view/puzzle/storm_screen.dart index bf09e798ef..bb4eb3998a 100644 --- a/lib/src/view/puzzle/storm_screen.dart +++ b/lib/src/view/puzzle/storm_screen.dart @@ -46,7 +46,7 @@ class _StormScreenState extends ConsumerState { @override Widget build(BuildContext context) { return WakelockWidget( - child: PlatformScaffold( + child: PlatformBoardThemeScaffold( appBar: PlatformAppBar( actions: [_StormDashboardButton(), const ToggleSoundButton()], title: const Text('Puzzle Storm'), @@ -578,7 +578,7 @@ class _RunStats extends StatelessWidget { @override Widget build(BuildContext context) { - return PlatformScaffold( + return PlatformBoardThemeScaffold( body: _RunStatsPopup(stats), appBar: PlatformAppBar( leading: IconButton( diff --git a/lib/src/view/settings/app_theme_screen.dart b/lib/src/view/settings/app_theme_screen.dart deleted file mode 100644 index 588408d26c..0000000000 --- a/lib/src/view/settings/app_theme_screen.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:flex_color_scheme/flex_color_scheme.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; -import 'package:lichess_mobile/src/model/settings/general_preferences.dart'; -import 'package:lichess_mobile/src/styles/styles.dart'; -import 'package:lichess_mobile/src/utils/color_palette.dart'; -import 'package:lichess_mobile/src/utils/l10n_context.dart'; -import 'package:lichess_mobile/src/widgets/platform.dart'; - -class AppThemeScreen extends StatelessWidget { - const AppThemeScreen({super.key}); - - @override - Widget build(BuildContext context) { - return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); - } - - Widget _androidBuilder(BuildContext context) { - return Scaffold(appBar: AppBar(title: Text(context.l10n.mobileTheme)), body: _Body()); - } - - Widget _iosBuilder(BuildContext context) { - return CupertinoPageScaffold(navigationBar: const CupertinoNavigationBar(), child: _Body()); - } -} - -class _Body extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - final appTheme = ref.watch(generalPreferencesProvider.select((p) => p.appTheme)); - final boardPrefs = ref.watch(boardPreferencesProvider); - final hasSystemColors = getCorePalette() != null; - - final choices = AppTheme.values.where((t) => t != AppTheme.system || hasSystemColors).toList(); - - void onChanged(AppTheme? value) => - ref.read(generalPreferencesProvider.notifier).setAppTheme(value ?? defaultAppTheme); - - final brightness = Theme.of(context).brightness; - - const itemsByRow = 4; - final width = MediaQuery.sizeOf(context).width; - final size = (width - 16 * (itemsByRow + 2)) / itemsByRow; - - return GridView.builder( - padding: MediaQuery.paddingOf(context) + Styles.bodyPadding, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: itemsByRow, - crossAxisSpacing: 16.0, - mainAxisSpacing: 16.0, - ), - itemBuilder: (context, index) { - final t = choices[index]; - final fsd = t.getFlexScheme(boardPrefs.boardTheme); - - return Center( - child: SizedBox( - width: size, - height: size, - child: FlexThemeModeOptionButton( - flexSchemeColor: brightness == Brightness.light ? fsd.light : fsd.dark, - selected: t == appTheme, - // unselectedBorder: BorderSide.none, - // selectedBorder: BorderSide(color: Theme.of(context).colorScheme.outline, width: 3), - backgroundColor: Theme.of(context).colorScheme.surface, - padding: EdgeInsets.zero, - width: size / 2, - height: size / 2, - borderRadius: 0, - optionButtonPadding: EdgeInsets.zero, - optionButtonMargin: EdgeInsets.zero, - optionButtonBorderRadius: 4, - onSelect: () => onChanged(t), - ), - ), - ); - }, - itemCount: choices.length, - ); - } -} diff --git a/lib/src/view/settings/board_background_theme_screen.dart b/lib/src/view/settings/board_background_theme_screen.dart new file mode 100644 index 0000000000..1a2aa52089 --- /dev/null +++ b/lib/src/view/settings/board_background_theme_screen.dart @@ -0,0 +1,155 @@ +import 'package:chessground/chessground.dart'; +import 'package:dartchess/dartchess.dart' show Side, kInitialFEN; +import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; +import 'package:lichess_mobile/src/styles/styles.dart'; +import 'package:lichess_mobile/src/utils/l10n_context.dart'; +import 'package:lichess_mobile/src/widgets/board_theme.dart' as wrapper; +import 'package:lichess_mobile/src/widgets/buttons.dart'; +import 'package:lichess_mobile/src/widgets/platform.dart'; + +class BoardBackgroundThemeScreen extends StatelessWidget { + const BoardBackgroundThemeScreen({super.key}); + + @override + Widget build(BuildContext context) { + return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); + } + + Widget _androidBuilder(BuildContext context) { + return Scaffold(appBar: AppBar(title: Text(context.l10n.background)), body: _Body()); + } + + Widget _iosBuilder(BuildContext context) { + return CupertinoPageScaffold(navigationBar: const CupertinoNavigationBar(), child: _Body()); + } +} + +const choices = BoardBackgroundTheme.values; + +class _Body extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final boardPrefs = ref.watch(boardPreferencesProvider); + + void onChanged(BoardBackgroundTheme? value) => + ref.read(boardPreferencesProvider.notifier).setBackgroundTheme(value); + + final brightness = Theme.of(context).brightness; + + const itemsByRow = 4; + + return GridView.builder( + padding: MediaQuery.paddingOf(context) + Styles.bodyPadding, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: itemsByRow, + crossAxisSpacing: 6.0, + mainAxisSpacing: 6.0, + childAspectRatio: 0.5, + ), + itemBuilder: (context, index) { + final t = choices[index]; + final fsd = t.getFlexScheme(boardPrefs.boardTheme); + + final theme = + brightness == Brightness.light + ? FlexThemeData.light( + colors: fsd.light, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + blendLevel: 20, + ) + : FlexThemeData.dark( + colors: fsd.dark, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + blendLevel: 20, + ); + + return AdaptiveInkWell( + onTap: + () => Navigator.of(context, rootNavigator: true) + .push( + MaterialPageRoute( + builder: (_) => ConfirmBackgroundScreen(initialIndex: index), + fullscreenDialog: true, + ), + ) + .then((value) { + if (value == true) { + onChanged(t); + } + }), + child: SizedBox.expand(child: ColoredBox(color: theme.scaffoldBackgroundColor)), + ); + }, + itemCount: choices.length, + ); + } +} + +class ConfirmBackgroundScreen extends StatefulWidget { + const ConfirmBackgroundScreen({required this.initialIndex, super.key}); + + final int initialIndex; + + @override + State createState() => _ConfirmBackgroundScreenState(); +} + +class _ConfirmBackgroundScreenState extends State { + late PageController _controller; + + @override + void initState() { + _controller = PageController(initialPage: widget.initialIndex); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + PageView.builder( + controller: _controller, + itemBuilder: (context, index) { + final backgroundTheme = choices[index]; + return wrapper.BoardTheme( + backgroundTheme: backgroundTheme, + child: const Scaffold(body: SizedBox.expand()), + ); + }, + itemCount: choices.length, + ), + Positioned.fill( + child: Align( + alignment: Alignment.center, + child: Chessboard.fixed( + size: MediaQuery.sizeOf(context).width, + fen: kInitialFEN, + orientation: Side.white, + ), + ), + ), + ], + ), + bottomNavigationBar: BottomAppBar( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + TextButton( + child: Text(context.l10n.accept), + onPressed: () => Navigator.pop(context, true), + ), + TextButton( + child: Text(context.l10n.cancel), + onPressed: () => Navigator.pop(context, false), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/view/settings/theme_screen.dart b/lib/src/view/settings/theme_screen.dart index 9dd9b0b531..3750178bd7 100644 --- a/lib/src/view/settings/theme_screen.dart +++ b/lib/src/view/settings/theme_screen.dart @@ -13,30 +13,37 @@ import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/navigation.dart'; import 'package:lichess_mobile/src/utils/screen.dart'; -import 'package:lichess_mobile/src/view/settings/app_theme_screen.dart'; +import 'package:lichess_mobile/src/view/settings/board_background_theme_screen.dart'; import 'package:lichess_mobile/src/view/settings/board_theme_screen.dart'; import 'package:lichess_mobile/src/view/settings/piece_set_screen.dart'; import 'package:lichess_mobile/src/widgets/adaptive_choice_picker.dart'; +import 'package:lichess_mobile/src/widgets/board_theme.dart' as wrapper; import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; import 'package:lichess_mobile/src/widgets/settings.dart'; -class ThemeScreen extends StatelessWidget { +class ThemeScreen extends ConsumerWidget { const ThemeScreen({super.key}); @override - Widget build(BuildContext context) { - return PlatformWidget( - androidBuilder: (context) => const Scaffold(body: _Body()), - iosBuilder: - (context) => CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - automaticBackgroundVisibility: false, - backgroundColor: CupertinoTheme.of(context).barBackgroundColor.withValues(alpha: 0.0), - border: null, + Widget build(BuildContext context, WidgetRef ref) { + final boardPrefs = ref.read(boardPreferencesProvider); + return wrapper.BoardTheme( + backgroundTheme: boardPrefs.backgroundTheme, + child: PlatformWidget( + androidBuilder: (context) => const Scaffold(body: _Body()), + iosBuilder: + (context) => CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + automaticBackgroundVisibility: false, + backgroundColor: CupertinoTheme.of( + context, + ).barBackgroundColor.withValues(alpha: 0.0), + border: null, + ), + child: const _Body(), ), - child: const _Body(), - ), + ), ); } } @@ -168,26 +175,26 @@ class _BodyState extends ConsumerState<_Body> { hasLeading: true, children: [ SettingsListTile( - icon: const Icon(Icons.palette), - settingsLabel: Text(context.l10n.mobileTheme), - settingsValue: generalPrefs.appTheme.getFlexScheme(boardPrefs.boardTheme).name, + icon: const Icon(LichessIcons.chess_board), + settingsLabel: Text(context.l10n.board), + settingsValue: boardPrefs.boardTheme.label, onTap: () { pushPlatformRoute( context, - title: context.l10n.mobileTheme, - builder: (context) => const AppThemeScreen(), + title: context.l10n.board, + builder: (context) => const BoardThemeScreen(), ); }, ), SettingsListTile( - icon: const Icon(LichessIcons.chess_board), - settingsLabel: Text(context.l10n.board), - settingsValue: boardPrefs.boardTheme.label, + icon: const Icon(Icons.brightness_medium_outlined), + settingsLabel: Text(context.l10n.background), + settingsValue: generalPrefs.appTheme.label, onTap: () { pushPlatformRoute( context, - title: context.l10n.board, - builder: (context) => const BoardThemeScreen(), + title: context.l10n.background, + builder: (context) => const BoardBackgroundThemeScreen(), ); }, ), diff --git a/lib/src/view/study/study_screen.dart b/lib/src/view/study/study_screen.dart index 36de300b88..c08c4ac81c 100644 --- a/lib/src/view/study/study_screen.dart +++ b/lib/src/view/study/study_screen.dart @@ -52,7 +52,7 @@ class StudyScreen extends ConsumerWidget { return _StudyScreen(id: id, studyState: value); case AsyncError(:final error, :final stackTrace): _logger.severe('Cannot load study: $error', stackTrace); - return PlatformScaffold( + return PlatformBoardThemeScaffold( appBar: const PlatformAppBar(title: Text('')), body: DefaultTabController( length: 1, @@ -72,7 +72,7 @@ class StudyScreen extends ConsumerWidget { ), ); case _: - return PlatformScaffold( + return PlatformBoardThemeScaffold( appBar: PlatformAppBar( title: Shimmer( child: ShimmerLoading( @@ -163,7 +163,7 @@ class _StudyScreenState extends ConsumerState<_StudyScreen> with TickerProviderS @override Widget build(BuildContext context) { - return PlatformScaffold( + return PlatformBoardThemeScaffold( appBar: PlatformAppBar( title: AutoSizeText( widget.studyState.currentChapterTitle, diff --git a/lib/src/view/watch/tv_screen.dart b/lib/src/view/watch/tv_screen.dart index f57adc6aac..f734b794fe 100644 --- a/lib/src/view/watch/tv_screen.dart +++ b/lib/src/view/watch/tv_screen.dart @@ -45,7 +45,7 @@ class _TvScreenState extends ConsumerState { ref.read(_tvGameCtrl.notifier).stopWatching(); } }, - child: PlatformScaffold( + child: PlatformBoardThemeScaffold( appBar: PlatformAppBar( title: Text('${widget.channel.label} TV'), actions: const [ToggleSoundButton()], diff --git a/lib/src/widgets/board_theme.dart b/lib/src/widgets/board_theme.dart new file mode 100644 index 0000000000..6faa1d8271 --- /dev/null +++ b/lib/src/widgets/board_theme.dart @@ -0,0 +1,127 @@ +import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; +import 'package:lichess_mobile/src/styles/styles.dart'; +import 'package:lichess_mobile/src/utils/screen.dart'; + +/// Applies the configured board theme to the child widget. +/// +/// Typically used in screens that need to display a chess board. +class BoardTheme extends ConsumerWidget { + const BoardTheme({required this.child, this.backgroundTheme, super.key}); + + final Widget child; + final BoardBackgroundTheme? backgroundTheme; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final boardPrefs = ref.watch(boardPreferencesProvider); + final effectiveBackgroundTheme = backgroundTheme ?? boardPrefs.backgroundTheme; + + if (effectiveBackgroundTheme == null) { + return child; + } + + final boardTheme = boardPrefs.boardTheme; + final flexScheme = effectiveBackgroundTheme.getFlexScheme(boardTheme); + final isTablet = isTabletOrLarger(context); + final isIOS = Theme.of(context).platform == TargetPlatform.iOS; + + final flexSchemeLightColors = flexScheme.light; + final flexSchemeDarkColors = flexScheme.dark; + + final lightTheme = FlexThemeData.light( + colors: flexSchemeLightColors, + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, + blendLevel: 20, + ); + final darkTheme = + FlexColorScheme.dark( + colors: flexSchemeDarkColors, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + blendLevel: 20, + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, + ).toTheme; + + final lightCupertinoTheme = CupertinoThemeData( + primaryColor: lightTheme.colorScheme.primary, + primaryContrastingColor: lightTheme.colorScheme.onPrimary, + brightness: Brightness.light, + textTheme: CupertinoTheme.of(context).textTheme.copyWith( + primaryColor: lightTheme.colorScheme.primary, + textStyle: CupertinoTheme.of( + context, + ).textTheme.textStyle.copyWith(color: lightTheme.colorScheme.onSurface), + navTitleTextStyle: CupertinoTheme.of( + context, + ).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + navLargeTitleTextStyle: CupertinoTheme.of( + context, + ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + ), + scaffoldBackgroundColor: lightTheme.scaffoldBackgroundColor, + barBackgroundColor: lightTheme.appBarTheme.backgroundColor?.withValues( + alpha: isTablet ? 1.0 : 0.9, + ), + applyThemeToAll: true, + ); + + final darkCupertinoTheme = CupertinoThemeData( + primaryColor: darkTheme.colorScheme.primary, + primaryContrastingColor: darkTheme.colorScheme.onPrimary, + brightness: Brightness.dark, + textTheme: CupertinoTheme.of(context).textTheme.copyWith( + primaryColor: darkTheme.colorScheme.primary, + textStyle: CupertinoTheme.of( + context, + ).textTheme.textStyle.copyWith(color: darkTheme.colorScheme.onSurface), + navTitleTextStyle: CupertinoTheme.of( + context, + ).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + navLargeTitleTextStyle: CupertinoTheme.of( + context, + ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + ), + scaffoldBackgroundColor: darkTheme.scaffoldBackgroundColor, + barBackgroundColor: darkTheme.appBarTheme.backgroundColor?.withValues( + alpha: isTablet ? 1.0 : 0.9, + ), + applyThemeToAll: true, + ); + + final brightness = Theme.of(context).brightness; + + final theme = brightness == Brightness.light ? lightTheme : darkTheme; + final cupertinoTheme = + brightness == Brightness.light ? lightCupertinoTheme : darkCupertinoTheme; + + return Theme( + data: theme.copyWith( + cupertinoOverrideTheme: cupertinoTheme, + splashFactory: isIOS ? NoSplash.splashFactory : null, + textTheme: + isIOS + ? brightness == Brightness.light + ? Typography.blackCupertino + : Typography.whiteCupertino + : null, + extensions: [lichessCustomColors.harmonized(darkTheme.colorScheme)], + ), + child: + isIOS + ? CupertinoTheme( + data: cupertinoTheme, + child: IconTheme.merge( + data: IconThemeData(color: CupertinoTheme.of(context).textTheme.textStyle.color), + child: child, + ), + ) + : child, + ); + } +} diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index d71518b3a9..9e91f3441a 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -404,6 +404,7 @@ class AdaptiveListTile extends StatelessWidget { this.onTap, this.isThreeLine = false, this.contentPadding, + this.selected = false, super.key, }); @@ -413,6 +414,7 @@ class AdaptiveListTile extends StatelessWidget { final Widget? trailing; final GestureTapCallback? onTap; final bool isThreeLine; + final bool selected; final EdgeInsetsGeometry? contentPadding; @override @@ -425,6 +427,7 @@ class AdaptiveListTile extends StatelessWidget { subtitle: subtitle, trailing: trailing, onTap: onTap, + selected: selected, isThreeLine: isThreeLine, contentPadding: contentPadding, ), diff --git a/lib/src/widgets/platform_scaffold.dart b/lib/src/widgets/platform_scaffold.dart index 0edfc5d194..23102322ed 100644 --- a/lib/src/widgets/platform_scaffold.dart +++ b/lib/src/widgets/platform_scaffold.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:lichess_mobile/src/widgets/board_theme.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; const kCupertinoAppBarWithActionPadding = EdgeInsetsDirectional.only(start: 16.0, end: 8.0); @@ -152,3 +153,34 @@ class PlatformScaffold extends StatelessWidget { return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); } } + +class PlatformBoardThemeScaffold extends StatelessWidget { + const PlatformBoardThemeScaffold({ + super.key, + this.appBar, + required this.body, + this.resizeToAvoidBottomInset = true, + }); + + /// Acts as the [AppBar] for Android and as the [CupertinoNavigationBar] for iOS. + /// + /// Usually an instance of [PlatformAppBar]. + final Widget? appBar; + + /// The main content of the screen, displayed below the navigation bar. + final Widget body; + + /// See [Scaffold.resizeToAvoidBottomInset] and [CupertinoPageScaffold.resizeToAvoidBottomInset] + final bool resizeToAvoidBottomInset; + + @override + Widget build(BuildContext context) { + return BoardTheme( + child: PlatformScaffold( + resizeToAvoidBottomInset: resizeToAvoidBottomInset, + appBar: appBar, + body: body, + ), + ); + } +} diff --git a/lib/src/widgets/shimmer.dart b/lib/src/widgets/shimmer.dart index 6e5364913a..c858268ee0 100644 --- a/lib/src/widgets/shimmer.dart +++ b/lib/src/widgets/shimmer.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:lichess_mobile/src/styles/styles.dart'; class Shimmer extends StatefulWidget { static ShimmerState? of(BuildContext context) { @@ -18,14 +19,14 @@ class ShimmerState extends State with SingleTickerProviderStateMixin { LinearGradient get _defaultGradient { final brightness = Theme.of(context).brightness; - final colorScheme = Theme.of(context).colorScheme; + final scaffoldBackgroundColor = Theme.of(context).scaffoldBackgroundColor; switch (brightness) { case Brightness.light: return LinearGradient( colors: [ - colorScheme.surfaceContainer, - colorScheme.surfaceContainerLow, - colorScheme.surfaceContainerLowest, + darken(scaffoldBackgroundColor, 0.1), + darken(scaffoldBackgroundColor, 0.2), + darken(scaffoldBackgroundColor, 0.3), ], stops: const [0.1, 0.3, 0.4], begin: const Alignment(-1.0, -0.3), @@ -36,9 +37,9 @@ class ShimmerState extends State with SingleTickerProviderStateMixin { case Brightness.dark: return LinearGradient( colors: [ - colorScheme.surfaceContainer, - colorScheme.surfaceContainerHigh, - colorScheme.surfaceContainerHighest, + lighten(scaffoldBackgroundColor, 0.1), + lighten(scaffoldBackgroundColor, 0.2), + lighten(scaffoldBackgroundColor, 0.3), ], stops: const [0.1, 0.3, 0.4], begin: const Alignment(-1.0, -0.3), From 7886ae684d008b06d50c3be1aed26d9545b66cc9 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 23 Jan 2025 16:45:25 +0800 Subject: [PATCH 14/41] More work on custom board background theme --- lib/src/app.dart | 12 ++-- lib/src/view/puzzle/dashboard_screen.dart | 58 ++++++++++--------- .../board_background_theme_screen.dart | 13 +++-- .../view/settings/settings_tab_screen.dart | 1 - lib/src/view/settings/theme_screen.dart | 8 +-- lib/src/view/tools/tools_tab_screen.dart | 36 +++++------- lib/src/widgets/adaptive_choice_picker.dart | 3 +- lib/src/widgets/board_theme.dart | 9 ++- lib/src/widgets/list.dart | 7 +-- 9 files changed, 77 insertions(+), 70 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index ea4a848faf..7107de3e41 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -130,14 +130,14 @@ class _AppState extends ConsumerState { final lightTheme = FlexThemeData.light( colors: flexSchemeLightColors, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), - surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, blendLevel: 10, ); final darkTheme = FlexThemeData.dark( colors: flexSchemeDarkColors, - surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: 10, + surfaceMode: FlexSurfaceMode.level, + blendLevel: 16, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, ); @@ -167,7 +167,7 @@ class _AppState extends ConsumerState { final darkCupertinoTheme = CupertinoThemeData( primaryColor: darkTheme.colorScheme.primaryFixedDim, - primaryContrastingColor: darkTheme.colorScheme.onPrimaryFixed, + primaryContrastingColor: darkTheme.colorScheme.onPrimaryFixedVariant, brightness: Brightness.dark, textTheme: CupertinoTheme.of(context).textTheme.copyWith( primaryColor: darkTheme.colorScheme.primaryFixedDim, @@ -203,6 +203,8 @@ class _AppState extends ConsumerState { splashFactory: isIOS ? NoSplash.splashFactory : null, textTheme: isIOS ? Typography.blackCupertino : null, listTileTheme: ListTileTheme.of(context).copyWith( + tileColor: lightTheme.colorScheme.surfaceContainerLow, + selectedTileColor: lightTheme.colorScheme.surfaceContainer, titleTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, subtitleTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, leadingAndTrailingTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, @@ -221,6 +223,8 @@ class _AppState extends ConsumerState { splashFactory: isIOS ? NoSplash.splashFactory : null, textTheme: isIOS ? Typography.whiteCupertino : null, listTileTheme: ListTileTheme.of(context).copyWith( + tileColor: darkTheme.colorScheme.surfaceContainerLow, + selectedTileColor: darkTheme.colorScheme.surfaceContainer, titleTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, subtitleTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, leadingAndTrailingTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, diff --git a/lib/src/view/puzzle/dashboard_screen.dart b/lib/src/view/puzzle/dashboard_screen.dart index f8231c65ea..08f0ada871 100644 --- a/lib/src/view/puzzle/dashboard_screen.dart +++ b/lib/src/view/puzzle/dashboard_screen.dart @@ -94,13 +94,9 @@ class PuzzleDashboardWidget extends ConsumerWidget { ]), ), if (chartData.length >= 3) - Padding( - padding: const EdgeInsets.all(10.0), - child: AspectRatio( - aspectRatio: MediaQuery.sizeOf(context).width > FormFactor.desktop ? 2.8 : 1.2, - child: PuzzleChart(chartData), - ), - ), + Theme.of(context).platform == TargetPlatform.iOS + ? PuzzleChart(chartData) + : Card(margin: Styles.horizontalBodyPadding, child: PuzzleChart(chartData)), ], ); }, @@ -172,27 +168,35 @@ class PuzzleChart extends StatelessWidget { Widget build(BuildContext context) { final radarColor = Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5); final chartColor = Theme.of(context).colorScheme.secondary; - return RadarChart( - RadarChartData( - radarBorderData: BorderSide(width: 0.5, color: radarColor), - gridBorderData: BorderSide(width: 0.5, color: radarColor), - tickBorderData: BorderSide(width: 0.5, color: radarColor), - radarShape: RadarShape.polygon, - dataSets: [ - RadarDataSet( - fillColor: chartColor.withValues(alpha: 0.2), - borderColor: chartColor, - dataEntries: - puzzleData.map((theme) => RadarEntry(value: theme.performance.toDouble())).toList(), + return Padding( + padding: const EdgeInsets.all(10.0), + child: AspectRatio( + aspectRatio: MediaQuery.sizeOf(context).width > FormFactor.desktop ? 2.8 : 1.2, + child: RadarChart( + RadarChartData( + radarBorderData: BorderSide(width: 0.5, color: radarColor), + gridBorderData: BorderSide(width: 0.5, color: radarColor), + tickBorderData: BorderSide(width: 0.5, color: radarColor), + radarShape: RadarShape.polygon, + dataSets: [ + RadarDataSet( + fillColor: chartColor.withValues(alpha: 0.2), + borderColor: chartColor, + dataEntries: + puzzleData + .map((theme) => RadarEntry(value: theme.performance.toDouble())) + .toList(), + ), + ], + getTitle: + (index, angle) => + RadarChartTitle(text: puzzleData[index].theme.l10n(context.l10n).name), + titleTextStyle: const TextStyle(fontSize: 10), + titlePositionPercentageOffset: 0.09, + tickCount: 3, + ticksTextStyle: const TextStyle(fontSize: 8), ), - ], - getTitle: - (index, angle) => - RadarChartTitle(text: puzzleData[index].theme.l10n(context.l10n).name), - titleTextStyle: const TextStyle(fontSize: 10), - titlePositionPercentageOffset: 0.09, - tickCount: 3, - ticksTextStyle: const TextStyle(fontSize: 8), + ), ), ); } diff --git a/lib/src/view/settings/board_background_theme_screen.dart b/lib/src/view/settings/board_background_theme_screen.dart index 1a2aa52089..3b7720a9a0 100644 --- a/lib/src/view/settings/board_background_theme_screen.dart +++ b/lib/src/view/settings/board_background_theme_screen.dart @@ -71,14 +71,17 @@ class _Body extends ConsumerWidget { onTap: () => Navigator.of(context, rootNavigator: true) .push( - MaterialPageRoute( + MaterialPageRoute( builder: (_) => ConfirmBackgroundScreen(initialIndex: index), fullscreenDialog: true, ), ) .then((value) { - if (value == true) { - onChanged(t); + if (context.mounted) { + if (value != null) { + onChanged(choices[value.toInt()]); + Navigator.pop(context); + } } }), child: SizedBox.expand(child: ColoredBox(color: theme.scaffoldBackgroundColor)), @@ -141,11 +144,11 @@ class _ConfirmBackgroundScreenState extends State { children: [ TextButton( child: Text(context.l10n.accept), - onPressed: () => Navigator.pop(context, true), + onPressed: () => Navigator.pop(context, _controller.page), ), TextButton( child: Text(context.l10n.cancel), - onPressed: () => Navigator.pop(context, false), + onPressed: () => Navigator.pop(context, null), ), ], ), diff --git a/lib/src/view/settings/settings_tab_screen.dart b/lib/src/view/settings/settings_tab_screen.dart index b36ff480af..71f42f2bd7 100644 --- a/lib/src/view/settings/settings_tab_screen.dart +++ b/lib/src/view/settings/settings_tab_screen.dart @@ -343,7 +343,6 @@ class _Body extends ConsumerWidget { ), ListSection( hasLeading: true, - showDivider: true, children: [ PlatformListTile( leading: const Icon(Icons.storage_outlined), diff --git a/lib/src/view/settings/theme_screen.dart b/lib/src/view/settings/theme_screen.dart index 3750178bd7..048b0174a6 100644 --- a/lib/src/view/settings/theme_screen.dart +++ b/lib/src/view/settings/theme_screen.dart @@ -7,7 +7,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lichess_mobile/src/constants.dart'; import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; -import 'package:lichess_mobile/src/model/settings/general_preferences.dart'; import 'package:lichess_mobile/src/styles/lichess_icons.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; @@ -27,7 +26,7 @@ class ThemeScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final boardPrefs = ref.read(boardPreferencesProvider); + final boardPrefs = ref.watch(boardPreferencesProvider); return wrapper.BoardTheme( backgroundTheme: boardPrefs.backgroundTheme, child: PlatformWidget( @@ -107,7 +106,6 @@ class _BodyState extends ConsumerState<_Body> { @override Widget build(BuildContext context) { - final generalPrefs = ref.watch(generalPreferencesProvider); final boardPrefs = ref.watch(boardPreferencesProvider); final bool hasAjustedColors = @@ -187,9 +185,9 @@ class _BodyState extends ConsumerState<_Body> { }, ), SettingsListTile( - icon: const Icon(Icons.brightness_medium_outlined), + icon: const Icon(Icons.wallpaper), settingsLabel: Text(context.l10n.background), - settingsValue: generalPrefs.appTheme.label, + settingsValue: boardPrefs.backgroundTheme?.label ?? 'Default', onTap: () { pushPlatformRoute( context, diff --git a/lib/src/view/tools/tools_tab_screen.dart b/lib/src/view/tools/tools_tab_screen.dart index 40e2abde94..7aeb213472 100644 --- a/lib/src/view/tools/tools_tab_screen.dart +++ b/lib/src/view/tools/tools_tab_screen.dart @@ -78,29 +78,23 @@ class _ToolsButton extends StatelessWidget { ? 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, - 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)), - trailing: + return Opacity( + opacity: onTap == null ? 0.5 : 1.0, + child: PlatformListTile( + leading: Icon( + icon, + size: Styles.mainListTileIconSize, + color: Theme.of(context).platform == TargetPlatform.iOS - ? const CupertinoListTileChevron() - : null, - onTap: onTap, + ? CupertinoTheme.of(context).primaryColor + : Theme.of(context).colorScheme.primary, ), + title: Padding(padding: tilePadding, child: Text(title, style: Styles.callout)), + trailing: + Theme.of(context).platform == TargetPlatform.iOS + ? const CupertinoListTileChevron() + : null, + onTap: onTap, ), ); } diff --git a/lib/src/widgets/adaptive_choice_picker.dart b/lib/src/widgets/adaptive_choice_picker.dart index 89a65fa1ad..4e303646a4 100644 --- a/lib/src/widgets/adaptive_choice_picker.dart +++ b/lib/src/widgets/adaptive_choice_picker.dart @@ -21,7 +21,8 @@ Future showChoicePicker( context: context, builder: (context) { return AlertDialog( - contentPadding: const EdgeInsets.only(top: 12), + clipBehavior: Clip.hardEdge, + contentPadding: EdgeInsets.zero, scrollable: true, content: Builder( builder: (context) { diff --git a/lib/src/widgets/board_theme.dart b/lib/src/widgets/board_theme.dart index 6faa1d8271..3b099c492e 100644 --- a/lib/src/widgets/board_theme.dart +++ b/lib/src/widgets/board_theme.dart @@ -43,7 +43,7 @@ class BoardTheme extends ConsumerWidget { FlexColorScheme.dark( colors: flexSchemeDarkColors, surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: 20, + blendLevel: 18, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, ).toTheme; @@ -103,6 +103,13 @@ class BoardTheme extends ConsumerWidget { return Theme( data: theme.copyWith( cupertinoOverrideTheme: cupertinoTheme, + listTileTheme: ListTileTheme.of(context).copyWith( + tileColor: theme.colorScheme.surfaceContainerLow, + selectedTileColor: theme.colorScheme.surfaceContainer, + titleTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, + subtitleTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, + leadingAndTrailingTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, + ), splashFactory: isIOS ? NoSplash.splashFactory : null, textTheme: isIOS diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index 9e91f3441a..3764615afa 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -117,6 +117,7 @@ class ListSection extends StatelessWidget { children: [ if (header != null) ListTile( + tileColor: theme.scaffoldBackgroundColor, dense: true, title: DefaultTextStyle.merge(style: Styles.sectionTitle, child: header!), trailing: headerTrailing, @@ -125,11 +126,7 @@ class ListSection extends StatelessWidget { ...ListTile.divideTiles(context: context, tiles: children) else ...children, - if (showDivider) - const Padding( - padding: EdgeInsets.only(top: 10.0), - child: Divider(thickness: 0), - ), + if (showDivider) const SizedBox(height: 16.0), ], ), ); From ff7ec6a7d9403780e81a44fe6a00f62a5e75d5c1 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 23 Jan 2025 18:42:27 +0800 Subject: [PATCH 15/41] More work on board theme --- lib/src/model/settings/board_preferences.dart | 47 +- .../board_background_theme_screen.dart | 70 ++- .../view/settings/board_choice_screen.dart | 71 +++ lib/src/view/settings/board_theme_screen.dart | 409 ++++++++++++++++-- .../view/settings/settings_tab_screen.dart | 4 +- lib/src/view/settings/theme_screen.dart | 376 ---------------- lib/src/widgets/board_theme.dart | 2 +- lib/src/widgets/list.dart | 4 +- lib/src/widgets/platform.dart | 42 +- lib/src/widgets/settings.dart | 2 +- lib/src/widgets/shimmer.dart | 2 +- 11 files changed, 515 insertions(+), 514 deletions(-) create mode 100644 lib/src/view/settings/board_choice_screen.dart delete mode 100644 lib/src/view/settings/theme_screen.dart diff --git a/lib/src/model/settings/board_preferences.dart b/lib/src/model/settings/board_preferences.dart index 4484d6510f..ec08f7ed9c 100644 --- a/lib/src/model/settings/board_preferences.dart +++ b/lib/src/model/settings/board_preferences.dart @@ -404,61 +404,32 @@ enum BoardBackgroundTheme { board, /// Below values from [FlexScheme] - // blue, - // indigo, - // hippieBlue, - // sakura, // mandyRed, - // green, - // money, - // jungle, - // greyLaw, - // wasabi, - // mango, - // amber, - // vesuviusBurn, - // deepPurple, - // ebonyClay, - // barossa, - // shark, - // bigStone, - // damask, - // bahamaBlue, - // mallardGreen, - // espresso, - // outerSpace, - // blueWhale, - // sanJuanBlue, - // rosewood, - // blumineBlue, - // verdunHemlock, - // dellGenoa, - redM3, - red, redWine, pinkM3, + // amber, purpleBrown, purpleM3, indigoM3, blueM3, aquaBlue, - // brandBlue, - // deepBlue, - cyanM3, + // cyanM3, tealM3, greenM3, - limeM3, + jungle, + // limeM3, yellowM3, orangeM3, deepOrangeM3, - gold, - greys, + mango, + // gold, + // greys, sepia; static final _flexSchemesNameMap = FlexScheme.values.asNameMap(); - String get label => - this == BoardBackgroundTheme.board ? 'Chessboard' : _flexSchemesNameMap[name]!.data.name; + String label(AppLocalizations l10n) => + this == BoardBackgroundTheme.board ? l10n.board : _flexSchemesNameMap[name]!.data.name; FlexSchemeData getFlexScheme(BoardTheme boardTheme) => this == BoardBackgroundTheme.board ? boardTheme.flexScheme : _flexSchemesNameMap[name]!.data; diff --git a/lib/src/view/settings/board_background_theme_screen.dart b/lib/src/view/settings/board_background_theme_screen.dart index 3b7720a9a0..080625eb1e 100644 --- a/lib/src/view/settings/board_background_theme_screen.dart +++ b/lib/src/view/settings/board_background_theme_screen.dart @@ -5,6 +5,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; +import 'package:lichess_mobile/src/styles/lichess_icons.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/widgets/board_theme.dart' as wrapper; @@ -67,24 +68,53 @@ class _Body extends ConsumerWidget { blendLevel: 20, ); - return AdaptiveInkWell( - onTap: - () => Navigator.of(context, rootNavigator: true) - .push( - MaterialPageRoute( - builder: (_) => ConfirmBackgroundScreen(initialIndex: index), - fullscreenDialog: true, - ), - ) - .then((value) { - if (context.mounted) { - if (value != null) { - onChanged(choices[value.toInt()]); - Navigator.pop(context); + final autoColor = + brightness == Brightness.light + ? darken(theme.scaffoldBackgroundColor, 0.2) + : lighten(theme.scaffoldBackgroundColor, 0.2); + + return Tooltip( + message: 'Background based on chessboard colors.', + triggerMode: t == BoardBackgroundTheme.board ? null : TooltipTriggerMode.manual, + child: GestureDetector( + onTap: + () => Navigator.of(context, rootNavigator: true) + .push( + MaterialPageRoute( + builder: (_) => ConfirmBackgroundScreen(initialIndex: index), + fullscreenDialog: true, + ), + ) + .then((value) { + if (context.mounted) { + if (value != null) { + onChanged(choices[value.toInt()]); + Navigator.pop(context); + } } - } - }), - child: SizedBox.expand(child: ColoredBox(color: theme.scaffoldBackgroundColor)), + }), + child: SizedBox.expand( + child: ColoredBox( + color: theme.scaffoldBackgroundColor, + child: + t == BoardBackgroundTheme.board + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(LichessIcons.chess_board, color: autoColor), + const SizedBox(height: 8), + Center( + child: Text( + 'auto', + style: theme.textTheme.labelSmall?.copyWith(color: autoColor), + ), + ), + ], + ) + : null, + ), + ), + ), ); }, itemCount: choices.length, @@ -136,6 +166,12 @@ class _ConfirmBackgroundScreenState extends State { ), ), ), + Positioned( + bottom: MediaQuery.paddingOf(context).bottom + 16.0, + left: 0, + right: 0, + child: Text('Swipe to display other backgrounds', textAlign: TextAlign.center), + ), ], ), bottomNavigationBar: BottomAppBar( diff --git a/lib/src/view/settings/board_choice_screen.dart b/lib/src/view/settings/board_choice_screen.dart new file mode 100644 index 0000000000..a6b5fab007 --- /dev/null +++ b/lib/src/view/settings/board_choice_screen.dart @@ -0,0 +1,71 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; +import 'package:lichess_mobile/src/utils/color_palette.dart'; +import 'package:lichess_mobile/src/utils/l10n_context.dart'; +import 'package:lichess_mobile/src/widgets/list.dart'; +import 'package:lichess_mobile/src/widgets/platform.dart'; + +class BoardChoiceScreen extends StatelessWidget { + const BoardChoiceScreen({super.key}); + + @override + Widget build(BuildContext context) { + return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); + } + + Widget _androidBuilder(BuildContext context) { + return Scaffold(appBar: AppBar(title: Text(context.l10n.board)), body: _Body()); + } + + Widget _iosBuilder(BuildContext context) { + 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 hasSystemColors = getCorePalette() != null; + + final choices = + BoardTheme.values.where((t) => t != BoardTheme.system || hasSystemColors).toList(); + + 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, + ); + + return SafeArea( + child: ListView.separated( + itemBuilder: (context, index) { + final t = choices[index]; + return PlatformListTile( + selected: t == boardTheme, + trailing: t == boardTheme ? checkedIcon : null, + title: Text(t.label), + subtitle: Align(alignment: Alignment.topLeft, child: t.thumbnail), + 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, + ), + itemCount: choices.length, + ), + ); + } +} diff --git a/lib/src/view/settings/board_theme_screen.dart b/lib/src/view/settings/board_theme_screen.dart index 902f6e98a3..ffcf8c9f21 100644 --- a/lib/src/view/settings/board_theme_screen.dart +++ b/lib/src/view/settings/board_theme_screen.dart @@ -1,70 +1,383 @@ +import 'dart:ui' show ImageFilter; +import 'package:chessground/chessground.dart'; +import 'package:dartchess/dartchess.dart'; +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lichess_mobile/src/constants.dart'; import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; -import 'package:lichess_mobile/src/utils/color_palette.dart'; +import 'package:lichess_mobile/src/styles/lichess_icons.dart'; +import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; +import 'package:lichess_mobile/src/utils/navigation.dart'; +import 'package:lichess_mobile/src/utils/screen.dart'; +import 'package:lichess_mobile/src/view/settings/board_background_theme_screen.dart'; +import 'package:lichess_mobile/src/view/settings/board_choice_screen.dart'; +import 'package:lichess_mobile/src/view/settings/piece_set_screen.dart'; +import 'package:lichess_mobile/src/widgets/adaptive_choice_picker.dart'; +import 'package:lichess_mobile/src/widgets/board_theme.dart' as wrapper; import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; +import 'package:lichess_mobile/src/widgets/settings.dart'; -class BoardThemeScreen extends StatelessWidget { +class BoardThemeScreen extends ConsumerWidget { const BoardThemeScreen({super.key}); @override - Widget build(BuildContext context) { - return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); + Widget build(BuildContext context, WidgetRef ref) { + final boardPrefs = ref.watch(boardPreferencesProvider); + return wrapper.BoardTheme( + backgroundTheme: boardPrefs.backgroundTheme, + child: PlatformWidget( + androidBuilder: (context) => const Scaffold(body: _Body()), + iosBuilder: + (context) => CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + automaticBackgroundVisibility: false, + backgroundColor: CupertinoTheme.of( + context, + ).barBackgroundColor.withValues(alpha: 0.0), + border: null, + ), + child: const _Body(), + ), + ), + ); } +} + +String shapeColorL10n(BuildContext context, ShapeColor shapeColor) => +// TODO add l10n +switch (shapeColor) { + ShapeColor.green => 'Green', + ShapeColor.red => 'Red', + ShapeColor.blue => 'Blue', + ShapeColor.yellow => 'Yellow', +}; - Widget _androidBuilder(BuildContext context) { - return Scaffold(appBar: AppBar(title: Text(context.l10n.board)), body: _Body()); +class _Body extends ConsumerStatefulWidget { + const _Body(); + + @override + ConsumerState<_Body> createState() => _BodyState(); +} + +class _BodyState extends ConsumerState<_Body> { + late double brightness; + late double hue; + + double headerOpacity = 0; + + bool openAdjustColorSection = false; + + @override + void initState() { + super.initState(); + final boardPrefs = ref.read(boardPreferencesProvider); + brightness = boardPrefs.brightness; + hue = boardPrefs.hue; } - Widget _iosBuilder(BuildContext context) { - return CupertinoPageScaffold(navigationBar: const CupertinoNavigationBar(), child: _Body()); + bool handleScrollNotification(ScrollNotification notification) { + if (notification is ScrollUpdateNotification && notification.depth == 0) { + final ScrollMetrics metrics = notification.metrics; + double scrollExtent = 0.0; + switch (metrics.axisDirection) { + case AxisDirection.up: + scrollExtent = metrics.extentAfter; + case AxisDirection.down: + scrollExtent = metrics.extentBefore; + case AxisDirection.right: + case AxisDirection.left: + break; + } + + final opacity = scrollExtent > 0.0 ? 1.0 : 0.0; + + if (opacity != headerOpacity) { + setState(() { + headerOpacity = opacity; + }); + } + } + return false; } -} -class _Body extends ConsumerWidget { @override - Widget build(BuildContext context, WidgetRef ref) { - final boardTheme = ref.watch(boardPreferencesProvider.select((p) => p.boardTheme)); - - final hasSystemColors = getCorePalette() != null; - - final choices = - BoardTheme.values.where((t) => t != BoardTheme.system || hasSystemColors).toList(); - - 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, - ); - - return SafeArea( - child: ListView.separated( - itemBuilder: (context, index) { - final t = choices[index]; - return PlatformListTile( - selected: t == boardTheme, - trailing: t == boardTheme ? checkedIcon : null, - title: Text(t.label), - subtitle: Align(alignment: Alignment.topLeft, child: t.thumbnail), - 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, + Widget build(BuildContext context) { + final boardPrefs = ref.watch(boardPreferencesProvider); + + final bool hasAjustedColors = + brightness != kBoardDefaultBrightnessFilter || hue != kBoardDefaultHueFilter; + + final boardSize = isTabletOrLarger(context) ? 350.0 : 200.0; + + final backgroundColor = CupertinoTheme.of(context).barBackgroundColor; + + return NotificationListener( + onNotification: handleScrollNotification, + child: CustomScrollView( + slivers: [ + if (Theme.of(context).platform == TargetPlatform.iOS) + PinnedHeaderSliver( + child: ClipRect( + child: BackdropFilter( + enabled: backgroundColor.a != 1, + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: ShapeDecoration( + color: headerOpacity == 1.0 ? backgroundColor : backgroundColor.withAlpha(0), + shape: LinearBorder.bottom( + side: BorderSide( + color: + headerOpacity == 1.0 ? const Color(0x4D000000) : Colors.transparent, + width: 0.0, + ), + ), + ), + padding: + Styles.bodyPadding + + EdgeInsets.only(top: MediaQuery.paddingOf(context).top), + child: _BoardPreview( + size: boardSize, + boardPrefs: boardPrefs, + brightness: brightness, + hue: hue, + ), + ), + ), + ), + ) + else + SliverAppBar( + pinned: true, + title: Text(context.l10n.mobileTheme), + bottom: PreferredSize( + preferredSize: Size.fromHeight(boardSize + 16.0), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: _BoardPreview( + size: boardSize, + boardPrefs: boardPrefs, + brightness: brightness, + hue: hue, + ), + ), + ), ), - itemCount: choices.length, + SliverList.list( + children: [ + ListSection( + hasLeading: true, + children: [ + SettingsListTile( + icon: const Icon(LichessIcons.chess_board), + settingsLabel: Text(context.l10n.board), + settingsValue: boardPrefs.boardTheme.label, + onTap: () { + pushPlatformRoute( + context, + title: context.l10n.board, + builder: (context) => const BoardChoiceScreen(), + ); + }, + ), + SettingsListTile( + icon: const Icon(Icons.wallpaper), + settingsLabel: Text(context.l10n.background), + settingsValue: boardPrefs.backgroundTheme?.label(context.l10n) ?? 'Default', + onTap: () { + pushPlatformRoute( + context, + title: context.l10n.background, + builder: (context) => const BoardBackgroundThemeScreen(), + ); + }, + ), + if (boardPrefs.backgroundTheme != null) + PlatformListTile( + leading: const Icon(Icons.cancel), + title: const Text('Reset background'), + onTap: () { + ref.read(boardPreferencesProvider.notifier).setBackgroundTheme(null); + }, + ), + SettingsListTile( + icon: const Icon(LichessIcons.chess_pawn), + settingsLabel: Text(context.l10n.pieceSet), + settingsValue: boardPrefs.pieceSet.label, + onTap: () { + pushPlatformRoute( + context, + title: context.l10n.pieceSet, + builder: (context) => const PieceSetScreen(), + ); + }, + ), + SettingsListTile( + icon: const Icon(LichessIcons.arrow_full_upperright), + settingsLabel: const Text('Shape color'), + settingsValue: shapeColorL10n(context, boardPrefs.shapeColor), + onTap: () { + showChoicePicker( + context, + choices: ShapeColor.values, + selectedItem: boardPrefs.shapeColor, + labelBuilder: + (t) => Text.rich( + TextSpan( + children: [ + TextSpan(text: shapeColorL10n(context, t)), + const TextSpan(text: ' '), + WidgetSpan( + child: Container(width: 15, height: 15, color: t.color), + ), + ], + ), + ), + onSelectedItemChanged: (ShapeColor? value) { + ref + .read(boardPreferencesProvider.notifier) + .setShapeColor(value ?? ShapeColor.green); + }, + ); + }, + ), + SwitchSettingTile( + leading: const Icon(Icons.location_on), + title: Text(context.l10n.preferencesBoardCoordinates), + value: boardPrefs.coordinates, + onChanged: (value) { + ref.read(boardPreferencesProvider.notifier).toggleCoordinates(); + }, + ), + SwitchSettingTile( + // TODO translate + leading: const Icon(Icons.border_outer), + title: const Text('Show border'), + value: boardPrefs.showBorder, + onChanged: (value) { + ref.read(boardPreferencesProvider.notifier).toggleBorder(); + }, + ), + ], + ), + ListSection( + header: SettingsSectionTitle(context.l10n.advancedSettings), + hasLeading: true, + children: [ + PlatformListTile( + leading: const Icon(Icons.brightness_6), + title: Slider.adaptive( + min: 0.2, + max: 1.4, + value: brightness, + onChanged: (value) { + setState(() { + brightness = value; + }); + }, + onChangeEnd: (value) { + ref + .read(boardPreferencesProvider.notifier) + .adjustColors(brightness: brightness); + }, + ), + ), + PlatformListTile( + leading: const Icon(Icons.invert_colors), + title: Slider.adaptive( + min: 0.0, + max: 360.0, + value: hue, + onChanged: (value) { + setState(() { + hue = value; + }); + }, + onChangeEnd: (value) { + ref.read(boardPreferencesProvider.notifier).adjustColors(hue: hue); + }, + ), + ), + PlatformListTile( + leading: Opacity( + opacity: hasAjustedColors ? 1.0 : 0.5, + child: const Icon(Icons.cancel), + ), + title: Opacity( + opacity: hasAjustedColors ? 1.0 : 0.5, + child: Text(context.l10n.boardReset), + ), + onTap: + hasAjustedColors + ? () { + setState(() { + brightness = kBoardDefaultBrightnessFilter; + hue = kBoardDefaultHueFilter; + }); + ref + .read(boardPreferencesProvider.notifier) + .adjustColors(brightness: brightness, hue: hue); + } + : null, + ), + ], + ), + ], + ), + const SliverSafeArea( + top: false, + sliver: SliverToBoxAdapter(child: SizedBox(height: 16.0)), + ), + ], + ), + ); + } +} + +class _BoardPreview extends StatelessWidget { + const _BoardPreview({ + required this.size, + required this.boardPrefs, + required this.brightness, + required this.hue, + }); + + final BoardPrefs boardPrefs; + final double brightness; + final double hue; + final double size; + + @override + Widget build(BuildContext context) { + return Center( + child: BrightnessHueFilter( + brightness: brightness, + hue: hue, + child: Chessboard.fixed( + size: size, + 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, + settings: boardPrefs.toBoardSettings().copyWith( + brightness: kBoardDefaultBrightnessFilter, + hue: kBoardDefaultHueFilter, + borderRadius: const BorderRadius.all(Radius.circular(4.0)), + boxShadow: boardShadows, + ), + ), ), ); } diff --git a/lib/src/view/settings/settings_tab_screen.dart b/lib/src/view/settings/settings_tab_screen.dart index 71f42f2bd7..d3a70cceb2 100644 --- a/lib/src/view/settings/settings_tab_screen.dart +++ b/lib/src/view/settings/settings_tab_screen.dart @@ -19,8 +19,8 @@ import 'package:lichess_mobile/src/view/account/profile_screen.dart'; import 'package:lichess_mobile/src/view/settings/account_preferences_screen.dart'; import 'package:lichess_mobile/src/view/settings/app_background_mode_screen.dart'; import 'package:lichess_mobile/src/view/settings/board_settings_screen.dart'; +import 'package:lichess_mobile/src/view/settings/board_theme_screen.dart'; import 'package:lichess_mobile/src/view/settings/sound_settings_screen.dart'; -import 'package:lichess_mobile/src/view/settings/theme_screen.dart'; import 'package:lichess_mobile/src/widgets/adaptive_action_sheet.dart'; import 'package:lichess_mobile/src/widgets/adaptive_choice_picker.dart'; import 'package:lichess_mobile/src/widgets/feedback.dart'; @@ -230,7 +230,7 @@ class _Body extends ConsumerWidget { pushPlatformRoute( context, title: context.l10n.mobileTheme, - builder: (context) => const ThemeScreen(), + builder: (context) => const BoardThemeScreen(), ); }, ), diff --git a/lib/src/view/settings/theme_screen.dart b/lib/src/view/settings/theme_screen.dart deleted file mode 100644 index 048b0174a6..0000000000 --- a/lib/src/view/settings/theme_screen.dart +++ /dev/null @@ -1,376 +0,0 @@ -import 'dart:ui' show ImageFilter; -import 'package:chessground/chessground.dart'; -import 'package:dartchess/dartchess.dart'; -import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:lichess_mobile/src/constants.dart'; -import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; -import 'package:lichess_mobile/src/styles/lichess_icons.dart'; -import 'package:lichess_mobile/src/styles/styles.dart'; -import 'package:lichess_mobile/src/utils/l10n_context.dart'; -import 'package:lichess_mobile/src/utils/navigation.dart'; -import 'package:lichess_mobile/src/utils/screen.dart'; -import 'package:lichess_mobile/src/view/settings/board_background_theme_screen.dart'; -import 'package:lichess_mobile/src/view/settings/board_theme_screen.dart'; -import 'package:lichess_mobile/src/view/settings/piece_set_screen.dart'; -import 'package:lichess_mobile/src/widgets/adaptive_choice_picker.dart'; -import 'package:lichess_mobile/src/widgets/board_theme.dart' as wrapper; -import 'package:lichess_mobile/src/widgets/list.dart'; -import 'package:lichess_mobile/src/widgets/platform.dart'; -import 'package:lichess_mobile/src/widgets/settings.dart'; - -class ThemeScreen extends ConsumerWidget { - const ThemeScreen({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final boardPrefs = ref.watch(boardPreferencesProvider); - return wrapper.BoardTheme( - backgroundTheme: boardPrefs.backgroundTheme, - child: PlatformWidget( - androidBuilder: (context) => const Scaffold(body: _Body()), - iosBuilder: - (context) => CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - automaticBackgroundVisibility: false, - backgroundColor: CupertinoTheme.of( - context, - ).barBackgroundColor.withValues(alpha: 0.0), - border: null, - ), - child: const _Body(), - ), - ), - ); - } -} - -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 ConsumerStatefulWidget { - const _Body(); - - @override - ConsumerState<_Body> createState() => _BodyState(); -} - -class _BodyState extends ConsumerState<_Body> { - late double brightness; - late double hue; - - double headerOpacity = 0; - - bool openAdjustColorSection = false; - - @override - void initState() { - super.initState(); - final boardPrefs = ref.read(boardPreferencesProvider); - brightness = boardPrefs.brightness; - hue = boardPrefs.hue; - } - - bool handleScrollNotification(ScrollNotification notification) { - if (notification is ScrollUpdateNotification && notification.depth == 0) { - final ScrollMetrics metrics = notification.metrics; - double scrollExtent = 0.0; - switch (metrics.axisDirection) { - case AxisDirection.up: - scrollExtent = metrics.extentAfter; - case AxisDirection.down: - scrollExtent = metrics.extentBefore; - case AxisDirection.right: - case AxisDirection.left: - break; - } - - final opacity = scrollExtent > 0.0 ? 1.0 : 0.0; - - if (opacity != headerOpacity) { - setState(() { - headerOpacity = opacity; - }); - } - } - return false; - } - - @override - Widget build(BuildContext context) { - final boardPrefs = ref.watch(boardPreferencesProvider); - - final bool hasAjustedColors = - brightness != kBoardDefaultBrightnessFilter || hue != kBoardDefaultHueFilter; - - final boardSize = isTabletOrLarger(context) ? 350.0 : 200.0; - - final backgroundColor = CupertinoTheme.of(context).barBackgroundColor; - - return NotificationListener( - onNotification: handleScrollNotification, - child: CustomScrollView( - slivers: [ - if (Theme.of(context).platform == TargetPlatform.iOS) - PinnedHeaderSliver( - child: ClipRect( - child: BackdropFilter( - enabled: backgroundColor.a != 1, - filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - decoration: ShapeDecoration( - color: headerOpacity == 1.0 ? backgroundColor : backgroundColor.withAlpha(0), - shape: LinearBorder.bottom( - side: BorderSide( - color: - headerOpacity == 1.0 ? const Color(0x4D000000) : Colors.transparent, - width: 0.0, - ), - ), - ), - padding: - Styles.bodyPadding + - EdgeInsets.only(top: MediaQuery.paddingOf(context).top), - child: _BoardPreview( - size: boardSize, - boardPrefs: boardPrefs, - brightness: brightness, - hue: hue, - ), - ), - ), - ), - ) - else - SliverAppBar( - pinned: true, - title: Text(context.l10n.mobileTheme), - bottom: PreferredSize( - preferredSize: Size.fromHeight(boardSize + 16.0), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: _BoardPreview( - size: boardSize, - boardPrefs: boardPrefs, - brightness: brightness, - hue: hue, - ), - ), - ), - ), - SliverList.list( - children: [ - ListSection( - hasLeading: true, - children: [ - SettingsListTile( - icon: const Icon(LichessIcons.chess_board), - settingsLabel: Text(context.l10n.board), - settingsValue: boardPrefs.boardTheme.label, - onTap: () { - pushPlatformRoute( - context, - title: context.l10n.board, - builder: (context) => const BoardThemeScreen(), - ); - }, - ), - SettingsListTile( - icon: const Icon(Icons.wallpaper), - settingsLabel: Text(context.l10n.background), - settingsValue: boardPrefs.backgroundTheme?.label ?? 'Default', - onTap: () { - pushPlatformRoute( - context, - title: context.l10n.background, - builder: (context) => const BoardBackgroundThemeScreen(), - ); - }, - ), - SettingsListTile( - icon: const Icon(LichessIcons.chess_pawn), - settingsLabel: Text(context.l10n.pieceSet), - settingsValue: boardPrefs.pieceSet.label, - onTap: () { - pushPlatformRoute( - context, - title: context.l10n.pieceSet, - builder: (context) => const PieceSetScreen(), - ); - }, - ), - SettingsListTile( - icon: const Icon(LichessIcons.arrow_full_upperright), - settingsLabel: const Text('Shape color'), - settingsValue: shapeColorL10n(context, boardPrefs.shapeColor), - onTap: () { - showChoicePicker( - context, - choices: ShapeColor.values, - selectedItem: boardPrefs.shapeColor, - labelBuilder: - (t) => Text.rich( - TextSpan( - children: [ - TextSpan(text: shapeColorL10n(context, t)), - const TextSpan(text: ' '), - WidgetSpan( - child: Container(width: 15, height: 15, color: t.color), - ), - ], - ), - ), - onSelectedItemChanged: (ShapeColor? value) { - ref - .read(boardPreferencesProvider.notifier) - .setShapeColor(value ?? ShapeColor.green); - }, - ); - }, - ), - SwitchSettingTile( - leading: const Icon(Icons.location_on), - title: Text(context.l10n.preferencesBoardCoordinates), - value: boardPrefs.coordinates, - onChanged: (value) { - ref.read(boardPreferencesProvider.notifier).toggleCoordinates(); - }, - ), - SwitchSettingTile( - // TODO translate - leading: const Icon(Icons.border_outer), - title: const Text('Show border'), - value: boardPrefs.showBorder, - onChanged: (value) { - ref.read(boardPreferencesProvider.notifier).toggleBorder(); - }, - ), - ], - ), - ListSection( - header: SettingsSectionTitle(context.l10n.advancedSettings), - hasLeading: true, - children: [ - PlatformListTile( - leading: const Icon(Icons.brightness_6), - title: Slider.adaptive( - min: 0.2, - max: 1.4, - value: brightness, - onChanged: (value) { - setState(() { - brightness = value; - }); - }, - onChangeEnd: (value) { - ref - .read(boardPreferencesProvider.notifier) - .adjustColors(brightness: brightness); - }, - ), - ), - PlatformListTile( - leading: const Icon(Icons.invert_colors), - title: Slider.adaptive( - min: 0.0, - max: 360.0, - value: hue, - onChanged: (value) { - setState(() { - hue = value; - }); - }, - onChangeEnd: (value) { - ref.read(boardPreferencesProvider.notifier).adjustColors(hue: hue); - }, - ), - ), - PlatformListTile( - leading: Opacity( - opacity: hasAjustedColors ? 1.0 : 0.5, - child: const Icon(Icons.cancel), - ), - title: Opacity( - opacity: hasAjustedColors ? 1.0 : 0.5, - child: Text(context.l10n.boardReset), - ), - onTap: - hasAjustedColors - ? () { - setState(() { - brightness = kBoardDefaultBrightnessFilter; - hue = kBoardDefaultHueFilter; - }); - ref - .read(boardPreferencesProvider.notifier) - .adjustColors(brightness: brightness, hue: hue); - } - : null, - ), - ], - ), - ], - ), - const SliverSafeArea( - top: false, - sliver: SliverToBoxAdapter(child: SizedBox(height: 16.0)), - ), - ], - ), - ); - } -} - -class _BoardPreview extends StatelessWidget { - const _BoardPreview({ - required this.size, - required this.boardPrefs, - required this.brightness, - required this.hue, - }); - - final BoardPrefs boardPrefs; - final double brightness; - final double hue; - final double size; - - @override - Widget build(BuildContext context) { - return Center( - child: BrightnessHueFilter( - brightness: brightness, - hue: hue, - child: Chessboard.fixed( - size: size, - 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, - settings: boardPrefs.toBoardSettings().copyWith( - brightness: kBoardDefaultBrightnessFilter, - hue: kBoardDefaultHueFilter, - borderRadius: const BorderRadius.all(Radius.circular(4.0)), - boxShadow: boardShadows, - ), - ), - ), - ); - } -} diff --git a/lib/src/widgets/board_theme.dart b/lib/src/widgets/board_theme.dart index 3b099c492e..0527f9d46d 100644 --- a/lib/src/widgets/board_theme.dart +++ b/lib/src/widgets/board_theme.dart @@ -43,7 +43,7 @@ class BoardTheme extends ConsumerWidget { FlexColorScheme.dark( colors: flexSchemeDarkColors, surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: 18, + blendLevel: 20, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, ).toTheme; diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index 3764615afa..da4b9047c8 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -205,7 +205,7 @@ class ListSection extends StatelessWidget { cupertinoBackgroundColor ?? (theme.brightness == Brightness.light ? theme.colorScheme.surfaceContainerLowest - : theme.colorScheme.surfaceContainerHigh), + : theme.colorScheme.surfaceContainer), borderRadius: cupertinoBorderRadius ?? const BorderRadius.all(Radius.circular(10.0)), ), @@ -352,7 +352,7 @@ class PlatformListTile extends StatelessWidget { case TargetPlatform.iOS: final activatedColor = colorScheme.surfaceContainerHighest; return IconTheme( - data: CupertinoIconThemeData(color: CupertinoColors.systemGrey.resolveFrom(context)), + data: CupertinoIconThemeData(color: colorScheme.outline), child: GestureDetector( onLongPress: onLongPress, child: CupertinoListTile.notched( diff --git a/lib/src/widgets/platform.dart b/lib/src/widgets/platform.dart index fc97f3bb34..1564b89d25 100644 --- a/lib/src/widgets/platform.dart +++ b/lib/src/widgets/platform.dart @@ -85,36 +85,22 @@ class PlatformCard extends StatelessWidget { @override Widget build(BuildContext context) { final brightness = Theme.of(context).brightness; + final cardFactory = brightness == Brightness.dark ? Card.filled : Card.new; return MediaQuery.withClampedTextScaling( maxScaleFactor: kCardTextScaleFactor, - child: - Theme.of(context).platform == TargetPlatform.iOS - ? Card.filled( - margin: margin ?? EdgeInsets.zero, - elevation: elevation ?? 0, - color: color, - shadowColor: shadowColor, - shape: - borderRadius != null - ? RoundedRectangleBorder(borderRadius: borderRadius!) - : const RoundedRectangleBorder(borderRadius: kCardBorderRadius), - semanticContainer: semanticContainer, - clipBehavior: clipBehavior, - child: child, - ) - : (brightness == Brightness.dark ? Card.filled : Card.new)( - shape: - borderRadius != null - ? RoundedRectangleBorder(borderRadius: borderRadius!) - : const RoundedRectangleBorder(borderRadius: kCardBorderRadius), - color: color, - shadowColor: shadowColor, - semanticContainer: semanticContainer, - elevation: elevation, - margin: margin ?? EdgeInsets.zero, - clipBehavior: clipBehavior, - child: child, - ), + child: cardFactory( + shape: + borderRadius != null + ? RoundedRectangleBorder(borderRadius: borderRadius!) + : const RoundedRectangleBorder(borderRadius: kCardBorderRadius), + color: color, + shadowColor: shadowColor, + semanticContainer: semanticContainer, + elevation: elevation ?? (Theme.of(context).platform == TargetPlatform.iOS ? 0 : null), + margin: margin ?? EdgeInsets.zero, + clipBehavior: clipBehavior, + child: child, + ), ); } } diff --git a/lib/src/widgets/settings.dart b/lib/src/widgets/settings.dart index d920f967ed..57c39cc11b 100644 --- a/lib/src/widgets/settings.dart +++ b/lib/src/widgets/settings.dart @@ -283,7 +283,7 @@ class ChoicePicker extends StatelessWidget { color: theme.brightness == Brightness.light ? theme.colorScheme.surfaceContainerLowest - : theme.colorScheme.surfaceContainerHigh, + : theme.colorScheme.surfaceContainer, borderRadius: const BorderRadius.all(Radius.circular(10.0)), ), separatorColor: Styles.cupertinoSeparatorColor.resolveFrom(context), diff --git a/lib/src/widgets/shimmer.dart b/lib/src/widgets/shimmer.dart index c858268ee0..acee719b78 100644 --- a/lib/src/widgets/shimmer.dart +++ b/lib/src/widgets/shimmer.dart @@ -24,9 +24,9 @@ class ShimmerState extends State with SingleTickerProviderStateMixin { case Brightness.light: return LinearGradient( colors: [ + darken(scaffoldBackgroundColor, 0.05), darken(scaffoldBackgroundColor, 0.1), darken(scaffoldBackgroundColor, 0.2), - darken(scaffoldBackgroundColor, 0.3), ], stops: const [0.1, 0.3, 0.4], begin: const Alignment(-1.0, -0.3), From ffefc06500a1c9a9457ea0ac0a96d1da7a87dd72 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 23 Jan 2025 18:46:46 +0800 Subject: [PATCH 16/41] Color fixes --- lib/src/widgets/pgn.dart | 7 +------ lib/src/widgets/shimmer.dart | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/src/widgets/pgn.dart b/lib/src/widgets/pgn.dart index 4dd34ff66b..930908df8d 100644 --- a/lib/src/widgets/pgn.dart +++ b/lib/src/widgets/pgn.dart @@ -972,12 +972,7 @@ class InlineMove extends ConsumerWidget { 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, + color: isCurrentMove ? Theme.of(context).focusColor : null, shape: BoxShape.rectangle, borderRadius: borderRadius, border: isLiveMove ? Border.all(width: 2, color: Colors.orange) : null, diff --git a/lib/src/widgets/shimmer.dart b/lib/src/widgets/shimmer.dart index acee719b78..ee5bca2610 100644 --- a/lib/src/widgets/shimmer.dart +++ b/lib/src/widgets/shimmer.dart @@ -37,9 +37,9 @@ class ShimmerState extends State with SingleTickerProviderStateMixin { case Brightness.dark: return LinearGradient( colors: [ + lighten(scaffoldBackgroundColor, 0.05), lighten(scaffoldBackgroundColor, 0.1), lighten(scaffoldBackgroundColor, 0.2), - lighten(scaffoldBackgroundColor, 0.3), ], stops: const [0.1, 0.3, 0.4], begin: const Alignment(-1.0, -0.3), From ef2f74b0bfa34d3e7db7e1708a1f36774d327e5c Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 23 Jan 2025 18:56:57 +0800 Subject: [PATCH 17/41] Fix lint --- lib/src/view/settings/board_background_theme_screen.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/view/settings/board_background_theme_screen.dart b/lib/src/view/settings/board_background_theme_screen.dart index 080625eb1e..845653092b 100644 --- a/lib/src/view/settings/board_background_theme_screen.dart +++ b/lib/src/view/settings/board_background_theme_screen.dart @@ -9,7 +9,6 @@ import 'package:lichess_mobile/src/styles/lichess_icons.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/widgets/board_theme.dart' as wrapper; -import 'package:lichess_mobile/src/widgets/buttons.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; class BoardBackgroundThemeScreen extends StatelessWidget { @@ -170,7 +169,7 @@ class _ConfirmBackgroundScreenState extends State { bottom: MediaQuery.paddingOf(context).bottom + 16.0, left: 0, right: 0, - child: Text('Swipe to display other backgrounds', textAlign: TextAlign.center), + child: const Text('Swipe to display other backgrounds', textAlign: TextAlign.center), ), ], ), From 43718bcd7ed23dac2ee4f2475538ff78c684ba77 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 23 Jan 2025 19:42:41 +0800 Subject: [PATCH 18/41] Fix board prefs in bg selector screen --- .../view/settings/board_background_theme_screen.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/src/view/settings/board_background_theme_screen.dart b/lib/src/view/settings/board_background_theme_screen.dart index 845653092b..36ea9ea5eb 100644 --- a/lib/src/view/settings/board_background_theme_screen.dart +++ b/lib/src/view/settings/board_background_theme_screen.dart @@ -80,7 +80,11 @@ class _Body extends ConsumerWidget { () => Navigator.of(context, rootNavigator: true) .push( MaterialPageRoute( - builder: (_) => ConfirmBackgroundScreen(initialIndex: index), + builder: + (_) => ConfirmBackgroundScreen( + boardPrefs: boardPrefs, + initialIndex: index, + ), fullscreenDialog: true, ), ) @@ -122,9 +126,10 @@ class _Body extends ConsumerWidget { } class ConfirmBackgroundScreen extends StatefulWidget { - const ConfirmBackgroundScreen({required this.initialIndex, super.key}); + const ConfirmBackgroundScreen({required this.initialIndex, required this.boardPrefs, super.key}); final int initialIndex; + final BoardPrefs boardPrefs; @override State createState() => _ConfirmBackgroundScreenState(); @@ -162,6 +167,7 @@ class _ConfirmBackgroundScreenState extends State { size: MediaQuery.sizeOf(context).width, fen: kInitialFEN, orientation: Side.white, + settings: widget.boardPrefs.toBoardSettings(), ), ), ), From d35c930aeaa9fa6770a68f4091c9f5abd38dba69 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Fri, 24 Jan 2025 19:19:01 +0800 Subject: [PATCH 19/41] Use global grey theme for now, more tweaks --- lib/src/app.dart | 107 +++--------------- lib/src/init.dart | 4 +- .../model/settings/general_preferences.dart | 94 +-------------- lib/src/theme.dart | 72 ++++++++++++ lib/src/view/clock/clock_tool_screen.dart | 14 +-- lib/src/view/game/message_screen.dart | 12 +- .../view/puzzle/puzzle_session_widget.dart | 4 +- .../board_background_theme_screen.dart | 11 +- lib/src/widgets/settings.dart | 2 +- 9 files changed, 112 insertions(+), 208 deletions(-) create mode 100644 lib/src/theme.dart diff --git a/lib/src/app.dart b/lib/src/app.dart index 7107de3e41..b6770acf64 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -18,6 +18,7 @@ import 'package:lichess_mobile/src/network/http.dart'; import 'package:lichess_mobile/src/network/socket.dart'; import 'package:lichess_mobile/src/styles/lichess_colors.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; +import 'package:lichess_mobile/src/theme.dart'; import 'package:lichess_mobile/src/utils/screen.dart'; /// Application initialization and main entry point. @@ -119,75 +120,9 @@ class _AppState extends ConsumerState { @override Widget build(BuildContext context) { final generalPrefs = ref.watch(generalPreferencesProvider); - final isTablet = isTabletOrLarger(context); final isIOS = Theme.of(context).platform == TargetPlatform.iOS; final remainingHeight = estimateRemainingHeightLeftBoard(context); - final flexScheme = FlexScheme.espresso.data; - final flexSchemeLightColors = flexScheme.light; - final flexSchemeDarkColors = flexScheme.dark; - - final lightTheme = FlexThemeData.light( - colors: flexSchemeLightColors, - cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), - surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, - appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, - blendLevel: 10, - ); - final darkTheme = FlexThemeData.dark( - colors: flexSchemeDarkColors, - surfaceMode: FlexSurfaceMode.level, - blendLevel: 16, - cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), - appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, - ); - - final lightCupertinoTheme = CupertinoThemeData( - primaryColor: lightTheme.colorScheme.primary, - primaryContrastingColor: lightTheme.colorScheme.onPrimary, - brightness: Brightness.light, - textTheme: CupertinoTheme.of(context).textTheme.copyWith( - primaryColor: lightTheme.colorScheme.primary, - textStyle: CupertinoTheme.of( - context, - ).textTheme.textStyle.copyWith(color: lightTheme.colorScheme.onSurface), - navTitleTextStyle: CupertinoTheme.of( - context, - ).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), - navLargeTitleTextStyle: CupertinoTheme.of( - context, - ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), - ), - scaffoldBackgroundColor: lightTheme.scaffoldBackgroundColor, - barBackgroundColor: lightTheme.appBarTheme.backgroundColor?.withValues( - alpha: isTablet ? 1.0 : 0.9, - ), - applyThemeToAll: true, - ); - - final darkCupertinoTheme = CupertinoThemeData( - primaryColor: darkTheme.colorScheme.primaryFixedDim, - primaryContrastingColor: darkTheme.colorScheme.onPrimaryFixedVariant, - brightness: Brightness.dark, - textTheme: CupertinoTheme.of(context).textTheme.copyWith( - primaryColor: darkTheme.colorScheme.primaryFixedDim, - textStyle: CupertinoTheme.of( - context, - ).textTheme.textStyle.copyWith(color: darkTheme.colorScheme.onSurface), - navTitleTextStyle: CupertinoTheme.of( - context, - ).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), - navLargeTitleTextStyle: CupertinoTheme.of( - context, - ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), - ), - scaffoldBackgroundColor: darkTheme.scaffoldBackgroundColor, - barBackgroundColor: darkTheme.appBarTheme.backgroundColor?.withValues( - alpha: isTablet ? 1.0 : 0.9, - ), - applyThemeToAll: true, - ); - return AnnotatedRegion( value: FlexColorScheme.themedSystemNavigationBar( context, @@ -198,45 +133,37 @@ class _AppState extends ConsumerState { supportedLocales: kSupportedLocales, onGenerateTitle: (BuildContext context) => 'lichess.org', locale: generalPrefs.locale, - theme: lightTheme.copyWith( - cupertinoOverrideTheme: lightCupertinoTheme, + theme: AppTheme.light.copyWith( + cupertinoOverrideTheme: AppTheme.lightCupertino, splashFactory: isIOS ? NoSplash.splashFactory : null, textTheme: isIOS ? Typography.blackCupertino : null, listTileTheme: ListTileTheme.of(context).copyWith( - tileColor: lightTheme.colorScheme.surfaceContainerLow, - selectedTileColor: lightTheme.colorScheme.surfaceContainer, - titleTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, - subtitleTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, - leadingAndTrailingTextStyle: isIOS ? lightCupertinoTheme.textTheme.textStyle : null, - ), - floatingActionButtonTheme: const FloatingActionButtonThemeData( - backgroundColor: LichessColors.brag, - foregroundColor: Colors.white, + tileColor: AppTheme.light.colorScheme.surfaceContainerLow, + selectedTileColor: AppTheme.light.colorScheme.surfaceContainer, + titleTextStyle: isIOS ? AppTheme.lightCupertino.textTheme.textStyle : null, + subtitleTextStyle: isIOS ? AppTheme.lightCupertino.textTheme.textStyle : null, + leadingAndTrailingTextStyle: isIOS ? AppTheme.lightCupertino.textTheme.textStyle : null, ), navigationBarTheme: NavigationBarTheme.of( context, ).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null), - extensions: [lichessCustomColors.harmonized(lightTheme.colorScheme)], + extensions: [lichessCustomColors.harmonized(AppTheme.light.colorScheme)], ), - darkTheme: darkTheme.copyWith( - cupertinoOverrideTheme: darkCupertinoTheme, + darkTheme: AppTheme.dark.copyWith( + cupertinoOverrideTheme: AppTheme.darkCupertino, splashFactory: isIOS ? NoSplash.splashFactory : null, textTheme: isIOS ? Typography.whiteCupertino : null, listTileTheme: ListTileTheme.of(context).copyWith( - tileColor: darkTheme.colorScheme.surfaceContainerLow, - selectedTileColor: darkTheme.colorScheme.surfaceContainer, - titleTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, - subtitleTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, - leadingAndTrailingTextStyle: isIOS ? darkCupertinoTheme.textTheme.textStyle : null, - ), - floatingActionButtonTheme: const FloatingActionButtonThemeData( - backgroundColor: LichessColors.brag, - foregroundColor: Colors.white, + tileColor: AppTheme.dark.colorScheme.surfaceContainerLow, + selectedTileColor: AppTheme.dark.colorScheme.surfaceContainer, + titleTextStyle: isIOS ? AppTheme.darkCupertino.textTheme.textStyle : null, + subtitleTextStyle: isIOS ? AppTheme.darkCupertino.textTheme.textStyle : null, + leadingAndTrailingTextStyle: isIOS ? AppTheme.darkCupertino.textTheme.textStyle : null, ), navigationBarTheme: NavigationBarTheme.of( context, ).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null), - extensions: [lichessCustomColors.harmonized(darkTheme.colorScheme)], + extensions: [lichessCustomColors.harmonized(AppTheme.dark.colorScheme)], ), themeMode: switch (generalPrefs.themeMode) { BackgroundThemeMode.light => ThemeMode.light, diff --git a/lib/src/init.dart b/lib/src/init.dart index fecd053802..ae966e8dca 100644 --- a/lib/src/init.dart +++ b/lib/src/init.dart @@ -68,9 +68,9 @@ Future _migrateThemeSettings() async { } final generalPrefs = GeneralPrefs.fromJson(jsonDecode(stored) as Map); final migrated = generalPrefs.copyWith( - appTheme: + systemColors: // ignore: deprecated_member_use_from_same_package - generalPrefs.appThemeSeed == AppThemeSeed.system ? AppTheme.system : defaultAppTheme, + generalPrefs.appThemeSeed == AppThemeSeed.system ? true : null, ); await prefs.setString(PrefCategory.general.storageKey, jsonEncode(migrated.toJson())); } catch (e) { diff --git a/lib/src/model/settings/general_preferences.dart b/lib/src/model/settings/general_preferences.dart index 7a668a1a4a..8a312221ac 100644 --- a/lib/src/model/settings/general_preferences.dart +++ b/lib/src/model/settings/general_preferences.dart @@ -1,10 +1,7 @@ import 'dart:ui' show Locale; -import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; import 'package:lichess_mobile/src/model/settings/preferences_storage.dart'; -import 'package:lichess_mobile/src/utils/color_palette.dart'; import 'package:lichess_mobile/src/utils/json.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -48,14 +45,8 @@ class GeneralPreferences extends _$GeneralPreferences with PreferencesStorage setMasterVolume(double volume) { return save(state.copyWith(masterVolume: volume)); } - - Future setAppTheme(AppTheme appTheme) { - return save(state.copyWith(appTheme: appTheme)); - } } -const defaultAppTheme = AppTheme.board; - @Freezed(fromJson: true, toJson: true) class GeneralPrefs with _$GeneralPrefs implements Serializable { const factory GeneralPrefs({ @@ -65,11 +56,11 @@ class GeneralPrefs with _$GeneralPrefs implements Serializable { @JsonKey(unknownEnumValue: SoundTheme.standard) required SoundTheme soundTheme, @JsonKey(defaultValue: 0.8) required double masterVolume, - @JsonKey(unknownEnumValue: defaultAppTheme, defaultValue: defaultAppTheme) - required AppTheme appTheme, + /// Whether to use system colors on android 10+. + bool? systemColors, /// App theme seed - @Deprecated('Use appTheme instead') + @Deprecated('Use systemColors instead') @JsonKey(unknownEnumValue: AppThemeSeed.board, defaultValue: AppThemeSeed.board) required AppThemeSeed appThemeSeed, @@ -83,7 +74,6 @@ class GeneralPrefs with _$GeneralPrefs implements Serializable { soundTheme: SoundTheme.standard, masterVolume: 0.8, appThemeSeed: AppThemeSeed.board, - appTheme: defaultAppTheme, ); factory GeneralPrefs.fromJson(Map json) { @@ -99,84 +89,6 @@ enum AppThemeSeed { board, } -enum AppTheme { - /// The app theme is based on the user's system theme (only available on Android 10+). - system, - - /// The app theme is based on the chess board - board, - - /// Below values from [FlexScheme] - blue, - indigo, - // hippieBlue, - // aquaBlue, - // brandBlue, - deepBlue, - // sakura, - // mandyRed, - // red, - redWine, - purpleBrown, - green, - // money, - jungle, - // greyLaw, - // wasabi, - gold, - // mango, - // amber, - // vesuviusBurn, - // deepPurple, - // ebonyClay, - // barossa, - // shark, - bigStone, - // damask, - // bahamaBlue, - // mallardGreen, - // espresso, - // outerSpace, - // blueWhale, - // sanJuanBlue, - // rosewood, - // blumineBlue, - // verdunHemlock, - // dellGenoa, - redM3, - pinkM3, - purpleM3, - indigoM3, - blueM3, - cyanM3, - tealM3, - greenM3, - limeM3, - yellowM3, - orangeM3, - deepOrangeM3, - greys, - sepia; - - static final _flexSchemesNameMap = FlexScheme.values.asNameMap(); - - String get label => - this == AppTheme.system - ? 'System' - : this == defaultAppTheme - ? 'Default' - : this == AppTheme.board - ? 'Chessboard' - : _flexSchemesNameMap[name]!.data.name; - - FlexSchemeData getFlexScheme(BoardTheme boardTheme) => - this == AppTheme.system - ? getSystemScheme()! - : this == AppTheme.board - ? boardTheme.flexScheme - : _flexSchemesNameMap[name]!.data; -} - /// Describes the background theme of the app. enum BackgroundThemeMode { /// Use either the light or dark theme based on what the user has selected in diff --git a/lib/src/theme.dart b/lib/src/theme.dart new file mode 100644 index 0000000000..8acace15bf --- /dev/null +++ b/lib/src/theme.dart @@ -0,0 +1,72 @@ +import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +// ignore: avoid_classes_with_only_static_members +abstract final class AppTheme { + static ThemeData light = FlexThemeData.light( + scheme: FlexScheme.greys, + surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, + blendLevel: 10, + visualDensity: FlexColorScheme.comfortablePlatformDensity, + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + ); + // The defined dark theme. + static ThemeData dark = FlexThemeData.dark( + scheme: FlexScheme.greys, + surfaceMode: FlexSurfaceMode.level, + blendLevel: 20, + visualDensity: FlexColorScheme.comfortablePlatformDensity, + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + ); + + static final floatingActionButtonTheme = FloatingActionButtonThemeData( + backgroundColor: AppTheme.light.colorScheme.secondaryFixedDim, + foregroundColor: AppTheme.light.colorScheme.onSecondaryFixedVariant, + ); + + static const cupertinoTitleColor = CupertinoDynamicColor.withBrightness( + color: Color(0xFF000000), + darkColor: Color(0xFFF5F5F5), + ); + + static final lightCupertino = CupertinoThemeData( + primaryColor: AppTheme.light.colorScheme.primary, + primaryContrastingColor: AppTheme.light.colorScheme.onPrimary, + brightness: Brightness.light, + scaffoldBackgroundColor: AppTheme.light.scaffoldBackgroundColor, + barBackgroundColor: AppTheme.light.appBarTheme.backgroundColor, + textTheme: const CupertinoThemeData().textTheme.copyWith( + primaryColor: AppTheme.light.colorScheme.primary, + textStyle: const CupertinoThemeData().textTheme.textStyle.copyWith( + color: AppTheme.light.colorScheme.onSurface, + ), + navTitleTextStyle: const CupertinoThemeData().textTheme.navTitleTextStyle.copyWith( + color: cupertinoTitleColor, + ), + navLargeTitleTextStyle: const CupertinoThemeData().textTheme.navLargeTitleTextStyle.copyWith( + color: cupertinoTitleColor, + ), + ), + ); + + static final darkCupertino = CupertinoThemeData( + primaryColor: AppTheme.dark.colorScheme.primary, + primaryContrastingColor: AppTheme.dark.colorScheme.onPrimary, + brightness: Brightness.dark, + scaffoldBackgroundColor: AppTheme.dark.scaffoldBackgroundColor, + barBackgroundColor: AppTheme.dark.appBarTheme.backgroundColor, + textTheme: const CupertinoThemeData().textTheme.copyWith( + primaryColor: AppTheme.dark.colorScheme.primary, + textStyle: const CupertinoThemeData().textTheme.textStyle.copyWith( + color: AppTheme.dark.colorScheme.onSurface, + ), + navTitleTextStyle: const CupertinoThemeData().textTheme.navTitleTextStyle.copyWith( + color: cupertinoTitleColor, + ), + navLargeTitleTextStyle: const CupertinoThemeData().textTheme.navLargeTitleTextStyle.copyWith( + color: cupertinoTitleColor, + ), + ), + ); +} diff --git a/lib/src/view/clock/clock_tool_screen.dart b/lib/src/view/clock/clock_tool_screen.dart index 1ef600bf51..4e6bab0020 100644 --- a/lib/src/view/clock/clock_tool_screen.dart +++ b/lib/src/view/clock/clock_tool_screen.dart @@ -80,19 +80,17 @@ class ClockTile extends ConsumerWidget { final colorScheme = Theme.of(context).colorScheme; final backgroundColor = clockState.isFlagged(playerType) - ? context.lichessColors.error + ? colorScheme.error : !clockState.paused && clockState.isPlayersTurn(playerType) - ? colorScheme.primary + ? colorScheme.primaryFixed : clockState.activeSide == playerType - ? colorScheme.secondaryContainer - : colorScheme.surfaceContainer; + ? colorScheme.primaryFixedDim + : colorScheme.surface; final clockStyle = ClockStyle( textColor: - clockState.activeSide == playerType - ? colorScheme.onSecondaryContainer - : colorScheme.onSurface, - activeTextColor: colorScheme.onPrimary, + clockState.activeSide == playerType ? colorScheme.onPrimaryFixed : colorScheme.onSurface, + activeTextColor: colorScheme.onPrimaryFixed, emergencyTextColor: Colors.white, backgroundColor: Colors.transparent, activeBackgroundColor: Colors.transparent, diff --git a/lib/src/view/game/message_screen.dart b/lib/src/view/game/message_screen.dart index b84b3ad2a7..384bbb99a0 100644 --- a/lib/src/view/game/message_screen.dart +++ b/lib/src/view/game/message_screen.dart @@ -113,17 +113,11 @@ class _MessageBubble extends ConsumerWidget { Color _bubbleColor(BuildContext context, Brightness brightness) => you - ? Theme.of(context).colorScheme.primaryContainer - : brightness == Brightness.light - ? lighten(LichessColors.grey) - : darken(LichessColors.grey, 0.5); + ? Theme.of(context).colorScheme.secondary + : lighten(Theme.of(context).scaffoldBackgroundColor, 0.4); Color _textColor(BuildContext context, Brightness brightness) => - you - ? Theme.of(context).colorScheme.onPrimaryContainer - : brightness == Brightness.light - ? Colors.black - : Colors.white; + you ? Theme.of(context).colorScheme.onSecondary : Theme.of(context).colorScheme.onSurface; @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/src/view/puzzle/puzzle_session_widget.dart b/lib/src/view/puzzle/puzzle_session_widget.dart index de00ea2404..4dec720796 100644 --- a/lib/src/view/puzzle/puzzle_session_widget.dart +++ b/lib/src/view/puzzle/puzzle_session_widget.dart @@ -180,12 +180,12 @@ class _SessionItem extends StatelessWidget { decoration: BoxDecoration( color: isCurrent - ? Colors.grey + ? Colors.grey.harmonizeWith(colorScheme.primary) : attempt != null ? attempt!.win ? good.harmonizeWith(colorScheme.primary) : error.harmonizeWith(colorScheme.primary) - : next, + : next.harmonizeWith(colorScheme.primary), borderRadius: const BorderRadius.all(Radius.circular(5)), ), child: diff --git a/lib/src/view/settings/board_background_theme_screen.dart b/lib/src/view/settings/board_background_theme_screen.dart index 36ea9ea5eb..c3f3337364 100644 --- a/lib/src/view/settings/board_background_theme_screen.dart +++ b/lib/src/view/settings/board_background_theme_screen.dart @@ -9,6 +9,7 @@ import 'package:lichess_mobile/src/styles/lichess_icons.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/widgets/board_theme.dart' as wrapper; +import 'package:lichess_mobile/src/widgets/buttons.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; class BoardBackgroundThemeScreen extends StatelessWidget { @@ -183,14 +184,14 @@ class _ConfirmBackgroundScreenState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - TextButton( - child: Text(context.l10n.accept), - onPressed: () => Navigator.pop(context, _controller.page), - ), - TextButton( + AdaptiveTextButton( child: Text(context.l10n.cancel), onPressed: () => Navigator.pop(context, null), ), + AdaptiveTextButton( + child: Text(context.l10n.accept), + onPressed: () => Navigator.pop(context, _controller.page), + ), ], ), ), diff --git a/lib/src/widgets/settings.dart b/lib/src/widgets/settings.dart index 57c39cc11b..2a64747ee3 100644 --- a/lib/src/widgets/settings.dart +++ b/lib/src/widgets/settings.dart @@ -89,7 +89,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), ); } } From 0c38ebb34c613ba7ce3c1c93b989739bbb682f25 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Fri, 24 Jan 2025 20:39:13 +0800 Subject: [PATCH 20/41] Tweak clock style --- lib/src/app.dart | 2 - lib/src/model/settings/board_preferences.dart | 1 + lib/src/theme.dart | 5 +++ .../game/correspondence_clock_widget.dart | 15 +++---- .../over_the_board/over_the_board_screen.dart | 5 --- lib/src/view/settings/board_theme_screen.dart | 2 +- lib/src/widgets/clock.dart | 44 ++++++++----------- 7 files changed, 33 insertions(+), 41 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index b6770acf64..c8c6af691f 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,5 +1,4 @@ import 'package:flex_color_scheme/flex_color_scheme.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show SystemUiOverlayStyle; import 'package:flutter_native_splash/flutter_native_splash.dart'; @@ -16,7 +15,6 @@ import 'package:lichess_mobile/src/navigation.dart'; import 'package:lichess_mobile/src/network/connectivity.dart'; import 'package:lichess_mobile/src/network/http.dart'; import 'package:lichess_mobile/src/network/socket.dart'; -import 'package:lichess_mobile/src/styles/lichess_colors.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/theme.dart'; import 'package:lichess_mobile/src/utils/screen.dart'; diff --git a/lib/src/model/settings/board_preferences.dart b/lib/src/model/settings/board_preferences.dart index ec08f7ed9c..b9d60815f7 100644 --- a/lib/src/model/settings/board_preferences.dart +++ b/lib/src/model/settings/board_preferences.dart @@ -147,6 +147,7 @@ class BoardPrefs with _$BoardPrefs implements Serializable { static const defaults = BoardPrefs( pieceSet: PieceSet.staunty, boardTheme: BoardTheme.brown, + backgroundTheme: BoardBackgroundTheme.board, immersiveModeWhilePlaying: false, hapticFeedback: true, showLegalMoves: true, diff --git a/lib/src/theme.dart b/lib/src/theme.dart index 8acace15bf..bfa8df8c84 100644 --- a/lib/src/theme.dart +++ b/lib/src/theme.dart @@ -1,5 +1,6 @@ import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart' show defaultTargetPlatform; import 'package:flutter/material.dart'; // ignore: avoid_classes_with_only_static_members @@ -10,6 +11,8 @@ abstract final class AppTheme { blendLevel: 10, visualDensity: FlexColorScheme.comfortablePlatformDensity, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + appBarStyle: + defaultTargetPlatform == TargetPlatform.iOS ? null : FlexAppBarStyle.scaffoldBackground, ); // The defined dark theme. static ThemeData dark = FlexThemeData.dark( @@ -18,6 +21,8 @@ abstract final class AppTheme { blendLevel: 20, visualDensity: FlexColorScheme.comfortablePlatformDensity, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + appBarStyle: + defaultTargetPlatform == TargetPlatform.iOS ? null : FlexAppBarStyle.scaffoldBackground, ); static final floatingActionButtonTheme = FloatingActionButtonThemeData( diff --git a/lib/src/view/game/correspondence_clock_widget.dart b/lib/src/view/game/correspondence_clock_widget.dart index 8743daf3c8..eb121bc0e3 100644 --- a/lib/src/view/game/correspondence_clock_widget.dart +++ b/lib/src/view/game/correspondence_clock_widget.dart @@ -1,14 +1,12 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lichess_mobile/src/constants.dart'; -import 'package:lichess_mobile/src/model/settings/brightness.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/screen.dart'; import 'package:lichess_mobile/src/widgets/clock.dart'; -class CorrespondenceClock extends ConsumerStatefulWidget { +class CorrespondenceClock extends StatefulWidget { /// The duration left on the clock. final Duration duration; @@ -21,12 +19,12 @@ class CorrespondenceClock extends ConsumerStatefulWidget { const CorrespondenceClock({required this.duration, required this.active, this.onFlag, super.key}); @override - ConsumerState createState() => _CorrespondenceClockState(); + State createState() => _CorrespondenceClockState(); } const _period = Duration(seconds: 1); -class _CorrespondenceClockState extends ConsumerState { +class _CorrespondenceClockState extends State { Timer? _timer; Duration timeLeft = Duration.zero; @@ -88,9 +86,10 @@ class _CorrespondenceClockState extends ConsumerState { final hours = timeLeft.inHours.remainder(24); 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 brightness = Theme.of(context).brightness; + final colorScheme = Theme.of(context).colorScheme; + final clockStyle = ClockStyle.defaultStyle(brightness, colorScheme); + final remainingHeight = estimateRemainingHeightLeftBoard(context); final daysStr = 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 460b75b52e..d752d6052b 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 @@ -265,10 +265,6 @@ class _Player extends ConsumerWidget { final boardPreferences = ref.watch(boardPreferencesProvider); final clock = ref.watch(overTheBoardClockProvider); - final brightness = ref.watch(currentBrightnessProvider); - final clockStyle = - brightness == Brightness.dark ? ClockStyle.darkThemeStyle : ClockStyle.lightThemeStyle; - return RotatedBox( quarterTurns: upsideDown ? 2 : 0, child: GamePlayer( @@ -287,7 +283,6 @@ class _Player extends ConsumerWidget { 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/settings/board_theme_screen.dart b/lib/src/view/settings/board_theme_screen.dart index ffcf8c9f21..758b555405 100644 --- a/lib/src/view/settings/board_theme_screen.dart +++ b/lib/src/view/settings/board_theme_screen.dart @@ -199,7 +199,7 @@ class _BodyState extends ConsumerState<_Body> { if (boardPrefs.backgroundTheme != null) PlatformListTile( leading: const Icon(Icons.cancel), - title: const Text('Reset background'), + title: const Text('Clear background'), onTap: () { ref.read(boardPreferencesProvider.notifier).setBackgroundTheme(null); }, diff --git a/lib/src/widgets/clock.dart b/lib/src/widgets/clock.dart index 091bb04d49..ccc2579244 100644 --- a/lib/src/widgets/clock.dart +++ b/lib/src/widgets/clock.dart @@ -53,9 +53,8 @@ class Clock extends StatelessWidget { 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 colorScheme = Theme.of(context).colorScheme; + final effectiveClockStyle = clockStyle ?? ClockStyle.defaultStyle(brightness, colorScheme); return LayoutBuilder( builder: (context, constraints) { @@ -68,9 +67,9 @@ class Clock extends StatelessWidget { color: active ? isEmergency - ? activeClockStyle.emergencyBackgroundColor - : activeClockStyle.activeBackgroundColor - : activeClockStyle.backgroundColor, + ? effectiveClockStyle.emergencyBackgroundColor + : effectiveClockStyle.activeBackgroundColor + : effectiveClockStyle.backgroundColor, ), child: Padding( padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 5.0), @@ -86,9 +85,9 @@ class Clock extends StatelessWidget { color: active ? isEmergency - ? activeClockStyle.emergencyTextColor - : activeClockStyle.activeTextColor - : activeClockStyle.textColor, + ? effectiveClockStyle.emergencyTextColor + : effectiveClockStyle.activeTextColor + : effectiveClockStyle.textColor, fontSize: _kClockFontSize * fontScaleFactor, height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 1.0 : null, fontFeatures: const [FontFeature.tabularFigures()], @@ -133,22 +132,17 @@ class ClockStyle { final Color activeBackgroundColor; final Color emergencyBackgroundColor; - static const darkThemeStyle = ClockStyle( - textColor: Colors.grey, - activeTextColor: Colors.black, - emergencyTextColor: Colors.white, - backgroundColor: Colors.black, - activeBackgroundColor: Color(0xFFDDDDDD), - emergencyBackgroundColor: Color(0xFF673431), - ); - - static const lightThemeStyle = ClockStyle( - textColor: Colors.grey, - activeTextColor: Colors.black, - emergencyTextColor: Colors.black, - backgroundColor: Colors.white, - activeBackgroundColor: Color(0xFFD0E0BD), - emergencyBackgroundColor: Color(0xFFF2CCCC), + factory ClockStyle.defaultStyle(Brightness brightness, ColorScheme colorScheme) => ClockStyle( + backgroundColor: colorScheme.surface, + textColor: colorScheme.outline, + activeBackgroundColor: + brightness == Brightness.dark ? colorScheme.inverseSurface : colorScheme.primaryFixedDim, + activeTextColor: + brightness == Brightness.dark + ? colorScheme.onInverseSurface + : colorScheme.onPrimaryFixedVariant, + emergencyBackgroundColor: colorScheme.errorContainer, + emergencyTextColor: colorScheme.onErrorContainer, ); } From 3648375187c2510107680394d752616c5469a298 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Sat, 25 Jan 2025 11:45:46 +0800 Subject: [PATCH 21/41] Restore android system theme --- lib/src/app.dart | 47 +++++++++++++--- lib/src/init.dart | 2 +- .../model/settings/general_preferences.dart | 7 ++- lib/src/theme.dart | 39 +++++++------- lib/src/utils/color_palette.dart | 54 +++++++++++++++++++ .../view/play/create_custom_game_screen.dart | 5 +- .../view/settings/settings_tab_screen.dart | 10 ++++ lib/src/view/user/game_history_screen.dart | 2 +- lib/src/widgets/list.dart | 7 ++- 9 files changed, 138 insertions(+), 35 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index c8c6af691f..e8618e3165 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,4 +1,5 @@ import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show SystemUiOverlayStyle; import 'package:flutter_native_splash/flutter_native_splash.dart'; @@ -17,6 +18,7 @@ import 'package:lichess_mobile/src/network/http.dart'; import 'package:lichess_mobile/src/network/socket.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/theme.dart'; +import 'package:lichess_mobile/src/utils/color_palette.dart' show getSystemScheme; import 'package:lichess_mobile/src/utils/screen.dart'; /// Application initialization and main entry point. @@ -121,6 +123,37 @@ class _AppState extends ConsumerState { final isIOS = Theme.of(context).platform == TargetPlatform.iOS; final remainingHeight = estimateRemainingHeightLeftBoard(context); + final flexScheme = + generalPrefs.systemColors == true ? getSystemScheme()! : AppTheme.defaultScheme; + final flexSchemeLightColors = flexScheme.light; + final flexSchemeDarkColors = flexScheme.dark; + + final themeLight = FlexThemeData.light( + colors: flexSchemeLightColors, + surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, + blendLevel: 10, + visualDensity: FlexColorScheme.comfortablePlatformDensity, + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, + ); + // The defined dark theme. + final themeDark = FlexThemeData.dark( + colors: flexSchemeDarkColors, + surfaceMode: FlexSurfaceMode.level, + blendLevel: 20, + visualDensity: FlexColorScheme.comfortablePlatformDensity, + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, + ); + + final floatingActionButtonTheme = + generalPrefs.systemColors + ? null + : FloatingActionButtonThemeData( + backgroundColor: themeLight.colorScheme.secondaryFixedDim, + foregroundColor: themeLight.colorScheme.onSecondaryFixedVariant, + ); + return AnnotatedRegion( value: FlexColorScheme.themedSystemNavigationBar( context, @@ -131,37 +164,35 @@ class _AppState extends ConsumerState { supportedLocales: kSupportedLocales, onGenerateTitle: (BuildContext context) => 'lichess.org', locale: generalPrefs.locale, - theme: AppTheme.light.copyWith( + theme: themeLight.copyWith( cupertinoOverrideTheme: AppTheme.lightCupertino, splashFactory: isIOS ? NoSplash.splashFactory : null, textTheme: isIOS ? Typography.blackCupertino : null, listTileTheme: ListTileTheme.of(context).copyWith( - tileColor: AppTheme.light.colorScheme.surfaceContainerLow, - selectedTileColor: AppTheme.light.colorScheme.surfaceContainer, titleTextStyle: isIOS ? AppTheme.lightCupertino.textTheme.textStyle : null, subtitleTextStyle: isIOS ? AppTheme.lightCupertino.textTheme.textStyle : null, leadingAndTrailingTextStyle: isIOS ? AppTheme.lightCupertino.textTheme.textStyle : null, ), + floatingActionButtonTheme: floatingActionButtonTheme, navigationBarTheme: NavigationBarTheme.of( context, ).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null), - extensions: [lichessCustomColors.harmonized(AppTheme.light.colorScheme)], + extensions: [lichessCustomColors.harmonized(themeLight.colorScheme)], ), - darkTheme: AppTheme.dark.copyWith( + darkTheme: themeDark.copyWith( cupertinoOverrideTheme: AppTheme.darkCupertino, splashFactory: isIOS ? NoSplash.splashFactory : null, textTheme: isIOS ? Typography.whiteCupertino : null, listTileTheme: ListTileTheme.of(context).copyWith( - tileColor: AppTheme.dark.colorScheme.surfaceContainerLow, - selectedTileColor: AppTheme.dark.colorScheme.surfaceContainer, titleTextStyle: isIOS ? AppTheme.darkCupertino.textTheme.textStyle : null, subtitleTextStyle: isIOS ? AppTheme.darkCupertino.textTheme.textStyle : null, leadingAndTrailingTextStyle: isIOS ? AppTheme.darkCupertino.textTheme.textStyle : null, ), + floatingActionButtonTheme: floatingActionButtonTheme, navigationBarTheme: NavigationBarTheme.of( context, ).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null), - extensions: [lichessCustomColors.harmonized(AppTheme.dark.colorScheme)], + extensions: [lichessCustomColors.harmonized(themeDark.colorScheme)], ), themeMode: switch (generalPrefs.themeMode) { BackgroundThemeMode.light => ThemeMode.light, diff --git a/lib/src/init.dart b/lib/src/init.dart index ae966e8dca..070493bfb7 100644 --- a/lib/src/init.dart +++ b/lib/src/init.dart @@ -70,7 +70,7 @@ Future _migrateThemeSettings() async { final migrated = generalPrefs.copyWith( systemColors: // ignore: deprecated_member_use_from_same_package - generalPrefs.appThemeSeed == AppThemeSeed.system ? true : null, + generalPrefs.appThemeSeed == AppThemeSeed.system, ); await prefs.setString(PrefCategory.general.storageKey, jsonEncode(migrated.toJson())); } catch (e) { diff --git a/lib/src/model/settings/general_preferences.dart b/lib/src/model/settings/general_preferences.dart index 8a312221ac..09a2a6f3df 100644 --- a/lib/src/model/settings/general_preferences.dart +++ b/lib/src/model/settings/general_preferences.dart @@ -45,6 +45,10 @@ class GeneralPreferences extends _$GeneralPreferences with PreferencesStorage setMasterVolume(double volume) { return save(state.copyWith(masterVolume: volume)); } + + Future toggleSystemColors() { + return save(state.copyWith(systemColors: !state.systemColors)); + } } @Freezed(fromJson: true, toJson: true) @@ -57,7 +61,7 @@ class GeneralPrefs with _$GeneralPrefs implements Serializable { @JsonKey(defaultValue: 0.8) required double masterVolume, /// Whether to use system colors on android 10+. - bool? systemColors, + @JsonKey(defaultValue: false) required bool systemColors, /// App theme seed @Deprecated('Use systemColors instead') @@ -73,6 +77,7 @@ class GeneralPrefs with _$GeneralPrefs implements Serializable { isSoundEnabled: true, soundTheme: SoundTheme.standard, masterVolume: 0.8, + systemColors: false, appThemeSeed: AppThemeSeed.board, ); diff --git a/lib/src/theme.dart b/lib/src/theme.dart index bfa8df8c84..d4425ce90a 100644 --- a/lib/src/theme.dart +++ b/lib/src/theme.dart @@ -5,8 +5,10 @@ import 'package:flutter/material.dart'; // ignore: avoid_classes_with_only_static_members abstract final class AppTheme { - static ThemeData light = FlexThemeData.light( - scheme: FlexScheme.greys, + static const FlexSchemeData defaultScheme = FlexColor.espresso; + + static final ThemeData _light = FlexThemeData.light( + scheme: FlexScheme.espresso, surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, blendLevel: 10, visualDensity: FlexColorScheme.comfortablePlatformDensity, @@ -15,8 +17,8 @@ abstract final class AppTheme { defaultTargetPlatform == TargetPlatform.iOS ? null : FlexAppBarStyle.scaffoldBackground, ); // The defined dark theme. - static ThemeData dark = FlexThemeData.dark( - scheme: FlexScheme.greys, + static final ThemeData _dark = FlexThemeData.dark( + scheme: FlexScheme.espresso, surfaceMode: FlexSurfaceMode.level, blendLevel: 20, visualDensity: FlexColorScheme.comfortablePlatformDensity, @@ -25,26 +27,21 @@ abstract final class AppTheme { defaultTargetPlatform == TargetPlatform.iOS ? null : FlexAppBarStyle.scaffoldBackground, ); - static final floatingActionButtonTheme = FloatingActionButtonThemeData( - backgroundColor: AppTheme.light.colorScheme.secondaryFixedDim, - foregroundColor: AppTheme.light.colorScheme.onSecondaryFixedVariant, - ); - static const cupertinoTitleColor = CupertinoDynamicColor.withBrightness( color: Color(0xFF000000), darkColor: Color(0xFFF5F5F5), ); static final lightCupertino = CupertinoThemeData( - primaryColor: AppTheme.light.colorScheme.primary, - primaryContrastingColor: AppTheme.light.colorScheme.onPrimary, + primaryColor: _light.colorScheme.primary, + primaryContrastingColor: _light.colorScheme.onPrimary, brightness: Brightness.light, - scaffoldBackgroundColor: AppTheme.light.scaffoldBackgroundColor, - barBackgroundColor: AppTheme.light.appBarTheme.backgroundColor, + scaffoldBackgroundColor: _light.scaffoldBackgroundColor, + barBackgroundColor: _light.appBarTheme.backgroundColor, textTheme: const CupertinoThemeData().textTheme.copyWith( - primaryColor: AppTheme.light.colorScheme.primary, + primaryColor: _light.colorScheme.primary, textStyle: const CupertinoThemeData().textTheme.textStyle.copyWith( - color: AppTheme.light.colorScheme.onSurface, + color: _light.colorScheme.onSurface, ), navTitleTextStyle: const CupertinoThemeData().textTheme.navTitleTextStyle.copyWith( color: cupertinoTitleColor, @@ -56,15 +53,15 @@ abstract final class AppTheme { ); static final darkCupertino = CupertinoThemeData( - primaryColor: AppTheme.dark.colorScheme.primary, - primaryContrastingColor: AppTheme.dark.colorScheme.onPrimary, + primaryColor: _dark.colorScheme.primaryFixed, + primaryContrastingColor: _dark.colorScheme.onPrimaryFixed, brightness: Brightness.dark, - scaffoldBackgroundColor: AppTheme.dark.scaffoldBackgroundColor, - barBackgroundColor: AppTheme.dark.appBarTheme.backgroundColor, + scaffoldBackgroundColor: _dark.scaffoldBackgroundColor, + barBackgroundColor: _dark.appBarTheme.backgroundColor, textTheme: const CupertinoThemeData().textTheme.copyWith( - primaryColor: AppTheme.dark.colorScheme.primary, + primaryColor: _dark.colorScheme.primaryFixed, textStyle: const CupertinoThemeData().textTheme.textStyle.copyWith( - color: AppTheme.dark.colorScheme.onSurface, + color: _dark.colorScheme.onSurface, ), navTitleTextStyle: const CupertinoThemeData().textTheme.navTitleTextStyle.copyWith( color: cupertinoTitleColor, diff --git a/lib/src/utils/color_palette.dart b/lib/src/utils/color_palette.dart index 8e80820fac..4ca070f564 100644 --- a/lib/src/utils/color_palette.dart +++ b/lib/src/utils/color_palette.dart @@ -4,12 +4,15 @@ import 'package:chessground/chessground.dart'; import 'package:dartchess/dartchess.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart' show FlexSchemeColor, FlexSchemeData; +import 'package:flutter/material.dart' show ColorScheme; import 'package:material_color_utilities/material_color_utilities.dart'; CorePalette? _corePalette; FlexSchemeData? _systemScheme; +(ColorScheme, ColorScheme)? _colorSchemes; + ChessboardColorScheme? _boardColorScheme; /// Set the system core palette if available (android 12+ only). @@ -22,6 +25,8 @@ void setCorePalette(CorePalette? palette) { final lightScheme = palette.toColorScheme(); final darkScheme = palette.toColorScheme(brightness: Brightness.dark); + _colorSchemes ??= _generateDynamicColourSchemes(lightScheme, darkScheme); + _systemScheme ??= FlexSchemeData( name: 'System', description: 'System core palette on Android 12+', @@ -90,7 +95,56 @@ FlexSchemeData? getSystemScheme() { return _systemScheme; } +/// Get the system color schemes based on the core palette, if available (android 12+). +(ColorScheme light, ColorScheme dark)? getDynamicColorSchemes() { + return _colorSchemes; +} + /// Get the board colors based on the core palette, if available (android 12+). ChessboardColorScheme? getBoardColorScheme() { return _boardColorScheme; } + +// -- + +(ColorScheme light, ColorScheme dark) _generateDynamicColourSchemes( + ColorScheme lightDynamic, + ColorScheme darkDynamic, +) { + final lightBase = ColorScheme.fromSeed(seedColor: lightDynamic.primary); + final darkBase = ColorScheme.fromSeed( + seedColor: darkDynamic.primary, + brightness: Brightness.dark, + ); + + final lightAdditionalColours = _extractAdditionalColours(lightBase); + final darkAdditionalColours = _extractAdditionalColours(darkBase); + + 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, +]; + +ColorScheme _insertAdditionalColours(ColorScheme scheme, List additionalColours) => + scheme.copyWith( + surface: additionalColours[0], + surfaceDim: additionalColours[1], + surfaceBright: additionalColours[2], + surfaceContainerLowest: additionalColours[3], + surfaceContainerLow: additionalColours[4], + surfaceContainer: additionalColours[5], + surfaceContainerHigh: additionalColours[6], + surfaceContainerHighest: additionalColours[7], + ); diff --git a/lib/src/view/play/create_custom_game_screen.dart b/lib/src/view/play/create_custom_game_screen.dart index e5e8ccb632..0ad2e9f7f2 100644 --- a/lib/src/view/play/create_custom_game_screen.dart +++ b/lib/src/view/play/create_custom_game_screen.dart @@ -312,7 +312,10 @@ class _ChallengesBodyState extends ConsumerState<_ChallengesBody> { return SliverList.separated( itemCount: supportedChallenges.length, separatorBuilder: - (context, index) => const PlatformDivider(height: 1, cupertinoHasLeading: true), + (context, index) => + Theme.of(context).platform == TargetPlatform.iOS + ? const PlatformDivider(height: 1, cupertinoHasLeading: true) + : const SizedBox.shrink(), itemBuilder: (context, index) { final challenge = supportedChallenges[index]; final isMySeek = UserId.fromUserName(challenge.username) == session?.user.id; diff --git a/lib/src/view/settings/settings_tab_screen.dart b/lib/src/view/settings/settings_tab_screen.dart index d3a70cceb2..f67b1a59d9 100644 --- a/lib/src/view/settings/settings_tab_screen.dart +++ b/lib/src/view/settings/settings_tab_screen.dart @@ -12,6 +12,7 @@ import 'package:lichess_mobile/src/model/settings/general_preferences.dart'; import 'package:lichess_mobile/src/navigation.dart'; import 'package:lichess_mobile/src/styles/lichess_icons.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; +import 'package:lichess_mobile/src/utils/color_palette.dart' show getCorePalette; import 'package:lichess_mobile/src/utils/l10n.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/navigation.dart'; @@ -194,6 +195,15 @@ class _Body extends ConsumerWidget { ); }, ), + if (getCorePalette() != null) + SwitchSettingTile( + leading: const Icon(Icons.colorize_outlined), + title: Text(context.l10n.mobileSystemColors), + value: generalPrefs.systemColors, + onChanged: (value) { + ref.read(generalPreferencesProvider.notifier).toggleSystemColors(); + }, + ), SettingsListTile( icon: const Icon(Icons.brightness_medium_outlined), settingsLabel: Text(context.l10n.background), diff --git a/lib/src/view/user/game_history_screen.dart b/lib/src/view/user/game_history_screen.dart index ba61eda4b0..7a3fcebb60 100644 --- a/lib/src/view/user/game_history_screen.dart +++ b/lib/src/view/user/game_history_screen.dart @@ -154,7 +154,7 @@ class _BodyState extends ConsumerState<_Body> { (context, index) => Theme.of(context).platform == TargetPlatform.iOS ? const PlatformDivider(height: 1, cupertinoHasLeading: true) - : const PlatformDivider(height: 1, color: Colors.transparent), + : const SizedBox.shrink(), itemCount: list.length + (state.isLoading ? 1 : 0), itemBuilder: (context, index) { if (state.isLoading && index == list.length) { diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index da4b9047c8..24fbfbf0f5 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -117,7 +117,6 @@ class ListSection extends StatelessWidget { children: [ if (header != null) ListTile( - tileColor: theme.scaffoldBackgroundColor, dense: true, title: DefaultTextStyle.merge(style: Styles.sectionTitle, child: header!), trailing: headerTrailing, @@ -126,7 +125,11 @@ class ListSection extends StatelessWidget { ...ListTile.divideTiles(context: context, tiles: children) else ...children, - if (showDivider) const SizedBox(height: 16.0), + if (showDivider) + const Padding( + padding: EdgeInsets.only(top: 10.0), + child: Divider(thickness: 0), + ), ], ), ); From c1c6694eef3c9f621b4e6424e7729aade34dee86 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Sat, 25 Jan 2025 12:59:32 +0800 Subject: [PATCH 22/41] Refactor --- lib/src/app.dart | 72 +++++++++++++++++++++++++++++++++++--------- lib/src/theme.dart | 74 ---------------------------------------------- 2 files changed, 58 insertions(+), 88 deletions(-) delete mode 100644 lib/src/theme.dart diff --git a/lib/src/app.dart b/lib/src/app.dart index e8618e3165..ae6d6fbaf0 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -17,7 +17,6 @@ import 'package:lichess_mobile/src/network/connectivity.dart'; import 'package:lichess_mobile/src/network/http.dart'; import 'package:lichess_mobile/src/network/socket.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; -import 'package:lichess_mobile/src/theme.dart'; import 'package:lichess_mobile/src/utils/color_palette.dart' show getSystemScheme; import 'package:lichess_mobile/src/utils/screen.dart'; @@ -120,11 +119,11 @@ class _AppState extends ConsumerState { @override Widget build(BuildContext context) { final generalPrefs = ref.watch(generalPreferencesProvider); + final isTablet = isTabletOrLarger(context); final isIOS = Theme.of(context).platform == TargetPlatform.iOS; final remainingHeight = estimateRemainingHeightLeftBoard(context); - final flexScheme = - generalPrefs.systemColors == true ? getSystemScheme()! : AppTheme.defaultScheme; + final flexScheme = generalPrefs.systemColors == true ? getSystemScheme()! : FlexColor.espresso; final flexSchemeLightColors = flexScheme.light; final flexSchemeDarkColors = flexScheme.dark; @@ -132,7 +131,6 @@ class _AppState extends ConsumerState { colors: flexSchemeLightColors, surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, blendLevel: 10, - visualDensity: FlexColorScheme.comfortablePlatformDensity, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, ); @@ -140,8 +138,7 @@ class _AppState extends ConsumerState { final themeDark = FlexThemeData.dark( colors: flexSchemeDarkColors, surfaceMode: FlexSurfaceMode.level, - blendLevel: 20, - visualDensity: FlexColorScheme.comfortablePlatformDensity, + blendLevel: 40, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, ); @@ -154,6 +151,53 @@ class _AppState extends ConsumerState { foregroundColor: themeLight.colorScheme.onSecondaryFixedVariant, ); + const cupertinoTitleColor = CupertinoDynamicColor.withBrightness( + color: Color(0xFF000000), + darkColor: Color(0xFFF5F5F5), + ); + + final lightCupertino = CupertinoThemeData( + primaryColor: themeLight.colorScheme.primary, + primaryContrastingColor: themeLight.colorScheme.onPrimary, + brightness: Brightness.light, + scaffoldBackgroundColor: themeLight.scaffoldBackgroundColor, + barBackgroundColor: themeLight.appBarTheme.backgroundColor?.withValues( + alpha: isTablet ? 1.0 : 0.9, + ), + textTheme: const CupertinoThemeData().textTheme.copyWith( + primaryColor: themeLight.colorScheme.primary, + textStyle: const CupertinoThemeData().textTheme.textStyle.copyWith( + color: themeLight.colorScheme.onSurface, + ), + navTitleTextStyle: const CupertinoThemeData().textTheme.navTitleTextStyle.copyWith( + color: cupertinoTitleColor, + ), + navLargeTitleTextStyle: const CupertinoThemeData().textTheme.navLargeTitleTextStyle + .copyWith(color: cupertinoTitleColor), + ), + ); + + final darkCupertino = CupertinoThemeData( + primaryColor: themeDark.colorScheme.primaryFixed, + primaryContrastingColor: themeDark.colorScheme.onPrimaryFixed, + brightness: Brightness.dark, + scaffoldBackgroundColor: themeDark.scaffoldBackgroundColor, + barBackgroundColor: themeDark.appBarTheme.backgroundColor?.withValues( + alpha: isTablet ? 1.0 : 0.9, + ), + textTheme: const CupertinoThemeData().textTheme.copyWith( + primaryColor: themeDark.colorScheme.primaryFixed, + textStyle: const CupertinoThemeData().textTheme.textStyle.copyWith( + color: themeDark.colorScheme.onSurface, + ), + navTitleTextStyle: const CupertinoThemeData().textTheme.navTitleTextStyle.copyWith( + color: cupertinoTitleColor, + ), + navLargeTitleTextStyle: const CupertinoThemeData().textTheme.navLargeTitleTextStyle + .copyWith(color: cupertinoTitleColor), + ), + ); + return AnnotatedRegion( value: FlexColorScheme.themedSystemNavigationBar( context, @@ -165,13 +209,13 @@ class _AppState extends ConsumerState { onGenerateTitle: (BuildContext context) => 'lichess.org', locale: generalPrefs.locale, theme: themeLight.copyWith( - cupertinoOverrideTheme: AppTheme.lightCupertino, + cupertinoOverrideTheme: lightCupertino, splashFactory: isIOS ? NoSplash.splashFactory : null, textTheme: isIOS ? Typography.blackCupertino : null, listTileTheme: ListTileTheme.of(context).copyWith( - titleTextStyle: isIOS ? AppTheme.lightCupertino.textTheme.textStyle : null, - subtitleTextStyle: isIOS ? AppTheme.lightCupertino.textTheme.textStyle : null, - leadingAndTrailingTextStyle: isIOS ? AppTheme.lightCupertino.textTheme.textStyle : null, + titleTextStyle: isIOS ? lightCupertino.textTheme.textStyle : null, + subtitleTextStyle: isIOS ? lightCupertino.textTheme.textStyle : null, + leadingAndTrailingTextStyle: isIOS ? lightCupertino.textTheme.textStyle : null, ), floatingActionButtonTheme: floatingActionButtonTheme, navigationBarTheme: NavigationBarTheme.of( @@ -180,13 +224,13 @@ class _AppState extends ConsumerState { extensions: [lichessCustomColors.harmonized(themeLight.colorScheme)], ), darkTheme: themeDark.copyWith( - cupertinoOverrideTheme: AppTheme.darkCupertino, + cupertinoOverrideTheme: darkCupertino, splashFactory: isIOS ? NoSplash.splashFactory : null, textTheme: isIOS ? Typography.whiteCupertino : null, listTileTheme: ListTileTheme.of(context).copyWith( - titleTextStyle: isIOS ? AppTheme.darkCupertino.textTheme.textStyle : null, - subtitleTextStyle: isIOS ? AppTheme.darkCupertino.textTheme.textStyle : null, - leadingAndTrailingTextStyle: isIOS ? AppTheme.darkCupertino.textTheme.textStyle : null, + titleTextStyle: isIOS ? darkCupertino.textTheme.textStyle : null, + subtitleTextStyle: isIOS ? darkCupertino.textTheme.textStyle : null, + leadingAndTrailingTextStyle: isIOS ? darkCupertino.textTheme.textStyle : null, ), floatingActionButtonTheme: floatingActionButtonTheme, navigationBarTheme: NavigationBarTheme.of( diff --git a/lib/src/theme.dart b/lib/src/theme.dart deleted file mode 100644 index d4425ce90a..0000000000 --- a/lib/src/theme.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:flex_color_scheme/flex_color_scheme.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart' show defaultTargetPlatform; -import 'package:flutter/material.dart'; - -// ignore: avoid_classes_with_only_static_members -abstract final class AppTheme { - static const FlexSchemeData defaultScheme = FlexColor.espresso; - - static final ThemeData _light = FlexThemeData.light( - scheme: FlexScheme.espresso, - surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, - blendLevel: 10, - visualDensity: FlexColorScheme.comfortablePlatformDensity, - cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), - appBarStyle: - defaultTargetPlatform == TargetPlatform.iOS ? null : FlexAppBarStyle.scaffoldBackground, - ); - // The defined dark theme. - static final ThemeData _dark = FlexThemeData.dark( - scheme: FlexScheme.espresso, - surfaceMode: FlexSurfaceMode.level, - blendLevel: 20, - visualDensity: FlexColorScheme.comfortablePlatformDensity, - cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), - appBarStyle: - defaultTargetPlatform == TargetPlatform.iOS ? null : FlexAppBarStyle.scaffoldBackground, - ); - - static const cupertinoTitleColor = CupertinoDynamicColor.withBrightness( - color: Color(0xFF000000), - darkColor: Color(0xFFF5F5F5), - ); - - static final lightCupertino = CupertinoThemeData( - primaryColor: _light.colorScheme.primary, - primaryContrastingColor: _light.colorScheme.onPrimary, - brightness: Brightness.light, - scaffoldBackgroundColor: _light.scaffoldBackgroundColor, - barBackgroundColor: _light.appBarTheme.backgroundColor, - textTheme: const CupertinoThemeData().textTheme.copyWith( - primaryColor: _light.colorScheme.primary, - textStyle: const CupertinoThemeData().textTheme.textStyle.copyWith( - color: _light.colorScheme.onSurface, - ), - navTitleTextStyle: const CupertinoThemeData().textTheme.navTitleTextStyle.copyWith( - color: cupertinoTitleColor, - ), - navLargeTitleTextStyle: const CupertinoThemeData().textTheme.navLargeTitleTextStyle.copyWith( - color: cupertinoTitleColor, - ), - ), - ); - - static final darkCupertino = CupertinoThemeData( - primaryColor: _dark.colorScheme.primaryFixed, - primaryContrastingColor: _dark.colorScheme.onPrimaryFixed, - brightness: Brightness.dark, - scaffoldBackgroundColor: _dark.scaffoldBackgroundColor, - barBackgroundColor: _dark.appBarTheme.backgroundColor, - textTheme: const CupertinoThemeData().textTheme.copyWith( - primaryColor: _dark.colorScheme.primaryFixed, - textStyle: const CupertinoThemeData().textTheme.textStyle.copyWith( - color: _dark.colorScheme.onSurface, - ), - navTitleTextStyle: const CupertinoThemeData().textTheme.navTitleTextStyle.copyWith( - color: cupertinoTitleColor, - ), - navLargeTitleTextStyle: const CupertinoThemeData().textTheme.navLargeTitleTextStyle.copyWith( - color: cupertinoTitleColor, - ), - ), - ); -} From 2fe117b9db11d60fd16385900e970dc23b898ee3 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Sat, 25 Jan 2025 16:30:51 +0800 Subject: [PATCH 23/41] Tweaks --- lib/src/app.dart | 20 +++++++++++++++---- lib/src/model/settings/board_preferences.dart | 1 - .../board_background_theme_screen.dart | 2 +- lib/src/view/settings/board_theme_screen.dart | 2 +- lib/src/widgets/board_theme.dart | 2 +- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index ae6d6fbaf0..0efa901bbe 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -17,7 +17,8 @@ import 'package:lichess_mobile/src/network/connectivity.dart'; import 'package:lichess_mobile/src/network/http.dart'; import 'package:lichess_mobile/src/network/socket.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; -import 'package:lichess_mobile/src/utils/color_palette.dart' show getSystemScheme; +import 'package:lichess_mobile/src/utils/color_palette.dart' + show getDynamicColorSchemes, getSystemScheme; import 'package:lichess_mobile/src/utils/screen.dart'; /// Application initialization and main entry point. @@ -127,6 +128,8 @@ class _AppState extends ConsumerState { final flexSchemeLightColors = flexScheme.light; final flexSchemeDarkColors = flexScheme.dark; + final systemColors = generalPrefs.systemColors == true ? getDynamicColorSchemes() : null; + final themeLight = FlexThemeData.light( colors: flexSchemeLightColors, surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, @@ -198,6 +201,12 @@ class _AppState extends ConsumerState { ), ); + final highBlendThemeLight = FlexThemeData.light( + colors: flexSchemeLightColors, + surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold, + blendLevel: 20, + ); + return AnnotatedRegion( value: FlexColorScheme.themedSystemNavigationBar( context, @@ -218,9 +227,12 @@ class _AppState extends ConsumerState { leadingAndTrailingTextStyle: isIOS ? lightCupertino.textTheme.textStyle : null, ), floatingActionButtonTheme: floatingActionButtonTheme, - navigationBarTheme: NavigationBarTheme.of( - context, - ).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null), + navigationBarTheme: NavigationBarTheme.of(context).copyWith( + height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null, + backgroundColor: highBlendThemeLight.colorScheme.surface, + indicatorColor: highBlendThemeLight.colorScheme.secondaryContainer, + elevation: 3, + ), extensions: [lichessCustomColors.harmonized(themeLight.colorScheme)], ), darkTheme: themeDark.copyWith( diff --git a/lib/src/model/settings/board_preferences.dart b/lib/src/model/settings/board_preferences.dart index b9d60815f7..ec08f7ed9c 100644 --- a/lib/src/model/settings/board_preferences.dart +++ b/lib/src/model/settings/board_preferences.dart @@ -147,7 +147,6 @@ class BoardPrefs with _$BoardPrefs implements Serializable { static const defaults = BoardPrefs( pieceSet: PieceSet.staunty, boardTheme: BoardTheme.brown, - backgroundTheme: BoardBackgroundTheme.board, immersiveModeWhilePlaying: false, hapticFeedback: true, showLegalMoves: true, diff --git a/lib/src/view/settings/board_background_theme_screen.dart b/lib/src/view/settings/board_background_theme_screen.dart index c3f3337364..8ec069de8e 100644 --- a/lib/src/view/settings/board_background_theme_screen.dart +++ b/lib/src/view/settings/board_background_theme_screen.dart @@ -60,7 +60,7 @@ class _Body extends ConsumerWidget { ? FlexThemeData.light( colors: fsd.light, surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: 20, + blendLevel: 16, ) : FlexThemeData.dark( colors: fsd.dark, diff --git a/lib/src/view/settings/board_theme_screen.dart b/lib/src/view/settings/board_theme_screen.dart index 758b555405..ffcf8c9f21 100644 --- a/lib/src/view/settings/board_theme_screen.dart +++ b/lib/src/view/settings/board_theme_screen.dart @@ -199,7 +199,7 @@ class _BodyState extends ConsumerState<_Body> { if (boardPrefs.backgroundTheme != null) PlatformListTile( leading: const Icon(Icons.cancel), - title: const Text('Clear background'), + title: const Text('Reset background'), onTap: () { ref.read(boardPreferencesProvider.notifier).setBackgroundTheme(null); }, diff --git a/lib/src/widgets/board_theme.dart b/lib/src/widgets/board_theme.dart index 0527f9d46d..04d2b98288 100644 --- a/lib/src/widgets/board_theme.dart +++ b/lib/src/widgets/board_theme.dart @@ -37,7 +37,7 @@ class BoardTheme extends ConsumerWidget { cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, - blendLevel: 20, + blendLevel: 16, ); final darkTheme = FlexColorScheme.dark( From 87fe3c15af91da3e2add6ac52eb913669e8c8b9e Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 27 Jan 2025 04:13:01 +0800 Subject: [PATCH 24/41] First implem of custom image background --- ios/Podfile.lock | 6 + ios/Runner.xcodeproj/project.pbxproj | 3 + ios/Runner/Info.plist | 2 + lib/src/app.dart | 13 +- lib/src/model/settings/board_preferences.dart | 84 +++- lib/src/navigation.dart | 29 +- lib/src/view/clock/clock_tool_screen.dart | 1 - lib/src/view/game/game_result_dialog.dart | 22 +- lib/src/view/game/message_screen.dart | 1 - .../opening_explorer_screen.dart | 17 +- .../over_the_board/over_the_board_screen.dart | 1 - .../board_background_theme_screen.dart | 373 +++++++++++++----- lib/src/view/settings/board_theme_screen.dart | 17 +- lib/src/widgets/board_theme.dart | 233 +++++++---- lib/src/widgets/platform.dart | 12 +- lib/src/widgets/platform_scaffold.dart | 13 +- pubspec.lock | 106 ++++- pubspec.yaml | 2 + 18 files changed, 711 insertions(+), 224 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c2beba4739..cd2d5f3924 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -112,6 +112,8 @@ PODS: - GoogleUtilities/UserDefaults (8.0.2): - GoogleUtilities/Logger - GoogleUtilities/Privacy + - image_picker_ios (0.0.1): + - Flutter - nanopb (3.30910.0): - nanopb/decode (= 3.30910.0) - nanopb/encode (= 3.30910.0) @@ -157,6 +159,7 @@ DEPENDENCIES: - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - objective_c (from `.symlinks/plugins/objective_c/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) @@ -211,6 +214,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_native_splash/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" objective_c: :path: ".symlinks/plugins/objective_c/ios" package_info_plus: @@ -257,6 +262,7 @@ SPEC CHECKSUMS: flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d + image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 objective_c: 89e720c30d716b036faf9c9684022048eee1eee2 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 727b6aeec5..da03ef103c 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -424,6 +424,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -566,6 +567,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -602,6 +604,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index fb3991392e..27e697401c 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -107,6 +107,8 @@ LSRequiresIPhoneOS + NSPhotoLibraryUsageDescription + Photo Library Access Warning UIApplicationSupportsIndirectInputEvents UIBackgroundModes diff --git a/lib/src/app.dart b/lib/src/app.dart index 0efa901bbe..8b5839ab78 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -17,8 +17,7 @@ import 'package:lichess_mobile/src/network/connectivity.dart'; import 'package:lichess_mobile/src/network/http.dart'; import 'package:lichess_mobile/src/network/socket.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; -import 'package:lichess_mobile/src/utils/color_palette.dart' - show getDynamicColorSchemes, getSystemScheme; +import 'package:lichess_mobile/src/utils/color_palette.dart' show getSystemScheme; import 'package:lichess_mobile/src/utils/screen.dart'; /// Application initialization and main entry point. @@ -128,8 +127,6 @@ class _AppState extends ConsumerState { final flexSchemeLightColors = flexScheme.light; final flexSchemeDarkColors = flexScheme.dark; - final systemColors = generalPrefs.systemColors == true ? getDynamicColorSchemes() : null; - final themeLight = FlexThemeData.light( colors: flexSchemeLightColors, surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, @@ -255,10 +252,10 @@ class _AppState extends ConsumerState { BackgroundThemeMode.dark => ThemeMode.dark, BackgroundThemeMode.system => ThemeMode.system, }, - builder: - Theme.of(context).platform == TargetPlatform.iOS - ? (context, child) => Material(child: child) - : null, + // builder: + // Theme.of(context).platform == TargetPlatform.iOS + // ? (context, child) => Material(child: child) + // : null, home: const BottomNavScaffold(), navigatorObservers: [rootNavPageRouteObserver], ), diff --git a/lib/src/model/settings/board_preferences.dart b/lib/src/model/settings/board_preferences.dart index ec08f7ed9c..d793e2876a 100644 --- a/lib/src/model/settings/board_preferences.dart +++ b/lib/src/model/settings/board_preferences.dart @@ -105,6 +105,81 @@ class BoardPreferences extends _$BoardPreferences with PreferencesStorage setBackgroundTheme(BoardBackgroundTheme? backgroundTheme) { return save(state.copyWith(backgroundTheme: backgroundTheme)); } + + Future setBackgroundImage(BoardBackgroundImage? backgroundImage) { + return save(state.copyWith(backgroundImage: backgroundImage)); + } +} + +typedef BoardBackgroundImage = + ({String path, Matrix4 transform, bool isBlurred, ColorScheme darkColors}); + +class BoardBackgroundImageConverter + implements JsonConverter?> { + const BoardBackgroundImageConverter(); + + @override + BoardBackgroundImage? fromJson(Map? json) { + if (json == null) { + return null; + } + + final darkColors = ColorScheme( + brightness: Brightness.dark, + primary: Color(json['darkPrimary'] as int), + onPrimary: Color(json['darkOnPrimary'] as int), + primaryContainer: Color(json['darkPrimaryContainer'] as int), + secondary: Color(json['darkSecondary'] as int), + onSecondary: Color(json['darkOnSecondary'] as int), + secondaryContainer: Color(json['darkSecondaryContainer'] as int), + tertiary: Color(json['darkTertiary'] as int), + tertiaryContainer: Color(json['darkTertiaryContainer'] as int), + error: Color(json['darkError'] as int), + onError: Color(json['darkOnError'] as int), + errorContainer: Color(json['darkErrorContainer'] as int), + surface: Color(json['darkSurface'] as int), + onSurface: Color(json['darkOnSurface'] as int), + ); + + final transform = json['transform'] as List; + + return ( + path: json['path'] as String, + transform: Matrix4.fromList(transform.map((e) => (e as num).toDouble()).toList()), + isBlurred: json['isBlurred'] as bool, + darkColors: darkColors, + ); + } + + @override + Map? toJson(BoardBackgroundImage? object) { + if (object == null) { + return null; + } + + final Map darkColors = { + 'darkPrimary': object.darkColors.primary.toARGB32(), + 'darkOnPrimary': object.darkColors.onPrimary.toARGB32(), + 'darkPrimaryContainer': object.darkColors.primaryContainer.toARGB32(), + 'darkSecondary': object.darkColors.secondary.toARGB32(), + 'darkOnSecondary': object.darkColors.onSecondary.toARGB32(), + 'darkSecondaryContainer': object.darkColors.secondaryContainer.toARGB32(), + 'darkTertiary': object.darkColors.tertiary.toARGB32(), + 'darkTertiaryContainer': object.darkColors.tertiaryContainer.toARGB32(), + 'darkError': object.darkColors.error.toARGB32(), + 'darkOnError': object.darkColors.onError.toARGB32(), + 'darkErrorContainer': object.darkColors.errorContainer.toARGB32(), + 'darkSurface': object.darkColors.surface.toARGB32(), + 'darkOnSurface': object.darkColors.onSurface.toARGB32(), + }; + + return { + 'path': object.path, + 'transform': object.transform.storage, + 'isBlurred': object.isBlurred, + ...darkColors, + }; + } } @Freezed(fromJson: true, toJson: true) @@ -142,6 +217,7 @@ class BoardPrefs with _$BoardPrefs implements Serializable { @JsonKey(defaultValue: kBoardDefaultBrightnessFilter) required double brightness, @JsonKey(defaultValue: kBoardDefaultHueFilter) required double hue, BoardBackgroundTheme? backgroundTheme, + @BoardBackgroundImageConverter() BoardBackgroundImage? backgroundImage, }) = _BoardPrefs; static const defaults = BoardPrefs( @@ -400,30 +476,24 @@ String dragTargetKindLabel(DragTargetKind kind) => switch (kind) { }; enum BoardBackgroundTheme { - /// The app theme is based on the chess board + /// The background theme is based on the chess board board, /// Below values from [FlexScheme] - // mandyRed, redWine, pinkM3, - // amber, purpleBrown, purpleM3, indigoM3, blueM3, aquaBlue, - // cyanM3, tealM3, greenM3, jungle, - // limeM3, yellowM3, orangeM3, deepOrangeM3, mango, - // gold, - // greys, sepia; static final _flexSchemesNameMap = FlexScheme.values.asNameMap(); diff --git a/lib/src/navigation.dart b/lib/src/navigation.dart index 26ff184dd6..71f3f78b09 100644 --- a/lib/src/navigation.dart +++ b/lib/src/navigation.dart @@ -150,19 +150,22 @@ class BottomNavScaffold extends ConsumerWidget { ); case TargetPlatform.iOS: final isOnline = ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? true; - return CupertinoTabScaffold( - tabBuilder: _iOSTabBuilder, - controller: _cupertinoTabController, - tabBar: CupertinoTabBar( - currentIndex: currentTab.index, - items: [ - for (final tab in BottomTab.values) - BottomNavigationBarItem( - icon: Icon(tab == currentTab ? tab.activeIcon : tab.icon), - label: tab.label(context.l10n), - ), - ], - onTap: (i) => _onItemTapped(ref, i, isOnline: isOnline), + return Material( + color: Colors.transparent, + child: CupertinoTabScaffold( + tabBuilder: _iOSTabBuilder, + controller: _cupertinoTabController, + tabBar: CupertinoTabBar( + currentIndex: currentTab.index, + items: [ + for (final tab in BottomTab.values) + BottomNavigationBarItem( + icon: Icon(tab == currentTab ? tab.activeIcon : tab.icon), + label: tab.label(context.l10n), + ), + ], + onTap: (i) => _onItemTapped(ref, i, isOnline: isOnline), + ), ), ); default: diff --git a/lib/src/view/clock/clock_tool_screen.dart b/lib/src/view/clock/clock_tool_screen.dart index 4e6bab0020..55d98998a2 100644 --- a/lib/src/view/clock/clock_tool_screen.dart +++ b/lib/src/view/clock/clock_tool_screen.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lichess_mobile/src/model/clock/clock_tool_controller.dart'; import 'package:lichess_mobile/src/model/common/time_increment.dart'; -import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/immersive_mode.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/view/clock/clock_settings.dart'; diff --git a/lib/src/view/game/game_result_dialog.dart b/lib/src/view/game/game_result_dialog.dart index d15cd52c3c..5de2131c98 100644 --- a/lib/src/view/game/game_result_dialog.dart +++ b/lib/src/view/game/game_result_dialog.dart @@ -43,19 +43,17 @@ Widget _adaptiveDialog(BuildContext context, Widget content) { final screenWidth = MediaQuery.of(context).size.width; final paddedContent = Padding(padding: const EdgeInsets.all(16.0), child: content); - return BoardTheme( - child: Dialog( - backgroundColor: + return Dialog( + 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 - ? CupertinoDynamicColor.resolve(dialogColor, context) - : null, - child: SizedBox( - width: min(screenWidth, kMaterialPopupMenuMaxWidth), - child: - Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoPopupSurface(child: paddedContent) - : paddedContent, - ), + ? CupertinoPopupSurface(child: paddedContent) + : paddedContent, ), ); } diff --git a/lib/src/view/game/message_screen.dart b/lib/src/view/game/message_screen.dart index 384bbb99a0..4b387cf26d 100644 --- a/lib/src/view/game/message_screen.dart +++ b/lib/src/view/game/message_screen.dart @@ -7,7 +7,6 @@ import 'package:lichess_mobile/src/model/game/chat_controller.dart'; import 'package:lichess_mobile/src/model/settings/brightness.dart'; import 'package:lichess_mobile/src/model/user/user.dart'; import 'package:lichess_mobile/src/navigation.dart'; -import 'package:lichess_mobile/src/styles/lichess_colors.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/widgets/adaptive_text_field.dart'; diff --git a/lib/src/view/opening_explorer/opening_explorer_screen.dart b/lib/src/view/opening_explorer/opening_explorer_screen.dart index 237190fda1..a7b7087e4b 100644 --- a/lib/src/view/opening_explorer/opening_explorer_screen.dart +++ b/lib/src/view/opening_explorer/opening_explorer_screen.dart @@ -40,7 +40,7 @@ class OpeningExplorerScreen extends ConsumerWidget { _ => const CenterLoadingIndicator(), }; - return BoardTheme( + return BoardBackgroundThemeWidget( child: PlatformWidget( androidBuilder: (_) => Scaffold( @@ -51,13 +51,16 @@ class OpeningExplorerScreen extends ConsumerWidget { ), ), iosBuilder: - (_) => CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: Text(context.l10n.openingExplorer), - automaticBackgroundVisibility: false, - border: null, + (_) => Material( + color: Colors.transparent, + child: CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(context.l10n.openingExplorer), + automaticBackgroundVisibility: false, + border: null, + ), + child: body, ), - child: body, ), ), ); 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 d752d6052b..203fd9b595 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 @@ -10,7 +10,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lichess_mobile/src/model/over_the_board/over_the_board_clock.dart'; import 'package:lichess_mobile/src/model/over_the_board/over_the_board_game_controller.dart'; import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; -import 'package:lichess_mobile/src/model/settings/brightness.dart'; import 'package:lichess_mobile/src/model/settings/over_the_board_preferences.dart'; import 'package:lichess_mobile/src/utils/immersive_mode.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; diff --git a/lib/src/view/settings/board_background_theme_screen.dart b/lib/src/view/settings/board_background_theme_screen.dart index 8ec069de8e..c73f41566c 100644 --- a/lib/src/view/settings/board_background_theme_screen.dart +++ b/lib/src/view/settings/board_background_theme_screen.dart @@ -1,16 +1,22 @@ +import 'dart:io'; +import 'dart:ui' show ImageFilter; + import 'package:chessground/chessground.dart'; import 'package:dartchess/dartchess.dart' show Side, kInitialFEN; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; import 'package:lichess_mobile/src/styles/lichess_icons.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/widgets/board_theme.dart' as wrapper; import 'package:lichess_mobile/src/widgets/buttons.dart'; +import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; +import 'package:path_provider/path_provider.dart'; class BoardBackgroundThemeScreen extends StatelessWidget { const BoardBackgroundThemeScreen({super.key}); @@ -29,7 +35,7 @@ class BoardBackgroundThemeScreen extends StatelessWidget { } } -const choices = BoardBackgroundTheme.values; +const colorChoices = BoardBackgroundTheme.values; class _Body extends ConsumerWidget { @override @@ -43,100 +49,156 @@ class _Body extends ConsumerWidget { const itemsByRow = 4; - return GridView.builder( - padding: MediaQuery.paddingOf(context) + Styles.bodyPadding, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: itemsByRow, - crossAxisSpacing: 6.0, - mainAxisSpacing: 6.0, - childAspectRatio: 0.5, - ), - itemBuilder: (context, index) { - final t = choices[index]; - final fsd = t.getFlexScheme(boardPrefs.boardTheme); - - final theme = - brightness == Brightness.light - ? FlexThemeData.light( - colors: fsd.light, - surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: 16, - ) - : FlexThemeData.dark( - colors: fsd.dark, - surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: 20, + final viewport = MediaQuery.sizeOf(context); + final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); + + return ListView( + children: [ + ListSection( + children: [ + ListTile( + leading: const Icon(Icons.image_outlined), + title: const Text('Pick an image'), + trailing: + Theme.of(context).platform == TargetPlatform.iOS + ? const CupertinoListTileChevron() + : null, + onTap: () async { + final ImagePicker picker = ImagePicker(); + final XFile? image = await picker.pickImage( + source: ImageSource.gallery, + maxWidth: viewport.width * devicePixelRatio * 2, + maxHeight: viewport.height * devicePixelRatio * 2, + imageQuality: 80, ); - final autoColor = - brightness == Brightness.light - ? darken(theme.scaffoldBackgroundColor, 0.2) - : lighten(theme.scaffoldBackgroundColor, 0.2); - - return Tooltip( - message: 'Background based on chessboard colors.', - triggerMode: t == BoardBackgroundTheme.board ? null : TooltipTriggerMode.manual, - child: GestureDetector( - onTap: - () => Navigator.of(context, rootNavigator: true) - .push( - MaterialPageRoute( - builder: - (_) => ConfirmBackgroundScreen( - boardPrefs: boardPrefs, - initialIndex: index, - ), - fullscreenDialog: true, - ), - ) - .then((value) { - if (context.mounted) { - if (value != null) { - onChanged(choices[value.toInt()]); + if (context.mounted && image != null) { + Navigator.of(context, rootNavigator: true) + .push( + MaterialPageRoute( + builder: + (_) => ConfirmImageBackgroundScreen( + boardPrefs: boardPrefs, + image: image, + ), + fullscreenDialog: true, + ), + ) + .then((value) { + if (context.mounted && value != null) { + ref.read(boardPreferencesProvider.notifier).setBackgroundImage(value); Navigator.pop(context); } - } - }), - child: SizedBox.expand( - child: ColoredBox( - color: theme.scaffoldBackgroundColor, - child: - t == BoardBackgroundTheme.board - ? Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(LichessIcons.chess_board, color: autoColor), - const SizedBox(height: 8), - Center( - child: Text( - 'auto', - style: theme.textTheme.labelSmall?.copyWith(color: autoColor), - ), - ), - ], - ) - : null, - ), + }); + } + }, ), + ], + ), + + GridView.builder( + primary: false, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(16.0), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: itemsByRow, + crossAxisSpacing: 6.0, + mainAxisSpacing: 6.0, + childAspectRatio: 0.5, ), - ); - }, - itemCount: choices.length, + itemBuilder: (context, index) { + final t = colorChoices[index]; + final fsd = t.getFlexScheme(boardPrefs.boardTheme); + + final theme = + brightness == Brightness.light + ? FlexThemeData.light( + colors: fsd.light, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + blendLevel: 16, + ) + : FlexThemeData.dark( + colors: fsd.dark, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + blendLevel: 20, + ); + + final autoColor = + brightness == Brightness.light + ? darken(theme.scaffoldBackgroundColor, 0.2) + : lighten(theme.scaffoldBackgroundColor, 0.2); + + return Tooltip( + message: 'Background based on chessboard colors.', + triggerMode: t == BoardBackgroundTheme.board ? null : TooltipTriggerMode.manual, + child: GestureDetector( + onTap: + () => Navigator.of(context, rootNavigator: true) + .push( + MaterialPageRoute( + builder: + (_) => ConfirmColorBackgroundScreen( + boardPrefs: boardPrefs, + initialIndex: index, + ), + fullscreenDialog: true, + ), + ) + .then((value) { + if (context.mounted) { + if (value != null) { + onChanged(colorChoices[value.toInt()]); + Navigator.pop(context); + } + } + }), + child: SizedBox.expand( + child: ColoredBox( + color: theme.scaffoldBackgroundColor, + child: + t == BoardBackgroundTheme.board + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(LichessIcons.chess_board, color: autoColor), + const SizedBox(height: 8), + Center( + child: Text( + 'auto', + style: theme.textTheme.labelSmall?.copyWith(color: autoColor), + ), + ), + ], + ) + : null, + ), + ), + ), + ); + }, + itemCount: colorChoices.length, + ), + ], ); } } -class ConfirmBackgroundScreen extends StatefulWidget { - const ConfirmBackgroundScreen({required this.initialIndex, required this.boardPrefs, super.key}); +class ConfirmColorBackgroundScreen extends StatefulWidget { + const ConfirmColorBackgroundScreen({ + required this.initialIndex, + required this.boardPrefs, + super.key, + }); final int initialIndex; final BoardPrefs boardPrefs; @override - State createState() => _ConfirmBackgroundScreenState(); + State createState() => _ConfirmBackgroundScreenState(); } -class _ConfirmBackgroundScreenState extends State { +class _ConfirmBackgroundScreenState extends State { late PageController _controller; @override @@ -153,13 +215,13 @@ class _ConfirmBackgroundScreenState extends State { PageView.builder( controller: _controller, itemBuilder: (context, index) { - final backgroundTheme = choices[index]; - return wrapper.BoardTheme( + final backgroundTheme = colorChoices[index]; + return wrapper.BoardBackgroundThemeWidget( backgroundTheme: backgroundTheme, child: const Scaffold(body: SizedBox.expand()), ); }, - itemCount: choices.length, + itemCount: colorChoices.length, ), Positioned.fill( child: Align( @@ -180,21 +242,144 @@ class _ConfirmBackgroundScreenState extends State { ), ], ), - bottomNavigationBar: BottomAppBar( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - AdaptiveTextButton( - child: Text(context.l10n.cancel), - onPressed: () => Navigator.pop(context, null), - ), - AdaptiveTextButton( - child: Text(context.l10n.accept), - onPressed: () => Navigator.pop(context, _controller.page), - ), - ], + persistentFooterButtons: [ + AdaptiveTextButton( + child: Text(context.l10n.cancel), + onPressed: () => Navigator.pop(context, null), + ), + AdaptiveTextButton( + child: Text(context.l10n.accept), + onPressed: () => Navigator.pop(context, _controller.page), ), + ], + ); + } +} + +class ConfirmImageBackgroundScreen extends StatefulWidget { + const ConfirmImageBackgroundScreen({required this.image, required this.boardPrefs, super.key}); + + final XFile image; + final BoardPrefs boardPrefs; + + @override + State createState() => _ConfirmImageBackgroundScreenState(); +} + +class _ConfirmImageBackgroundScreenState extends State { + bool blur = false; + + final _controller = TransformationController(); + Matrix4 _transformationMatrix = Matrix4.identity(); + + @override + void initState() { + super.initState(); + + _controller.addListener(() { + _transformationMatrix = _controller.value; + }); + } + + @override + void dispose() { + super.dispose(); + _controller.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: LayoutBuilder( + builder: (context, constraints) { + return Stack( + children: [ + InteractiveViewer( + transformationController: _controller, + constrained: false, + minScale: 1, + maxScale: 2, + child: Container( + width: constraints.maxWidth, + height: constraints.maxHeight, + decoration: BoxDecoration( + image: DecorationImage( + image: Image.file(File(widget.image.path)).image, + fit: BoxFit.cover, + ), + ), + child: BackdropFilter( + enabled: blur, + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: const SizedBox.expand(), + ), + ), + ), + Positioned.fill( + child: Align( + alignment: Alignment.center, + child: IgnorePointer( + child: Chessboard.fixed( + size: MediaQuery.sizeOf(context).width, + fen: kInitialFEN, + orientation: Side.white, + settings: widget.boardPrefs.toBoardSettings(), + ), + ), + ), + ), + Positioned( + bottom: MediaQuery.paddingOf(context).bottom + 16.0, + left: 0, + right: 0, + child: Center( + child: PlatformCard( + child: AdaptiveInkWell( + onTap: () { + setState(() { + blur = !blur; + }); + }, + borderRadius: const BorderRadius.all(Radius.circular(10)), + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Text('Blur image', textAlign: TextAlign.center), + ), + ), + ), + ), + ), + ], + ); + }, ), + persistentFooterButtons: [ + AdaptiveTextButton( + child: Text(context.l10n.cancel), + onPressed: () => Navigator.pop(context, null), + ), + AdaptiveTextButton( + child: Text(context.l10n.accept), + onPressed: () async { + final directory = await getApplicationDocumentsDirectory(); + final targetPath = '${directory.path}/custom-board-background.jpg'; + final darkScheme = await ColorScheme.fromImageProvider( + provider: FileImage(File(widget.image.path)), + brightness: Brightness.dark, + ); + await FileImage(File(targetPath)).evict(); + await File(widget.image.path).copy(targetPath); + if (context.mounted) { + return Navigator.pop(context, ( + path: targetPath, + transform: _transformationMatrix, + isBlurred: blur, + darkColors: darkScheme, + )); + } + }, + ), + ], ); } } diff --git a/lib/src/view/settings/board_theme_screen.dart b/lib/src/view/settings/board_theme_screen.dart index ffcf8c9f21..50034c331b 100644 --- a/lib/src/view/settings/board_theme_screen.dart +++ b/lib/src/view/settings/board_theme_screen.dart @@ -16,7 +16,7 @@ import 'package:lichess_mobile/src/view/settings/board_background_theme_screen.d import 'package:lichess_mobile/src/view/settings/board_choice_screen.dart'; import 'package:lichess_mobile/src/view/settings/piece_set_screen.dart'; import 'package:lichess_mobile/src/widgets/adaptive_choice_picker.dart'; -import 'package:lichess_mobile/src/widgets/board_theme.dart' as wrapper; +import 'package:lichess_mobile/src/widgets/board_theme.dart'; import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; import 'package:lichess_mobile/src/widgets/settings.dart'; @@ -27,7 +27,7 @@ class BoardThemeScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final boardPrefs = ref.watch(boardPreferencesProvider); - return wrapper.BoardTheme( + return BoardBackgroundThemeWidget( backgroundTheme: boardPrefs.backgroundTheme, child: PlatformWidget( androidBuilder: (context) => const Scaffold(body: _Body()), @@ -187,7 +187,9 @@ class _BodyState extends ConsumerState<_Body> { SettingsListTile( icon: const Icon(Icons.wallpaper), settingsLabel: Text(context.l10n.background), - settingsValue: boardPrefs.backgroundTheme?.label(context.l10n) ?? 'Default', + settingsValue: + boardPrefs.backgroundTheme?.label(context.l10n) ?? + (boardPrefs.backgroundImage != null ? 'Image' : 'Default'), onTap: () { pushPlatformRoute( context, @@ -196,12 +198,17 @@ class _BodyState extends ConsumerState<_Body> { ); }, ), - if (boardPrefs.backgroundTheme != null) + if (boardPrefs.backgroundTheme != null || boardPrefs.backgroundImage != null) PlatformListTile( leading: const Icon(Icons.cancel), title: const Text('Reset background'), onTap: () { - ref.read(boardPreferencesProvider.notifier).setBackgroundTheme(null); + if (ref.read(boardPreferencesProvider).backgroundTheme != null) { + ref.read(boardPreferencesProvider.notifier).setBackgroundTheme(null); + } + if (ref.read(boardPreferencesProvider).backgroundImage != null) { + ref.read(boardPreferencesProvider.notifier).setBackgroundImage(null); + } }, ), SettingsListTile( diff --git a/lib/src/widgets/board_theme.dart b/lib/src/widgets/board_theme.dart index 04d2b98288..120f2fc16c 100644 --- a/lib/src/widgets/board_theme.dart +++ b/lib/src/widgets/board_theme.dart @@ -1,39 +1,68 @@ +import 'dart:io' show File; +import 'dart:ui' show ImageFilter; + import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; -import 'package:lichess_mobile/src/utils/screen.dart'; /// Applies the configured board theme to the child widget. /// /// Typically used in screens that need to display a chess board. -class BoardTheme extends ConsumerWidget { - const BoardTheme({required this.child, this.backgroundTheme, super.key}); +class BoardBackgroundThemeWidget extends ConsumerWidget { + const BoardBackgroundThemeWidget({ + required this.child, + this.backgroundImage, + this.backgroundTheme, + super.key, + }); final Widget child; + final BoardBackgroundImage? backgroundImage; final BoardBackgroundTheme? backgroundTheme; @override Widget build(BuildContext context, WidgetRef ref) { final boardPrefs = ref.watch(boardPreferencesProvider); final effectiveBackgroundTheme = backgroundTheme ?? boardPrefs.backgroundTheme; + final effectiveBackgroundImage = backgroundImage ?? boardPrefs.backgroundImage; - if (effectiveBackgroundTheme == null) { + if (effectiveBackgroundTheme == null && effectiveBackgroundImage == null) { return child; } - final boardTheme = boardPrefs.boardTheme; - final flexScheme = effectiveBackgroundTheme.getFlexScheme(boardTheme); - final isTablet = isTabletOrLarger(context); - final isIOS = Theme.of(context).platform == TargetPlatform.iOS; + if (effectiveBackgroundImage != null) { + return _BoardBackgroundImage(backgroundImage: effectiveBackgroundImage, child: child); + } else { + return _BoardBackgroundTheme( + backgroundTheme: effectiveBackgroundTheme!, + boardTheme: boardPrefs.boardTheme, + child: child, + ); + } + } +} + +class _BoardBackgroundTheme extends StatelessWidget { + const _BoardBackgroundTheme({ + required this.backgroundTheme, + required this.boardTheme, + required this.child, + }); - final flexSchemeLightColors = flexScheme.light; - final flexSchemeDarkColors = flexScheme.dark; + final Widget child; + final BoardTheme boardTheme; + final BoardBackgroundTheme backgroundTheme; + + @override + Widget build(BuildContext context) { + final flexScheme = backgroundTheme.getFlexScheme(boardTheme); + final isIOS = Theme.of(context).platform == TargetPlatform.iOS; final lightTheme = FlexThemeData.light( - colors: flexSchemeLightColors, + colors: flexScheme.light, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, @@ -41,75 +70,125 @@ class BoardTheme extends ConsumerWidget { ); final darkTheme = FlexColorScheme.dark( - colors: flexSchemeDarkColors, + colors: flexScheme.dark, surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, blendLevel: 20, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, ).toTheme; - final lightCupertinoTheme = CupertinoThemeData( - primaryColor: lightTheme.colorScheme.primary, - primaryContrastingColor: lightTheme.colorScheme.onPrimary, - brightness: Brightness.light, - textTheme: CupertinoTheme.of(context).textTheme.copyWith( - primaryColor: lightTheme.colorScheme.primary, - textStyle: CupertinoTheme.of( - context, - ).textTheme.textStyle.copyWith(color: lightTheme.colorScheme.onSurface), - navTitleTextStyle: CupertinoTheme.of( - context, - ).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), - navLargeTitleTextStyle: CupertinoTheme.of( - context, - ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), - ), - scaffoldBackgroundColor: lightTheme.scaffoldBackgroundColor, - barBackgroundColor: lightTheme.appBarTheme.backgroundColor?.withValues( - alpha: isTablet ? 1.0 : 0.9, + final brightness = Theme.of(context).brightness; + + final theme = brightness == Brightness.light ? lightTheme : darkTheme; + final cupertinoTheme = _makeCupertinoTheme(theme, brightness: brightness); + + return _ThemeWrapper( + isIOS: isIOS, + theme: theme, + cupertinoTheme: cupertinoTheme, + brightness: brightness, + child: child, + ); + } +} + +class _BoardBackgroundImage extends StatelessWidget { + const _BoardBackgroundImage({required this.backgroundImage, required this.child}); + + final BoardBackgroundImage backgroundImage; + final Widget child; + + @override + Widget build(BuildContext context) { + final isIOS = Theme.of(context).platform == TargetPlatform.iOS; + + final theme = FlexThemeData.dark( + colors: FlexSchemeColor( + primary: backgroundImage.darkColors.primary, + primaryContainer: backgroundImage.darkColors.primaryContainer, + secondary: backgroundImage.darkColors.secondary, + secondaryContainer: backgroundImage.darkColors.secondaryContainer, + tertiary: backgroundImage.darkColors.tertiary, + tertiaryContainer: backgroundImage.darkColors.tertiaryContainer, + error: backgroundImage.darkColors.error, + errorContainer: backgroundImage.darkColors.errorContainer, ), - applyThemeToAll: true, + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + appBarOpacity: 0, ); - final darkCupertinoTheme = CupertinoThemeData( - primaryColor: darkTheme.colorScheme.primary, - primaryContrastingColor: darkTheme.colorScheme.onPrimary, + final cupertinoTheme = _makeCupertinoTheme( + theme, brightness: Brightness.dark, - textTheme: CupertinoTheme.of(context).textTheme.copyWith( - primaryColor: darkTheme.colorScheme.primary, - textStyle: CupertinoTheme.of( - context, - ).textTheme.textStyle.copyWith(color: darkTheme.colorScheme.onSurface), - navTitleTextStyle: CupertinoTheme.of( - context, - ).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), - navLargeTitleTextStyle: CupertinoTheme.of( - context, - ).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor), + transparentScaffold: true, + ); + + final content = Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + image: DecorationImage( + image: FileImage(File(backgroundImage.path)), + fit: BoxFit.cover, + colorFilter: const ColorFilter.mode(Color(0x44000000), BlendMode.srcOver), + ), ), - scaffoldBackgroundColor: darkTheme.scaffoldBackgroundColor, - barBackgroundColor: darkTheme.appBarTheme.backgroundColor?.withValues( - alpha: isTablet ? 1.0 : 0.9, + child: ClipRect( + child: BackdropFilter( + enabled: backgroundImage.isBlurred, + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: child, + ), ), - applyThemeToAll: true, ); - final brightness = Theme.of(context).brightness; + return _ThemeWrapper( + isIOS: isIOS, + theme: theme, + cupertinoTheme: cupertinoTheme, + brightness: Brightness.dark, + transparentScaffold: true, + child: content, + ); + } +} - final theme = brightness == Brightness.light ? lightTheme : darkTheme; - final cupertinoTheme = - brightness == Brightness.light ? lightCupertinoTheme : darkCupertinoTheme; +class _ThemeWrapper extends StatelessWidget { + const _ThemeWrapper({ + required this.child, + required this.isIOS, + required this.theme, + required this.cupertinoTheme, + required this.brightness, + this.transparentScaffold = false, + }); + + final Widget child; + final bool isIOS; + final ThemeData theme; + final CupertinoThemeData cupertinoTheme; + final Brightness brightness; + final bool transparentScaffold; + @override + Widget build(BuildContext context) { return Theme( data: theme.copyWith( cupertinoOverrideTheme: cupertinoTheme, listTileTheme: ListTileTheme.of(context).copyWith( - tileColor: theme.colorScheme.surfaceContainerLow, - selectedTileColor: theme.colorScheme.surfaceContainer, + tileColor: theme.colorScheme.surfaceContainerLow.withValues( + alpha: transparentScaffold ? 0.5 : 1, + ), + selectedTileColor: theme.colorScheme.surfaceContainer.withValues( + alpha: transparentScaffold ? 0.5 : 1, + ), titleTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, subtitleTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, leadingAndTrailingTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, ), + scaffoldBackgroundColor: theme.scaffoldBackgroundColor.withValues( + alpha: transparentScaffold ? 0 : 1, + ), splashFactory: isIOS ? NoSplash.splashFactory : null, textTheme: isIOS @@ -117,18 +196,38 @@ class BoardTheme extends ConsumerWidget { ? Typography.blackCupertino : Typography.whiteCupertino : null, - extensions: [lichessCustomColors.harmonized(darkTheme.colorScheme)], + extensions: [lichessCustomColors.harmonized(theme.colorScheme)], ), - child: - isIOS - ? CupertinoTheme( - data: cupertinoTheme, - child: IconTheme.merge( - data: IconThemeData(color: CupertinoTheme.of(context).textTheme.textStyle.color), - child: child, - ), - ) - : child, + child: isIOS ? CupertinoTheme(data: cupertinoTheme, child: child) : child, ); } } + +CupertinoThemeData _makeCupertinoTheme( + ThemeData theme, { + Brightness brightness = Brightness.light, + bool transparentScaffold = false, +}) { + return CupertinoThemeData( + primaryColor: theme.colorScheme.primary, + primaryContrastingColor: theme.colorScheme.onPrimary, + brightness: brightness, + textTheme: const CupertinoThemeData().textTheme.copyWith( + primaryColor: theme.colorScheme.primary, + textStyle: const CupertinoThemeData().textTheme.textStyle.copyWith( + color: theme.colorScheme.onSurface, + ), + navTitleTextStyle: const CupertinoThemeData().textTheme.navTitleTextStyle.copyWith( + color: Styles.cupertinoTitleColor, + ), + navLargeTitleTextStyle: const CupertinoThemeData().textTheme.navLargeTitleTextStyle.copyWith( + color: Styles.cupertinoTitleColor, + ), + ), + scaffoldBackgroundColor: theme.scaffoldBackgroundColor.withValues( + alpha: transparentScaffold ? 0 : 1, + ), + barBackgroundColor: theme.appBarTheme.backgroundColor?.withValues(alpha: 0), + applyThemeToAll: true, + ); +} diff --git a/lib/src/widgets/platform.dart b/lib/src/widgets/platform.dart index 1564b89d25..4f8b357b83 100644 --- a/lib/src/widgets/platform.dart +++ b/lib/src/widgets/platform.dart @@ -84,7 +84,9 @@ class PlatformCard extends StatelessWidget { @override Widget build(BuildContext context) { + final platform = Theme.of(context).platform; final brightness = Theme.of(context).brightness; + final colorScheme = Theme.of(context).colorScheme; final cardFactory = brightness == Brightness.dark ? Card.filled : Card.new; return MediaQuery.withClampedTextScaling( maxScaleFactor: kCardTextScaleFactor, @@ -93,10 +95,16 @@ class PlatformCard extends StatelessWidget { borderRadius != null ? RoundedRectangleBorder(borderRadius: borderRadius!) : const RoundedRectangleBorder(borderRadius: kCardBorderRadius), - color: color, + color: + color ?? + (platform == TargetPlatform.iOS + ? brightness == Brightness.light + ? colorScheme.surfaceContainerLowest + : colorScheme.surfaceContainer + : null), shadowColor: shadowColor, semanticContainer: semanticContainer, - elevation: elevation ?? (Theme.of(context).platform == TargetPlatform.iOS ? 0 : null), + elevation: elevation ?? (platform == TargetPlatform.iOS ? 0 : null), margin: margin ?? EdgeInsets.zero, clipBehavior: clipBehavior, child: child, diff --git a/lib/src/widgets/platform_scaffold.dart b/lib/src/widgets/platform_scaffold.dart index 23102322ed..0eee8f937e 100644 --- a/lib/src/widgets/platform_scaffold.dart +++ b/lib/src/widgets/platform_scaffold.dart @@ -141,10 +141,13 @@ class PlatformScaffold extends StatelessWidget { } Widget _iosBuilder(BuildContext context) { - return CupertinoPageScaffold( - resizeToAvoidBottomInset: resizeToAvoidBottomInset, - navigationBar: appBar != null ? _CupertinoNavBarWrapper(child: appBar!) : null, - child: body, + return Material( + color: Colors.transparent, + child: CupertinoPageScaffold( + resizeToAvoidBottomInset: resizeToAvoidBottomInset, + navigationBar: appBar != null ? _CupertinoNavBarWrapper(child: appBar!) : null, + child: body, + ), ); } @@ -175,7 +178,7 @@ class PlatformBoardThemeScaffold extends StatelessWidget { @override Widget build(BuildContext context) { - return BoardTheme( + return BoardBackgroundThemeWidget( child: PlatformScaffold( resizeToAvoidBottomInset: resizeToAvoidBottomInset, appBar: appBar, diff --git a/pubspec.lock b/pubspec.lock index 4ece265b72..18d4f94e39 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -446,6 +446,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" + url: "https://pub.dev" + source: hosted + version: "0.9.3+3" firebase_core: dependency: "direct main" description: @@ -640,6 +672,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.4" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" + url: "https://pub.dev" + source: hosted + version: "2.0.24" flutter_riverpod: dependency: "direct main" description: @@ -819,6 +859,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c + url: "https://pub.dev" + source: hosted + version: "0.8.12+20" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + url: "https://pub.dev" + source: hosted + version: "0.8.12+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" intl: dependency: "direct main" description: @@ -1044,7 +1148,7 @@ packages: source: hosted version: "1.9.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" diff --git a/pubspec.yaml b/pubspec.yaml index 0a718b708e..e8f3682c7b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,6 +52,7 @@ dependencies: flutter_spinkit: ^5.2.0 freezed_annotation: ^2.2.0 http: ^1.1.0 + image_picker: ^1.1.2 intl: ^0.19.0 json_annotation: ^4.7.0 linkify: ^5.0.0 @@ -60,6 +61,7 @@ dependencies: meta: ^1.8.0 package_info_plus: ^8.0.0 path: ^1.8.2 + path_provider: ^2.1.5 popover: ^0.3.0 pub_semver: ^2.1.4 result_extensions: ^0.1.0 From 0051a711cc820102cbbf613cf20dfaaedd929e97 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 27 Jan 2025 17:55:36 +0800 Subject: [PATCH 25/41] More work on custom image --- lib/src/app.dart | 4 - lib/src/model/settings/board_preferences.dart | 77 ++- lib/src/navigation.dart | 4 +- .../view/broadcast/broadcast_list_screen.dart | 19 +- .../broadcast/broadcast_round_screen.dart | 93 +-- lib/src/view/game/game_result_dialog.dart | 1 - .../opening_explorer_screen.dart | 6 +- .../board_background_theme_choice_screen.dart | 542 ++++++++++++++++++ .../board_background_theme_screen.dart | 385 ------------- lib/src/view/settings/board_theme_screen.dart | 17 +- ...theme.dart => board_background_theme.dart} | 268 +++++---- lib/src/widgets/cupertino.dart | 17 + lib/src/widgets/list.dart | 4 +- lib/src/widgets/platform_scaffold.dart | 6 +- 14 files changed, 837 insertions(+), 606 deletions(-) create mode 100644 lib/src/view/settings/board_background_theme_choice_screen.dart delete mode 100644 lib/src/view/settings/board_background_theme_screen.dart rename lib/src/widgets/{board_theme.dart => board_background_theme.dart} (58%) create mode 100644 lib/src/widgets/cupertino.dart diff --git a/lib/src/app.dart b/lib/src/app.dart index 8b5839ab78..d32f4ded62 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -252,10 +252,6 @@ class _AppState extends ConsumerState { BackgroundThemeMode.dark => ThemeMode.dark, BackgroundThemeMode.system => ThemeMode.system, }, - // builder: - // Theme.of(context).platform == TargetPlatform.iOS - // ? (context, child) => Material(child: child) - // : null, home: const BottomNavScaffold(), navigatorObservers: [rootNavPageRouteObserver], ), diff --git a/lib/src/model/settings/board_preferences.dart b/lib/src/model/settings/board_preferences.dart index d793e2876a..bfc5957881 100644 --- a/lib/src/model/settings/board_preferences.dart +++ b/lib/src/model/settings/board_preferences.dart @@ -1,5 +1,6 @@ import 'package:chessground/chessground.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:flutter/cupertino.dart' show CupertinoThemeData; import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:lichess_mobile/l10n/l10n.dart'; @@ -102,17 +103,59 @@ class BoardPreferences extends _$BoardPreferences with PreferencesStorage setBackgroundTheme(BoardBackgroundTheme? backgroundTheme) { - return save(state.copyWith(backgroundTheme: backgroundTheme)); - } - - Future setBackgroundImage(BoardBackgroundImage? backgroundImage) { - return save(state.copyWith(backgroundImage: backgroundImage)); + Future setBackground({ + BoardBackgroundTheme? backgroundTheme, + BoardBackgroundImage? backgroundImage, + }) { + assert( + !(backgroundTheme != null && backgroundImage != null), + 'Only one of backgroundTheme or backgroundImage should be set', + ); + return save(state.copyWith(backgroundTheme: backgroundTheme, backgroundImage: backgroundImage)); } } -typedef BoardBackgroundImage = - ({String path, Matrix4 transform, bool isBlurred, ColorScheme darkColors}); +@freezed +class BoardBackgroundImage with _$BoardBackgroundImage { + const BoardBackgroundImage._(); + + const factory BoardBackgroundImage({ + required String path, + required Matrix4 transform, + required bool isBlurred, + required ColorScheme darkColors, + required double meanLuminance, + }) = _BoardBackgroundImage; + + static Color getFilterColor(ColorScheme scheme, double meanLuminance) => + scheme.surface.withValues( + alpha: switch (meanLuminance) { + < 0.2 => 0, + < 0.4 => 0.25, + < 0.6 => 0.5, + _ => 0.8, + }, + ); + + static ThemeData getTheme(ColorScheme scheme) => FlexThemeData.dark( + colors: FlexSchemeColor( + primary: scheme.primary, + primaryContainer: scheme.primaryContainer, + secondary: scheme.secondary, + secondaryContainer: scheme.secondaryContainer, + tertiary: scheme.tertiary, + tertiaryContainer: scheme.tertiaryContainer, + error: scheme.error, + errorContainer: scheme.errorContainer, + ), + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + appBarOpacity: 0, + ); + + ThemeData get theme => getTheme(darkColors); + + Color get filterColor => getFilterColor(darkColors, meanLuminance); +} class BoardBackgroundImageConverter implements JsonConverter?> { @@ -143,11 +186,12 @@ class BoardBackgroundImageConverter final transform = json['transform'] as List; - return ( + return BoardBackgroundImage( path: json['path'] as String, transform: Matrix4.fromList(transform.map((e) => (e as num).toDouble()).toList()), isBlurred: json['isBlurred'] as bool, darkColors: darkColors, + meanLuminance: json['meanLuminance'] as double, ); } @@ -178,6 +222,7 @@ class BoardBackgroundImageConverter 'transform': object.transform.storage, 'isBlurred': object.isBlurred, ...darkColors, + 'meanLuminance': object.meanLuminance, }; } } @@ -481,19 +526,19 @@ enum BoardBackgroundTheme { /// Below values from [FlexScheme] redWine, + yellowM3, pinkM3, - purpleBrown, purpleM3, - indigoM3, + // indigoM3, blueM3, - aquaBlue, - tealM3, + // tealM3, greenM3, + aquaBlue, jungle, - yellowM3, orangeM3, - deepOrangeM3, - mango, + // deepOrangeM3, + // mango, + purpleBrown, sepia; static final _flexSchemesNameMap = FlexScheme.values.asNameMap(); diff --git a/lib/src/navigation.dart b/lib/src/navigation.dart index 71f3f78b09..63e42170b7 100644 --- a/lib/src/navigation.dart +++ b/lib/src/navigation.dart @@ -9,6 +9,7 @@ import 'package:lichess_mobile/src/view/puzzle/puzzle_tab_screen.dart'; import 'package:lichess_mobile/src/view/settings/settings_tab_screen.dart'; import 'package:lichess_mobile/src/view/tools/tools_tab_screen.dart'; import 'package:lichess_mobile/src/view/watch/watch_tab_screen.dart'; +import 'package:lichess_mobile/src/widgets/cupertino.dart'; import 'package:lichess_mobile/src/widgets/feedback.dart'; enum BottomTab { @@ -150,8 +151,7 @@ class BottomNavScaffold extends ConsumerWidget { ); case TargetPlatform.iOS: final isOnline = ref.watch(connectivityChangesProvider).valueOrNull?.isOnline ?? true; - return Material( - color: Colors.transparent, + return CupertinoMaterialWrapper( child: CupertinoTabScaffold( tabBuilder: _iOSTabBuilder, controller: _cupertinoTabController, diff --git a/lib/src/view/broadcast/broadcast_list_screen.dart b/lib/src/view/broadcast/broadcast_list_screen.dart index 18ede9cb17..07ebc84a55 100644 --- a/lib/src/view/broadcast/broadcast_list_screen.dart +++ b/lib/src/view/broadcast/broadcast_list_screen.dart @@ -19,6 +19,7 @@ import 'package:lichess_mobile/src/utils/l10n.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/navigation.dart'; import 'package:lichess_mobile/src/view/broadcast/broadcast_round_screen.dart'; +import 'package:lichess_mobile/src/widgets/cupertino.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; import 'package:lichess_mobile/src/widgets/shimmer.dart'; @@ -40,14 +41,18 @@ class BroadcastListScreen extends StatelessWidget { return PlatformWidget( androidBuilder: (_) => Scaffold(body: const _Body(), appBar: AppBar(title: title)), iosBuilder: - (_) => CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: title, - automaticBackgroundVisibility: false, - backgroundColor: CupertinoTheme.of(context).barBackgroundColor.withValues(alpha: 0.0), - border: null, + (_) => CupertinoMaterialWrapper( + child: CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: title, + automaticBackgroundVisibility: false, + backgroundColor: CupertinoTheme.of( + context, + ).barBackgroundColor.withValues(alpha: 0.0), + border: null, + ), + child: const _Body(), ), - child: const _Body(), ), ); } diff --git a/lib/src/view/broadcast/broadcast_round_screen.dart b/lib/src/view/broadcast/broadcast_round_screen.dart index 01682c03ac..2856754f60 100644 --- a/lib/src/view/broadcast/broadcast_round_screen.dart +++ b/lib/src/view/broadcast/broadcast_round_screen.dart @@ -16,6 +16,7 @@ import 'package:lichess_mobile/src/view/broadcast/broadcast_players_tab.dart'; import 'package:lichess_mobile/src/widgets/adaptive_bottom_sheet.dart'; import 'package:lichess_mobile/src/widgets/bottom_bar.dart'; import 'package:lichess_mobile/src/widgets/buttons.dart'; +import 'package:lichess_mobile/src/widgets/cupertino.dart'; import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; @@ -91,56 +92,58 @@ class _BroadcastRoundScreenState extends ConsumerState } }, ); - return CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: AutoSizeText( - widget.broadcast.title, - minFontSize: 14.0, - overflow: TextOverflow.ellipsis, - maxLines: 1, + return CupertinoMaterialWrapper( + child: CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: AutoSizeText( + widget.broadcast.title, + minFontSize: 14.0, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), ), - ), - child: Column( - children: [ - 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( + child: Column( + children: [ + Expanded( + child: switch (asyncRound) { + AsyncData(value: final _) => switch (selectedTab) { + _CupertinoView.overview => _TabView( + cupertinoTabSwitcher: tabSwitcher, + sliver: BroadcastOverviewTab( + broadcast: widget.broadcast, 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.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), + ), + }, + _ => const Center(child: CircularProgressIndicator.adaptive()), }, - _ => const Center(child: CircularProgressIndicator.adaptive()), - }, - ), - switch (asyncTournament) { - AsyncData(:final value) => _BottomBar( - tournament: value, - roundId: _selectedRoundId ?? value.defaultRoundId, - setTournamentId: setTournamentId, - setRoundId: setRoundId, ), - _ => const PlatformBottomBar.empty(transparentBackground: false), - }, - ], + switch (asyncTournament) { + AsyncData(:final value) => _BottomBar( + tournament: value, + roundId: _selectedRoundId ?? value.defaultRoundId, + setTournamentId: setTournamentId, + setRoundId: setRoundId, + ), + _ => const PlatformBottomBar.empty(transparentBackground: false), + }, + ], + ), ), ); } diff --git a/lib/src/view/game/game_result_dialog.dart b/lib/src/view/game/game_result_dialog.dart index 5de2131c98..295e9b5984 100644 --- a/lib/src/view/game/game_result_dialog.dart +++ b/lib/src/view/game/game_result_dialog.dart @@ -18,7 +18,6 @@ import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/navigation.dart'; import 'package:lichess_mobile/src/view/analysis/analysis_screen.dart'; import 'package:lichess_mobile/src/view/game/status_l10n.dart'; -import 'package:lichess_mobile/src/widgets/board_theme.dart'; import 'package:lichess_mobile/src/widgets/buttons.dart'; import 'package:lichess_mobile/src/widgets/pgn.dart'; diff --git a/lib/src/view/opening_explorer/opening_explorer_screen.dart b/lib/src/view/opening_explorer/opening_explorer_screen.dart index a7b7087e4b..1d4e1135be 100644 --- a/lib/src/view/opening_explorer/opening_explorer_screen.dart +++ b/lib/src/view/opening_explorer/opening_explorer_screen.dart @@ -13,10 +13,11 @@ import 'package:lichess_mobile/src/view/analysis/analysis_board.dart'; import 'package:lichess_mobile/src/view/opening_explorer/opening_explorer_settings.dart'; import 'package:lichess_mobile/src/view/opening_explorer/opening_explorer_view.dart'; import 'package:lichess_mobile/src/widgets/adaptive_bottom_sheet.dart'; -import 'package:lichess_mobile/src/widgets/board_theme.dart'; +import 'package:lichess_mobile/src/widgets/board_background_theme.dart'; import 'package:lichess_mobile/src/widgets/bottom_bar.dart'; import 'package:lichess_mobile/src/widgets/bottom_bar_button.dart'; import 'package:lichess_mobile/src/widgets/buttons.dart'; +import 'package:lichess_mobile/src/widgets/cupertino.dart'; import 'package:lichess_mobile/src/widgets/feedback.dart'; import 'package:lichess_mobile/src/widgets/move_list.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; @@ -51,8 +52,7 @@ class OpeningExplorerScreen extends ConsumerWidget { ), ), iosBuilder: - (_) => Material( - color: Colors.transparent, + (_) => CupertinoMaterialWrapper( child: CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( middle: Text(context.l10n.openingExplorer), diff --git a/lib/src/view/settings/board_background_theme_choice_screen.dart b/lib/src/view/settings/board_background_theme_choice_screen.dart new file mode 100644 index 0000000000..520c25c384 --- /dev/null +++ b/lib/src/view/settings/board_background_theme_choice_screen.dart @@ -0,0 +1,542 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:chessground/chessground.dart'; +import 'package:dartchess/dartchess.dart' show Side, kInitialFEN; +import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; +import 'package:lichess_mobile/src/styles/lichess_icons.dart'; +import 'package:lichess_mobile/src/styles/styles.dart'; +import 'package:lichess_mobile/src/utils/l10n_context.dart'; +import 'package:lichess_mobile/src/widgets/board_background_theme.dart'; +import 'package:lichess_mobile/src/widgets/buttons.dart'; +import 'package:lichess_mobile/src/widgets/list.dart'; +import 'package:lichess_mobile/src/widgets/platform.dart'; +import 'package:lichess_mobile/src/widgets/settings.dart'; +import 'package:material_color_utilities/quantize/quantizer.dart'; +import 'package:material_color_utilities/quantize/quantizer_celebi.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; + +class BoardBackgroundThemeChoiceScreen extends StatelessWidget { + const BoardBackgroundThemeChoiceScreen({super.key}); + + @override + Widget build(BuildContext context) { + return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); + } + + Widget _androidBuilder(BuildContext context) { + return Scaffold(appBar: AppBar(title: Text(context.l10n.background)), body: _Body()); + } + + Widget _iosBuilder(BuildContext context) { + return CupertinoPageScaffold(navigationBar: const CupertinoNavigationBar(), child: _Body()); + } +} + +const colorChoices = BoardBackgroundTheme.values; +const itemsByRow = 3; + +class _Body extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final boardPrefs = ref.watch(boardPreferencesProvider); + final brightness = Theme.of(context).brightness; + + final viewport = MediaQuery.sizeOf(context); + final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); + + return ListView( + children: [ + ListSection( + children: [ + PlatformListTile( + leading: const Icon(Icons.image_outlined), + title: const Text('Pick an image'), + subtitle: const Text('Choose a dark image for best result.'), + trailing: + Theme.of(context).platform == TargetPlatform.iOS + ? const CupertinoListTileChevron() + : null, + onTap: () async { + final ImagePicker picker = ImagePicker(); + final XFile? image = await picker.pickImage( + source: ImageSource.gallery, + maxWidth: viewport.width * devicePixelRatio * 2, + maxHeight: viewport.height * devicePixelRatio * 2, + imageQuality: 80, + ); + + if (image != null) { + final imageProvider = FileImage(File(image.path)); + final darkScheme = await ColorScheme.fromImageProvider( + provider: imageProvider, + brightness: Brightness.dark, + ); + final quantizerResult = await _extractColorsFromImageProvider(imageProvider); + final Map colorToCount = quantizerResult.colorToCount.map( + (int key, int value) => MapEntry(_getArgbFromAbgr(key), value), + ); + final meanLuminance = + colorToCount.entries.fold( + 0, + (double previousValue, MapEntry entry) => + previousValue + Color(entry.key).computeLuminance() * entry.value, + ) / + colorToCount.values.fold( + 0, + (int previousValue, int element) => previousValue + element, + ); + + if (context.mounted) { + Navigator.of(context, rootNavigator: true) + .push( + MaterialPageRoute( + builder: + (_) => ConfirmImageBackgroundScreen( + boardPrefs: boardPrefs, + image: image, + darkColorScheme: darkScheme, + meanLuminance: meanLuminance, + ), + fullscreenDialog: true, + ), + ) + .then((value) { + if (context.mounted && value != null) { + ref + .read(boardPreferencesProvider.notifier) + .setBackground(backgroundImage: value); + Navigator.pop(context); + } + }); + } + } + }, + ), + ], + ), + ListSection( + header: const SettingsSectionTitle('Color presets'), + cupertinoBackgroundColor: Theme.of(context).colorScheme.surfaceContainerLow, + children: [ + GridView.builder( + primary: false, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(16.0), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: itemsByRow, + crossAxisSpacing: 6.0, + mainAxisSpacing: 6.0, + childAspectRatio: 0.5, + ), + itemBuilder: (context, index) { + final t = colorChoices[index]; + final fsd = t.getFlexScheme(boardPrefs.boardTheme); + + final theme = + brightness == Brightness.light + ? FlexThemeData.light( + colors: fsd.light, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + blendLevel: 16, + ) + : FlexThemeData.dark( + colors: fsd.dark, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + blendLevel: 20, + ); + + final autoColor = + brightness == Brightness.light + ? darken(theme.scaffoldBackgroundColor, 0.2) + : lighten(theme.scaffoldBackgroundColor, 0.2); + + return Tooltip( + message: 'Background based on chessboard colors.', + triggerMode: t == BoardBackgroundTheme.board ? null : TooltipTriggerMode.manual, + child: GestureDetector( + onTap: + () => Navigator.of(context, rootNavigator: true) + .push( + MaterialPageRoute( + builder: + (_) => ConfirmColorBackgroundScreen( + boardPrefs: boardPrefs, + initialIndex: index, + ), + fullscreenDialog: true, + ), + ) + .then((value) { + if (context.mounted) { + if (value != null) { + ref + .read(boardPreferencesProvider.notifier) + .setBackground(backgroundTheme: colorChoices[value.toInt()]); + Navigator.pop(context); + } + } + }), + child: SizedBox.expand( + child: ColoredBox( + color: theme.scaffoldBackgroundColor, + child: + t == BoardBackgroundTheme.board + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(LichessIcons.chess_board, color: autoColor), + const SizedBox(height: 8), + Center( + child: Text( + 'auto', + style: theme.textTheme.labelSmall?.copyWith( + color: autoColor, + ), + ), + ), + ], + ) + : null, + ), + ), + ), + ); + }, + itemCount: colorChoices.length, + ), + ], + ), + ], + ); + } +} + +class ConfirmColorBackgroundScreen extends StatefulWidget { + const ConfirmColorBackgroundScreen({ + required this.initialIndex, + required this.boardPrefs, + super.key, + }); + + final int initialIndex; + final BoardPrefs boardPrefs; + + @override + State createState() => _ConfirmBackgroundScreenState(); +} + +class _ConfirmBackgroundScreenState extends State { + late PageController _controller; + + @override + void initState() { + _controller = PageController(initialPage: widget.initialIndex); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + PageView.builder( + controller: _controller, + itemBuilder: (context, index) { + final backgroundTheme = colorChoices[index]; + return BoardBackgroundThemeWidget( + backgroundTheme: backgroundTheme, + child: const Scaffold(body: SizedBox.expand()), + ); + }, + itemCount: colorChoices.length, + ), + Positioned.fill( + child: Align( + alignment: Alignment.center, + child: Chessboard.fixed( + size: MediaQuery.sizeOf(context).width, + fen: kInitialFEN, + orientation: Side.white, + settings: widget.boardPrefs.toBoardSettings(), + ), + ), + ), + Positioned( + bottom: MediaQuery.paddingOf(context).bottom + 16.0, + left: 0, + right: 0, + child: const Text('Swipe to display other backgrounds', textAlign: TextAlign.center), + ), + ], + ), + persistentFooterButtons: [ + AdaptiveTextButton( + child: Text(context.l10n.cancel), + onPressed: () => Navigator.pop(context, null), + ), + AdaptiveTextButton( + child: Text(context.l10n.accept), + onPressed: () => Navigator.pop(context, _controller.page), + ), + ], + ); + } +} + +class ConfirmImageBackgroundScreen extends StatefulWidget { + const ConfirmImageBackgroundScreen({ + required this.image, + required this.boardPrefs, + required this.darkColorScheme, + required this.meanLuminance, + super.key, + }); + + final XFile image; + final BoardPrefs boardPrefs; + final ColorScheme darkColorScheme; + final double meanLuminance; + + @override + State createState() => _ConfirmImageBackgroundScreenState(); +} + +class _ConfirmImageBackgroundScreenState extends State { + bool blur = false; + + final _controller = TransformationController(); + Matrix4 _transformationMatrix = Matrix4.identity(); + + @override + void initState() { + super.initState(); + + _controller.addListener(() { + _transformationMatrix = _controller.value; + }); + } + + @override + void dispose() { + super.dispose(); + _controller.dispose(); + } + + @override + Widget build(BuildContext context) { + final filterColor = BoardBackgroundImage.getFilterColor( + widget.darkColorScheme, + widget.meanLuminance, + ); + + return BoardThemeWrapper( + theme: BoardBackgroundImage.getTheme(widget.darkColorScheme), + brightness: Brightness.dark, + transparentScaffold: true, + child: Scaffold( + body: LayoutBuilder( + builder: (context, constraints) { + return Stack( + children: [ + InteractiveViewer( + transformationController: _controller, + constrained: false, + minScale: 1, + maxScale: 2, + child: Container( + width: constraints.maxWidth, + height: constraints.maxHeight, + decoration: BoxDecoration( + image: DecorationImage( + image: Image.file(File(widget.image.path)).image, + colorFilter: ColorFilter.mode(filterColor, BlendMode.srcOver), + fit: BoxFit.cover, + ), + ), + child: BackdropFilter( + enabled: blur, + filter: ui.ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: const SizedBox.expand(), + ), + ), + ), + Positioned.fill( + child: Align( + alignment: Alignment.center, + child: IgnorePointer( + child: Chessboard.fixed( + size: MediaQuery.sizeOf(context).width, + fen: kInitialFEN, + orientation: Side.white, + settings: widget.boardPrefs.toBoardSettings(), + ), + ), + ), + ), + Positioned( + top: MediaQuery.paddingOf(context).top + 26.0, + left: 0, + right: 0, + child: Center( + child: PlatformCard( + child: AdaptiveInkWell( + onTap: () { + setState(() { + blur = !blur; + }); + }, + borderRadius: const BorderRadius.all(Radius.circular(10)), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(blur ? Icons.blur_off_outlined : Icons.blur_on_outlined), + const SizedBox(width: 4.0), + Text( + blur ? 'Remove blur' : 'Apply blur', + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ), + ), + ), + Positioned( + bottom: MediaQuery.paddingOf(context).bottom, + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + AdaptiveTextButton( + child: Text(context.l10n.cancel), + onPressed: () => Navigator.pop(context, null), + ), + AdaptiveTextButton( + child: Text(context.l10n.accept), + onPressed: () async { + final directory = await getApplicationDocumentsDirectory(); + final ext = extension(widget.image.path); + final targetPath = '${directory.path}/custom-board-background$ext'; + await FileImage(File(targetPath)).evict(); + await File(widget.image.path).copy(targetPath); + if (context.mounted) { + return Navigator.pop( + context, + BoardBackgroundImage( + path: targetPath, + transform: _transformationMatrix, + isBlurred: blur, + darkColors: widget.darkColorScheme, + meanLuminance: widget.meanLuminance, + ), + ); + } + }, + ), + ], + ), + ), + ], + ); + }, + ), + ), + ); + } +} + +// -- +// code below taken from: https://github.com/flutter/flutter/blob/74669e4bf1352a5134ad68398a6bf7fac0a6473b/packages/flutter/lib/src/material/color_scheme.dart + +Future _extractColorsFromImageProvider(ImageProvider imageProvider) async { + final ui.Image scaledImage = await _imageProviderToScaled(imageProvider); + final ByteData? imageBytes = await scaledImage.toByteData(); + + final QuantizerResult quantizerResult = await QuantizerCelebi().quantize( + imageBytes!.buffer.asUint32List(), + 128, + returnInputPixelToClusterPixel: true, + ); + return quantizerResult; +} + +// Scale image size down to reduce computation time of color extraction. +Future _imageProviderToScaled(ImageProvider imageProvider) async { + const double maxDimension = 112.0; + final ImageStream stream = imageProvider.resolve( + const ImageConfiguration(size: Size(maxDimension, maxDimension)), + ); + final Completer imageCompleter = Completer(); + late ImageStreamListener listener; + late ui.Image scaledImage; + Timer? loadFailureTimeout; + + listener = ImageStreamListener( + (ImageInfo info, bool sync) async { + loadFailureTimeout?.cancel(); + stream.removeListener(listener); + final ui.Image image = info.image; + final int width = image.width; + final int height = image.height; + double paintWidth = width.toDouble(); + double paintHeight = height.toDouble(); + assert(width > 0 && height > 0); + + final bool rescale = width > maxDimension || height > maxDimension; + if (rescale) { + 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); + paintImage( + canvas: canvas, + rect: Rect.fromLTRB(0, 0, paintWidth, paintHeight), + image: image, + filterQuality: FilterQuality.none, + ); + + final ui.Picture picture = pictureRecorder.endRecording(); + scaledImage = await picture.toImage(paintWidth.toInt(), paintHeight.toInt()); + imageCompleter.complete(info.image); + }, + onError: (Object exception, StackTrace? stackTrace) { + stream.removeListener(listener); + throw Exception('Failed to render image: $exception'); + }, + ); + + loadFailureTimeout = Timer(const Duration(seconds: 5), () { + stream.removeListener(listener); + imageCompleter.completeError(TimeoutException('Timeout occurred trying to load image')); + }); + + stream.addListener(listener); + await imageCompleter.future; + return scaledImage; +} + +// Converts AABBGGRR color int to AARRGGBB format. +int _getArgbFromAbgr(int abgr) { + const int exceptRMask = 0xFF00FFFF; + const int onlyRMask = ~exceptRMask; + const int exceptBMask = 0xFFFFFF00; + const int onlyBMask = ~exceptBMask; + final int r = (abgr & onlyRMask) >> 16; + final int b = abgr & onlyBMask; + return (abgr & exceptRMask & exceptBMask) | (b << 16) | r; +} diff --git a/lib/src/view/settings/board_background_theme_screen.dart b/lib/src/view/settings/board_background_theme_screen.dart deleted file mode 100644 index c73f41566c..0000000000 --- a/lib/src/view/settings/board_background_theme_screen.dart +++ /dev/null @@ -1,385 +0,0 @@ -import 'dart:io'; -import 'dart:ui' show ImageFilter; - -import 'package:chessground/chessground.dart'; -import 'package:dartchess/dartchess.dart' show Side, kInitialFEN; -import 'package:flex_color_scheme/flex_color_scheme.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; -import 'package:lichess_mobile/src/styles/lichess_icons.dart'; -import 'package:lichess_mobile/src/styles/styles.dart'; -import 'package:lichess_mobile/src/utils/l10n_context.dart'; -import 'package:lichess_mobile/src/widgets/board_theme.dart' as wrapper; -import 'package:lichess_mobile/src/widgets/buttons.dart'; -import 'package:lichess_mobile/src/widgets/list.dart'; -import 'package:lichess_mobile/src/widgets/platform.dart'; -import 'package:path_provider/path_provider.dart'; - -class BoardBackgroundThemeScreen extends StatelessWidget { - const BoardBackgroundThemeScreen({super.key}); - - @override - Widget build(BuildContext context) { - return PlatformWidget(androidBuilder: _androidBuilder, iosBuilder: _iosBuilder); - } - - Widget _androidBuilder(BuildContext context) { - return Scaffold(appBar: AppBar(title: Text(context.l10n.background)), body: _Body()); - } - - Widget _iosBuilder(BuildContext context) { - return CupertinoPageScaffold(navigationBar: const CupertinoNavigationBar(), child: _Body()); - } -} - -const colorChoices = BoardBackgroundTheme.values; - -class _Body extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - final boardPrefs = ref.watch(boardPreferencesProvider); - - void onChanged(BoardBackgroundTheme? value) => - ref.read(boardPreferencesProvider.notifier).setBackgroundTheme(value); - - final brightness = Theme.of(context).brightness; - - const itemsByRow = 4; - - final viewport = MediaQuery.sizeOf(context); - final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); - - return ListView( - children: [ - ListSection( - children: [ - ListTile( - leading: const Icon(Icons.image_outlined), - title: const Text('Pick an image'), - trailing: - Theme.of(context).platform == TargetPlatform.iOS - ? const CupertinoListTileChevron() - : null, - onTap: () async { - final ImagePicker picker = ImagePicker(); - final XFile? image = await picker.pickImage( - source: ImageSource.gallery, - maxWidth: viewport.width * devicePixelRatio * 2, - maxHeight: viewport.height * devicePixelRatio * 2, - imageQuality: 80, - ); - - if (context.mounted && image != null) { - Navigator.of(context, rootNavigator: true) - .push( - MaterialPageRoute( - builder: - (_) => ConfirmImageBackgroundScreen( - boardPrefs: boardPrefs, - image: image, - ), - fullscreenDialog: true, - ), - ) - .then((value) { - if (context.mounted && value != null) { - ref.read(boardPreferencesProvider.notifier).setBackgroundImage(value); - Navigator.pop(context); - } - }); - } - }, - ), - ], - ), - - GridView.builder( - primary: false, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.all(16.0), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: itemsByRow, - crossAxisSpacing: 6.0, - mainAxisSpacing: 6.0, - childAspectRatio: 0.5, - ), - itemBuilder: (context, index) { - final t = colorChoices[index]; - final fsd = t.getFlexScheme(boardPrefs.boardTheme); - - final theme = - brightness == Brightness.light - ? FlexThemeData.light( - colors: fsd.light, - surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: 16, - ) - : FlexThemeData.dark( - colors: fsd.dark, - surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: 20, - ); - - final autoColor = - brightness == Brightness.light - ? darken(theme.scaffoldBackgroundColor, 0.2) - : lighten(theme.scaffoldBackgroundColor, 0.2); - - return Tooltip( - message: 'Background based on chessboard colors.', - triggerMode: t == BoardBackgroundTheme.board ? null : TooltipTriggerMode.manual, - child: GestureDetector( - onTap: - () => Navigator.of(context, rootNavigator: true) - .push( - MaterialPageRoute( - builder: - (_) => ConfirmColorBackgroundScreen( - boardPrefs: boardPrefs, - initialIndex: index, - ), - fullscreenDialog: true, - ), - ) - .then((value) { - if (context.mounted) { - if (value != null) { - onChanged(colorChoices[value.toInt()]); - Navigator.pop(context); - } - } - }), - child: SizedBox.expand( - child: ColoredBox( - color: theme.scaffoldBackgroundColor, - child: - t == BoardBackgroundTheme.board - ? Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(LichessIcons.chess_board, color: autoColor), - const SizedBox(height: 8), - Center( - child: Text( - 'auto', - style: theme.textTheme.labelSmall?.copyWith(color: autoColor), - ), - ), - ], - ) - : null, - ), - ), - ), - ); - }, - itemCount: colorChoices.length, - ), - ], - ); - } -} - -class ConfirmColorBackgroundScreen extends StatefulWidget { - const ConfirmColorBackgroundScreen({ - required this.initialIndex, - required this.boardPrefs, - super.key, - }); - - final int initialIndex; - final BoardPrefs boardPrefs; - - @override - State createState() => _ConfirmBackgroundScreenState(); -} - -class _ConfirmBackgroundScreenState extends State { - late PageController _controller; - - @override - void initState() { - _controller = PageController(initialPage: widget.initialIndex); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Stack( - children: [ - PageView.builder( - controller: _controller, - itemBuilder: (context, index) { - final backgroundTheme = colorChoices[index]; - return wrapper.BoardBackgroundThemeWidget( - backgroundTheme: backgroundTheme, - child: const Scaffold(body: SizedBox.expand()), - ); - }, - itemCount: colorChoices.length, - ), - Positioned.fill( - child: Align( - alignment: Alignment.center, - child: Chessboard.fixed( - size: MediaQuery.sizeOf(context).width, - fen: kInitialFEN, - orientation: Side.white, - settings: widget.boardPrefs.toBoardSettings(), - ), - ), - ), - Positioned( - bottom: MediaQuery.paddingOf(context).bottom + 16.0, - left: 0, - right: 0, - child: const Text('Swipe to display other backgrounds', textAlign: TextAlign.center), - ), - ], - ), - persistentFooterButtons: [ - AdaptiveTextButton( - child: Text(context.l10n.cancel), - onPressed: () => Navigator.pop(context, null), - ), - AdaptiveTextButton( - child: Text(context.l10n.accept), - onPressed: () => Navigator.pop(context, _controller.page), - ), - ], - ); - } -} - -class ConfirmImageBackgroundScreen extends StatefulWidget { - const ConfirmImageBackgroundScreen({required this.image, required this.boardPrefs, super.key}); - - final XFile image; - final BoardPrefs boardPrefs; - - @override - State createState() => _ConfirmImageBackgroundScreenState(); -} - -class _ConfirmImageBackgroundScreenState extends State { - bool blur = false; - - final _controller = TransformationController(); - Matrix4 _transformationMatrix = Matrix4.identity(); - - @override - void initState() { - super.initState(); - - _controller.addListener(() { - _transformationMatrix = _controller.value; - }); - } - - @override - void dispose() { - super.dispose(); - _controller.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: LayoutBuilder( - builder: (context, constraints) { - return Stack( - children: [ - InteractiveViewer( - transformationController: _controller, - constrained: false, - minScale: 1, - maxScale: 2, - child: Container( - width: constraints.maxWidth, - height: constraints.maxHeight, - decoration: BoxDecoration( - image: DecorationImage( - image: Image.file(File(widget.image.path)).image, - fit: BoxFit.cover, - ), - ), - child: BackdropFilter( - enabled: blur, - filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), - child: const SizedBox.expand(), - ), - ), - ), - Positioned.fill( - child: Align( - alignment: Alignment.center, - child: IgnorePointer( - child: Chessboard.fixed( - size: MediaQuery.sizeOf(context).width, - fen: kInitialFEN, - orientation: Side.white, - settings: widget.boardPrefs.toBoardSettings(), - ), - ), - ), - ), - Positioned( - bottom: MediaQuery.paddingOf(context).bottom + 16.0, - left: 0, - right: 0, - child: Center( - child: PlatformCard( - child: AdaptiveInkWell( - onTap: () { - setState(() { - blur = !blur; - }); - }, - borderRadius: const BorderRadius.all(Radius.circular(10)), - child: const Padding( - padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), - child: Text('Blur image', textAlign: TextAlign.center), - ), - ), - ), - ), - ), - ], - ); - }, - ), - persistentFooterButtons: [ - AdaptiveTextButton( - child: Text(context.l10n.cancel), - onPressed: () => Navigator.pop(context, null), - ), - AdaptiveTextButton( - child: Text(context.l10n.accept), - onPressed: () async { - final directory = await getApplicationDocumentsDirectory(); - final targetPath = '${directory.path}/custom-board-background.jpg'; - final darkScheme = await ColorScheme.fromImageProvider( - provider: FileImage(File(widget.image.path)), - brightness: Brightness.dark, - ); - await FileImage(File(targetPath)).evict(); - await File(widget.image.path).copy(targetPath); - if (context.mounted) { - return Navigator.pop(context, ( - path: targetPath, - transform: _transformationMatrix, - isBlurred: blur, - darkColors: darkScheme, - )); - } - }, - ), - ], - ); - } -} diff --git a/lib/src/view/settings/board_theme_screen.dart b/lib/src/view/settings/board_theme_screen.dart index 50034c331b..c2787f811a 100644 --- a/lib/src/view/settings/board_theme_screen.dart +++ b/lib/src/view/settings/board_theme_screen.dart @@ -12,11 +12,11 @@ import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/navigation.dart'; import 'package:lichess_mobile/src/utils/screen.dart'; -import 'package:lichess_mobile/src/view/settings/board_background_theme_screen.dart'; +import 'package:lichess_mobile/src/view/settings/board_background_theme_choice_screen.dart'; import 'package:lichess_mobile/src/view/settings/board_choice_screen.dart'; import 'package:lichess_mobile/src/view/settings/piece_set_screen.dart'; import 'package:lichess_mobile/src/widgets/adaptive_choice_picker.dart'; -import 'package:lichess_mobile/src/widgets/board_theme.dart'; +import 'package:lichess_mobile/src/widgets/board_background_theme.dart'; import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; import 'package:lichess_mobile/src/widgets/settings.dart'; @@ -26,9 +26,7 @@ class BoardThemeScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final boardPrefs = ref.watch(boardPreferencesProvider); return BoardBackgroundThemeWidget( - backgroundTheme: boardPrefs.backgroundTheme, child: PlatformWidget( androidBuilder: (context) => const Scaffold(body: _Body()), iosBuilder: @@ -194,7 +192,7 @@ class _BodyState extends ConsumerState<_Body> { pushPlatformRoute( context, title: context.l10n.background, - builder: (context) => const BoardBackgroundThemeScreen(), + builder: (context) => const BoardBackgroundThemeChoiceScreen(), ); }, ), @@ -203,12 +201,9 @@ class _BodyState extends ConsumerState<_Body> { leading: const Icon(Icons.cancel), title: const Text('Reset background'), onTap: () { - if (ref.read(boardPreferencesProvider).backgroundTheme != null) { - ref.read(boardPreferencesProvider.notifier).setBackgroundTheme(null); - } - if (ref.read(boardPreferencesProvider).backgroundImage != null) { - ref.read(boardPreferencesProvider.notifier).setBackgroundImage(null); - } + ref + .read(boardPreferencesProvider.notifier) + .setBackground(backgroundTheme: null, backgroundImage: null); }, ), SettingsListTile( diff --git a/lib/src/widgets/board_theme.dart b/lib/src/widgets/board_background_theme.dart similarity index 58% rename from lib/src/widgets/board_theme.dart rename to lib/src/widgets/board_background_theme.dart index 120f2fc16c..eda968b6c7 100644 --- a/lib/src/widgets/board_theme.dart +++ b/lib/src/widgets/board_background_theme.dart @@ -10,34 +10,39 @@ import 'package:lichess_mobile/src/styles/styles.dart'; /// Applies the configured board theme to the child widget. /// +/// Tries first to apply the theme provided ar argument, and then from the stored settings. +/// /// Typically used in screens that need to display a chess board. class BoardBackgroundThemeWidget extends ConsumerWidget { - const BoardBackgroundThemeWidget({ - required this.child, - this.backgroundImage, - this.backgroundTheme, - super.key, - }); + const BoardBackgroundThemeWidget({required this.child, this.backgroundTheme, super.key}); + /// The child widget to apply the theme to. final Widget child; - final BoardBackgroundImage? backgroundImage; + + /// The background theme to apply to the child. If null, the theme from the stored settings will be used. final BoardBackgroundTheme? backgroundTheme; @override Widget build(BuildContext context, WidgetRef ref) { final boardPrefs = ref.watch(boardPreferencesProvider); - final effectiveBackgroundTheme = backgroundTheme ?? boardPrefs.backgroundTheme; - final effectiveBackgroundImage = backgroundImage ?? boardPrefs.backgroundImage; - if (effectiveBackgroundTheme == null && effectiveBackgroundImage == null) { + if (backgroundTheme == null && + boardPrefs.backgroundTheme == null && + boardPrefs.backgroundImage == null) { return child; } - if (effectiveBackgroundImage != null) { - return _BoardBackgroundImage(backgroundImage: effectiveBackgroundImage, child: child); + if (backgroundTheme != null) { + return _BoardBackgroundTheme( + backgroundTheme: backgroundTheme!, + boardTheme: boardPrefs.boardTheme, + child: child, + ); + } else if (boardPrefs.backgroundImage != null) { + return _BoardBackgroundImage(backgroundImage: boardPrefs.backgroundImage!, child: child); } else { return _BoardBackgroundTheme( - backgroundTheme: effectiveBackgroundTheme!, + backgroundTheme: boardPrefs.backgroundTheme!, boardTheme: boardPrefs.boardTheme, child: child, ); @@ -45,6 +50,67 @@ class BoardBackgroundThemeWidget extends ConsumerWidget { } } +/// Applies the configured board theme to the child widget. +class BoardThemeWrapper extends StatelessWidget { + const BoardThemeWrapper({ + required this.child, + required this.theme, + required this.brightness, + this.transparentScaffold = false, + }); + + /// The child widget to apply the theme to. + final Widget child; + + /// The theme to apply to the child. + final ThemeData theme; + + /// The brightness of the theme. + final Brightness brightness; + + /// If true, the scaffold background will be transparent. Useful for displaying a background image. + final bool transparentScaffold; + + @override + Widget build(BuildContext context) { + final isIOS = Theme.of(context).platform == TargetPlatform.iOS; + final cupertinoTheme = _makeCupertinoTheme( + theme, + brightness: brightness, + transparentScaffold: transparentScaffold, + ); + + return Theme( + data: theme.copyWith( + cupertinoOverrideTheme: cupertinoTheme, + listTileTheme: ListTileTheme.of(context).copyWith( + tileColor: theme.colorScheme.surfaceContainerLow.withValues( + alpha: transparentScaffold ? 0.5 : 1, + ), + selectedTileColor: theme.colorScheme.surfaceContainer.withValues( + alpha: transparentScaffold ? 0.5 : 1, + ), + titleTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, + subtitleTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, + leadingAndTrailingTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, + ), + scaffoldBackgroundColor: theme.scaffoldBackgroundColor.withValues( + alpha: transparentScaffold ? 0 : 1, + ), + splashFactory: isIOS ? NoSplash.splashFactory : null, + textTheme: + isIOS + ? brightness == Brightness.light + ? Typography.blackCupertino + : Typography.whiteCupertino + : null, + extensions: [lichessCustomColors.harmonized(theme.colorScheme)], + ), + child: isIOS ? CupertinoTheme(data: cupertinoTheme, child: child) : child, + ); + } +} + class _BoardBackgroundTheme extends StatelessWidget { const _BoardBackgroundTheme({ required this.backgroundTheme, @@ -68,137 +134,85 @@ class _BoardBackgroundTheme extends StatelessWidget { appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, blendLevel: 16, ); - final darkTheme = - FlexColorScheme.dark( - colors: flexScheme.dark, - surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: 20, - cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), - appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, - ).toTheme; + final darkTheme = FlexThemeData.dark( + colors: flexScheme.dark, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + blendLevel: 20, + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, + ); final brightness = Theme.of(context).brightness; - final theme = brightness == Brightness.light ? lightTheme : darkTheme; - final cupertinoTheme = _makeCupertinoTheme(theme, brightness: brightness); - return _ThemeWrapper( - isIOS: isIOS, - theme: theme, - cupertinoTheme: cupertinoTheme, - brightness: brightness, - child: child, - ); + return BoardThemeWrapper(theme: theme, brightness: brightness, child: child); } } -class _BoardBackgroundImage extends StatelessWidget { +class _BoardBackgroundImage extends StatefulWidget { const _BoardBackgroundImage({required this.backgroundImage, required this.child}); final BoardBackgroundImage backgroundImage; final Widget child; @override - Widget build(BuildContext context) { - final isIOS = Theme.of(context).platform == TargetPlatform.iOS; - - final theme = FlexThemeData.dark( - colors: FlexSchemeColor( - primary: backgroundImage.darkColors.primary, - primaryContainer: backgroundImage.darkColors.primaryContainer, - secondary: backgroundImage.darkColors.secondary, - secondaryContainer: backgroundImage.darkColors.secondaryContainer, - tertiary: backgroundImage.darkColors.tertiary, - tertiaryContainer: backgroundImage.darkColors.tertiaryContainer, - error: backgroundImage.darkColors.error, - errorContainer: backgroundImage.darkColors.errorContainer, - ), - cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), - appBarOpacity: 0, - ); + State<_BoardBackgroundImage> createState() => _BoardBackgroundImageState(); +} - final cupertinoTheme = _makeCupertinoTheme( - theme, - brightness: Brightness.dark, - transparentScaffold: true, - ); +class _BoardBackgroundImageState extends State<_BoardBackgroundImage> { + late TransformationController _controller; - final content = Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - image: DecorationImage( - image: FileImage(File(backgroundImage.path)), - fit: BoxFit.cover, - colorFilter: const ColorFilter.mode(Color(0x44000000), BlendMode.srcOver), - ), - ), - child: ClipRect( - child: BackdropFilter( - enabled: backgroundImage.isBlurred, - filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), - child: child, - ), - ), - ); - - return _ThemeWrapper( - isIOS: isIOS, - theme: theme, - cupertinoTheme: cupertinoTheme, - brightness: Brightness.dark, - transparentScaffold: true, - child: content, - ); + @override + void initState() { + _controller = TransformationController(widget.backgroundImage.transform); + super.initState(); } -} - -class _ThemeWrapper extends StatelessWidget { - const _ThemeWrapper({ - required this.child, - required this.isIOS, - required this.theme, - required this.cupertinoTheme, - required this.brightness, - this.transparentScaffold = false, - }); - final Widget child; - final bool isIOS; - final ThemeData theme; - final CupertinoThemeData cupertinoTheme; - final Brightness brightness; - final bool transparentScaffold; + @override + void dispose() { + super.dispose(); + _controller.dispose(); + } @override Widget build(BuildContext context) { - return Theme( - data: theme.copyWith( - cupertinoOverrideTheme: cupertinoTheme, - listTileTheme: ListTileTheme.of(context).copyWith( - tileColor: theme.colorScheme.surfaceContainerLow.withValues( - alpha: transparentScaffold ? 0.5 : 1, - ), - selectedTileColor: theme.colorScheme.surfaceContainer.withValues( - alpha: transparentScaffold ? 0.5 : 1, - ), - titleTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, - subtitleTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, - leadingAndTrailingTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, - ), - scaffoldBackgroundColor: theme.scaffoldBackgroundColor.withValues( - alpha: transparentScaffold ? 0 : 1, - ), - splashFactory: isIOS ? NoSplash.splashFactory : null, - textTheme: - isIOS - ? brightness == Brightness.light - ? Typography.blackCupertino - : Typography.whiteCupertino - : null, - extensions: [lichessCustomColors.harmonized(theme.colorScheme)], + return BoardThemeWrapper( + theme: widget.backgroundImage.theme, + brightness: Brightness.dark, + transparentScaffold: true, + child: LayoutBuilder( + builder: (context, constraints) { + return InteractiveViewer( + transformationController: _controller, + constrained: false, + minScale: 1, + maxScale: 2, + panEnabled: false, + scaleEnabled: false, + child: Container( + width: constraints.minWidth, + height: constraints.maxHeight, + decoration: BoxDecoration( + image: DecorationImage( + image: FileImage(File(widget.backgroundImage.path)), + fit: BoxFit.cover, + colorFilter: ColorFilter.mode( + widget.backgroundImage.filterColor, + BlendMode.srcOver, + ), + ), + ), + child: ClipRect( + child: BackdropFilter( + enabled: widget.backgroundImage.isBlurred, + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: widget.child, + ), + ), + ), + ); + }, ), - child: isIOS ? CupertinoTheme(data: cupertinoTheme, child: child) : child, ); } } @@ -208,12 +222,14 @@ CupertinoThemeData _makeCupertinoTheme( Brightness brightness = Brightness.light, bool transparentScaffold = false, }) { + final primary = theme.colorScheme.primary; + final onPrimary = theme.colorScheme.onPrimary; return CupertinoThemeData( - primaryColor: theme.colorScheme.primary, - primaryContrastingColor: theme.colorScheme.onPrimary, + primaryColor: primary, + primaryContrastingColor: onPrimary, brightness: brightness, textTheme: const CupertinoThemeData().textTheme.copyWith( - primaryColor: theme.colorScheme.primary, + primaryColor: primary, textStyle: const CupertinoThemeData().textTheme.textStyle.copyWith( color: theme.colorScheme.onSurface, ), diff --git a/lib/src/widgets/cupertino.dart b/lib/src/widgets/cupertino.dart new file mode 100644 index 0000000000..5ac94cb146 --- /dev/null +++ b/lib/src/widgets/cupertino.dart @@ -0,0 +1,17 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// Wraps a widget in a [Material] widget with a default text color for icons. +class CupertinoMaterialWrapper extends StatelessWidget { + const CupertinoMaterialWrapper({required this.child, super.key}); + + final Widget child; + + @override + Widget build(BuildContext context) { + return IconTheme.merge( + data: IconThemeData(color: CupertinoTheme.of(context).textTheme.textStyle.color), + child: Material(color: Colors.transparent, child: child), + ); + } +} diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index 24fbfbf0f5..55c9e0306b 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -200,9 +200,7 @@ class ListSection extends StatelessWidget { ), CupertinoListSection.insetGrouped( clipBehavior: cupertinoClipBehavior, - backgroundColor: - cupertinoBackgroundColor ?? - CupertinoTheme.of(context).scaffoldBackgroundColor, + backgroundColor: CupertinoTheme.of(context).scaffoldBackgroundColor, decoration: BoxDecoration( color: cupertinoBackgroundColor ?? diff --git a/lib/src/widgets/platform_scaffold.dart b/lib/src/widgets/platform_scaffold.dart index 0eee8f937e..1242b390d2 100644 --- a/lib/src/widgets/platform_scaffold.dart +++ b/lib/src/widgets/platform_scaffold.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:lichess_mobile/src/widgets/board_theme.dart'; +import 'package:lichess_mobile/src/widgets/board_background_theme.dart'; +import 'package:lichess_mobile/src/widgets/cupertino.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; const kCupertinoAppBarWithActionPadding = EdgeInsetsDirectional.only(start: 16.0, end: 8.0); @@ -141,8 +142,7 @@ class PlatformScaffold extends StatelessWidget { } Widget _iosBuilder(BuildContext context) { - return Material( - color: Colors.transparent, + return CupertinoMaterialWrapper( child: CupertinoPageScaffold( resizeToAvoidBottomInset: resizeToAvoidBottomInset, navigationBar: appBar != null ? _CupertinoNavBarWrapper(child: appBar!) : null, From bda8235412ac8be227308341d68667d03a84e48d Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 27 Jan 2025 19:34:51 +0100 Subject: [PATCH 26/41] More work on custom background theme --- android/.gitignore | 1 + lib/src/app.dart | 2 +- lib/src/model/settings/board_preferences.dart | 50 ++++++++-------- lib/src/view/puzzle/storm_screen.dart | 50 +++++----------- lib/src/view/puzzle/streak_screen.dart | 2 +- .../board_background_theme_choice_screen.dart | 17 +++--- lib/src/widgets/board_background_theme.dart | 59 ++++++++++--------- 7 files changed, 83 insertions(+), 98 deletions(-) diff --git a/android/.gitignore b/android/.gitignore index 6f568019d3..3989f02545 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -11,3 +11,4 @@ GeneratedPluginRegistrant.java key.properties **/*.keystore **/*.jks +**/.cxx diff --git a/lib/src/app.dart b/lib/src/app.dart index d32f4ded62..54fa53dc10 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -201,7 +201,7 @@ class _AppState extends ConsumerState { final highBlendThemeLight = FlexThemeData.light( colors: flexSchemeLightColors, surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold, - blendLevel: 20, + blendLevel: 16, ); return AnnotatedRegion( diff --git a/lib/src/model/settings/board_preferences.dart b/lib/src/model/settings/board_preferences.dart index bfc5957881..dca344fbb3 100644 --- a/lib/src/model/settings/board_preferences.dart +++ b/lib/src/model/settings/board_preferences.dart @@ -522,30 +522,32 @@ String dragTargetKindLabel(DragTargetKind kind) => switch (kind) { enum BoardBackgroundTheme { /// The background theme is based on the chess board - board, - - /// Below values from [FlexScheme] - redWine, - yellowM3, - pinkM3, - purpleM3, - // indigoM3, - blueM3, - // tealM3, - greenM3, - aquaBlue, - jungle, - orangeM3, - // deepOrangeM3, - // mango, - purpleBrown, - sepia; - - static final _flexSchemesNameMap = FlexScheme.values.asNameMap(); - - String label(AppLocalizations l10n) => - this == BoardBackgroundTheme.board ? l10n.board : _flexSchemesNameMap[name]!.data.name; + board(20, 30), + blue(30, 36, FlexScheme.blue, 'Blue'), + indigo(30, 36, FlexScheme.indigo, 'indigo'), + green(30, 36, FlexScheme.jungle, 'Green'), + brown(30, 36, FlexScheme.purpleBrown, 'Brown'), + gold(30, 36, FlexScheme.gold, 'Gold'), + red(36, 36, FlexScheme.redWine, 'Red'), + purple(30, 36, FlexScheme.purpleM3, 'Purple'), + sepia(28, 36, FlexScheme.sepia, 'Sepia'), + + dimRed(16, 20, FlexScheme.redWine, 'Dim Red'), + dimPurple(16, 20, FlexScheme.purpleM3, 'Dim Purple'), + dimIndigo(16, 20, FlexScheme.indigo, 'Dim indigo'), + dimGold(16, 20, FlexScheme.gold, 'Dim Gold'), + dimBlue(16, 20, FlexScheme.blue, 'Dim Blue'), + dimGreen(16, 20, FlexScheme.greenM3, 'Dim Green'); + + final int lightBlend; + final int darkBlend; + final FlexScheme? scheme; + final String? _label; + + const BoardBackgroundTheme(this.lightBlend, this.darkBlend, [this.scheme, this._label]); + + String label(AppLocalizations l10n) => this == BoardBackgroundTheme.board ? l10n.board : _label!; FlexSchemeData getFlexScheme(BoardTheme boardTheme) => - this == BoardBackgroundTheme.board ? boardTheme.flexScheme : _flexSchemesNameMap[name]!.data; + this == BoardBackgroundTheme.board ? boardTheme.flexScheme : scheme!.data; } diff --git a/lib/src/view/puzzle/storm_screen.dart b/lib/src/view/puzzle/storm_screen.dart index bb4eb3998a..b6d6614bcf 100644 --- a/lib/src/view/puzzle/storm_screen.dart +++ b/lib/src/view/puzzle/storm_screen.dart @@ -12,7 +12,6 @@ import 'package:lichess_mobile/src/model/puzzle/puzzle_repository.dart'; import 'package:lichess_mobile/src/model/puzzle/storm.dart'; import 'package:lichess_mobile/src/model/puzzle/storm_controller.dart'; import 'package:lichess_mobile/src/model/settings/board_preferences.dart'; -import 'package:lichess_mobile/src/model/settings/brightness.dart'; import 'package:lichess_mobile/src/styles/lichess_icons.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/gestures_exclusion.dart'; @@ -296,14 +295,14 @@ class _TopTable extends ConsumerWidget { ), ) else ...[ - Icon(LichessIcons.storm, size: 50.0, color: context.lichessColors.brag), + Icon(LichessIcons.storm, size: 50.0, color: ColorScheme.of(context).primary), const SizedBox(width: 8), Text( stormState.numSolved.toString(), style: TextStyle( fontSize: 30.0, fontWeight: FontWeight.bold, - color: context.lichessColors.brag, + color: ColorScheme.of(context).primary, ), ), const Spacer(), @@ -372,8 +371,8 @@ class _ComboState extends ConsumerState<_Combo> with SingleTickerProviderStateMi final indicatorColor = Theme.of(context).colorScheme.secondary; final comboShades = generateShades( - indicatorColor, - ref.watch(currentBrightnessProvider) == Brightness.light, + ColorScheme.of(context).secondary, + Theme.of(context).brightness, ); return AnimatedBuilder( animation: _controller, @@ -487,36 +486,17 @@ class _ComboState extends ConsumerState<_Combo> with SingleTickerProviderStateMi ); } - List generateShades(Color baseColor, bool light) { - final shades = []; - - final double r = baseColor.r; - final double g = baseColor.g; - final double b = baseColor.b; - - const int step = 20; - - // Generate darker shades - for (int i = 4; i >= 2; i = i - 2) { - 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)); - } - - // Generate lighter shades - for (int i = 2; i <= 3; i++) { - 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)); - } - - if (light) { - return shades.reversed.toList(); - } - - return shades; + List generateShades(Color baseColor, Brightness brightness) { + return List.generate(4, (index) { + final shade = switch (index) { + 0 => 0.1, + 1 => 0.3, + 2 => 0.5, + 3 => 0.7, + _ => 0.0, + }; + return brightness == Brightness.light ? darken(baseColor, shade) : lighten(baseColor, shade); + }); } } diff --git a/lib/src/view/puzzle/streak_screen.dart b/lib/src/view/puzzle/streak_screen.dart index 17313dcd2a..7f8e1d1493 100644 --- a/lib/src/view/puzzle/streak_screen.dart +++ b/lib/src/view/puzzle/streak_screen.dart @@ -37,7 +37,7 @@ class StreakScreen extends StatelessWidget { @override Widget build(BuildContext context) { return const WakelockWidget( - child: PlatformScaffold( + child: PlatformBoardThemeScaffold( appBar: PlatformAppBar(actions: [ToggleSoundButton()], title: Text('Puzzle Streak')), body: _Load(), ), diff --git a/lib/src/view/settings/board_background_theme_choice_screen.dart b/lib/src/view/settings/board_background_theme_choice_screen.dart index 520c25c384..00d8993d34 100644 --- a/lib/src/view/settings/board_background_theme_choice_screen.dart +++ b/lib/src/view/settings/board_background_theme_choice_screen.dart @@ -60,7 +60,6 @@ class _Body extends ConsumerWidget { PlatformListTile( leading: const Icon(Icons.image_outlined), title: const Text('Pick an image'), - subtitle: const Text('Choose a dark image for best result.'), trailing: Theme.of(context).platform == TargetPlatform.iOS ? const CupertinoListTileChevron() @@ -147,12 +146,12 @@ class _Body extends ConsumerWidget { ? FlexThemeData.light( colors: fsd.light, surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: 16, + blendLevel: t.lightBlend, ) : FlexThemeData.dark( colors: fsd.dark, surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: 20, + blendLevel: t.darkBlend, ); final autoColor = @@ -365,7 +364,7 @@ class _ConfirmImageBackgroundScreenState extends State { transparentScaffold: true, child: LayoutBuilder( builder: (context, constraints) { - return InteractiveViewer( - transformationController: _controller, - constrained: false, - minScale: 1, - maxScale: 2, - panEnabled: false, - scaleEnabled: false, - child: Container( - width: constraints.minWidth, - height: constraints.maxHeight, - decoration: BoxDecoration( - image: DecorationImage( - image: FileImage(File(widget.backgroundImage.path)), - fit: BoxFit.cover, - colorFilter: ColorFilter.mode( - widget.backgroundImage.filterColor, - BlendMode.srcOver, + return Stack( + children: [ + InteractiveViewer( + transformationController: _controller, + constrained: false, + minScale: 1, + maxScale: 2, + panEnabled: false, + scaleEnabled: false, + child: Container( + width: constraints.minWidth, + height: constraints.maxHeight, + decoration: BoxDecoration( + image: DecorationImage( + image: FileImage(File(widget.backgroundImage.path)), + fit: BoxFit.cover, + colorFilter: ColorFilter.mode( + widget.backgroundImage.filterColor, + BlendMode.srcOver, + ), + ), + ), + child: ClipRect( + child: BackdropFilter( + enabled: widget.backgroundImage.isBlurred, + filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0), + child: const SizedBox.expand(), + ), ), ), ), - child: ClipRect( - child: BackdropFilter( - enabled: widget.backgroundImage.isBlurred, - filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), - child: widget.child, - ), - ), - ), + Positioned.fill(child: widget.child), + ], ); }, ), From c5595e9a2cc88e09af3ebc7779fc29ee9f1e4e5f Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Tue, 28 Jan 2025 10:01:35 +0100 Subject: [PATCH 27/41] Fix shimmer for transp scaffolds --- lib/src/widgets/shimmer.dart | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/src/widgets/shimmer.dart b/lib/src/widgets/shimmer.dart index ee5bca2610..6403d742df 100644 --- a/lib/src/widgets/shimmer.dart +++ b/lib/src/widgets/shimmer.dart @@ -1,3 +1,5 @@ +import 'dart:math' show max; + import 'package:flutter/material.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; @@ -20,13 +22,17 @@ class ShimmerState extends State with SingleTickerProviderStateMixin { LinearGradient get _defaultGradient { final brightness = Theme.of(context).brightness; final scaffoldBackgroundColor = Theme.of(context).scaffoldBackgroundColor; + final scaffoldOpacity = scaffoldBackgroundColor.a; + final effectiveScaffoldBackgroundColor = scaffoldBackgroundColor.withValues( + alpha: max(0.2, scaffoldOpacity), + ); switch (brightness) { - case Brightness.light: + case Brightness.light when scaffoldOpacity > 0: return LinearGradient( colors: [ - darken(scaffoldBackgroundColor, 0.05), - darken(scaffoldBackgroundColor, 0.1), - darken(scaffoldBackgroundColor, 0.2), + darken(effectiveScaffoldBackgroundColor, 0.05), + darken(effectiveScaffoldBackgroundColor, 0.1), + darken(effectiveScaffoldBackgroundColor, 0.2), ], stops: const [0.1, 0.3, 0.4], begin: const Alignment(-1.0, -0.3), @@ -34,12 +40,12 @@ class ShimmerState extends State with SingleTickerProviderStateMixin { tileMode: TileMode.clamp, ); - case Brightness.dark: + case _: return LinearGradient( colors: [ - lighten(scaffoldBackgroundColor, 0.05), - lighten(scaffoldBackgroundColor, 0.1), - lighten(scaffoldBackgroundColor, 0.2), + lighten(effectiveScaffoldBackgroundColor, 0.05), + lighten(effectiveScaffoldBackgroundColor, 0.1), + lighten(effectiveScaffoldBackgroundColor, 0.2), ], stops: const [0.1, 0.3, 0.4], begin: const Alignment(-1.0, -0.3), @@ -135,6 +141,8 @@ class _ShimmerLoadingState extends State { return widget.child; } + final scaffoldOpacity = Theme.of(context).scaffoldBackgroundColor.a; + final shimmer = Shimmer.of(context)!; if (!shimmer.isSized) { return const SizedBox(); @@ -147,7 +155,7 @@ class _ShimmerLoadingState extends State { ); return ShaderMask( - blendMode: BlendMode.srcATop, + blendMode: scaffoldOpacity == 0 ? BlendMode.modulate : BlendMode.srcATop, shaderCallback: (bounds) { return gradient.createShader( Rect.fromLTWH( From 821b5f9c69068b506c2ee123294bb2e38b6e188e Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Tue, 28 Jan 2025 11:02:22 +0100 Subject: [PATCH 28/41] Tweak clock tool colors --- lib/src/view/clock/clock_tool_screen.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/src/view/clock/clock_tool_screen.dart b/lib/src/view/clock/clock_tool_screen.dart index 55d98998a2..f1980f0a8d 100644 --- a/lib/src/view/clock/clock_tool_screen.dart +++ b/lib/src/view/clock/clock_tool_screen.dart @@ -77,19 +77,21 @@ class ClockTile extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final colorScheme = Theme.of(context).colorScheme; + final activeColor = colorScheme.primaryFixedDim; + final activeTextColor = colorScheme.onPrimaryFixed; + final pausedColor = activeColor.withValues(alpha: 0.5); final backgroundColor = clockState.isFlagged(playerType) ? colorScheme.error : !clockState.paused && clockState.isPlayersTurn(playerType) - ? colorScheme.primaryFixed + ? activeColor : clockState.activeSide == playerType - ? colorScheme.primaryFixedDim + ? pausedColor : colorScheme.surface; final clockStyle = ClockStyle( - textColor: - clockState.activeSide == playerType ? colorScheme.onPrimaryFixed : colorScheme.onSurface, - activeTextColor: colorScheme.onPrimaryFixed, + textColor: clockState.activeSide == playerType ? activeTextColor : colorScheme.onSurface, + activeTextColor: activeTextColor, emergencyTextColor: Colors.white, backgroundColor: Colors.transparent, activeBackgroundColor: Colors.transparent, From 1d51b39c74948c80cfaa906a0a2dac114be26d6e Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Tue, 28 Jan 2025 11:12:52 +0100 Subject: [PATCH 29/41] Move code to end of file --- lib/src/model/settings/board_preferences.dart | 224 +++++++++--------- 1 file changed, 112 insertions(+), 112 deletions(-) diff --git a/lib/src/model/settings/board_preferences.dart b/lib/src/model/settings/board_preferences.dart index dca344fbb3..e97736160f 100644 --- a/lib/src/model/settings/board_preferences.dart +++ b/lib/src/model/settings/board_preferences.dart @@ -115,118 +115,6 @@ class BoardPreferences extends _$BoardPreferences with PreferencesStorage - scheme.surface.withValues( - alpha: switch (meanLuminance) { - < 0.2 => 0, - < 0.4 => 0.25, - < 0.6 => 0.5, - _ => 0.8, - }, - ); - - static ThemeData getTheme(ColorScheme scheme) => FlexThemeData.dark( - colors: FlexSchemeColor( - primary: scheme.primary, - primaryContainer: scheme.primaryContainer, - secondary: scheme.secondary, - secondaryContainer: scheme.secondaryContainer, - tertiary: scheme.tertiary, - tertiaryContainer: scheme.tertiaryContainer, - error: scheme.error, - errorContainer: scheme.errorContainer, - ), - cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), - appBarOpacity: 0, - ); - - ThemeData get theme => getTheme(darkColors); - - Color get filterColor => getFilterColor(darkColors, meanLuminance); -} - -class BoardBackgroundImageConverter - implements JsonConverter?> { - const BoardBackgroundImageConverter(); - - @override - BoardBackgroundImage? fromJson(Map? json) { - if (json == null) { - return null; - } - - final darkColors = ColorScheme( - brightness: Brightness.dark, - primary: Color(json['darkPrimary'] as int), - onPrimary: Color(json['darkOnPrimary'] as int), - primaryContainer: Color(json['darkPrimaryContainer'] as int), - secondary: Color(json['darkSecondary'] as int), - onSecondary: Color(json['darkOnSecondary'] as int), - secondaryContainer: Color(json['darkSecondaryContainer'] as int), - tertiary: Color(json['darkTertiary'] as int), - tertiaryContainer: Color(json['darkTertiaryContainer'] as int), - error: Color(json['darkError'] as int), - onError: Color(json['darkOnError'] as int), - errorContainer: Color(json['darkErrorContainer'] as int), - surface: Color(json['darkSurface'] as int), - onSurface: Color(json['darkOnSurface'] as int), - ); - - final transform = json['transform'] as List; - - return BoardBackgroundImage( - path: json['path'] as String, - transform: Matrix4.fromList(transform.map((e) => (e as num).toDouble()).toList()), - isBlurred: json['isBlurred'] as bool, - darkColors: darkColors, - meanLuminance: json['meanLuminance'] as double, - ); - } - - @override - Map? toJson(BoardBackgroundImage? object) { - if (object == null) { - return null; - } - - final Map darkColors = { - 'darkPrimary': object.darkColors.primary.toARGB32(), - 'darkOnPrimary': object.darkColors.onPrimary.toARGB32(), - 'darkPrimaryContainer': object.darkColors.primaryContainer.toARGB32(), - 'darkSecondary': object.darkColors.secondary.toARGB32(), - 'darkOnSecondary': object.darkColors.onSecondary.toARGB32(), - 'darkSecondaryContainer': object.darkColors.secondaryContainer.toARGB32(), - 'darkTertiary': object.darkColors.tertiary.toARGB32(), - 'darkTertiaryContainer': object.darkColors.tertiaryContainer.toARGB32(), - 'darkError': object.darkColors.error.toARGB32(), - 'darkOnError': object.darkColors.onError.toARGB32(), - 'darkErrorContainer': object.darkColors.errorContainer.toARGB32(), - 'darkSurface': object.darkColors.surface.toARGB32(), - 'darkOnSurface': object.darkColors.onSurface.toARGB32(), - }; - - return { - 'path': object.path, - 'transform': object.transform.storage, - 'isBlurred': object.isBlurred, - ...darkColors, - 'meanLuminance': object.meanLuminance, - }; - } -} - @Freezed(fromJson: true, toJson: true) class BoardPrefs with _$BoardPrefs implements Serializable { const BoardPrefs._(); @@ -551,3 +439,115 @@ enum BoardBackgroundTheme { FlexSchemeData getFlexScheme(BoardTheme boardTheme) => this == BoardBackgroundTheme.board ? boardTheme.flexScheme : scheme!.data; } + +@freezed +class BoardBackgroundImage with _$BoardBackgroundImage { + const BoardBackgroundImage._(); + + const factory BoardBackgroundImage({ + required String path, + required Matrix4 transform, + required bool isBlurred, + required ColorScheme darkColors, + required double meanLuminance, + }) = _BoardBackgroundImage; + + static Color getFilterColor(ColorScheme scheme, double meanLuminance) => + scheme.surface.withValues( + alpha: switch (meanLuminance) { + < 0.2 => 0, + < 0.4 => 0.25, + < 0.6 => 0.5, + _ => 0.8, + }, + ); + + static ThemeData getTheme(ColorScheme scheme) => FlexThemeData.dark( + colors: FlexSchemeColor( + primary: scheme.primary, + primaryContainer: scheme.primaryContainer, + secondary: scheme.secondary, + secondaryContainer: scheme.secondaryContainer, + tertiary: scheme.tertiary, + tertiaryContainer: scheme.tertiaryContainer, + error: scheme.error, + errorContainer: scheme.errorContainer, + ), + cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), + appBarOpacity: 0, + ); + + ThemeData get theme => getTheme(darkColors); + + Color get filterColor => getFilterColor(darkColors, meanLuminance); +} + +class BoardBackgroundImageConverter + implements JsonConverter?> { + const BoardBackgroundImageConverter(); + + @override + BoardBackgroundImage? fromJson(Map? json) { + if (json == null) { + return null; + } + + final darkColors = ColorScheme( + brightness: Brightness.dark, + primary: Color(json['darkPrimary'] as int), + onPrimary: Color(json['darkOnPrimary'] as int), + primaryContainer: Color(json['darkPrimaryContainer'] as int), + secondary: Color(json['darkSecondary'] as int), + onSecondary: Color(json['darkOnSecondary'] as int), + secondaryContainer: Color(json['darkSecondaryContainer'] as int), + tertiary: Color(json['darkTertiary'] as int), + tertiaryContainer: Color(json['darkTertiaryContainer'] as int), + error: Color(json['darkError'] as int), + onError: Color(json['darkOnError'] as int), + errorContainer: Color(json['darkErrorContainer'] as int), + surface: Color(json['darkSurface'] as int), + onSurface: Color(json['darkOnSurface'] as int), + ); + + final transform = json['transform'] as List; + + return BoardBackgroundImage( + path: json['path'] as String, + transform: Matrix4.fromList(transform.map((e) => (e as num).toDouble()).toList()), + isBlurred: json['isBlurred'] as bool, + darkColors: darkColors, + meanLuminance: json['meanLuminance'] as double, + ); + } + + @override + Map? toJson(BoardBackgroundImage? object) { + if (object == null) { + return null; + } + + final Map darkColors = { + 'darkPrimary': object.darkColors.primary.toARGB32(), + 'darkOnPrimary': object.darkColors.onPrimary.toARGB32(), + 'darkPrimaryContainer': object.darkColors.primaryContainer.toARGB32(), + 'darkSecondary': object.darkColors.secondary.toARGB32(), + 'darkOnSecondary': object.darkColors.onSecondary.toARGB32(), + 'darkSecondaryContainer': object.darkColors.secondaryContainer.toARGB32(), + 'darkTertiary': object.darkColors.tertiary.toARGB32(), + 'darkTertiaryContainer': object.darkColors.tertiaryContainer.toARGB32(), + 'darkError': object.darkColors.error.toARGB32(), + 'darkOnError': object.darkColors.onError.toARGB32(), + 'darkErrorContainer': object.darkColors.errorContainer.toARGB32(), + 'darkSurface': object.darkColors.surface.toARGB32(), + 'darkOnSurface': object.darkColors.onSurface.toARGB32(), + }; + + return { + 'path': object.path, + 'transform': object.transform.storage, + 'isBlurred': object.isBlurred, + ...darkColors, + 'meanLuminance': object.meanLuminance, + }; + } +} From 98d70be174cdda17707add6a21dc0bfa45d34713 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Tue, 28 Jan 2025 11:57:50 +0100 Subject: [PATCH 30/41] Make sure system scheme exists --- lib/src/app.dart | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 54fa53dc10..d56bbdbc4d 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -122,21 +122,20 @@ class _AppState extends ConsumerState { final isTablet = isTabletOrLarger(context); final isIOS = Theme.of(context).platform == TargetPlatform.iOS; final remainingHeight = estimateRemainingHeightLeftBoard(context); - - final flexScheme = generalPrefs.systemColors == true ? getSystemScheme()! : FlexColor.espresso; - final flexSchemeLightColors = flexScheme.light; - final flexSchemeDarkColors = flexScheme.dark; - + final systemScheme = getSystemScheme(); + final flexScheme = + generalPrefs.systemColors == true && systemScheme != null + ? systemScheme + : FlexColor.espresso; final themeLight = FlexThemeData.light( - colors: flexSchemeLightColors, + colors: flexScheme.light, surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, blendLevel: 10, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, ); - // The defined dark theme. final themeDark = FlexThemeData.dark( - colors: flexSchemeDarkColors, + colors: flexScheme.dark, surfaceMode: FlexSurfaceMode.level, blendLevel: 40, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), @@ -198,8 +197,9 @@ class _AppState extends ConsumerState { ), ); + // The high blend theme is used only for the navigation bar in light mode. final highBlendThemeLight = FlexThemeData.light( - colors: flexSchemeLightColors, + colors: flexScheme.light, surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold, blendLevel: 16, ); From 970e199d9e15acafc724d0a6436076d88472b29c Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Tue, 28 Jan 2025 12:17:06 +0100 Subject: [PATCH 31/41] Fix board background select screen on tablets --- .../board_background_theme_choice_screen.dart | 109 ++++++++++++------ 1 file changed, 72 insertions(+), 37 deletions(-) diff --git a/lib/src/view/settings/board_background_theme_choice_screen.dart b/lib/src/view/settings/board_background_theme_choice_screen.dart index 00d8993d34..4c92f09973 100644 --- a/lib/src/view/settings/board_background_theme_choice_screen.dart +++ b/lib/src/view/settings/board_background_theme_choice_screen.dart @@ -246,37 +246,56 @@ class _ConfirmBackgroundScreenState extends State @override Widget build(BuildContext context) { return Scaffold( - body: Stack( - children: [ - PageView.builder( - controller: _controller, - itemBuilder: (context, index) { - final backgroundTheme = colorChoices[index]; - return BoardBackgroundThemeWidget( - backgroundTheme: backgroundTheme, - child: const Scaffold(body: SizedBox.expand()), - ); - }, - itemCount: colorChoices.length, - ), - Positioned.fill( - child: Align( - alignment: Alignment.center, - child: Chessboard.fixed( - size: MediaQuery.sizeOf(context).width, - fen: kInitialFEN, - orientation: Side.white, - settings: widget.boardPrefs.toBoardSettings(), + body: LayoutBuilder( + builder: (context, constraints) { + final orientation = + constraints.maxWidth > constraints.maxHeight + ? Orientation.landscape + : Orientation.portrait; + final landscapeBoardPadding = MediaQuery.paddingOf(context).top + 16.0; + return Stack( + children: [ + PageView.builder( + controller: _controller, + itemBuilder: (context, index) { + final backgroundTheme = colorChoices[index]; + return BoardBackgroundThemeWidget( + backgroundTheme: backgroundTheme, + child: const Scaffold(body: SizedBox.expand()), + ); + }, + itemCount: colorChoices.length, ), - ), - ), - Positioned( - bottom: MediaQuery.paddingOf(context).bottom + 16.0, - left: 0, - right: 0, - child: const Text('Swipe to display other backgrounds', textAlign: TextAlign.center), - ), - ], + Positioned.fill( + child: Align( + alignment: + orientation == Orientation.portrait ? Alignment.center : Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.only(left: landscapeBoardPadding), + child: Chessboard.fixed( + size: + orientation == Orientation.portrait + ? constraints.maxWidth + : constraints.maxHeight - landscapeBoardPadding * 2, + fen: kInitialFEN, + orientation: Side.white, + settings: widget.boardPrefs.toBoardSettings(), + ), + ), + ), + ), + Positioned( + bottom: MediaQuery.paddingOf(context).bottom + 16.0, + left: orientation == Orientation.portrait ? 0 : null, + right: 0, + child: const Padding( + padding: EdgeInsets.all(16.0), + child: Text('Swipe to display other backgrounds', textAlign: TextAlign.center), + ), + ), + ], + ); + }, ), persistentFooterButtons: [ AdaptiveTextButton( @@ -345,6 +364,11 @@ class _ConfirmImageBackgroundScreenState extends State constraints.maxHeight + ? Orientation.landscape + : Orientation.portrait; + final landscapeBoardPadding = MediaQuery.paddingOf(context).top + 16.0; return Stack( children: [ InteractiveViewer( @@ -371,23 +395,33 @@ class _ConfirmImageBackgroundScreenState extends State Navigator.pop(context, null), ), + const SizedBox(width: 16.0), AdaptiveTextButton( child: Text(context.l10n.accept), onPressed: () async { From a203b8839918f66d2943be3c82d33731d9ac1803 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Tue, 28 Jan 2025 12:20:10 +0100 Subject: [PATCH 32/41] Fix board alignment on portrait --- .../settings/board_background_theme_choice_screen.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/src/view/settings/board_background_theme_choice_screen.dart b/lib/src/view/settings/board_background_theme_choice_screen.dart index 4c92f09973..4fefb4aced 100644 --- a/lib/src/view/settings/board_background_theme_choice_screen.dart +++ b/lib/src/view/settings/board_background_theme_choice_screen.dart @@ -271,7 +271,9 @@ class _ConfirmBackgroundScreenState extends State alignment: orientation == Orientation.portrait ? Alignment.center : Alignment.centerLeft, child: Padding( - padding: EdgeInsets.only(left: landscapeBoardPadding), + padding: EdgeInsets.only( + left: orientation == Orientation.portrait ? 0 : landscapeBoardPadding, + ), child: Chessboard.fixed( size: orientation == Orientation.portrait @@ -401,7 +403,9 @@ class _ConfirmImageBackgroundScreenState extends State Date: Tue, 28 Jan 2025 15:26:16 +0100 Subject: [PATCH 33/41] Tweaks --- lib/src/app.dart | 8 ++++---- lib/src/model/settings/board_preferences.dart | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index d56bbdbc4d..915cdaf071 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -130,7 +130,7 @@ class _AppState extends ConsumerState { final themeLight = FlexThemeData.light( colors: flexScheme.light, surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, - blendLevel: 10, + blendLevel: isIOS ? 10 : 8, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, ); @@ -146,8 +146,8 @@ class _AppState extends ConsumerState { generalPrefs.systemColors ? null : FloatingActionButtonThemeData( - backgroundColor: themeLight.colorScheme.secondaryFixedDim, - foregroundColor: themeLight.colorScheme.onSecondaryFixedVariant, + backgroundColor: themeLight.colorScheme.primaryFixedDim, + foregroundColor: themeLight.colorScheme.onPrimaryFixed, ); const cupertinoTitleColor = CupertinoDynamicColor.withBrightness( @@ -227,7 +227,7 @@ class _AppState extends ConsumerState { navigationBarTheme: NavigationBarTheme.of(context).copyWith( height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null, backgroundColor: highBlendThemeLight.colorScheme.surface, - indicatorColor: highBlendThemeLight.colorScheme.secondaryContainer, + indicatorColor: darken(highBlendThemeLight.colorScheme.secondaryContainer, 0.05), elevation: 3, ), extensions: [lichessCustomColors.harmonized(themeLight.colorScheme)], diff --git a/lib/src/model/settings/board_preferences.dart b/lib/src/model/settings/board_preferences.dart index e97736160f..71e974efd6 100644 --- a/lib/src/model/settings/board_preferences.dart +++ b/lib/src/model/settings/board_preferences.dart @@ -411,14 +411,14 @@ String dragTargetKindLabel(DragTargetKind kind) => switch (kind) { enum BoardBackgroundTheme { /// The background theme is based on the chess board board(20, 30), - blue(30, 36, FlexScheme.blue, 'Blue'), - indigo(30, 36, FlexScheme.indigo, 'indigo'), - green(30, 36, FlexScheme.jungle, 'Green'), - brown(30, 36, FlexScheme.purpleBrown, 'Brown'), - gold(30, 36, FlexScheme.gold, 'Gold'), - red(36, 36, FlexScheme.redWine, 'Red'), - purple(30, 36, FlexScheme.purpleM3, 'Purple'), - sepia(28, 36, FlexScheme.sepia, 'Sepia'), + blue(30, 34, FlexScheme.blue, 'Blue'), + indigo(30, 34, FlexScheme.indigo, 'indigo'), + green(30, 34, FlexScheme.jungle, 'Green'), + brown(30, 34, FlexScheme.purpleBrown, 'Brown'), + gold(30, 34, FlexScheme.gold, 'Gold'), + red(34, 34, FlexScheme.redWine, 'Red'), + purple(30, 34, FlexScheme.purpleM3, 'Purple'), + sepia(28, 34, FlexScheme.sepia, 'Sepia'), dimRed(16, 20, FlexScheme.redWine, 'Dim Red'), dimPurple(16, 20, FlexScheme.purpleM3, 'Dim Purple'), From 009a6ad66acd4174a4291817c4d065d8925c26e5 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Wed, 29 Jan 2025 14:19:20 +0100 Subject: [PATCH 34/41] Ensure board background theme widget is not built on each state change --- lib/src/view/analysis/analysis_screen.dart | 27 +++++++++++++++++----- lib/src/view/study/study_screen.dart | 22 ++++++++++++++---- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/lib/src/view/analysis/analysis_screen.dart b/lib/src/view/analysis/analysis_screen.dart index 3dd9fee064..5b9a9ee338 100644 --- a/lib/src/view/analysis/analysis_screen.dart +++ b/lib/src/view/analysis/analysis_screen.dart @@ -23,6 +23,7 @@ import 'package:lichess_mobile/src/view/engine/engine_gauge.dart'; import 'package:lichess_mobile/src/view/engine/engine_lines.dart'; import 'package:lichess_mobile/src/view/opening_explorer/opening_explorer_view.dart'; import 'package:lichess_mobile/src/widgets/adaptive_action_sheet.dart'; +import 'package:lichess_mobile/src/widgets/board_background_theme.dart'; import 'package:lichess_mobile/src/widgets/bottom_bar.dart'; import 'package:lichess_mobile/src/widgets/bottom_bar_button.dart'; import 'package:lichess_mobile/src/widgets/buttons.dart'; @@ -32,18 +33,32 @@ import 'package:logging/logging.dart'; final _logger = Logger('AnalysisScreen'); -class AnalysisScreen extends ConsumerStatefulWidget { - const AnalysisScreen({required this.options, this.enableDrawingShapes = true}); +class AnalysisScreen extends StatelessWidget { + const AnalysisScreen({required this.options, this.enableDrawingShapes = true, super.key}); + + final AnalysisOptions options; + final bool enableDrawingShapes; + + @override + Widget build(BuildContext context) { + return BoardBackgroundThemeWidget( + child: _AnalysisScreen(options: options, enableDrawingShapes: enableDrawingShapes), + ); + } +} + +class _AnalysisScreen extends ConsumerStatefulWidget { + const _AnalysisScreen({required this.options, this.enableDrawingShapes = true}); final AnalysisOptions options; final bool enableDrawingShapes; @override - ConsumerState createState() => _AnalysisScreenState(); + ConsumerState<_AnalysisScreen> createState() => _AnalysisScreenState(); } -class _AnalysisScreenState extends ConsumerState +class _AnalysisScreenState extends ConsumerState<_AnalysisScreen> with SingleTickerProviderStateMixin { late final List tabs; late final TabController _tabController; @@ -92,7 +107,7 @@ class _AnalysisScreenState extends ConsumerState switch (asyncState) { case AsyncData(:final value): - return PlatformBoardThemeScaffold( + return PlatformScaffold( resizeToAvoidBottomInset: false, appBar: PlatformAppBar(title: _Title(variant: value.variant), actions: appBarActions), body: _Body( @@ -109,7 +124,7 @@ class _AnalysisScreenState extends ConsumerState }, ); case _: - return PlatformBoardThemeScaffold( + return PlatformScaffold( resizeToAvoidBottomInset: false, appBar: PlatformAppBar( title: const _Title(variant: Variant.standard), diff --git a/lib/src/view/study/study_screen.dart b/lib/src/view/study/study_screen.dart index c08c4ac81c..553d053b93 100644 --- a/lib/src/view/study/study_screen.dart +++ b/lib/src/view/study/study_screen.dart @@ -30,6 +30,7 @@ import 'package:lichess_mobile/src/view/study/study_gamebook.dart'; import 'package:lichess_mobile/src/view/study/study_settings.dart'; import 'package:lichess_mobile/src/view/study/study_tree_view.dart'; import 'package:lichess_mobile/src/widgets/adaptive_action_sheet.dart'; +import 'package:lichess_mobile/src/widgets/board_background_theme.dart'; import 'package:lichess_mobile/src/widgets/buttons.dart'; import 'package:lichess_mobile/src/widgets/feedback.dart'; import 'package:lichess_mobile/src/widgets/pgn.dart'; @@ -39,8 +40,19 @@ import 'package:logging/logging.dart'; final _logger = Logger('StudyScreen'); -class StudyScreen extends ConsumerWidget { - const StudyScreen({required this.id}); +class StudyScreen extends StatelessWidget { + const StudyScreen({required this.id, super.key}); + + final StudyId id; + + @override + Widget build(BuildContext context) { + return BoardBackgroundThemeWidget(child: _StudyScreenLoader(id: id)); + } +} + +class _StudyScreenLoader extends ConsumerWidget { + const _StudyScreenLoader({required this.id}); final StudyId id; @@ -52,7 +64,7 @@ class StudyScreen extends ConsumerWidget { return _StudyScreen(id: id, studyState: value); case AsyncError(:final error, :final stackTrace): _logger.severe('Cannot load study: $error', stackTrace); - return PlatformBoardThemeScaffold( + return PlatformScaffold( appBar: const PlatformAppBar(title: Text('')), body: DefaultTabController( length: 1, @@ -72,7 +84,7 @@ class StudyScreen extends ConsumerWidget { ), ); case _: - return PlatformBoardThemeScaffold( + return PlatformScaffold( appBar: PlatformAppBar( title: Shimmer( child: ShimmerLoading( @@ -163,7 +175,7 @@ class _StudyScreenState extends ConsumerState<_StudyScreen> with TickerProviderS @override Widget build(BuildContext context) { - return PlatformBoardThemeScaffold( + return PlatformScaffold( appBar: PlatformAppBar( title: AutoSizeText( widget.studyState.currentChapterTitle, From 0b50a277e91620296d7103635eeab29577ec2730 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Wed, 29 Jan 2025 15:22:07 +0100 Subject: [PATCH 35/41] Keep preferences notifiers alive --- lib/src/model/analysis/analysis_preferences.dart | 2 +- lib/src/model/challenge/challenge_preferences.dart | 2 +- .../coordinate_training/coordinate_training_preferences.dart | 2 +- lib/src/model/game/game_preferences.dart | 2 +- lib/src/model/lobby/game_setup_preferences.dart | 2 +- .../model/opening_explorer/opening_explorer_preferences.dart | 2 +- lib/src/model/puzzle/puzzle_preferences.dart | 2 +- lib/src/model/settings/board_preferences.dart | 2 +- lib/src/model/settings/general_preferences.dart | 2 +- lib/src/model/settings/home_preferences.dart | 2 +- lib/src/model/settings/over_the_board_preferences.dart | 2 +- lib/src/model/settings/preferences_storage.dart | 4 ++-- lib/src/model/study/study_preferences.dart | 2 +- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/src/model/analysis/analysis_preferences.dart b/lib/src/model/analysis/analysis_preferences.dart index 5b16d74da4..f700265f63 100644 --- a/lib/src/model/analysis/analysis_preferences.dart +++ b/lib/src/model/analysis/analysis_preferences.dart @@ -6,7 +6,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'analysis_preferences.freezed.dart'; part 'analysis_preferences.g.dart'; -@riverpod +@Riverpod(keepAlive: true) class AnalysisPreferences extends _$AnalysisPreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties @override diff --git a/lib/src/model/challenge/challenge_preferences.dart b/lib/src/model/challenge/challenge_preferences.dart index dbbe66db28..44c8b6bda0 100644 --- a/lib/src/model/challenge/challenge_preferences.dart +++ b/lib/src/model/challenge/challenge_preferences.dart @@ -11,7 +11,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'challenge_preferences.freezed.dart'; part 'challenge_preferences.g.dart'; -@riverpod +@Riverpod(keepAlive: true) class ChallengePreferences extends _$ChallengePreferences with SessionPreferencesStorage { // ignore: avoid_public_notifier_properties diff --git a/lib/src/model/coordinate_training/coordinate_training_preferences.dart b/lib/src/model/coordinate_training/coordinate_training_preferences.dart index 09273ce082..ebaf302056 100644 --- a/lib/src/model/coordinate_training/coordinate_training_preferences.dart +++ b/lib/src/model/coordinate_training/coordinate_training_preferences.dart @@ -8,7 +8,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'coordinate_training_preferences.freezed.dart'; part 'coordinate_training_preferences.g.dart'; -@riverpod +@Riverpod(keepAlive: true) class CoordinateTrainingPreferences extends _$CoordinateTrainingPreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties diff --git a/lib/src/model/game/game_preferences.dart b/lib/src/model/game/game_preferences.dart index 31311b132a..5d96d84e57 100644 --- a/lib/src/model/game/game_preferences.dart +++ b/lib/src/model/game/game_preferences.dart @@ -6,7 +6,7 @@ part 'game_preferences.freezed.dart'; part 'game_preferences.g.dart'; /// Local game preferences, defined client-side only. -@riverpod +@Riverpod(keepAlive: true) class GamePreferences extends _$GamePreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties @override diff --git a/lib/src/model/lobby/game_setup_preferences.dart b/lib/src/model/lobby/game_setup_preferences.dart index ca9da1e218..d93e4af128 100644 --- a/lib/src/model/lobby/game_setup_preferences.dart +++ b/lib/src/model/lobby/game_setup_preferences.dart @@ -11,7 +11,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'game_setup_preferences.freezed.dart'; part 'game_setup_preferences.g.dart'; -@riverpod +@Riverpod(keepAlive: true) class GameSetupPreferences extends _$GameSetupPreferences with SessionPreferencesStorage { // ignore: avoid_public_notifier_properties diff --git a/lib/src/model/opening_explorer/opening_explorer_preferences.dart b/lib/src/model/opening_explorer/opening_explorer_preferences.dart index f3dcbc1188..440b096f0b 100644 --- a/lib/src/model/opening_explorer/opening_explorer_preferences.dart +++ b/lib/src/model/opening_explorer/opening_explorer_preferences.dart @@ -10,7 +10,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'opening_explorer_preferences.freezed.dart'; part 'opening_explorer_preferences.g.dart'; -@riverpod +@Riverpod(keepAlive: true) class OpeningExplorerPreferences extends _$OpeningExplorerPreferences with SessionPreferencesStorage { // ignore: avoid_public_notifier_properties diff --git a/lib/src/model/puzzle/puzzle_preferences.dart b/lib/src/model/puzzle/puzzle_preferences.dart index 3f71177c44..774fc549bb 100644 --- a/lib/src/model/puzzle/puzzle_preferences.dart +++ b/lib/src/model/puzzle/puzzle_preferences.dart @@ -8,7 +8,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'puzzle_preferences.freezed.dart'; part 'puzzle_preferences.g.dart'; -@riverpod +@Riverpod(keepAlive: true) class PuzzlePreferences extends _$PuzzlePreferences with SessionPreferencesStorage { // ignore: avoid_public_notifier_properties @override diff --git a/lib/src/model/settings/board_preferences.dart b/lib/src/model/settings/board_preferences.dart index 71e974efd6..e10a79d593 100644 --- a/lib/src/model/settings/board_preferences.dart +++ b/lib/src/model/settings/board_preferences.dart @@ -15,7 +15,7 @@ part 'board_preferences.g.dart'; const kBoardDefaultBrightnessFilter = 1.0; const kBoardDefaultHueFilter = 0.0; -@riverpod +@Riverpod(keepAlive: true) class BoardPreferences extends _$BoardPreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties @override diff --git a/lib/src/model/settings/general_preferences.dart b/lib/src/model/settings/general_preferences.dart index 09a2a6f3df..cca7ae16ed 100644 --- a/lib/src/model/settings/general_preferences.dart +++ b/lib/src/model/settings/general_preferences.dart @@ -8,7 +8,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'general_preferences.freezed.dart'; part 'general_preferences.g.dart'; -@riverpod +@Riverpod(keepAlive: true) class GeneralPreferences extends _$GeneralPreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties @override diff --git a/lib/src/model/settings/home_preferences.dart b/lib/src/model/settings/home_preferences.dart index ff9fa70910..e30bd76ef8 100644 --- a/lib/src/model/settings/home_preferences.dart +++ b/lib/src/model/settings/home_preferences.dart @@ -5,7 +5,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'home_preferences.freezed.dart'; part 'home_preferences.g.dart'; -@riverpod +@Riverpod(keepAlive: true) class HomePreferences extends _$HomePreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties @override diff --git a/lib/src/model/settings/over_the_board_preferences.dart b/lib/src/model/settings/over_the_board_preferences.dart index 4fe2b72f7c..21eb05f2ed 100644 --- a/lib/src/model/settings/over_the_board_preferences.dart +++ b/lib/src/model/settings/over_the_board_preferences.dart @@ -5,7 +5,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'over_the_board_preferences.freezed.dart'; part 'over_the_board_preferences.g.dart'; -@riverpod +@Riverpod(keepAlive: true) class OverTheBoardPreferences extends _$OverTheBoardPreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties diff --git a/lib/src/model/settings/preferences_storage.dart b/lib/src/model/settings/preferences_storage.dart index 9234927cde..af17393472 100644 --- a/lib/src/model/settings/preferences_storage.dart +++ b/lib/src/model/settings/preferences_storage.dart @@ -34,7 +34,7 @@ enum PrefCategory { } /// A [Notifier] mixin to provide a way to store and retrieve preferences. -mixin PreferencesStorage on AutoDisposeNotifier { +mixin PreferencesStorage on Notifier { T fromJson(Map json); T get defaults; @@ -64,7 +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 Notifier { T fromJson(Map json); T defaults({LightUser? user}); diff --git a/lib/src/model/study/study_preferences.dart b/lib/src/model/study/study_preferences.dart index d309eeb7ab..280b19731c 100644 --- a/lib/src/model/study/study_preferences.dart +++ b/lib/src/model/study/study_preferences.dart @@ -5,7 +5,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'study_preferences.freezed.dart'; part 'study_preferences.g.dart'; -@riverpod +@Riverpod(keepAlive: true) class StudyPreferences extends _$StudyPreferences with PreferencesStorage { // ignore: avoid_public_notifier_properties @override From 1d6ee1f29dabcb2d7fc23fb805bf2b526495ca43 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Wed, 29 Jan 2025 15:27:30 +0100 Subject: [PATCH 36/41] Switch to system board when system colors are activated --- .../model/settings/general_preferences.dart | 7 ++++++- lib/src/view/settings/settings_tab_screen.dart | 18 +++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/src/model/settings/general_preferences.dart b/lib/src/model/settings/general_preferences.dart index cca7ae16ed..807e86b033 100644 --- a/lib/src/model/settings/general_preferences.dart +++ b/lib/src/model/settings/general_preferences.dart @@ -1,6 +1,8 @@ import 'dart:ui' show Locale; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:lichess_mobile/src/model/settings/board_preferences.dart' + show BoardTheme, boardPreferencesProvider; import 'package:lichess_mobile/src/model/settings/preferences_storage.dart'; import 'package:lichess_mobile/src/utils/json.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -47,7 +49,10 @@ class GeneralPreferences extends _$GeneralPreferences with PreferencesStorage toggleSystemColors() { - return save(state.copyWith(systemColors: !state.systemColors)); + return Future.wait([ + save(state.copyWith(systemColors: !state.systemColors)), + ref.read(boardPreferencesProvider.notifier).setBoardTheme(BoardTheme.system), + ]).then((_) => {}); } } diff --git a/lib/src/view/settings/settings_tab_screen.dart b/lib/src/view/settings/settings_tab_screen.dart index f67b1a59d9..49afd37f31 100644 --- a/lib/src/view/settings/settings_tab_screen.dart +++ b/lib/src/view/settings/settings_tab_screen.dart @@ -195,15 +195,6 @@ class _Body extends ConsumerWidget { ); }, ), - if (getCorePalette() != null) - SwitchSettingTile( - leading: const Icon(Icons.colorize_outlined), - title: Text(context.l10n.mobileSystemColors), - value: generalPrefs.systemColors, - onChanged: (value) { - ref.read(generalPreferencesProvider.notifier).toggleSystemColors(); - }, - ), SettingsListTile( icon: const Icon(Icons.brightness_medium_outlined), settingsLabel: Text(context.l10n.background), @@ -281,6 +272,15 @@ class _Body extends ConsumerWidget { } }, ), + if (getCorePalette() != null) + SwitchSettingTile( + leading: const Icon(Icons.colorize_outlined), + title: Text(context.l10n.mobileSystemColors), + value: generalPrefs.systemColors, + onChanged: (value) { + ref.read(generalPreferencesProvider.notifier).toggleSystemColors(); + }, + ), ], ), ListSection( From da08a91fef4c1b67c3d5efbe81b791a77ab42cbe Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Wed, 29 Jan 2025 15:34:36 +0100 Subject: [PATCH 37/41] List tile icon color tweak --- lib/src/widgets/list.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index 55c9e0306b..57d00b2607 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -333,7 +333,7 @@ class PlatformListTile extends StatelessWidget { return ListTile( leading: leading, title: title, - iconColor: colorScheme.outline, + iconColor: colorScheme.onSurface.withValues(alpha: 0.7), subtitle: subtitle != null ? DefaultTextStyle.merge( @@ -353,7 +353,7 @@ class PlatformListTile extends StatelessWidget { case TargetPlatform.iOS: final activatedColor = colorScheme.surfaceContainerHighest; return IconTheme( - data: CupertinoIconThemeData(color: colorScheme.outline), + data: CupertinoIconThemeData(color: colorScheme.onSurface.withValues(alpha: 0.7)), child: GestureDetector( onLongPress: onLongPress, child: CupertinoListTile.notched( From 0d920270fd198ed125795852557619fb6270cb3a Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Wed, 29 Jan 2025 16:36:56 +0100 Subject: [PATCH 38/41] More harmonization between android and ios --- lib/src/styles/styles.dart | 6 + lib/src/view/puzzle/dashboard_screen.dart | 8 +- lib/src/view/relation/following_screen.dart | 5 +- .../view/settings/settings_tab_screen.dart | 4 - lib/src/view/study/study_list_screen.dart | 1 + lib/src/view/user/game_history_screen.dart | 2 + lib/src/view/user/perf_stats_screen.dart | 278 +++++++++--------- lib/src/view/user/user_activity.dart | 2 +- lib/src/view/watch/streamer_screen.dart | 2 + lib/src/widgets/board_background_theme.dart | 6 - lib/src/widgets/list.dart | 11 +- lib/src/widgets/platform.dart | 8 +- lib/src/widgets/platform_scaffold.dart | 5 + lib/src/widgets/settings.dart | 31 +- lib/src/widgets/stat_card.dart | 3 + 15 files changed, 195 insertions(+), 177 deletions(-) diff --git a/lib/src/styles/styles.dart b/lib/src/styles/styles.dart index 8df41c78a7..6a083d0516 100644 --- a/lib/src/styles/styles.dart +++ b/lib/src/styles/styles.dart @@ -161,6 +161,12 @@ abstract class Styles { ); } +/// Retrieve the background color for the screens where we display a list of items. +Color? listingsScreenBackgroundColor(BuildContext context) => + Theme.of(context).brightness == Brightness.light + ? ColorScheme.of(context).surfaceContainerLowest + : ColorScheme.of(context).surfaceContainerLow; + /// Retrieve the default text color and apply an opacity to it. Color? textShade(BuildContext context, double opacity) => DefaultTextStyle.of(context).style.color?.withValues(alpha: opacity); diff --git a/lib/src/view/puzzle/dashboard_screen.dart b/lib/src/view/puzzle/dashboard_screen.dart index 08f0ada871..4d963d5abe 100644 --- a/lib/src/view/puzzle/dashboard_screen.dart +++ b/lib/src/view/puzzle/dashboard_screen.dart @@ -76,6 +76,7 @@ class PuzzleDashboardWidget extends ConsumerWidget { context.l10n.performance, value: dashboard.global.performance.toString(), backgroundColor: cardColor, + elevation: 0, ), StatCard( context.l10n @@ -85,18 +86,17 @@ class PuzzleDashboardWidget extends ConsumerWidget { .capitalize(), value: dashboard.global.nb.toString().localizeNumbers(), backgroundColor: cardColor, + elevation: 0, ), StatCard( context.l10n.puzzleSolved.capitalize(), value: '${((dashboard.global.firstWins / dashboard.global.nb) * 100).round()}%', backgroundColor: cardColor, + elevation: 0, ), ]), ), - if (chartData.length >= 3) - Theme.of(context).platform == TargetPlatform.iOS - ? PuzzleChart(chartData) - : Card(margin: Styles.horizontalBodyPadding, child: PuzzleChart(chartData)), + if (chartData.length >= 3) PuzzleChart(chartData), ], ); }, diff --git a/lib/src/view/relation/following_screen.dart b/lib/src/view/relation/following_screen.dart index 8504b58e28..cfa55a4453 100644 --- a/lib/src/view/relation/following_screen.dart +++ b/lib/src/view/relation/following_screen.dart @@ -31,6 +31,7 @@ class FollowingScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( + backgroundColor: listingsScreenBackgroundColor(context), appBar: PlatformAppBar(title: Text(context.l10n.friends)), body: const _Body(), ); @@ -62,7 +63,9 @@ class _Body extends ConsumerWidget { itemCount: following.length, separatorBuilder: (context, index) => - const PlatformDivider(height: 1, cupertinoHasLeading: true), + Theme.of(context).platform == TargetPlatform.iOS + ? const PlatformDivider(height: 1, cupertinoHasLeading: true) + : const SizedBox.shrink(), itemBuilder: (context, index) { final user = following[index]; return Slidable( diff --git a/lib/src/view/settings/settings_tab_screen.dart b/lib/src/view/settings/settings_tab_screen.dart index 49afd37f31..b47b4b8b94 100644 --- a/lib/src/view/settings/settings_tab_screen.dart +++ b/lib/src/view/settings/settings_tab_screen.dart @@ -112,7 +112,6 @@ class _Body extends ConsumerWidget { ListSection( header: userSession != null ? UserFullNameWidget(user: userSession.user) : null, hasLeading: true, - showDivider: true, children: [ if (userSession != null) ...[ PlatformListTile( @@ -180,7 +179,6 @@ class _Body extends ConsumerWidget { ), ListSection( hasLeading: true, - showDivider: true, children: [ SettingsListTile( icon: const Icon(Icons.music_note_outlined), @@ -285,7 +283,6 @@ class _Body extends ConsumerWidget { ), ListSection( hasLeading: true, - showDivider: true, children: [ PlatformListTile( leading: const Icon(Icons.info_outlined), @@ -323,7 +320,6 @@ class _Body extends ConsumerWidget { ), ListSection( hasLeading: true, - showDivider: true, children: [ PlatformListTile( leading: const Icon(Icons.code_outlined), diff --git a/lib/src/view/study/study_list_screen.dart b/lib/src/view/study/study_list_screen.dart index e134f99849..7c2ed38883 100644 --- a/lib/src/view/study/study_list_screen.dart +++ b/lib/src/view/study/study_list_screen.dart @@ -35,6 +35,7 @@ class StudyListScreen extends ConsumerWidget { final title = Text(isLoggedIn ? filter.category.l10n(context.l10n) : context.l10n.studyMenu); return PlatformScaffold( + backgroundColor: listingsScreenBackgroundColor(context), appBar: PlatformAppBar( title: title, actions: [ diff --git a/lib/src/view/user/game_history_screen.dart b/lib/src/view/user/game_history_screen.dart index 7a3fcebb60..0fe81380d2 100644 --- a/lib/src/view/user/game_history_screen.dart +++ b/lib/src/view/user/game_history_screen.dart @@ -7,6 +7,7 @@ import 'package:lichess_mobile/src/model/game/game_filter.dart'; import 'package:lichess_mobile/src/model/game/game_history.dart'; import 'package:lichess_mobile/src/model/user/user.dart'; import 'package:lichess_mobile/src/model/user/user_repository_providers.dart'; +import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/view/game/game_list_tile.dart'; import 'package:lichess_mobile/src/widgets/adaptive_bottom_sheet.dart'; @@ -68,6 +69,7 @@ class GameHistoryScreen extends ConsumerWidget { ); return PlatformScaffold( + backgroundColor: listingsScreenBackgroundColor(context), appBar: PlatformAppBar(title: title, actions: [filterBtn]), body: _Body(user: user, isOnline: isOnline, gameFilter: gameFilter), ); diff --git a/lib/src/view/user/perf_stats_screen.dart b/lib/src/view/user/perf_stats_screen.dart index 56e0451563..331b7acca1 100644 --- a/lib/src/view/user/perf_stats_screen.dart +++ b/lib/src/view/user/perf_stats_screen.dart @@ -182,60 +182,69 @@ class _Body extends ConsumerWidget { ], ), if (perf != Perf.puzzle) ...[ - if (data.percentile != null && data.percentile! > 0.0) - Text( - (loggedInUser != null && loggedInUser.user.id == user.id) - ? context.l10n.youAreBetterThanPercentOfPerfTypePlayers( - '${data.percentile!.toStringAsFixed(2)}%', - perf.title, - ) - : context.l10n.userIsBetterThanPercentOfPerfTypePlayers( - user.username, - '${data.percentile!.toStringAsFixed(2)}%', - perf.title, + PlatformCard( + child: Column( + children: [ + if (data.percentile != null && data.percentile! > 0.0) + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + (loggedInUser != null && loggedInUser.user.id == user.id) + ? context.l10n.youAreBetterThanPercentOfPerfTypePlayers( + '${data.percentile!.toStringAsFixed(2)}%', + perf.title, + ) + : context.l10n.userIsBetterThanPercentOfPerfTypePlayers( + 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(':', ''), + child: ProgressionWidget(data.progress), + ), + StatCardRow([ + if (data.rank != null) + StatCard( + context.l10n.rank, + value: + data.rank == null + ? '?' + : NumberFormat.decimalPattern( + Intl.getCurrentLocale(), + ).format(data.rank), + ), + StatCard( + context.l10n.perfStatRatingDeviation('').replaceAll(': .', ''), + value: data.deviation.toStringAsFixed(2), ), - style: TextStyle(color: textShade(context, 0.7)), + ]), + StatCardRow([ + StatCard( + context.l10n.perfStatHighestRating('').replaceAll(':', ''), + child: _RatingWidget( + data.highestRating, + data.highestRatingGame, + context.lichessColors.good, + ), + ), + StatCard( + context.l10n.perfStatLowestRating('').replaceAll(':', ''), + child: _RatingWidget( + data.lowestRating, + data.lowestRatingGame, + context.lichessColors.error, + ), + ), + ]), + ], ), - 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(':', ''), - child: ProgressionWidget(data.progress), ), - StatCardRow([ - if (data.rank != null) - StatCard( - context.l10n.rank, - value: - data.rank == null - ? '?' - : NumberFormat.decimalPattern( - Intl.getCurrentLocale(), - ).format(data.rank), - ), - StatCard( - context.l10n.perfStatRatingDeviation('').replaceAll(': .', ''), - value: data.deviation.toStringAsFixed(2), - ), - ]), - StatCardRow([ - StatCard( - context.l10n.perfStatHighestRating('').replaceAll(':', ''), - child: _RatingWidget( - data.highestRating, - data.highestRatingGame, - context.lichessColors.good, - ), - ), - StatCard( - context.l10n.perfStatLowestRating('').replaceAll(':', ''), - child: _RatingWidget( - data.lowestRating, - data.lowestRatingGame, - context.lichessColors.error, - ), - ), - ]), statGroupSpace, Semantics( container: true, @@ -281,89 +290,94 @@ class _Body extends ConsumerWidget { ), ), ), - subStatSpace, - StatCardRow([ - StatCard( - context.l10n.wins, - child: _PercentageValueWidget( - data.wonGames, - data.totalGames, - color: context.lichessColors.good, - ), - ), - StatCard( - context.l10n.draws, - child: _PercentageValueWidget( - data.drawnGames, - data.totalGames, - color: textShade(context, _customOpacity), - isShaded: true, - ), - ), - StatCard( - context.l10n.losses, - child: _PercentageValueWidget( - data.lostGames, - data.totalGames, - color: context.lichessColors.error, - ), - ), - ]), - StatCardRow([ - StatCard( - context.l10n.rated, - child: _PercentageValueWidget(data.ratedGames, data.totalGames), - ), - StatCard( - context.l10n.tournament, - child: _PercentageValueWidget(data.tournamentGames, data.totalGames), - ), - StatCard( - context.l10n.perfStatBerserkedGames.replaceAll( - ' ${context.l10n.games.toLowerCase()}', - '', - ), - child: _PercentageValueWidget(data.berserkGames, data.totalGames), - ), - StatCard( - context.l10n.perfStatDisconnections, - child: _PercentageValueWidget(data.disconnections, data.totalGames), - ), - ]), - StatCardRow([ - StatCard( - context.l10n.averageOpponent, - value: data.avgOpponent == null ? '?' : data.avgOpponent.toString(), - ), - StatCard( - context.l10n.perfStatTimeSpentPlaying, - value: data.timePlayed.toDaysHoursMinutes(AppLocalizations.of(context)), - ), - ]), - StatCard( - context.l10n.perfStatWinningStreak, - child: _StreakWidget( - data.maxWinStreak, - data.curWinStreak, - color: context.lichessColors.good, - ), - ), - StatCard( - context.l10n.perfStatLosingStreak, - child: _StreakWidget( - data.maxLossStreak, - data.curLossStreak, - color: context.lichessColors.error, + PlatformCard( + child: Column( + children: [ + StatCardRow([ + StatCard( + context.l10n.wins, + child: _PercentageValueWidget( + data.wonGames, + data.totalGames, + color: context.lichessColors.good, + ), + ), + StatCard( + context.l10n.draws, + child: _PercentageValueWidget( + data.drawnGames, + data.totalGames, + color: textShade(context, _customOpacity), + isShaded: true, + ), + ), + StatCard( + context.l10n.losses, + child: _PercentageValueWidget( + data.lostGames, + data.totalGames, + color: context.lichessColors.error, + ), + ), + ]), + StatCardRow([ + StatCard( + context.l10n.rated, + child: _PercentageValueWidget(data.ratedGames, data.totalGames), + ), + StatCard( + context.l10n.tournament, + child: _PercentageValueWidget(data.tournamentGames, data.totalGames), + ), + StatCard( + context.l10n.perfStatBerserkedGames.replaceAll( + ' ${context.l10n.games.toLowerCase()}', + '', + ), + child: _PercentageValueWidget(data.berserkGames, data.totalGames), + ), + StatCard( + context.l10n.perfStatDisconnections, + child: _PercentageValueWidget(data.disconnections, data.totalGames), + ), + ]), + StatCardRow([ + StatCard( + context.l10n.averageOpponent, + value: data.avgOpponent == null ? '?' : data.avgOpponent.toString(), + ), + StatCard( + context.l10n.perfStatTimeSpentPlaying, + value: data.timePlayed.toDaysHoursMinutes(AppLocalizations.of(context)), + ), + ]), + StatCard( + context.l10n.perfStatWinningStreak, + child: _StreakWidget( + data.maxWinStreak, + data.curWinStreak, + color: context.lichessColors.good, + ), + ), + StatCard( + context.l10n.perfStatLosingStreak, + child: _StreakWidget( + data.maxLossStreak, + data.curLossStreak, + color: context.lichessColors.error, + ), + ), + StatCard( + context.l10n.perfStatGamesInARow, + child: _StreakWidget(data.maxPlayStreak, data.curPlayStreak), + ), + StatCard( + context.l10n.perfStatMaxTimePlaying, + child: _StreakWidget(data.maxTimeStreak, data.curTimeStreak), + ), + ], ), ), - StatCard( - context.l10n.perfStatGamesInARow, - child: _StreakWidget(data.maxPlayStreak, data.curPlayStreak), - ), - StatCard( - context.l10n.perfStatMaxTimePlaying, - child: _StreakWidget(data.maxTimeStreak, data.curTimeStreak), - ), if (data.bestWins != null && data.bestWins!.isNotEmpty) ...[ statGroupSpace, _GameListWidget( diff --git a/lib/src/view/user/user_activity.dart b/lib/src/view/user/user_activity.dart index d4268c7517..40970003ce 100644 --- a/lib/src/view/user/user_activity.dart +++ b/lib/src/view/user/user_activity.dart @@ -63,7 +63,7 @@ class UserActivityEntry extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); - final leadingIconSize = theme.platform == TargetPlatform.iOS ? 26.0 : 36.0; + const leadingIconSize = 26.0; final emptySubtitle = theme.platform == TargetPlatform.iOS ? const SizedBox.shrink() : null; final redColor = theme.extension()?.error; diff --git a/lib/src/view/watch/streamer_screen.dart b/lib/src/view/watch/streamer_screen.dart index 453465a060..7a866a3408 100644 --- a/lib/src/view/watch/streamer_screen.dart +++ b/lib/src/view/watch/streamer_screen.dart @@ -22,6 +22,7 @@ class StreamerScreen extends StatelessWidget { Widget _buildAndroid(BuildContext context) { return Scaffold( + backgroundColor: listingsScreenBackgroundColor(context), appBar: AppBar(title: Text(context.l10n.mobileLiveStreamers)), body: ListView.builder( itemCount: streamers.length, @@ -38,6 +39,7 @@ class StreamerScreen extends StatelessWidget { Widget _buildIos(BuildContext context) { return CupertinoPageScaffold( + backgroundColor: listingsScreenBackgroundColor(context), navigationBar: CupertinoNavigationBar(middle: Text(context.l10n.mobileLiveStreamers)), child: CustomScrollView( slivers: [ diff --git a/lib/src/widgets/board_background_theme.dart b/lib/src/widgets/board_background_theme.dart index bd74417237..c73f855b6c 100644 --- a/lib/src/widgets/board_background_theme.dart +++ b/lib/src/widgets/board_background_theme.dart @@ -84,12 +84,6 @@ class BoardThemeWrapper extends StatelessWidget { data: theme.copyWith( cupertinoOverrideTheme: cupertinoTheme, listTileTheme: ListTileTheme.of(context).copyWith( - tileColor: theme.colorScheme.surfaceContainerLow.withValues( - alpha: transparentScaffold ? 0.5 : 1, - ), - selectedTileColor: theme.colorScheme.surfaceContainer.withValues( - alpha: transparentScaffold ? 0.5 : 1, - ), titleTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, subtitleTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, leadingAndTrailingTextStyle: isIOS ? cupertinoTheme.textTheme.textStyle : null, diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index 57d00b2607..07df7db5e3 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; +import 'package:lichess_mobile/src/widgets/platform.dart' show PlatformCard; /// A platform agnostic list section. /// @@ -79,8 +80,9 @@ class ListSection extends StatelessWidget { switch (theme.platform) { case TargetPlatform.android: return _isLoading - ? Padding( - padding: margin ?? Styles.sectionBottomPadding, + ? PlatformCard( + clipBehavior: Clip.hardEdge, + margin: margin ?? Styles.bodySectionPadding, child: Column( children: [ if (header != null) @@ -110,8 +112,9 @@ class ListSection extends StatelessWidget { ], ), ) - : Padding( - padding: margin ?? Styles.sectionBottomPadding, + : PlatformCard( + clipBehavior: Clip.hardEdge, + margin: margin ?? Styles.bodySectionPadding, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/src/widgets/platform.dart b/lib/src/widgets/platform.dart index 4f8b357b83..91c7e31c0a 100644 --- a/lib/src/widgets/platform.dart +++ b/lib/src/widgets/platform.dart @@ -97,11 +97,9 @@ class PlatformCard extends StatelessWidget { : const RoundedRectangleBorder(borderRadius: kCardBorderRadius), color: color ?? - (platform == TargetPlatform.iOS - ? brightness == Brightness.light - ? colorScheme.surfaceContainerLowest - : colorScheme.surfaceContainer - : null), + (brightness == Brightness.light + ? colorScheme.surfaceContainerLowest + : colorScheme.surfaceContainer), shadowColor: shadowColor, semanticContainer: semanticContainer, elevation: elevation ?? (platform == TargetPlatform.iOS ? 0 : null), diff --git a/lib/src/widgets/platform_scaffold.dart b/lib/src/widgets/platform_scaffold.dart index 1242b390d2..a7ff4b839d 100644 --- a/lib/src/widgets/platform_scaffold.dart +++ b/lib/src/widgets/platform_scaffold.dart @@ -117,6 +117,7 @@ class PlatformScaffold extends StatelessWidget { this.appBar, required this.body, this.resizeToAvoidBottomInset = true, + this.backgroundColor, }); /// Acts as the [AppBar] for Android and as the [CupertinoNavigationBar] for iOS. @@ -130,9 +131,12 @@ class PlatformScaffold extends StatelessWidget { /// See [Scaffold.resizeToAvoidBottomInset] and [CupertinoPageScaffold.resizeToAvoidBottomInset] final bool resizeToAvoidBottomInset; + final Color? backgroundColor; + Widget _androidBuilder(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: resizeToAvoidBottomInset, + backgroundColor: backgroundColor, appBar: appBar != null ? PreferredSize(preferredSize: const Size.fromHeight(kToolbarHeight), child: appBar!) @@ -146,6 +150,7 @@ class PlatformScaffold extends StatelessWidget { child: CupertinoPageScaffold( resizeToAvoidBottomInset: resizeToAvoidBottomInset, navigationBar: appBar != null ? _CupertinoNavBarWrapper(child: appBar!) : null, + backgroundColor: backgroundColor, child: body, ), ); diff --git a/lib/src/widgets/settings.dart b/lib/src/widgets/settings.dart index 2a64747ee3..87ed3faeea 100644 --- a/lib/src/widgets/settings.dart +++ b/lib/src/widgets/settings.dart @@ -221,7 +221,6 @@ class ChoicePicker extends StatelessWidget { this.tileContentPadding, this.margin, this.notchedTile = true, - this.showDividerBetweenTiles = false, }); final List choices; @@ -231,9 +230,6 @@ class ChoicePicker extends StatelessWidget { final Widget Function(T choice)? leadingBuilder; final void Function(T choice)? onSelectedItemChanged; - /// Only on android. - final bool showDividerBetweenTiles; - /// Android tiles content padding. final EdgeInsetsGeometry? tileContentPadding; @@ -247,25 +243,20 @@ class ChoicePicker extends StatelessWidget { Widget build(BuildContext context) { switch (Theme.of(context).platform) { case TargetPlatform.android: - final tiles = choices.map((value) { - return ListTile( - selected: selectedItem == value, - trailing: selectedItem == value ? const Icon(Icons.check) : null, - contentPadding: tileContentPadding, - title: titleBuilder(value), - subtitle: subtitleBuilder?.call(value), - leading: leadingBuilder?.call(value), - onTap: onSelectedItemChanged != null ? () => onSelectedItemChanged!(value) : null, - ); - }); return Opacity( opacity: onSelectedItemChanged != null ? 1.0 : 0.5, - child: Column( + child: ListSection( children: [ - if (showDividerBetweenTiles) - ...ListTile.divideTiles(context: context, tiles: tiles) - else - ...tiles, + for (final value in choices) + ListTile( + selected: selectedItem == value, + trailing: selectedItem == value ? const Icon(Icons.check) : null, + contentPadding: tileContentPadding, + title: titleBuilder(value), + subtitle: subtitleBuilder?.call(value), + leading: leadingBuilder?.call(value), + onTap: onSelectedItemChanged != null ? () => onSelectedItemChanged!(value) : null, + ), ], ), ); diff --git a/lib/src/widgets/stat_card.dart b/lib/src/widgets/stat_card.dart index 616718553d..eb10333f32 100644 --- a/lib/src/widgets/stat_card.dart +++ b/lib/src/widgets/stat_card.dart @@ -17,6 +17,7 @@ class StatCard extends StatelessWidget { this.statFontSize, this.valueFontSize, this.backgroundColor, + this.elevation = 0, }); final String stat; @@ -27,6 +28,7 @@ class StatCard extends StatelessWidget { final double? statFontSize; final double? valueFontSize; final Color? backgroundColor; + final double elevation; @override Widget build(BuildContext context) { @@ -40,6 +42,7 @@ class StatCard extends StatelessWidget { return Padding( padding: padding ?? EdgeInsets.zero, child: PlatformCard( + elevation: elevation, color: backgroundColor, margin: const EdgeInsets.symmetric(vertical: 6.0), child: Padding( From b9985e9bc5f49f7a7b4ba55cc15fd5f404790aaa Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Wed, 29 Jan 2025 16:59:21 +0100 Subject: [PATCH 39/41] More harmonization --- lib/src/app.dart | 2 +- lib/src/styles/styles.dart | 20 +++++++++++++------ lib/src/view/play/online_bots_screen.dart | 1 + lib/src/view/relation/following_screen.dart | 2 +- lib/src/view/study/study_list_screen.dart | 2 +- .../view/user/challenge_requests_screen.dart | 1 + lib/src/view/user/game_history_screen.dart | 2 +- lib/src/view/user/user_activity.dart | 14 ++++++------- lib/src/view/watch/streamer_screen.dart | 4 ++-- lib/src/widgets/list.dart | 6 +----- lib/src/widgets/platform.dart | 8 ++------ lib/src/widgets/settings.dart | 5 +---- 12 files changed, 33 insertions(+), 34 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 915cdaf071..f34fd88d3a 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -130,7 +130,7 @@ class _AppState extends ConsumerState { final themeLight = FlexThemeData.light( colors: flexScheme.light, surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, - blendLevel: isIOS ? 10 : 8, + blendLevel: 10, cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true), appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground, ); diff --git a/lib/src/styles/styles.dart b/lib/src/styles/styles.dart index 6a083d0516..f91a499e62 100644 --- a/lib/src/styles/styles.dart +++ b/lib/src/styles/styles.dart @@ -51,6 +51,20 @@ abstract class Styles { ? CupertinoColors.secondaryLabel.resolveFrom(context) : null; + /// Retrieve the background color for the screens where we display a list of items. + static Color listingsScreenBackgroundColor(BuildContext context) => + Theme.of(context).brightness == Brightness.light + ? ColorScheme.of(context).surfaceContainerLowest + : ColorScheme.of(context).surfaceContainerLow; + + static Color cardColor(BuildContext context) { + final brightness = Theme.of(context).brightness; + final colorScheme = ColorScheme.of(context); + return brightness == Brightness.light + ? colorScheme.surfaceContainerLowest + : colorScheme.surfaceContainerLow; + } + static const _cupertinoDarkLabelColor = Color(0xFFDCDCDC); static const cupertinoTitleColor = CupertinoDynamicColor.withBrightness( color: Color(0xFF000000), @@ -161,12 +175,6 @@ abstract class Styles { ); } -/// Retrieve the background color for the screens where we display a list of items. -Color? listingsScreenBackgroundColor(BuildContext context) => - Theme.of(context).brightness == Brightness.light - ? ColorScheme.of(context).surfaceContainerLowest - : ColorScheme.of(context).surfaceContainerLow; - /// Retrieve the default text color and apply an opacity to it. Color? textShade(BuildContext context, double opacity) => DefaultTextStyle.of(context).style.color?.withValues(alpha: opacity); diff --git a/lib/src/view/play/online_bots_screen.dart b/lib/src/view/play/online_bots_screen.dart index 06eb8c900c..0ab2ffbc3f 100644 --- a/lib/src/view/play/online_bots_screen.dart +++ b/lib/src/view/play/online_bots_screen.dart @@ -36,6 +36,7 @@ class OnlineBotsScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( + backgroundColor: Styles.listingsScreenBackgroundColor(context), appBar: PlatformAppBar(title: Text(context.l10n.onlineBots)), body: _Body(), ); diff --git a/lib/src/view/relation/following_screen.dart b/lib/src/view/relation/following_screen.dart index cfa55a4453..5646f4400a 100644 --- a/lib/src/view/relation/following_screen.dart +++ b/lib/src/view/relation/following_screen.dart @@ -31,7 +31,7 @@ class FollowingScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( - backgroundColor: listingsScreenBackgroundColor(context), + backgroundColor: Styles.listingsScreenBackgroundColor(context), appBar: PlatformAppBar(title: Text(context.l10n.friends)), body: const _Body(), ); diff --git a/lib/src/view/study/study_list_screen.dart b/lib/src/view/study/study_list_screen.dart index 7c2ed38883..088899759b 100644 --- a/lib/src/view/study/study_list_screen.dart +++ b/lib/src/view/study/study_list_screen.dart @@ -35,7 +35,7 @@ class StudyListScreen extends ConsumerWidget { final title = Text(isLoggedIn ? filter.category.l10n(context.l10n) : context.l10n.studyMenu); return PlatformScaffold( - backgroundColor: listingsScreenBackgroundColor(context), + backgroundColor: Styles.listingsScreenBackgroundColor(context), appBar: PlatformAppBar( title: title, actions: [ diff --git a/lib/src/view/user/challenge_requests_screen.dart b/lib/src/view/user/challenge_requests_screen.dart index 990dd32b00..094040b6af 100644 --- a/lib/src/view/user/challenge_requests_screen.dart +++ b/lib/src/view/user/challenge_requests_screen.dart @@ -21,6 +21,7 @@ class ChallengeRequestsScreen extends StatelessWidget { @override Widget build(BuildContext context) { return PlatformScaffold( + backgroundColor: Styles.listingsScreenBackgroundColor(context), appBar: PlatformAppBar(title: Text(context.l10n.preferencesNotifyChallenge)), body: _Body(), ); diff --git a/lib/src/view/user/game_history_screen.dart b/lib/src/view/user/game_history_screen.dart index 0fe81380d2..dae0556405 100644 --- a/lib/src/view/user/game_history_screen.dart +++ b/lib/src/view/user/game_history_screen.dart @@ -69,7 +69,7 @@ class GameHistoryScreen extends ConsumerWidget { ); return PlatformScaffold( - backgroundColor: listingsScreenBackgroundColor(context), + backgroundColor: Styles.listingsScreenBackgroundColor(context), appBar: PlatformAppBar(title: title, actions: [filterBtn]), body: _Body(user: user, isOnline: isOnline, gameFilter: gameFilter), ); diff --git a/lib/src/view/user/user_activity.dart b/lib/src/view/user/user_activity.dart index 40970003ce..f67da22e6d 100644 --- a/lib/src/view/user/user_activity.dart +++ b/lib/src/view/user/user_activity.dart @@ -127,7 +127,7 @@ class UserActivityEntry extends ConsumerWidget { ), if (entry.puzzles != null) _UserActivityListTile( - leading: Icon(LichessIcons.target, size: leadingIconSize), + leading: const Icon(LichessIcons.target, size: leadingIconSize), title: context.l10n.activitySolvedNbPuzzles(entry.puzzles!.win + entry.puzzles!.loss), subtitle: RatingPrefAware( child: Row( @@ -167,21 +167,21 @@ class UserActivityEntry extends ConsumerWidget { ), if (entry.streak != null) _UserActivityListTile( - leading: Icon(LichessIcons.streak, size: leadingIconSize), + leading: const 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), ), if (entry.storm != null) _UserActivityListTile( - leading: Icon(LichessIcons.storm, size: leadingIconSize), + leading: const 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), ), if (entry.correspondenceEnds != null) _UserActivityListTile( - leading: Icon(LichessIcons.correspondence, size: leadingIconSize), + leading: const Icon(LichessIcons.correspondence, size: leadingIconSize), title: context.l10n.activityCompletedNbGames( entry.correspondenceEnds!.win + entry.correspondenceEnds!.draw + @@ -196,7 +196,7 @@ class UserActivityEntry extends ConsumerWidget { ), if (entry.correspondenceMovesNb != null && entry.correspondenceGamesNb != null) _UserActivityListTile( - leading: Icon(LichessIcons.correspondence, size: leadingIconSize), + leading: const Icon(LichessIcons.correspondence, size: leadingIconSize), title: context.l10n.activityPlayedNbMoves(entry.correspondenceMovesNb!), subtitle: Text( context.l10n.activityInNbCorrespondenceGames(entry.correspondenceGamesNb!), @@ -204,7 +204,7 @@ class UserActivityEntry extends ConsumerWidget { ), if (entry.tournamentNb != null) _UserActivityListTile( - leading: Icon(Icons.emoji_events, size: leadingIconSize), + leading: const Icon(Icons.emoji_events, size: leadingIconSize), title: context.l10n.activityCompetedInNbTournaments(entry.tournamentNb!), subtitle: entry.bestTournament != null @@ -221,7 +221,7 @@ class UserActivityEntry extends ConsumerWidget { ), if (entry.followInNb != null) _UserActivityListTile( - leading: Icon(Icons.thumb_up, size: leadingIconSize), + leading: const Icon(Icons.thumb_up, size: leadingIconSize), title: context.l10n.activityGainedNbFollowers(entry.followInNb!), subtitle: emptySubtitle, ), diff --git a/lib/src/view/watch/streamer_screen.dart b/lib/src/view/watch/streamer_screen.dart index 7a866a3408..02375b32a9 100644 --- a/lib/src/view/watch/streamer_screen.dart +++ b/lib/src/view/watch/streamer_screen.dart @@ -22,7 +22,7 @@ class StreamerScreen extends StatelessWidget { Widget _buildAndroid(BuildContext context) { return Scaffold( - backgroundColor: listingsScreenBackgroundColor(context), + backgroundColor: Styles.listingsScreenBackgroundColor(context), appBar: AppBar(title: Text(context.l10n.mobileLiveStreamers)), body: ListView.builder( itemCount: streamers.length, @@ -39,7 +39,7 @@ class StreamerScreen extends StatelessWidget { Widget _buildIos(BuildContext context) { return CupertinoPageScaffold( - backgroundColor: listingsScreenBackgroundColor(context), + backgroundColor: Styles.listingsScreenBackgroundColor(context), navigationBar: CupertinoNavigationBar(middle: Text(context.l10n.mobileLiveStreamers)), child: CustomScrollView( slivers: [ diff --git a/lib/src/widgets/list.dart b/lib/src/widgets/list.dart index 07df7db5e3..902fc53d70 100644 --- a/lib/src/widgets/list.dart +++ b/lib/src/widgets/list.dart @@ -205,11 +205,7 @@ class ListSection extends StatelessWidget { clipBehavior: cupertinoClipBehavior, backgroundColor: CupertinoTheme.of(context).scaffoldBackgroundColor, decoration: BoxDecoration( - color: - cupertinoBackgroundColor ?? - (theme.brightness == Brightness.light - ? theme.colorScheme.surfaceContainerLowest - : theme.colorScheme.surfaceContainer), + color: cupertinoBackgroundColor ?? Styles.cardColor(context), borderRadius: cupertinoBorderRadius ?? const BorderRadius.all(Radius.circular(10.0)), ), diff --git a/lib/src/widgets/platform.dart b/lib/src/widgets/platform.dart index 91c7e31c0a..329ecf67f2 100644 --- a/lib/src/widgets/platform.dart +++ b/lib/src/widgets/platform.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lichess_mobile/src/constants.dart'; +import 'package:lichess_mobile/src/styles/styles.dart'; /// A simple widget that builds different things on different platforms. class PlatformWidget extends StatelessWidget { @@ -86,7 +87,6 @@ class PlatformCard extends StatelessWidget { Widget build(BuildContext context) { final platform = Theme.of(context).platform; final brightness = Theme.of(context).brightness; - final colorScheme = Theme.of(context).colorScheme; final cardFactory = brightness == Brightness.dark ? Card.filled : Card.new; return MediaQuery.withClampedTextScaling( maxScaleFactor: kCardTextScaleFactor, @@ -95,11 +95,7 @@ class PlatformCard extends StatelessWidget { borderRadius != null ? RoundedRectangleBorder(borderRadius: borderRadius!) : const RoundedRectangleBorder(borderRadius: kCardBorderRadius), - color: - color ?? - (brightness == Brightness.light - ? colorScheme.surfaceContainerLowest - : colorScheme.surfaceContainer), + color: color ?? Styles.cardColor(context), shadowColor: shadowColor, semanticContainer: semanticContainer, elevation: elevation ?? (platform == TargetPlatform.iOS ? 0 : null), diff --git a/lib/src/widgets/settings.dart b/lib/src/widgets/settings.dart index 87ed3faeea..1a76cfc67d 100644 --- a/lib/src/widgets/settings.dart +++ b/lib/src/widgets/settings.dart @@ -271,10 +271,7 @@ class ChoicePicker extends StatelessWidget { child: CupertinoListSection.insetGrouped( backgroundColor: CupertinoTheme.of(context).scaffoldBackgroundColor, decoration: BoxDecoration( - color: - theme.brightness == Brightness.light - ? theme.colorScheme.surfaceContainerLowest - : theme.colorScheme.surfaceContainer, + color: Styles.cardColor(context), borderRadius: const BorderRadius.all(Radius.circular(10.0)), ), separatorColor: Styles.cupertinoSeparatorColor.resolveFrom(context), From 62dace7b7e396c02fd53c32aa64d485801107e38 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Wed, 29 Jan 2025 17:04:20 +0100 Subject: [PATCH 40/41] Tweak listings dark background, fix for iOS --- lib/src/styles/styles.dart | 4 +- lib/src/view/relation/following_screen.dart | 106 +++++++++----------- 2 files changed, 51 insertions(+), 59 deletions(-) diff --git a/lib/src/styles/styles.dart b/lib/src/styles/styles.dart index f91a499e62..a17fcae2d9 100644 --- a/lib/src/styles/styles.dart +++ b/lib/src/styles/styles.dart @@ -53,9 +53,7 @@ abstract class Styles { /// Retrieve the background color for the screens where we display a list of items. static Color listingsScreenBackgroundColor(BuildContext context) => - Theme.of(context).brightness == Brightness.light - ? ColorScheme.of(context).surfaceContainerLowest - : ColorScheme.of(context).surfaceContainerLow; + ColorScheme.of(context).surfaceContainerLowest; static Color cardColor(BuildContext context) { final brightness = Theme.of(context).brightness; diff --git a/lib/src/view/relation/following_screen.dart b/lib/src/view/relation/following_screen.dart index 5646f4400a..8da7157062 100644 --- a/lib/src/view/relation/following_screen.dart +++ b/lib/src/view/relation/following_screen.dart @@ -54,64 +54,58 @@ class _Body extends ConsumerWidget { return Center(child: Text(context.l10n.mobileNotFollowingAnyUser)); } return SafeArea( - child: ColoredBox( - color: - Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoColors.systemBackground.resolveFrom(context) - : Colors.transparent, - child: ListView.separated( - itemCount: following.length, - separatorBuilder: - (context, index) => - Theme.of(context).platform == TargetPlatform.iOS - ? const PlatformDivider(height: 1, cupertinoHasLeading: true) - : const SizedBox.shrink(), - itemBuilder: (context, index) { - final user = following[index]; - return Slidable( - dragStartBehavior: DragStartBehavior.start, - endActionPane: ActionPane( - motion: const StretchMotion(), - extentRatio: 0.3, - children: [ - SlidableAction( - onPressed: (BuildContext context) async { - final oldState = following; + child: ListView.separated( + itemCount: following.length, + separatorBuilder: + (context, index) => + Theme.of(context).platform == TargetPlatform.iOS + ? const PlatformDivider(height: 1, cupertinoHasLeading: true) + : const SizedBox.shrink(), + itemBuilder: (context, index) { + final user = following[index]; + return Slidable( + dragStartBehavior: DragStartBehavior.start, + endActionPane: ActionPane( + motion: const StretchMotion(), + extentRatio: 0.3, + children: [ + SlidableAction( + onPressed: (BuildContext context) async { + final oldState = following; + setState(() { + following = following.removeWhere((v) => v.id == user.id); + }); + try { + await ref.withClient( + (client) => RelationRepository(client).unfollow(user.id), + ); + } catch (_) { setState(() { - following = following.removeWhere((v) => v.id == user.id); + following = oldState; }); - try { - await ref.withClient( - (client) => RelationRepository(client).unfollow(user.id), - ); - } catch (_) { - setState(() { - following = oldState; - }); - } - }, - backgroundColor: context.lichessColors.error, - foregroundColor: Colors.white, - icon: Icons.person_remove, - // TODO translate - label: 'Unfollow', - ), - ], - ), - child: UserListTile.fromUser( - user, - _isOnline(user, data.$2), - onTap: - () => { - pushPlatformRoute( - context, - builder: (context) => UserScreen(user: user.lightUser), - ), - }, - ), - ); - }, - ), + } + }, + backgroundColor: context.lichessColors.error, + foregroundColor: Colors.white, + icon: Icons.person_remove, + // TODO translate + label: 'Unfollow', + ), + ], + ), + child: UserListTile.fromUser( + user, + _isOnline(user, data.$2), + onTap: + () => { + pushPlatformRoute( + context, + builder: (context) => UserScreen(user: user.lightUser), + ), + }, + ), + ); + }, ), ); }, From 873fba6bdc907715dd81c08947fbf8c1f7e23976 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Wed, 29 Jan 2025 17:12:29 +0100 Subject: [PATCH 41/41] Fix lint --- lib/src/view/relation/following_screen.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/view/relation/following_screen.dart b/lib/src/view/relation/following_screen.dart index 8da7157062..5d08075a79 100644 --- a/lib/src/view/relation/following_screen.dart +++ b/lib/src/view/relation/following_screen.dart @@ -1,5 +1,4 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';