diff --git a/lib/main.dart b/lib/main.dart index 4ce07f3..528c2a7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:logging/logging.dart'; @@ -51,24 +52,58 @@ class MyApp extends ConsumerWidget { if (needOnboarding) { routerConfig.goNamed(Routes.onboarding.name); } + final appSettings = ref.watch(appSettingsProvider); - try { - return MaterialApp.router( - // debugShowCheckedModeBanner: false, - theme: brandLightTheme, - darkTheme: brandDarkTheme, - themeMode: ref.watch(appSettingsProvider).themeSettings.isDarkMode - ? ThemeMode.dark - : ThemeMode.light, - routerConfig: routerConfig, - ); - } catch (e) { - debugPrintStack(stackTrace: StackTrace.current, label: e.toString()); - if (needOnboarding) { - routerConfig.goNamed(Routes.onboarding.name); - } - return const SizedBox.shrink(); - } + final themeSettings = appSettings.themeSettings; + return DynamicColorBuilder( + builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) { + // Decide on a colour/brightness scheme based on OS and user settings + ColorScheme lightColorScheme; + ColorScheme darkColorScheme; + if (lightDynamic != null && + darkDynamic != null && + themeSettings.useMaterialThemeFromSystem) { + lightColorScheme = lightDynamic.harmonized(); + darkColorScheme = darkDynamic.harmonized(); + } else { + lightColorScheme = brandLightColorScheme; + darkColorScheme = brandDarkColorScheme; + } + + // set the background and surface colors to pure black in the amoled theme + if (themeSettings.highContrast) { + darkColorScheme = + darkColorScheme.copyWith(surface: Colors.black).harmonized(); + } + + final themeLight = ThemeData( + useMaterial3: true, + colorScheme: lightColorScheme, + ); + final themeDark = ThemeData( + useMaterial3: true, + colorScheme: darkColorScheme, + brightness: Brightness.dark, + ); + + try { + return MaterialApp.router( + // debugShowCheckedModeBanner: false, + theme: themeLight, + darkTheme: themeDark, + themeMode: + themeSettings.isDarkMode ? ThemeMode.dark : ThemeMode.light, + routerConfig: routerConfig, + ); + } catch (e) { + debugPrintStack(stackTrace: StackTrace.current, label: e.toString()); + if (needOnboarding) { + routerConfig.goNamed(Routes.onboarding.name); + } + return const SizedBox.shrink(); + } + }, + ); } } diff --git a/lib/settings/models/app_settings.dart b/lib/settings/models/app_settings.dart index 80262ea..cf6a8a0 100644 --- a/lib/settings/models/app_settings.dart +++ b/lib/settings/models/app_settings.dart @@ -25,10 +25,17 @@ class AppSettings with _$AppSettings { _$AppSettingsFromJson(json); } +enum ThemeType { light, dark, system } + @freezed class ThemeSettings with _$ThemeSettings { const factory ThemeSettings({ @Default(true) bool isDarkMode, + @Default(ThemeType.system) ThemeType themeType, + @Default(false) bool highContrast, + @Default(true) bool useMaterialThemeFromSystem, + @Default('#FF311B92') String customThemeColor, + @Default(true) bool useMaterialThemeOfPlayingItem, @Default(true) bool useMaterialThemeOnItemPage, @Default(true) bool useCurrentPlayerThemeThroughoutApp, }) = _ThemeSettings; diff --git a/lib/settings/models/app_settings.freezed.dart b/lib/settings/models/app_settings.freezed.dart index 3f8fd48..9db9469 100644 --- a/lib/settings/models/app_settings.freezed.dart +++ b/lib/settings/models/app_settings.freezed.dart @@ -379,6 +379,11 @@ ThemeSettings _$ThemeSettingsFromJson(Map json) { /// @nodoc mixin _$ThemeSettings { bool get isDarkMode => throw _privateConstructorUsedError; + ThemeType get themeType => throw _privateConstructorUsedError; + bool get highContrast => throw _privateConstructorUsedError; + bool get useMaterialThemeFromSystem => throw _privateConstructorUsedError; + String get customThemeColor => throw _privateConstructorUsedError; + bool get useMaterialThemeOfPlayingItem => throw _privateConstructorUsedError; bool get useMaterialThemeOnItemPage => throw _privateConstructorUsedError; bool get useCurrentPlayerThemeThroughoutApp => throw _privateConstructorUsedError; @@ -401,6 +406,11 @@ abstract class $ThemeSettingsCopyWith<$Res> { @useResult $Res call( {bool isDarkMode, + ThemeType themeType, + bool highContrast, + bool useMaterialThemeFromSystem, + String customThemeColor, + bool useMaterialThemeOfPlayingItem, bool useMaterialThemeOnItemPage, bool useCurrentPlayerThemeThroughoutApp}); } @@ -421,6 +431,11 @@ class _$ThemeSettingsCopyWithImpl<$Res, $Val extends ThemeSettings> @override $Res call({ Object? isDarkMode = null, + Object? themeType = null, + Object? highContrast = null, + Object? useMaterialThemeFromSystem = null, + Object? customThemeColor = null, + Object? useMaterialThemeOfPlayingItem = null, Object? useMaterialThemeOnItemPage = null, Object? useCurrentPlayerThemeThroughoutApp = null, }) { @@ -429,6 +444,26 @@ class _$ThemeSettingsCopyWithImpl<$Res, $Val extends ThemeSettings> ? _value.isDarkMode : isDarkMode // ignore: cast_nullable_to_non_nullable as bool, + themeType: null == themeType + ? _value.themeType + : themeType // ignore: cast_nullable_to_non_nullable + as ThemeType, + highContrast: null == highContrast + ? _value.highContrast + : highContrast // ignore: cast_nullable_to_non_nullable + as bool, + useMaterialThemeFromSystem: null == useMaterialThemeFromSystem + ? _value.useMaterialThemeFromSystem + : useMaterialThemeFromSystem // ignore: cast_nullable_to_non_nullable + as bool, + customThemeColor: null == customThemeColor + ? _value.customThemeColor + : customThemeColor // ignore: cast_nullable_to_non_nullable + as String, + useMaterialThemeOfPlayingItem: null == useMaterialThemeOfPlayingItem + ? _value.useMaterialThemeOfPlayingItem + : useMaterialThemeOfPlayingItem // ignore: cast_nullable_to_non_nullable + as bool, useMaterialThemeOnItemPage: null == useMaterialThemeOnItemPage ? _value.useMaterialThemeOnItemPage : useMaterialThemeOnItemPage // ignore: cast_nullable_to_non_nullable @@ -452,6 +487,11 @@ abstract class _$$ThemeSettingsImplCopyWith<$Res> @useResult $Res call( {bool isDarkMode, + ThemeType themeType, + bool highContrast, + bool useMaterialThemeFromSystem, + String customThemeColor, + bool useMaterialThemeOfPlayingItem, bool useMaterialThemeOnItemPage, bool useCurrentPlayerThemeThroughoutApp}); } @@ -470,6 +510,11 @@ class __$$ThemeSettingsImplCopyWithImpl<$Res> @override $Res call({ Object? isDarkMode = null, + Object? themeType = null, + Object? highContrast = null, + Object? useMaterialThemeFromSystem = null, + Object? customThemeColor = null, + Object? useMaterialThemeOfPlayingItem = null, Object? useMaterialThemeOnItemPage = null, Object? useCurrentPlayerThemeThroughoutApp = null, }) { @@ -478,6 +523,26 @@ class __$$ThemeSettingsImplCopyWithImpl<$Res> ? _value.isDarkMode : isDarkMode // ignore: cast_nullable_to_non_nullable as bool, + themeType: null == themeType + ? _value.themeType + : themeType // ignore: cast_nullable_to_non_nullable + as ThemeType, + highContrast: null == highContrast + ? _value.highContrast + : highContrast // ignore: cast_nullable_to_non_nullable + as bool, + useMaterialThemeFromSystem: null == useMaterialThemeFromSystem + ? _value.useMaterialThemeFromSystem + : useMaterialThemeFromSystem // ignore: cast_nullable_to_non_nullable + as bool, + customThemeColor: null == customThemeColor + ? _value.customThemeColor + : customThemeColor // ignore: cast_nullable_to_non_nullable + as String, + useMaterialThemeOfPlayingItem: null == useMaterialThemeOfPlayingItem + ? _value.useMaterialThemeOfPlayingItem + : useMaterialThemeOfPlayingItem // ignore: cast_nullable_to_non_nullable + as bool, useMaterialThemeOnItemPage: null == useMaterialThemeOnItemPage ? _value.useMaterialThemeOnItemPage : useMaterialThemeOnItemPage // ignore: cast_nullable_to_non_nullable @@ -496,6 +561,11 @@ class __$$ThemeSettingsImplCopyWithImpl<$Res> class _$ThemeSettingsImpl implements _ThemeSettings { const _$ThemeSettingsImpl( {this.isDarkMode = true, + this.themeType = ThemeType.system, + this.highContrast = false, + this.useMaterialThemeFromSystem = true, + this.customThemeColor = '#FF311B92', + this.useMaterialThemeOfPlayingItem = true, this.useMaterialThemeOnItemPage = true, this.useCurrentPlayerThemeThroughoutApp = true}); @@ -507,6 +577,21 @@ class _$ThemeSettingsImpl implements _ThemeSettings { final bool isDarkMode; @override @JsonKey() + final ThemeType themeType; + @override + @JsonKey() + final bool highContrast; + @override + @JsonKey() + final bool useMaterialThemeFromSystem; + @override + @JsonKey() + final String customThemeColor; + @override + @JsonKey() + final bool useMaterialThemeOfPlayingItem; + @override + @JsonKey() final bool useMaterialThemeOnItemPage; @override @JsonKey() @@ -514,7 +599,7 @@ class _$ThemeSettingsImpl implements _ThemeSettings { @override String toString() { - return 'ThemeSettings(isDarkMode: $isDarkMode, useMaterialThemeOnItemPage: $useMaterialThemeOnItemPage, useCurrentPlayerThemeThroughoutApp: $useCurrentPlayerThemeThroughoutApp)'; + return 'ThemeSettings(isDarkMode: $isDarkMode, themeType: $themeType, highContrast: $highContrast, useMaterialThemeFromSystem: $useMaterialThemeFromSystem, customThemeColor: $customThemeColor, useMaterialThemeOfPlayingItem: $useMaterialThemeOfPlayingItem, useMaterialThemeOnItemPage: $useMaterialThemeOnItemPage, useCurrentPlayerThemeThroughoutApp: $useCurrentPlayerThemeThroughoutApp)'; } @override @@ -524,6 +609,20 @@ class _$ThemeSettingsImpl implements _ThemeSettings { other is _$ThemeSettingsImpl && (identical(other.isDarkMode, isDarkMode) || other.isDarkMode == isDarkMode) && + (identical(other.themeType, themeType) || + other.themeType == themeType) && + (identical(other.highContrast, highContrast) || + other.highContrast == highContrast) && + (identical(other.useMaterialThemeFromSystem, + useMaterialThemeFromSystem) || + other.useMaterialThemeFromSystem == + useMaterialThemeFromSystem) && + (identical(other.customThemeColor, customThemeColor) || + other.customThemeColor == customThemeColor) && + (identical(other.useMaterialThemeOfPlayingItem, + useMaterialThemeOfPlayingItem) || + other.useMaterialThemeOfPlayingItem == + useMaterialThemeOfPlayingItem) && (identical(other.useMaterialThemeOnItemPage, useMaterialThemeOnItemPage) || other.useMaterialThemeOnItemPage == @@ -536,8 +635,16 @@ class _$ThemeSettingsImpl implements _ThemeSettings { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, isDarkMode, - useMaterialThemeOnItemPage, useCurrentPlayerThemeThroughoutApp); + int get hashCode => Object.hash( + runtimeType, + isDarkMode, + themeType, + highContrast, + useMaterialThemeFromSystem, + customThemeColor, + useMaterialThemeOfPlayingItem, + useMaterialThemeOnItemPage, + useCurrentPlayerThemeThroughoutApp); /// Create a copy of ThemeSettings /// with the given fields replaced by the non-null parameter values. @@ -558,6 +665,11 @@ class _$ThemeSettingsImpl implements _ThemeSettings { abstract class _ThemeSettings implements ThemeSettings { const factory _ThemeSettings( {final bool isDarkMode, + final ThemeType themeType, + final bool highContrast, + final bool useMaterialThemeFromSystem, + final String customThemeColor, + final bool useMaterialThemeOfPlayingItem, final bool useMaterialThemeOnItemPage, final bool useCurrentPlayerThemeThroughoutApp}) = _$ThemeSettingsImpl; @@ -567,6 +679,16 @@ abstract class _ThemeSettings implements ThemeSettings { @override bool get isDarkMode; @override + ThemeType get themeType; + @override + bool get highContrast; + @override + bool get useMaterialThemeFromSystem; + @override + String get customThemeColor; + @override + bool get useMaterialThemeOfPlayingItem; + @override bool get useMaterialThemeOnItemPage; @override bool get useCurrentPlayerThemeThroughoutApp; diff --git a/lib/settings/models/app_settings.g.dart b/lib/settings/models/app_settings.g.dart index 90b317e..65921af 100644 --- a/lib/settings/models/app_settings.g.dart +++ b/lib/settings/models/app_settings.g.dart @@ -47,6 +47,14 @@ Map _$$AppSettingsImplToJson(_$AppSettingsImpl instance) => _$ThemeSettingsImpl _$$ThemeSettingsImplFromJson(Map json) => _$ThemeSettingsImpl( isDarkMode: json['isDarkMode'] as bool? ?? true, + themeType: $enumDecodeNullable(_$ThemeTypeEnumMap, json['themeType']) ?? + ThemeType.system, + highContrast: json['highContrast'] as bool? ?? false, + useMaterialThemeFromSystem: + json['useMaterialThemeFromSystem'] as bool? ?? true, + customThemeColor: json['customThemeColor'] as String? ?? '#FF311B92', + useMaterialThemeOfPlayingItem: + json['useMaterialThemeOfPlayingItem'] as bool? ?? true, useMaterialThemeOnItemPage: json['useMaterialThemeOnItemPage'] as bool? ?? true, useCurrentPlayerThemeThroughoutApp: @@ -56,11 +64,22 @@ _$ThemeSettingsImpl _$$ThemeSettingsImplFromJson(Map json) => Map _$$ThemeSettingsImplToJson(_$ThemeSettingsImpl instance) => { 'isDarkMode': instance.isDarkMode, + 'themeType': _$ThemeTypeEnumMap[instance.themeType]!, + 'highContrast': instance.highContrast, + 'useMaterialThemeFromSystem': instance.useMaterialThemeFromSystem, + 'customThemeColor': instance.customThemeColor, + 'useMaterialThemeOfPlayingItem': instance.useMaterialThemeOfPlayingItem, 'useMaterialThemeOnItemPage': instance.useMaterialThemeOnItemPage, 'useCurrentPlayerThemeThroughoutApp': instance.useCurrentPlayerThemeThroughoutApp, }; +const _$ThemeTypeEnumMap = { + ThemeType.light: 'light', + ThemeType.dark: 'dark', + ThemeType.system: 'system', +}; + _$PlayerSettingsImpl _$$PlayerSettingsImplFromJson(Map json) => _$PlayerSettingsImpl( miniPlayerSettings: json['miniPlayerSettings'] == null diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index facc1fd..35f4ad8 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -4,18 +4,12 @@ import 'package:flutter/material.dart'; const brandColor = Color(0xFF311B92); const brandColorLight = Color(0xFF604CEC); -final ThemeData brandLightTheme = ThemeData( +final brandLightColorScheme = ColorScheme.fromSeed( + seedColor: brandColor, brightness: Brightness.light, - colorScheme: ColorScheme.fromSeed( - seedColor: brandColor, - brightness: Brightness.light, - ), ); -final ThemeData brandDarkTheme = ThemeData( +final brandDarkColorScheme = ColorScheme.fromSeed( + seedColor: brandColor, brightness: Brightness.dark, - colorScheme: ColorScheme.fromSeed( - seedColor: brandColor, - brightness: Brightness.dark, - ), ); diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 879195f..606c5a6 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,11 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) dynamic_color_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); + dynamic_color_plugin_register_with_registrar(dynamic_color_registrar); g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin"); isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 026cbff..6023074 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + dynamic_color isar_flutter_libs media_kit_libs_linux url_launcher_linux diff --git a/pubspec.lock b/pubspec.lock index 670cc8f..a6ff204 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -390,6 +390,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + dynamic_color: + dependency: "direct main" + description: + name: dynamic_color + sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d + url: "https://pub.dev" + source: hosted + version: "1.7.0" easy_stepper: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 1b4fb1c..6366275 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,7 @@ dependencies: cupertino_icons: ^1.0.6 device_info_plus: ^10.1.0 duration_picker: ^1.2.0 + dynamic_color: ^1.7.0 easy_stepper: ^0.8.4 file_picker: ^8.1.2 flutter: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index d043b0b..de21556 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -13,6 +14,8 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { + DynamicColorPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); IsarFlutterLibsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin")); MediaKitLibsWindowsAudioPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 9839b50..13d504d 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + dynamic_color isar_flutter_libs media_kit_libs_windows_audio permission_handler_windows