diff --git a/lib/features/item_viewer/view/library_item_hero_section.dart b/lib/features/item_viewer/view/library_item_hero_section.dart index fb6a51c..c0efa6c 100644 --- a/lib/features/item_viewer/view/library_item_hero_section.dart +++ b/lib/features/item_viewer/view/library_item_hero_section.dart @@ -15,7 +15,7 @@ import 'package:vaani/settings/app_settings_provider.dart'; import 'package:vaani/shared/extensions/duration_format.dart'; import 'package:vaani/shared/extensions/model_conversions.dart'; import 'package:vaani/shared/widgets/shelves/book_shelf.dart'; -import 'package:vaani/theme/theme_from_cover_provider.dart'; +import 'package:vaani/theme/providers/theme_from_cover_provider.dart'; class LibraryItemHeroSection extends HookConsumerWidget { const LibraryItemHeroSection({ diff --git a/lib/features/player/view/audiobook_player.dart b/lib/features/player/view/audiobook_player.dart index b659650..cc6ecef 100644 --- a/lib/features/player/view/audiobook_player.dart +++ b/lib/features/player/view/audiobook_player.dart @@ -14,7 +14,7 @@ import 'package:vaani/features/player/providers/player_form.dart'; import 'package:vaani/settings/app_settings_provider.dart'; import 'package:vaani/shared/extensions/inverse_lerp.dart'; import 'package:vaani/shared/widgets/shelves/book_shelf.dart'; -import 'package:vaani/theme/theme_from_cover_provider.dart'; +import 'package:vaani/theme/providers/theme_from_cover_provider.dart'; import 'player_when_expanded.dart'; import 'player_when_minimized.dart'; diff --git a/lib/main.dart b/lib/main.dart index dda1925..06f7bbf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,12 +8,14 @@ import 'package:vaani/features/downloads/providers/download_manager.dart'; import 'package:vaani/features/logging/core/logger.dart'; import 'package:vaani/features/playback_reporting/providers/playback_reporter_provider.dart'; import 'package:vaani/features/player/core/init.dart'; -import 'package:vaani/features/player/providers/audiobook_player.dart'; +import 'package:vaani/features/player/providers/audiobook_player.dart' + show simpleAudiobookPlayerProvider; import 'package:vaani/features/shake_detection/providers/shake_detector.dart'; import 'package:vaani/features/sleep_timer/providers/sleep_timer_provider.dart'; import 'package:vaani/router/router.dart'; import 'package:vaani/settings/api_settings_provider.dart'; import 'package:vaani/settings/app_settings_provider.dart'; +import 'package:vaani/theme/providers/system_them_provider.dart'; import 'package:vaani/theme/theme.dart'; final appLogger = Logger('vaani'); @@ -53,73 +55,68 @@ class MyApp extends ConsumerWidget { routerConfig.goNamed(Routes.onboarding.name); } final appSettings = ref.watch(appSettingsProvider); - 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.harmonized(); - darkColorScheme = brandDarkColorScheme.harmonized(); - } - // set the background and surface colors to pure black in the amoled theme - if (MediaQuery.of(context).highContrast || themeSettings.highContrast) { - lightColorScheme = - lightColorScheme.copyWith(surface: Colors.white).harmonized(); - darkColorScheme = - darkColorScheme.copyWith(surface: Colors.black).harmonized(); - } + ColorScheme lightColorScheme = brandLightColorScheme; + ColorScheme darkColorScheme = brandDarkColorScheme; - final themeLight = ThemeData( - useMaterial3: true, - colorScheme: lightColorScheme, - ); - final themeDark = ThemeData( - useMaterial3: true, - colorScheme: darkColorScheme, - brightness: Brightness.dark, - // TODO bottom sheet theme is not working - bottomSheetTheme: BottomSheetThemeData( - backgroundColor: darkColorScheme.surface, - ), - ); - final themeLightHighContrast = themeLight.copyWith( - colorScheme: - lightColorScheme.copyWith(surface: Colors.white).harmonized(), - ); - final themeDarkHighContrast = themeDark.copyWith( - colorScheme: - darkColorScheme.copyWith(surface: Colors.black).harmonized(), - ); + final shouldUseHighContrast = + MediaQuery.of(context).highContrast || themeSettings.highContrast; - try { - return MaterialApp.router( - // debugShowCheckedModeBanner: false, - theme: themeLight, - darkTheme: themeDark, - themeMode: themeSettings.themeMode, - highContrastTheme: themeLightHighContrast, - highContrastDarkTheme: themeDarkHighContrast, - routerConfig: routerConfig, - themeAnimationCurve: Curves.easeInOut, - ); - } catch (e) { - debugPrintStack(stackTrace: StackTrace.current, label: e.toString()); - if (needOnboarding) { - routerConfig.goNamed(Routes.onboarding.name); - } - return const SizedBox.shrink(); - } - }, + if (shouldUseHighContrast) { + lightColorScheme = lightColorScheme.copyWith( + surface: Colors.white, + ); + darkColorScheme = darkColorScheme.copyWith( + surface: Colors.black, + ); + } + + if (themeSettings.useMaterialThemeFromSystem) { + appLogger.info('Using theme from system'); + var themes = + ref.watch(systemThemeProvider(highContrast: shouldUseHighContrast)); + if (themes.valueOrNull != null) { + lightColorScheme = themes.valueOrNull!.$1; + darkColorScheme = themes.valueOrNull!.$2; + } + } + + // AudiobookPlayer? player; + + // if (themeSettings.useMaterialThemeOfPlayingItem) { + // appLogger.info('Using theme from playing item'); + // player = ref.watch(audiobookPlayerProvider); + // } + final appThemeLight = ThemeData( + useMaterial3: true, + colorScheme: lightColorScheme.harmonized(), ); + final appThemeDark = ThemeData( + useMaterial3: true, + colorScheme: darkColorScheme.harmonized(), + brightness: Brightness.dark, + // TODO bottom sheet theme is not working + bottomSheetTheme: BottomSheetThemeData( + backgroundColor: darkColorScheme.surface, + ), + ); + try { + return MaterialApp.router( + // debugShowCheckedModeBanner: false, + theme: appThemeLight, + darkTheme: appThemeDark, + themeMode: themeSettings.themeMode, + routerConfig: routerConfig, + themeAnimationCurve: Curves.easeInOut, + ); + } catch (e) { + debugPrintStack(stackTrace: StackTrace.current, label: e.toString()); + if (needOnboarding) { + routerConfig.goNamed(Routes.onboarding.name); + } + return const SizedBox.shrink(); + } } } diff --git a/lib/shared/widgets/shelves/book_shelf.dart b/lib/shared/widgets/shelves/book_shelf.dart index 692d3b3..db6c1b3 100644 --- a/lib/shared/widgets/shelves/book_shelf.dart +++ b/lib/shared/widgets/shelves/book_shelf.dart @@ -17,7 +17,7 @@ import 'package:vaani/router/router.dart'; import 'package:vaani/settings/app_settings_provider.dart'; import 'package:vaani/shared/extensions/model_conversions.dart'; import 'package:vaani/shared/widgets/shelves/home_shelf.dart'; -import 'package:vaani/theme/theme_from_cover_provider.dart'; +import 'package:vaani/theme/providers/theme_from_cover_provider.dart'; /// A shelf that displays books on the home page class BookHomeShelf extends HookConsumerWidget { diff --git a/lib/theme/providers/system_them_provider.dart b/lib/theme/providers/system_them_provider.dart new file mode 100644 index 0000000..cd2e9a4 --- /dev/null +++ b/lib/theme/providers/system_them_provider.dart @@ -0,0 +1,73 @@ +import 'package:dynamic_color/dynamic_color.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:logging/logging.dart'; +import 'package:material_color_utilities/material_color_utilities.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'system_them_provider.g.dart'; + +final _logger = Logger('SystemThemeProvider'); + +/// copied from [DynamicColorBuilder] +@Riverpod(keepAlive: true) +FutureOr<(ColorScheme light, ColorScheme dark)?> systemTheme( + SystemThemeRef ref, { + bool highContrast = false, +}) async { + _logger.fine('Generating system theme'); + ColorScheme? schemeLight; + ColorScheme? schemeDark; + // Platform messages may fail, so we use a try/catch PlatformException. + try { + CorePalette? corePalette = await DynamicColorPlugin.getCorePalette(); + + if (corePalette != null) { + _logger.fine('dynamic_color: Core palette detected.'); + schemeLight = corePalette.toColorScheme(brightness: Brightness.light); + schemeDark = corePalette.toColorScheme(brightness: Brightness.dark); + } + } on PlatformException { + _logger.warning('dynamic_color: Failed to obtain core palette.'); + } + + if (schemeLight == null || schemeDark == null) { + try { + final Color? accentColor = await DynamicColorPlugin.getAccentColor(); + + if (accentColor != null) { + _logger.fine('dynamic_color: Accent color detected.'); + schemeLight = ColorScheme.fromSeed( + seedColor: accentColor, + brightness: Brightness.light, + ); + schemeDark = ColorScheme.fromSeed( + seedColor: accentColor, + brightness: Brightness.dark, + ); + } + } on PlatformException { + _logger.warning('dynamic_color: Failed to obtain accent color.'); + } + } + + if (schemeLight == null || schemeDark == null) { + _logger + .warning('dynamic_color: Dynamic color not detected on this device.'); + return null; + } + // set high contrast theme + if (highContrast) { + schemeLight = schemeLight + .copyWith( + surface: Colors.white, + ) + .harmonized(); + schemeDark = schemeDark + .copyWith( + surface: Colors.black, + ) + .harmonized(); + } + return (schemeLight, schemeDark); +} diff --git a/lib/theme/providers/system_them_provider.g.dart b/lib/theme/providers/system_them_provider.g.dart new file mode 100644 index 0000000..e64cf56 --- /dev/null +++ b/lib/theme/providers/system_them_provider.g.dart @@ -0,0 +1,177 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'system_them_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$systemThemeHash() => r'0af4a012a2a2b2fa91642a1313515cba02cd3535'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// copied from [DynamicColorBuilder] +/// +/// Copied from [systemTheme]. +@ProviderFor(systemTheme) +const systemThemeProvider = SystemThemeFamily(); + +/// copied from [DynamicColorBuilder] +/// +/// Copied from [systemTheme]. +class SystemThemeFamily + extends Family> { + /// copied from [DynamicColorBuilder] + /// + /// Copied from [systemTheme]. + const SystemThemeFamily(); + + /// copied from [DynamicColorBuilder] + /// + /// Copied from [systemTheme]. + SystemThemeProvider call({ + bool highContrast = false, + }) { + return SystemThemeProvider( + highContrast: highContrast, + ); + } + + @override + SystemThemeProvider getProviderOverride( + covariant SystemThemeProvider provider, + ) { + return call( + highContrast: provider.highContrast, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'systemThemeProvider'; +} + +/// copied from [DynamicColorBuilder] +/// +/// Copied from [systemTheme]. +class SystemThemeProvider + extends FutureProvider<(ColorScheme light, ColorScheme dark)?> { + /// copied from [DynamicColorBuilder] + /// + /// Copied from [systemTheme]. + SystemThemeProvider({ + bool highContrast = false, + }) : this._internal( + (ref) => systemTheme( + ref as SystemThemeRef, + highContrast: highContrast, + ), + from: systemThemeProvider, + name: r'systemThemeProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$systemThemeHash, + dependencies: SystemThemeFamily._dependencies, + allTransitiveDependencies: + SystemThemeFamily._allTransitiveDependencies, + highContrast: highContrast, + ); + + SystemThemeProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.highContrast, + }) : super.internal(); + + final bool highContrast; + + @override + Override overrideWith( + FutureOr<(ColorScheme light, ColorScheme dark)?> Function( + SystemThemeRef provider) + create, + ) { + return ProviderOverride( + origin: this, + override: SystemThemeProvider._internal( + (ref) => create(ref as SystemThemeRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + highContrast: highContrast, + ), + ); + } + + @override + FutureProviderElement<(ColorScheme light, ColorScheme dark)?> + createElement() { + return _SystemThemeProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is SystemThemeProvider && other.highContrast == highContrast; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, highContrast.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin SystemThemeRef + on FutureProviderRef<(ColorScheme light, ColorScheme dark)?> { + /// The parameter `highContrast` of this provider. + bool get highContrast; +} + +class _SystemThemeProviderElement + extends FutureProviderElement<(ColorScheme light, ColorScheme dark)?> + with SystemThemeRef { + _SystemThemeProviderElement(super.provider); + + @override + bool get highContrast => (origin as SystemThemeProvider).highContrast; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/theme/theme_from_cover_provider.dart b/lib/theme/providers/theme_from_cover_provider.dart similarity index 100% rename from lib/theme/theme_from_cover_provider.dart rename to lib/theme/providers/theme_from_cover_provider.dart diff --git a/lib/theme/theme_from_cover_provider.g.dart b/lib/theme/providers/theme_from_cover_provider.g.dart similarity index 100% rename from lib/theme/theme_from_cover_provider.g.dart rename to lib/theme/providers/theme_from_cover_provider.g.dart