Skip to content

Commit

Permalink
Merge pull request #1370 from lichess-org/custom_board_theme
Browse files Browse the repository at this point in the history
Custom board background and theme improvement
  • Loading branch information
veloce authored Jan 29, 2025
2 parents f515cc0 + 873fba6 commit 0fa6548
Show file tree
Hide file tree
Showing 86 changed files with 2,705 additions and 1,405 deletions.
1 change: 1 addition & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ GeneratedPluginRegistrant.java
key.properties
**/*.keystore
**/*.jks
**/.cxx
6 changes: 6 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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`)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand Down
2 changes: 2 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo Library Access Warning</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
Expand Down
267 changes: 130 additions & 137 deletions lib/src/app.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,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';
Expand All @@ -10,14 +11,13 @@ 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/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';
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/screen.dart';

/// Application initialization and main entry point.
Expand Down Expand Up @@ -119,149 +119,142 @@ class _AppState extends ConsumerState<Application> {
@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 systemScheme = getSystemScheme();
final flexScheme =
generalPrefs.systemColors == true && systemScheme != null
? systemScheme
: FlexColor.espresso;
final themeLight = FlexThemeData.light(
colors: flexScheme.light,
surfaceMode: FlexSurfaceMode.highScaffoldLowSurface,
blendLevel: 10,
cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true),
appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground,
);
final themeDark = FlexThemeData.dark(
colors: flexScheme.dark,
surfaceMode: FlexSurfaceMode.level,
blendLevel: 40,
cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true),
appBarStyle: isIOS ? null : FlexAppBarStyle.scaffoldBackground,
);

final brightness = ref.watch(currentBrightnessProvider);
final boardTheme = ref.watch(boardPreferencesProvider.select((state) => state.boardTheme));
final floatingActionButtonTheme =
generalPrefs.systemColors
? null
: FloatingActionButtonThemeData(
backgroundColor: themeLight.colorScheme.primaryFixedDim,
foregroundColor: themeLight.colorScheme.onPrimaryFixed,
);

final remainingHeight = estimateRemainingHeightLeftBoard(context);
const cupertinoTitleColor = CupertinoDynamicColor.withBrightness(
color: Color(0xFF000000),
darkColor: Color(0xFFF5F5F5),
);

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 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 isTablet = isTabletOrLarger(context);
final isIOS = Theme.of(context).platform == TargetPlatform.iOS;
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),
),
);

final dynamicColorScheme =
brightness == Brightness.light ? fixedLightScheme : fixedDarkScheme;
// The high blend theme is used only for the navigation bar in light mode.
final highBlendThemeLight = FlexThemeData.light(
colors: flexScheme.light,
surfaceMode: FlexSurfaceMode.highBackgroundLowScaffold,
blendLevel: 16,
);

final ColorScheme colorScheme = switch (generalPrefs.appThemeSeed) {
AppThemeSeed.board => ColorScheme.fromSeed(
seedColor: boardTheme.colors.darkSquare,
brightness: brightness,
return AnnotatedRegion<SystemUiOverlayStyle>(
value: FlexColorScheme.themedSystemNavigationBar(
context,
systemNavBarStyle: FlexSystemNavBarStyle.transparent,
),
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: kSupportedLocales,
onGenerateTitle: (BuildContext context) => 'lichess.org',
locale: generalPrefs.locale,
theme: themeLight.copyWith(
cupertinoOverrideTheme: lightCupertino,
splashFactory: isIOS ? NoSplash.splashFactory : null,
textTheme: isIOS ? Typography.blackCupertino : null,
listTileTheme: ListTileTheme.of(context).copyWith(
titleTextStyle: isIOS ? lightCupertino.textTheme.textStyle : null,
subtitleTextStyle: isIOS ? lightCupertino.textTheme.textStyle : null,
leadingAndTrailingTextStyle: isIOS ? lightCupertino.textTheme.textStyle : null,
),
AppThemeSeed.system =>
dynamicColorScheme ??
ColorScheme.fromSeed(
seedColor: boardTheme.colors.darkSquare,
brightness: brightness,
),
};

final cupertinoThemeData = CupertinoThemeData(
primaryColor: colorScheme.primary,
primaryContrastingColor: colorScheme.onPrimary,
brightness: brightness,
textTheme: CupertinoTheme.of(context).textTheme.copyWith(
primaryColor: colorScheme.primary,
textStyle: CupertinoTheme.of(
context,
).textTheme.textStyle.copyWith(color: Styles.cupertinoLabelColor),
navTitleTextStyle: CupertinoTheme.of(
context,
).textTheme.navTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor),
navLargeTitleTextStyle: CupertinoTheme.of(
context,
).textTheme.navLargeTitleTextStyle.copyWith(color: Styles.cupertinoTitleColor),
floatingActionButtonTheme: floatingActionButtonTheme,
navigationBarTheme: NavigationBarTheme.of(context).copyWith(
height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null,
backgroundColor: highBlendThemeLight.colorScheme.surface,
indicatorColor: darken(highBlendThemeLight.colorScheme.secondaryContainer, 0.05),
elevation: 3,
),
scaffoldBackgroundColor: Styles.cupertinoScaffoldColor,
barBackgroundColor:
isTablet ? Styles.cupertinoTabletAppBarColor : Styles.cupertinoAppBarColor,
);

return MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: kSupportedLocales,
onGenerateTitle: (BuildContext context) => 'lichess.org',
locale: generalPrefs.locale,
theme: ThemeData.from(
colorScheme: colorScheme,
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)],
extensions: [lichessCustomColors.harmonized(themeLight.colorScheme)],
),
darkTheme: themeDark.copyWith(
cupertinoOverrideTheme: darkCupertino,
splashFactory: isIOS ? NoSplash.splashFactory : null,
textTheme: isIOS ? Typography.whiteCupertino : null,
listTileTheme: ListTileTheme.of(context).copyWith(
titleTextStyle: isIOS ? darkCupertino.textTheme.textStyle : null,
subtitleTextStyle: isIOS ? darkCupertino.textTheme.textStyle : null,
leadingAndTrailingTextStyle: isIOS ? darkCupertino.textTheme.textStyle : null,
),
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),
),
);
}
: null,
home: const BottomNavScaffold(),
navigatorObservers: [rootNavPageRouteObserver],
);
},
floatingActionButtonTheme: floatingActionButtonTheme,
navigationBarTheme: NavigationBarTheme.of(
context,
).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null),
extensions: [lichessCustomColors.harmonized(themeDark.colorScheme)],
),
themeMode: switch (generalPrefs.themeMode) {
BackgroundThemeMode.light => ThemeMode.light,
BackgroundThemeMode.dark => ThemeMode.dark,
BackgroundThemeMode.system => ThemeMode.system,
},
home: const BottomNavScaffold(),
navigatorObservers: [rootNavPageRouteObserver],
),
);
}
}

// --

(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<Color> _extractAdditionalColours(ColorScheme scheme) => [
scheme.surface,
scheme.surfaceDim,
scheme.surfaceBright,
scheme.surfaceContainerLowest,
scheme.surfaceContainerLow,
scheme.surfaceContainer,
scheme.surfaceContainerHigh,
scheme.surfaceContainerHighest,
];

ColorScheme _insertAdditionalColours(ColorScheme scheme, List<Color> 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],
);
6 changes: 3 additions & 3 deletions lib/src/init.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Future<void> 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, 13, 14)) {
_migrateThemeSettings();
}

Expand Down Expand Up @@ -68,9 +68,9 @@ Future<void> _migrateThemeSettings() async {
}
final generalPrefs = GeneralPrefs.fromJson(jsonDecode(stored) as Map<String, dynamic>);
final migrated = generalPrefs.copyWith(
appThemeSeed:
systemColors:
// ignore: deprecated_member_use_from_same_package
generalPrefs.systemColors == true ? AppThemeSeed.system : AppThemeSeed.board,
generalPrefs.appThemeSeed == AppThemeSeed.system,
);
await prefs.setString(PrefCategory.general.storageKey, jsonEncode(migrated.toJson()));
} catch (e) {
Expand Down
Loading

0 comments on commit 0fa6548

Please sign in to comment.