diff --git a/lib/collections/spotube_icons.dart b/lib/collections/spotube_icons.dart index e0839277..9676237e 100644 --- a/lib/collections/spotube_icons.dart +++ b/lib/collections/spotube_icons.dart @@ -76,4 +76,5 @@ abstract class SpotubeIcons { static const hoverOff = Icons.back_hand_outlined; static const dragHandle = Icons.drag_indicator; static const lightning = Icons.flash_on_rounded; + static const colorSync = FeatherIcons.activity; } diff --git a/lib/main.dart b/lib/main.dart index 9bf71b9c..7bc7c71d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,6 +19,7 @@ import 'package:spotube/collections/routes.dart'; import 'package:spotube/collections/intents.dart'; import 'package:spotube/models/logger.dart'; import 'package:spotube/provider/downloader_provider.dart'; +import 'package:spotube/provider/palette_provider.dart'; import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/services/audio_player.dart'; import 'package:spotube/services/pocketbase.dart'; @@ -188,6 +189,8 @@ class SpotubeState extends ConsumerState { ref.watch(userPreferencesProvider.select((s) => s.themeMode)); final accentMaterialColor = ref.watch(userPreferencesProvider.select((s) => s.accentColorScheme)); + final paletteColor = + ref.watch(paletteProvider.select((s) => s?.dominantColor?.color)); useInitSysTray(ref); @@ -209,8 +212,8 @@ class SpotubeState extends ConsumerState { return DragToResizeArea(child: child!); }, themeMode: themeMode, - theme: theme(accentMaterialColor, Brightness.light), - darkTheme: theme(accentMaterialColor, Brightness.dark), + theme: theme(paletteColor ?? accentMaterialColor, Brightness.light), + darkTheme: theme(paletteColor ?? accentMaterialColor, Brightness.dark), shortcuts: { ...WidgetsApp.defaultShortcuts.map((key, value) { return MapEntry( diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 0ce9f7d3..32f118e0 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -203,6 +203,15 @@ class SettingsPage extends HookConsumerWidget { ), onTap: pickColorScheme(), ), + SwitchListTile( + secondary: const Icon(SpotubeIcons.colorSync), + title: const Text("Sync Album Color"), + subtitle: const Text( + "Uses the dominant color of the album art as the accent color", + ), + value: preferences.albumColorSync, + onChanged: preferences.setAlbumColorSync, + ), Text( " Playback", style: theme.textTheme.headlineSmall diff --git a/lib/provider/palette_provider.dart b/lib/provider/palette_provider.dart new file mode 100644 index 00000000..8f0e9e29 --- /dev/null +++ b/lib/provider/palette_provider.dart @@ -0,0 +1,4 @@ +import 'package:palette_generator/palette_generator.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final paletteProvider = StateProvider((ref) => null); diff --git a/lib/provider/playlist_queue_provider.dart b/lib/provider/playlist_queue_provider.dart index c15f10ef..75b00d89 100644 --- a/lib/provider/playlist_queue_provider.dart +++ b/lib/provider/playlist_queue_provider.dart @@ -2,11 +2,14 @@ import 'package:audio_service/audio_service.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:palette_generator/palette_generator.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/models/local_track.dart'; import 'package:spotube/models/spotube_track.dart'; import 'package:spotube/extensions/track.dart'; import 'package:spotube/provider/blacklist_provider.dart'; +import 'package:spotube/provider/palette_provider.dart'; import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/services/audio_player.dart'; import 'package:spotube/services/linux_audio_service.dart'; @@ -532,6 +535,30 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { state = state!.copyWith(tracks: Set.from(tracks), active: active); } + Future updatePalette() async { + final palette = await PaletteGenerator.fromImageProvider( + UniversalImage.imageProvider( + TypeConversionUtils.image_X_UrlString( + state?.activeTrack.album?.images, + placeholder: ImagePlaceholder.albumArt, + ), + height: 50, + width: 50, + ), + ); + ref.read(paletteProvider.notifier).state = palette; + } + + @override + set state(state) { + if (preferences.albumColorSync && + state != null && + state.active != this.state?.active) { + updatePalette(); + } + super.state = state; + } + @override Future? fromJson(Map json) { if (json.isEmpty) return null; diff --git a/lib/provider/user_preferences_provider.dart b/lib/provider/user_preferences_provider.dart index b1e2dcac..6d26aaf1 100644 --- a/lib/provider/user_preferences_provider.dart +++ b/lib/provider/user_preferences_provider.dart @@ -4,6 +4,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; import 'package:spotube/components/settings/color_scheme_picker_dialog.dart'; +import 'package:spotube/provider/palette_provider.dart'; +import 'package:spotube/provider/playlist_queue_provider.dart'; import 'package:spotube/utils/persisted_change_notifier.dart'; import 'package:spotube/utils/platform.dart'; @@ -33,6 +35,7 @@ class UserPreferences extends PersistedChangeNotifier { AudioQuality audioQuality; SpotubeColor accentColorScheme; + bool albumColorSync; bool skipSponsorSegments; String downloadLocation; @@ -45,12 +48,16 @@ class UserPreferences extends PersistedChangeNotifier { bool showSystemTrayIcon; - UserPreferences({ + final Ref ref; + + UserPreferences( + this.ref, { required this.recommendationMarket, required this.themeMode, required this.layoutMode, required this.predownload, required this.accentColorScheme, + this.albumColorSync = true, this.saveTrackLyrics = false, this.checkUpdate = true, this.audioQuality = AudioQuality.high, @@ -98,7 +105,13 @@ class UserPreferences extends PersistedChangeNotifier { updatePersistence(); } - void setBackgroundColorScheme(MaterialColor color) { + void setAlbumColorSync(bool sync) { + albumColorSync = sync; + if (!sync) { + ref.read(paletteProvider.notifier).state = null; + } else { + ref.read(PlaylistQueueNotifier.notifier).updatePalette(); + } notifyListeners(); updatePersistence(); } @@ -168,6 +181,7 @@ class UserPreferences extends PersistedChangeNotifier { accentColorScheme = map["accentColorScheme"] != null ? SpotubeColor.fromString(map["accentColorScheme"]) : accentColorScheme; + albumColorSync = map["albumColorSync"] ?? albumColorSync; audioQuality = map["audioQuality"] != null ? AudioQuality.values[map["audioQuality"]] : audioQuality; @@ -196,6 +210,7 @@ class UserPreferences extends PersistedChangeNotifier { "recommendationMarket": recommendationMarket, "themeMode": themeMode.index, "accentColorScheme": accentColorScheme.toString(), + "albumColorSync": albumColorSync, "checkUpdate": checkUpdate, "audioQuality": audioQuality.index, "skipSponsorSegments": skipSponsorSegments, @@ -209,7 +224,8 @@ class UserPreferences extends PersistedChangeNotifier { } final userPreferencesProvider = ChangeNotifierProvider( - (_) => UserPreferences( + (ref) => UserPreferences( + ref, accentColorScheme: SpotubeColor(Colors.blue.value, name: "Blue"), recommendationMarket: 'US', themeMode: ThemeMode.system,