diff --git a/lib/components/library/user_local_tracks.dart b/lib/components/library/user_local_tracks.dart index a7b2102b..42aec124 100644 --- a/lib/components/library/user_local_tracks.dart +++ b/lib/components/library/user_local_tracks.dart @@ -71,7 +71,20 @@ final localTracksProvider = FutureProvider>((ref) async { await downloadDir.create(recursive: true); return []; } - final entities = downloadDir.listSync(recursive: true); + final downloadEntities = downloadDir.listSync(recursive: true); + + final localLibraryLocation = ref.watch( + userPreferencesProvider.select((s) => s.localLibraryLocation), + ); + if (localLibraryLocation.isEmpty) return []; + final localLibraryDir = Directory(localLibraryLocation); + if (!await localLibraryDir.exists()) { + await localLibraryDir.create(recursive: true); + return []; + } + final localLibraryEntities = localLibraryDir.listSync(recursive: true); + + final entities = [...downloadEntities, ...localLibraryEntities]; final filesWithMetadata = (await Future.wait( entities.map((e) => File(e.path)).where((file) { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 832862c0..389bcda4 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -107,6 +107,7 @@ "always_on_top": "Always on top", "exit_mini_player": "Exit Mini player", "download_location": "Download location", + "local_library_location": "Local library location", "account": "Account", "login_with_spotify": "Login with your Spotify account", "connect_with_spotify": "Connect with Spotify", @@ -321,4 +322,4 @@ "connect_client_alert": "You're being controlled by {client}", "this_device": "This Device", "remote": "Remote" -} \ No newline at end of file +} diff --git a/lib/pages/settings/sections/downloads.dart b/lib/pages/settings/sections/downloads.dart index 76ef8e3e..9c2b34b9 100644 --- a/lib/pages/settings/sections/downloads.dart +++ b/lib/pages/settings/sections/downloads.dart @@ -33,6 +33,22 @@ class SettingsDownloadsSection extends HookConsumerWidget { } }, [preferences.downloadLocation]); + final pickLocalLibraryLocation = useCallback(() async { + if (DesktopTools.platform.isMobile || DesktopTools.platform.isMacOS) { + final dirStr = await FilePicker.platform.getDirectoryPath( + initialDirectory: preferences.localLibraryLocation, + ); + if (dirStr == null) return; + preferencesNotifier.setLocalLibraryLocation(dirStr); + } else { + String? dirStr = await getDirectoryPath( + initialDirectory: preferences.localLibraryLocation, + ); + if (dirStr == null) return; + preferencesNotifier.setLocalLibraryLocation(dirStr); + } + }, [preferences.localLibraryLocation]); + return SectionCardWithHeading( heading: context.l10n.downloads, children: [ @@ -46,6 +62,16 @@ class SettingsDownloadsSection extends HookConsumerWidget { ), onTap: pickDownloadLocation, ), + ListTile( + leading: const Icon(SpotubeIcons.folder), + title: Text(context.l10n.local_library_location), + subtitle: Text(preferences.localLibraryLocation), + trailing: FilledButton( + onPressed: pickLocalLibraryLocation, + child: const Icon(SpotubeIcons.folder), + ), + onTap: pickLocalLibraryLocation, + ), ], ); } diff --git a/lib/provider/user_preferences/user_preferences_provider.dart b/lib/provider/user_preferences/user_preferences_provider.dart index a537038e..44c48d99 100644 --- a/lib/provider/user_preferences/user_preferences_provider.dart +++ b/lib/provider/user_preferences/user_preferences_provider.dart @@ -69,6 +69,11 @@ class UserPreferencesNotifier extends PersistedStateNotifier { state = state.copyWith(downloadLocation: downloadDir); } + void setLocalLibraryLocation(String localLibraryDir) { + if (localLibraryDir.isEmpty) return; + state = state.copyWith(localLibraryLocation: localLibraryDir); + } + void setLayoutMode(LayoutMode mode) { state = state.copyWith(layoutMode: mode); } diff --git a/lib/provider/user_preferences/user_preferences_state.dart b/lib/provider/user_preferences/user_preferences_state.dart index 67eb18a2..501cf993 100644 --- a/lib/provider/user_preferences/user_preferences_state.dart +++ b/lib/provider/user_preferences/user_preferences_state.dart @@ -84,6 +84,7 @@ class UserPreferences with _$UserPreferences { @Default(Market.US) Market recommendationMarket, @Default(SearchMode.youtube) SearchMode searchMode, @Default("") String downloadLocation, + @Default("") String localLibraryLocation, @Default("https://pipedapi.kavin.rocks") String pipedInstance, @Default(ThemeMode.system) ThemeMode themeMode, @Default(AudioSource.youtube) AudioSource audioSource, diff --git a/lib/provider/user_preferences/user_preferences_state.freezed.dart b/lib/provider/user_preferences/user_preferences_state.freezed.dart index 94015d37..4ed22b67 100644 --- a/lib/provider/user_preferences/user_preferences_state.freezed.dart +++ b/lib/provider/user_preferences/user_preferences_state.freezed.dart @@ -43,6 +43,7 @@ mixin _$UserPreferences { Market get recommendationMarket => throw _privateConstructorUsedError; SearchMode get searchMode => throw _privateConstructorUsedError; String get downloadLocation => throw _privateConstructorUsedError; + String get localLibraryLocation => throw _privateConstructorUsedError; String get pipedInstance => throw _privateConstructorUsedError; ThemeMode get themeMode => throw _privateConstructorUsedError; AudioSource get audioSource => throw _privateConstructorUsedError; @@ -88,6 +89,7 @@ abstract class $UserPreferencesCopyWith<$Res> { Market recommendationMarket, SearchMode searchMode, String downloadLocation, + String localLibraryLocation, String pipedInstance, ThemeMode themeMode, AudioSource audioSource, @@ -126,6 +128,7 @@ class _$UserPreferencesCopyWithImpl<$Res, $Val extends UserPreferences> Object? recommendationMarket = null, Object? searchMode = null, Object? downloadLocation = null, + Object? localLibraryLocation = null, Object? pipedInstance = null, Object? themeMode = null, Object? audioSource = null, @@ -196,6 +199,10 @@ class _$UserPreferencesCopyWithImpl<$Res, $Val extends UserPreferences> ? _value.downloadLocation : downloadLocation // ignore: cast_nullable_to_non_nullable as String, + localLibraryLocation: null == localLibraryLocation + ? _value.localLibraryLocation + : localLibraryLocation // ignore: cast_nullable_to_non_nullable + as String, pipedInstance: null == pipedInstance ? _value.pipedInstance : pipedInstance // ignore: cast_nullable_to_non_nullable @@ -264,6 +271,7 @@ abstract class _$$UserPreferencesImplCopyWith<$Res> Market recommendationMarket, SearchMode searchMode, String downloadLocation, + String localLibraryLocation, String pipedInstance, ThemeMode themeMode, AudioSource audioSource, @@ -300,6 +308,7 @@ class __$$UserPreferencesImplCopyWithImpl<$Res> Object? recommendationMarket = null, Object? searchMode = null, Object? downloadLocation = null, + Object? localLibraryLocation = null, Object? pipedInstance = null, Object? themeMode = null, Object? audioSource = null, @@ -370,6 +379,10 @@ class __$$UserPreferencesImplCopyWithImpl<$Res> ? _value.downloadLocation : downloadLocation // ignore: cast_nullable_to_non_nullable as String, + localLibraryLocation: null == localLibraryLocation + ? _value.localLibraryLocation + : localLibraryLocation // ignore: cast_nullable_to_non_nullable + as String, pipedInstance: null == pipedInstance ? _value.pipedInstance : pipedInstance // ignore: cast_nullable_to_non_nullable @@ -433,6 +446,7 @@ class _$UserPreferencesImpl implements _UserPreferences { this.recommendationMarket = Market.US, this.searchMode = SearchMode.youtube, this.downloadLocation = "", + this.localLibraryLocation = "", this.pipedInstance = "https://pipedapi.kavin.rocks", this.themeMode = ThemeMode.system, this.audioSource = AudioSource.youtube, @@ -498,6 +512,9 @@ class _$UserPreferencesImpl implements _UserPreferences { final String downloadLocation; @override @JsonKey() + final String localLibraryLocation; + @override + @JsonKey() final String pipedInstance; @override @JsonKey() @@ -523,7 +540,7 @@ class _$UserPreferencesImpl implements _UserPreferences { @override String toString() { - return 'UserPreferences(audioQuality: $audioQuality, albumColorSync: $albumColorSync, amoledDarkTheme: $amoledDarkTheme, checkUpdate: $checkUpdate, normalizeAudio: $normalizeAudio, showSystemTrayIcon: $showSystemTrayIcon, skipNonMusic: $skipNonMusic, systemTitleBar: $systemTitleBar, closeBehavior: $closeBehavior, accentColorScheme: $accentColorScheme, layoutMode: $layoutMode, locale: $locale, recommendationMarket: $recommendationMarket, searchMode: $searchMode, downloadLocation: $downloadLocation, pipedInstance: $pipedInstance, themeMode: $themeMode, audioSource: $audioSource, streamMusicCodec: $streamMusicCodec, downloadMusicCodec: $downloadMusicCodec, discordPresence: $discordPresence, endlessPlayback: $endlessPlayback, enableConnect: $enableConnect)'; + return 'UserPreferences(audioQuality: $audioQuality, albumColorSync: $albumColorSync, amoledDarkTheme: $amoledDarkTheme, checkUpdate: $checkUpdate, normalizeAudio: $normalizeAudio, showSystemTrayIcon: $showSystemTrayIcon, skipNonMusic: $skipNonMusic, systemTitleBar: $systemTitleBar, closeBehavior: $closeBehavior, accentColorScheme: $accentColorScheme, layoutMode: $layoutMode, locale: $locale, recommendationMarket: $recommendationMarket, searchMode: $searchMode, downloadLocation: $downloadLocation, localLibraryLocation: $localLibraryLocation, pipedInstance: $pipedInstance, themeMode: $themeMode, audioSource: $audioSource, streamMusicCodec: $streamMusicCodec, downloadMusicCodec: $downloadMusicCodec, discordPresence: $discordPresence, endlessPlayback: $endlessPlayback, enableConnect: $enableConnect)'; } @override @@ -560,6 +577,8 @@ class _$UserPreferencesImpl implements _UserPreferences { other.searchMode == searchMode) && (identical(other.downloadLocation, downloadLocation) || other.downloadLocation == downloadLocation) && + (identical(other.localLibraryLocation, localLibraryLocation) || + other.localLibraryLocation == localLibraryLocation) && (identical(other.pipedInstance, pipedInstance) || other.pipedInstance == pipedInstance) && (identical(other.themeMode, themeMode) || @@ -597,6 +616,7 @@ class _$UserPreferencesImpl implements _UserPreferences { recommendationMarket, searchMode, downloadLocation, + localLibraryLocation, pipedInstance, themeMode, audioSource, @@ -647,6 +667,7 @@ abstract class _UserPreferences implements UserPreferences { final Market recommendationMarket, final SearchMode searchMode, final String downloadLocation, + final String localLibraryLocation, final String pipedInstance, final ThemeMode themeMode, final AudioSource audioSource, @@ -698,6 +719,8 @@ abstract class _UserPreferences implements UserPreferences { @override String get downloadLocation; @override + String get localLibraryLocation; + @override String get pipedInstance; @override ThemeMode get themeMode; diff --git a/lib/provider/user_preferences/user_preferences_state.g.dart b/lib/provider/user_preferences/user_preferences_state.g.dart index 930b1dd1..ba27389e 100644 --- a/lib/provider/user_preferences/user_preferences_state.g.dart +++ b/lib/provider/user_preferences/user_preferences_state.g.dart @@ -44,6 +44,7 @@ _$UserPreferencesImpl _$$UserPreferencesImplFromJson( $enumDecodeNullable(_$SearchModeEnumMap, json['searchMode']) ?? SearchMode.youtube, downloadLocation: json['downloadLocation'] as String? ?? "", + localLibraryLocation: json['localLibraryLocation'] as String? ?? "", pipedInstance: json['pipedInstance'] as String? ?? "https://pipedapi.kavin.rocks", themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ?? @@ -81,6 +82,7 @@ Map _$$UserPreferencesImplToJson( 'recommendationMarket': _$MarketEnumMap[instance.recommendationMarket]!, 'searchMode': _$SearchModeEnumMap[instance.searchMode]!, 'downloadLocation': instance.downloadLocation, + 'localLibraryLocation': instance.localLibraryLocation, 'pipedInstance': instance.pipedInstance, 'themeMode': _$ThemeModeEnumMap[instance.themeMode]!, 'audioSource': _$AudioSourceEnumMap[instance.audioSource]!, diff --git a/untranslated_messages.json b/untranslated_messages.json index 9e26dfee..c0b93615 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -1 +1,89 @@ -{} \ No newline at end of file +{ + "ar": [ + "local_library_location" + ], + + "bn": [ + "local_library_location" + ], + + "ca": [ + "local_library_location" + ], + + "cs": [ + "local_library_location" + ], + + "de": [ + "local_library_location" + ], + + "es": [ + "local_library_location" + ], + + "fa": [ + "local_library_location" + ], + + "fr": [ + "local_library_location" + ], + + "hi": [ + "local_library_location" + ], + + "it": [ + "local_library_location" + ], + + "ja": [ + "local_library_location" + ], + + "ko": [ + "local_library_location" + ], + + "ne": [ + "local_library_location" + ], + + "nl": [ + "local_library_location" + ], + + "pl": [ + "local_library_location" + ], + + "pt": [ + "local_library_location" + ], + + "ru": [ + "local_library_location" + ], + + "th": [ + "local_library_location" + ], + + "tr": [ + "local_library_location" + ], + + "uk": [ + "local_library_location" + ], + + "vi": [ + "local_library_location" + ], + + "zh": [ + "local_library_location" + ] +}