diff --git a/lib/main.dart b/lib/main.dart index 26a68cff0..a097f087e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,7 @@ import 'dart:ui'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/bootstrap/app_bootstrap.dart'; @@ -16,7 +14,6 @@ import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/providers/shared_provider.dart'; import 'package:fladder/providers/sync_provider.dart'; import 'package:fladder/routes/auto_router.dart'; -import 'package:fladder/theme.dart'; import 'package:fladder/util/adaptive_layout/adaptive_layout.dart'; import 'package:fladder/util/application_info.dart'; import 'package:fladder/util/deep_link_helper.dart'; @@ -24,6 +21,7 @@ import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/themes_data.dart'; import 'package:fladder/widgets/media_query_scaler.dart'; import 'package:fladder/widgets/pip_lifecycle_controller.dart'; +import 'package:fladder/widgets/shared/adaptive_color.dart'; void main(List args) async { WidgetsFlutterBinding.ensureInitialized(); @@ -70,86 +68,66 @@ class _FladderApp extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isLinux = defaultTargetPlatform == TargetPlatform.linux; final themeMode = ref.watch(clientSettingsProvider.select((value) => value.themeMode)); - final themeColor = ref.watch(clientSettingsProvider.select((value) => value.themeColor)); final amoledBlack = ref.watch(clientSettingsProvider.select((value) => value.amoledBlack)); final mouseDrag = ref.watch(clientSettingsProvider.select((value) => value.mouseDragSupport)); - final schemeVariant = ref.watch(clientSettingsProvider.select((value) => value.schemeVariant)); final language = ref.watch(clientSettingsProvider .select((value) => value.selectedLocale ?? WidgetsBinding.instance.platformDispatcher.locale)); final scrollBehaviour = const MaterialScrollBehavior(); - return DynamicColorBuilder( - builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) { - final baseLightTheme = themeColor == null - ? FladderTheme.theme(lightDynamic ?? FladderTheme.defaultScheme(Brightness.light), schemeVariant) - : FladderTheme.theme(themeColor.schemeLight, schemeVariant); - final baseDarkTheme = (themeColor == null - ? FladderTheme.theme(darkDynamic ?? FladderTheme.defaultScheme(Brightness.dark), schemeVariant) - : FladderTheme.theme(themeColor.schemeDark, schemeVariant)); + final amoledOverwrite = amoledBlack ? Colors.black : null; - // Apply Chinese font for non-Linux platforms (Windows, macOS, Android, iOS) - final lightTheme = isLinux - ? baseLightTheme - : FladderTheme.applyChineseFontToTheme( - lightTheme: baseLightTheme, - darkTheme: baseDarkTheme, - ); - final darkTheme = isLinux ? baseDarkTheme : FladderTheme.applyChineseFontToDarkTheme(darkTheme: baseDarkTheme); - - final amoledOverwrite = amoledBlack ? Colors.black : null; - return ThemesData( - light: lightTheme, - dark: darkTheme, - child: MaterialApp.router( - theme: lightTheme, - scrollBehavior: scrollBehaviour.copyWith( - dragDevices: { - ...scrollBehaviour.dragDevices, - mouseDrag ? PointerDeviceKind.mouse : null, - }.nonNulls.toSet(), - ), - localizationsDelegates: FladderLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - locale: language, - localeResolutionCallback: (locale, supportedLocales) { - const fallback = Locale('en'); - if (locale == null) return fallback; - if (supportedLocales.contains(locale)) { - return locale; - } - final matchByLanguage = supportedLocales.firstWhere( - (l) => l.languageCode == locale.languageCode, - orElse: () => fallback, - ); + return AdaptiveColor( + child: (darkTheme, lightTheme) => ThemesData( + light: lightTheme, + dark: darkTheme, + child: MaterialApp.router( + theme: lightTheme, + scrollBehavior: scrollBehaviour.copyWith( + dragDevices: { + ...scrollBehaviour.dragDevices, + mouseDrag ? PointerDeviceKind.mouse : null, + }.nonNulls.toSet(), + ), + localizationsDelegates: FladderLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + locale: language, + localeResolutionCallback: (locale, supportedLocales) { + const fallback = Locale('en'); + if (locale == null) return fallback; + if (supportedLocales.contains(locale)) { + return locale; + } + final matchByLanguage = supportedLocales.firstWhere( + (l) => l.languageCode == locale.languageCode, + orElse: () => fallback, + ); - return matchByLanguage; - }, - builder: (context, child) => MediaQueryScaler( - child: LocalizationContextWrapper( - child: PipLifecycleController(child: child ?? Container()), - currentLocale: language, - ), - enable: ref.read(argumentsStateProvider).leanBackMode, + return matchByLanguage; + }, + builder: (context, child) => MediaQueryScaler( + child: LocalizationContextWrapper( + child: PipLifecycleController(child: child ?? Container()), + currentLocale: language, ), - debugShowCheckedModeBanner: false, - darkTheme: darkTheme.copyWith( - scaffoldBackgroundColor: amoledOverwrite, - cardColor: amoledOverwrite, - canvasColor: amoledOverwrite, - colorScheme: darkTheme.colorScheme.copyWith( - surface: amoledOverwrite, - surfaceContainerHighest: amoledOverwrite, - surfaceContainerLow: amoledOverwrite, - ), - ), - themeMode: themeMode, - routerConfig: autoRouter.config( - deepLinkBuilder: (deepLink) => deepLinkBuilder(deepLink.uri), + enable: ref.read(argumentsStateProvider).leanBackMode, + ), + debugShowCheckedModeBanner: false, + darkTheme: darkTheme.copyWith( + scaffoldBackgroundColor: amoledOverwrite, + cardColor: amoledOverwrite, + canvasColor: amoledOverwrite, + colorScheme: darkTheme.colorScheme.copyWith( + surface: amoledOverwrite, + surfaceContainerHighest: amoledOverwrite, + surfaceContainerLow: amoledOverwrite, ), ), - ); - }, + themeMode: themeMode, + routerConfig: autoRouter.config( + deepLinkBuilder: (deepLink) => deepLinkBuilder(deepLink.uri), + ), + ), + ), ); } } diff --git a/lib/widgets/shared/adaptive_color.dart b/lib/widgets/shared/adaptive_color.dart new file mode 100644 index 000000000..71b769f9c --- /dev/null +++ b/lib/widgets/shared/adaptive_color.dart @@ -0,0 +1,110 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:dynamic_color/dynamic_color.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +// ignore: depend_on_referenced_packages +import 'package:material_color_utilities/material_color_utilities.dart'; + +import 'package:fladder/providers/settings/client_settings_provider.dart'; +import 'package:fladder/theme.dart'; +import 'package:fladder/util/themes_data.dart'; + +class AdaptiveColor extends ConsumerStatefulWidget { + final Widget Function(ThemeData dark, ThemeData light) child; + const AdaptiveColor({required this.child, super.key}); + + @override + ConsumerState createState() => AdaptiveColorState(); +} + +class AdaptiveColorState extends ConsumerState with WidgetsBindingObserver { + ColorScheme? _light; + ColorScheme? _dark; + + CorePalette? _corePalette; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + _fetchColors(); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + _fetchColors(); + } + } + + Future _fetchColors() async { + try { + final corePalette = await DynamicColorPlugin.getCorePalette(); + if (corePalette == _corePalette) { + return; + } + _corePalette = corePalette; + if (corePalette != null && mounted) { + setState(() { + _light = corePalette.toColorScheme(brightness: Brightness.light); + _dark = corePalette.toColorScheme(brightness: Brightness.dark); + }); + return; + } + } on PlatformException { + if (kDebugMode) debugPrint('dynamic_color: Failed to obtain core palette.'); + } + + try { + final accentColor = await DynamicColorPlugin.getAccentColor(); + if (accentColor != null && mounted) { + setState(() { + _light = ColorScheme.fromSeed(seedColor: accentColor, brightness: Brightness.light); + _dark = ColorScheme.fromSeed(seedColor: accentColor, brightness: Brightness.dark); + }); + return; + } + } on PlatformException { + if (kDebugMode) debugPrint('dynamic_color: Failed to obtain accent color.'); + } + } + + @override + Widget build(BuildContext context) { + final isLinux = defaultTargetPlatform == TargetPlatform.linux; + final themeColor = ref.watch(clientSettingsProvider.select((value) => value.themeColor)); + final schemeVariant = ref.watch(clientSettingsProvider.select((value) => value.schemeVariant)); + + final fallbackLight = FladderTheme.defaultScheme(Brightness.light); + final fallbackDark = FladderTheme.defaultScheme(Brightness.dark); + + final baseLightTheme = themeColor == null + ? FladderTheme.theme(_light ?? fallbackLight, schemeVariant) + : FladderTheme.theme(themeColor.schemeLight, schemeVariant); + + final baseDarkTheme = themeColor == null + ? FladderTheme.theme(_dark ?? fallbackDark, schemeVariant) + : FladderTheme.theme(themeColor.schemeDark, schemeVariant); + + // Apply fonts + final lightTheme = isLinux + ? baseLightTheme + : FladderTheme.applyChineseFontToTheme(lightTheme: baseLightTheme, darkTheme: baseDarkTheme); + + final darkTheme = isLinux ? baseDarkTheme : FladderTheme.applyChineseFontToDarkTheme(darkTheme: baseDarkTheme); + + return ThemesData( + light: lightTheme, + dark: darkTheme, + child: widget.child(darkTheme, lightTheme), + ); + } +}