feat: add one additional library folder

This folder just doesn't get downloaded to.
I think I'm going to rework it so that it can be multiple folders,
but I'm going to commit my progress so far anyway.

Signed-off-by: Blake Leonard <me@blakes.dev>
This commit is contained in:
Blake Leonard 2024-05-05 15:57:14 -04:00
parent 2b01e4fb4d
commit f423c20063
No known key found for this signature in database
GPG Key ID: 3B1965C22D07D9F6
8 changed files with 163 additions and 4 deletions

View File

@ -71,7 +71,20 @@ final localTracksProvider = FutureProvider<List<LocalTrack>>((ref) async {
await downloadDir.create(recursive: true); await downloadDir.create(recursive: true);
return []; 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( final filesWithMetadata = (await Future.wait(
entities.map((e) => File(e.path)).where((file) { entities.map((e) => File(e.path)).where((file) {

View File

@ -107,6 +107,7 @@
"always_on_top": "Always on top", "always_on_top": "Always on top",
"exit_mini_player": "Exit Mini player", "exit_mini_player": "Exit Mini player",
"download_location": "Download location", "download_location": "Download location",
"local_library_location": "Local library location",
"account": "Account", "account": "Account",
"login_with_spotify": "Login with your Spotify account", "login_with_spotify": "Login with your Spotify account",
"connect_with_spotify": "Connect with Spotify", "connect_with_spotify": "Connect with Spotify",
@ -321,4 +322,4 @@
"connect_client_alert": "You're being controlled by {client}", "connect_client_alert": "You're being controlled by {client}",
"this_device": "This Device", "this_device": "This Device",
"remote": "Remote" "remote": "Remote"
} }

View File

@ -33,6 +33,22 @@ class SettingsDownloadsSection extends HookConsumerWidget {
} }
}, [preferences.downloadLocation]); }, [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( return SectionCardWithHeading(
heading: context.l10n.downloads, heading: context.l10n.downloads,
children: [ children: [
@ -46,6 +62,16 @@ class SettingsDownloadsSection extends HookConsumerWidget {
), ),
onTap: pickDownloadLocation, 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,
),
], ],
); );
} }

View File

@ -69,6 +69,11 @@ class UserPreferencesNotifier extends PersistedStateNotifier<UserPreferences> {
state = state.copyWith(downloadLocation: downloadDir); state = state.copyWith(downloadLocation: downloadDir);
} }
void setLocalLibraryLocation(String localLibraryDir) {
if (localLibraryDir.isEmpty) return;
state = state.copyWith(localLibraryLocation: localLibraryDir);
}
void setLayoutMode(LayoutMode mode) { void setLayoutMode(LayoutMode mode) {
state = state.copyWith(layoutMode: mode); state = state.copyWith(layoutMode: mode);
} }

View File

@ -84,6 +84,7 @@ class UserPreferences with _$UserPreferences {
@Default(Market.US) Market recommendationMarket, @Default(Market.US) Market recommendationMarket,
@Default(SearchMode.youtube) SearchMode searchMode, @Default(SearchMode.youtube) SearchMode searchMode,
@Default("") String downloadLocation, @Default("") String downloadLocation,
@Default("") String localLibraryLocation,
@Default("https://pipedapi.kavin.rocks") String pipedInstance, @Default("https://pipedapi.kavin.rocks") String pipedInstance,
@Default(ThemeMode.system) ThemeMode themeMode, @Default(ThemeMode.system) ThemeMode themeMode,
@Default(AudioSource.youtube) AudioSource audioSource, @Default(AudioSource.youtube) AudioSource audioSource,

View File

@ -43,6 +43,7 @@ mixin _$UserPreferences {
Market get recommendationMarket => throw _privateConstructorUsedError; Market get recommendationMarket => throw _privateConstructorUsedError;
SearchMode get searchMode => throw _privateConstructorUsedError; SearchMode get searchMode => throw _privateConstructorUsedError;
String get downloadLocation => throw _privateConstructorUsedError; String get downloadLocation => throw _privateConstructorUsedError;
String get localLibraryLocation => throw _privateConstructorUsedError;
String get pipedInstance => throw _privateConstructorUsedError; String get pipedInstance => throw _privateConstructorUsedError;
ThemeMode get themeMode => throw _privateConstructorUsedError; ThemeMode get themeMode => throw _privateConstructorUsedError;
AudioSource get audioSource => throw _privateConstructorUsedError; AudioSource get audioSource => throw _privateConstructorUsedError;
@ -88,6 +89,7 @@ abstract class $UserPreferencesCopyWith<$Res> {
Market recommendationMarket, Market recommendationMarket,
SearchMode searchMode, SearchMode searchMode,
String downloadLocation, String downloadLocation,
String localLibraryLocation,
String pipedInstance, String pipedInstance,
ThemeMode themeMode, ThemeMode themeMode,
AudioSource audioSource, AudioSource audioSource,
@ -126,6 +128,7 @@ class _$UserPreferencesCopyWithImpl<$Res, $Val extends UserPreferences>
Object? recommendationMarket = null, Object? recommendationMarket = null,
Object? searchMode = null, Object? searchMode = null,
Object? downloadLocation = null, Object? downloadLocation = null,
Object? localLibraryLocation = null,
Object? pipedInstance = null, Object? pipedInstance = null,
Object? themeMode = null, Object? themeMode = null,
Object? audioSource = null, Object? audioSource = null,
@ -196,6 +199,10 @@ class _$UserPreferencesCopyWithImpl<$Res, $Val extends UserPreferences>
? _value.downloadLocation ? _value.downloadLocation
: downloadLocation // ignore: cast_nullable_to_non_nullable : downloadLocation // ignore: cast_nullable_to_non_nullable
as String, as String,
localLibraryLocation: null == localLibraryLocation
? _value.localLibraryLocation
: localLibraryLocation // ignore: cast_nullable_to_non_nullable
as String,
pipedInstance: null == pipedInstance pipedInstance: null == pipedInstance
? _value.pipedInstance ? _value.pipedInstance
: pipedInstance // ignore: cast_nullable_to_non_nullable : pipedInstance // ignore: cast_nullable_to_non_nullable
@ -264,6 +271,7 @@ abstract class _$$UserPreferencesImplCopyWith<$Res>
Market recommendationMarket, Market recommendationMarket,
SearchMode searchMode, SearchMode searchMode,
String downloadLocation, String downloadLocation,
String localLibraryLocation,
String pipedInstance, String pipedInstance,
ThemeMode themeMode, ThemeMode themeMode,
AudioSource audioSource, AudioSource audioSource,
@ -300,6 +308,7 @@ class __$$UserPreferencesImplCopyWithImpl<$Res>
Object? recommendationMarket = null, Object? recommendationMarket = null,
Object? searchMode = null, Object? searchMode = null,
Object? downloadLocation = null, Object? downloadLocation = null,
Object? localLibraryLocation = null,
Object? pipedInstance = null, Object? pipedInstance = null,
Object? themeMode = null, Object? themeMode = null,
Object? audioSource = null, Object? audioSource = null,
@ -370,6 +379,10 @@ class __$$UserPreferencesImplCopyWithImpl<$Res>
? _value.downloadLocation ? _value.downloadLocation
: downloadLocation // ignore: cast_nullable_to_non_nullable : downloadLocation // ignore: cast_nullable_to_non_nullable
as String, as String,
localLibraryLocation: null == localLibraryLocation
? _value.localLibraryLocation
: localLibraryLocation // ignore: cast_nullable_to_non_nullable
as String,
pipedInstance: null == pipedInstance pipedInstance: null == pipedInstance
? _value.pipedInstance ? _value.pipedInstance
: pipedInstance // ignore: cast_nullable_to_non_nullable : pipedInstance // ignore: cast_nullable_to_non_nullable
@ -433,6 +446,7 @@ class _$UserPreferencesImpl implements _UserPreferences {
this.recommendationMarket = Market.US, this.recommendationMarket = Market.US,
this.searchMode = SearchMode.youtube, this.searchMode = SearchMode.youtube,
this.downloadLocation = "", this.downloadLocation = "",
this.localLibraryLocation = "",
this.pipedInstance = "https://pipedapi.kavin.rocks", this.pipedInstance = "https://pipedapi.kavin.rocks",
this.themeMode = ThemeMode.system, this.themeMode = ThemeMode.system,
this.audioSource = AudioSource.youtube, this.audioSource = AudioSource.youtube,
@ -498,6 +512,9 @@ class _$UserPreferencesImpl implements _UserPreferences {
final String downloadLocation; final String downloadLocation;
@override @override
@JsonKey() @JsonKey()
final String localLibraryLocation;
@override
@JsonKey()
final String pipedInstance; final String pipedInstance;
@override @override
@JsonKey() @JsonKey()
@ -523,7 +540,7 @@ class _$UserPreferencesImpl implements _UserPreferences {
@override @override
String toString() { 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 @override
@ -560,6 +577,8 @@ class _$UserPreferencesImpl implements _UserPreferences {
other.searchMode == searchMode) && other.searchMode == searchMode) &&
(identical(other.downloadLocation, downloadLocation) || (identical(other.downloadLocation, downloadLocation) ||
other.downloadLocation == downloadLocation) && other.downloadLocation == downloadLocation) &&
(identical(other.localLibraryLocation, localLibraryLocation) ||
other.localLibraryLocation == localLibraryLocation) &&
(identical(other.pipedInstance, pipedInstance) || (identical(other.pipedInstance, pipedInstance) ||
other.pipedInstance == pipedInstance) && other.pipedInstance == pipedInstance) &&
(identical(other.themeMode, themeMode) || (identical(other.themeMode, themeMode) ||
@ -597,6 +616,7 @@ class _$UserPreferencesImpl implements _UserPreferences {
recommendationMarket, recommendationMarket,
searchMode, searchMode,
downloadLocation, downloadLocation,
localLibraryLocation,
pipedInstance, pipedInstance,
themeMode, themeMode,
audioSource, audioSource,
@ -647,6 +667,7 @@ abstract class _UserPreferences implements UserPreferences {
final Market recommendationMarket, final Market recommendationMarket,
final SearchMode searchMode, final SearchMode searchMode,
final String downloadLocation, final String downloadLocation,
final String localLibraryLocation,
final String pipedInstance, final String pipedInstance,
final ThemeMode themeMode, final ThemeMode themeMode,
final AudioSource audioSource, final AudioSource audioSource,
@ -698,6 +719,8 @@ abstract class _UserPreferences implements UserPreferences {
@override @override
String get downloadLocation; String get downloadLocation;
@override @override
String get localLibraryLocation;
@override
String get pipedInstance; String get pipedInstance;
@override @override
ThemeMode get themeMode; ThemeMode get themeMode;

View File

@ -44,6 +44,7 @@ _$UserPreferencesImpl _$$UserPreferencesImplFromJson(
$enumDecodeNullable(_$SearchModeEnumMap, json['searchMode']) ?? $enumDecodeNullable(_$SearchModeEnumMap, json['searchMode']) ??
SearchMode.youtube, SearchMode.youtube,
downloadLocation: json['downloadLocation'] as String? ?? "", downloadLocation: json['downloadLocation'] as String? ?? "",
localLibraryLocation: json['localLibraryLocation'] as String? ?? "",
pipedInstance: pipedInstance:
json['pipedInstance'] as String? ?? "https://pipedapi.kavin.rocks", json['pipedInstance'] as String? ?? "https://pipedapi.kavin.rocks",
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ?? themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
@ -81,6 +82,7 @@ Map<String, dynamic> _$$UserPreferencesImplToJson(
'recommendationMarket': _$MarketEnumMap[instance.recommendationMarket]!, 'recommendationMarket': _$MarketEnumMap[instance.recommendationMarket]!,
'searchMode': _$SearchModeEnumMap[instance.searchMode]!, 'searchMode': _$SearchModeEnumMap[instance.searchMode]!,
'downloadLocation': instance.downloadLocation, 'downloadLocation': instance.downloadLocation,
'localLibraryLocation': instance.localLibraryLocation,
'pipedInstance': instance.pipedInstance, 'pipedInstance': instance.pipedInstance,
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!, 'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
'audioSource': _$AudioSourceEnumMap[instance.audioSource]!, 'audioSource': _$AudioSourceEnumMap[instance.audioSource]!,

View File

@ -1 +1,89 @@
{} {
"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"
]
}