mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
refactor: user local library
This commit is contained in:
parent
4afe0cca68
commit
b8f2495acb
@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
|
||||||
@ -39,11 +39,8 @@ class ExpandableSearchField extends StatelessWidget {
|
|||||||
child: TextField(
|
child: TextField(
|
||||||
focusNode: searchFocus,
|
focusNode: searchFocus,
|
||||||
controller: searchController,
|
controller: searchController,
|
||||||
decoration: InputDecoration(
|
placeholder: Text(context.l10n.search_tracks),
|
||||||
hintText: context.l10n.search_tracks,
|
leading: const Icon(SpotubeIcons.search),
|
||||||
isDense: true,
|
|
||||||
prefixIcon: const Icon(SpotubeIcons.search),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -69,16 +66,9 @@ class ExpandableSearchButton extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
|
||||||
|
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: icon,
|
icon: icon,
|
||||||
style: IconButton.styleFrom(
|
variance: isFiltering ? ButtonVariance.secondary : ButtonVariance.outline,
|
||||||
backgroundColor:
|
|
||||||
isFiltering ? theme.colorScheme.secondaryContainer : null,
|
|
||||||
foregroundColor: isFiltering ? theme.colorScheme.secondary : null,
|
|
||||||
minimumSize: const Size(25, 25),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (isFiltering) {
|
if (isFiltering) {
|
||||||
searchFocus.requestFocus();
|
searchFocus.requestFocus();
|
||||||
|
@ -215,7 +215,7 @@ class Spotube extends HookConsumerWidget {
|
|||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
radius: .5,
|
radius: .5,
|
||||||
iconTheme: const IconThemeProperties(),
|
iconTheme: const IconThemeProperties(),
|
||||||
colorScheme: ColorSchemes.lightBlue(),
|
colorScheme: ColorSchemes.lightOrange(),
|
||||||
surfaceOpacity: .8,
|
surfaceOpacity: .8,
|
||||||
surfaceBlur: 10,
|
surfaceBlur: 10,
|
||||||
),
|
),
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
||||||
import 'package:spotube/extensions/string.dart';
|
import 'package:spotube/extensions/string.dart';
|
||||||
import 'package:spotube/hooks/utils/use_brightness_value.dart';
|
|
||||||
import 'package:spotube/pages/library/local_folder.dart';
|
import 'package:spotube/pages/library/local_folder.dart';
|
||||||
import 'package:spotube/provider/local_tracks/local_tracks_provider.dart';
|
import 'package:spotube/provider/local_tracks/local_tracks_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
@ -26,8 +25,6 @@ class LocalFolderItem extends HookConsumerWidget {
|
|||||||
final ThemeData(:colorScheme) = Theme.of(context);
|
final ThemeData(:colorScheme) = Theme.of(context);
|
||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
|
||||||
final lerpValue = useBrightnessValue(.9, .7);
|
|
||||||
|
|
||||||
final downloadFolder =
|
final downloadFolder =
|
||||||
ref.watch(userPreferencesProvider.select((s) => s.downloadLocation));
|
ref.watch(userPreferencesProvider.select((s) => s.downloadLocation));
|
||||||
final cacheFolder = useFuture(UserPreferencesNotifier.getMusicCacheDir());
|
final cacheFolder = useFuture(UserPreferencesNotifier.getMusicCacheDir());
|
||||||
@ -60,8 +57,8 @@ class LocalFolderItem extends HookConsumerWidget {
|
|||||||
|
|
||||||
final tracks = trackSnapshot.value ?? [];
|
final tracks = trackSnapshot.value ?? [];
|
||||||
|
|
||||||
return InkWell(
|
return Button(
|
||||||
onTap: () {
|
onPressed: () {
|
||||||
context.goNamed(
|
context.goNamed(
|
||||||
LocalLibraryPage.name,
|
LocalLibraryPage.name,
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
@ -71,18 +68,11 @@ class LocalFolderItem extends HookConsumerWidget {
|
|||||||
extra: folder,
|
extra: folder,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(8),
|
style: ButtonVariance.card.copyWith(
|
||||||
child: Ink(
|
padding: (context, states, value) {
|
||||||
decoration: BoxDecoration(
|
return const EdgeInsets.all(8);
|
||||||
borderRadius: BorderRadius.circular(8),
|
},
|
||||||
color: Color.lerp(
|
|
||||||
colorScheme.surfaceContainerHighest,
|
|
||||||
colorScheme.surface,
|
|
||||||
lerpValue,
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@ -123,6 +113,9 @@ class LocalFolderItem extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Stack(
|
Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -133,25 +126,47 @@ class LocalFolderItem extends HookConsumerWidget {
|
|||||||
: basename(folder),
|
: basename(folder),
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!isDownloadFolder)
|
Wrap(
|
||||||
|
spacing: 2,
|
||||||
|
runSpacing: 2,
|
||||||
|
children: [
|
||||||
|
for (final MapEntry(key: index, value: segment)
|
||||||
|
in segments.asMap().entries)
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
if (index != 0) const TextSpan(text: "/ "),
|
||||||
|
TextSpan(text: segment),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
).xSmall().muted(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (!isDownloadFolder && !isCacheFolder)
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.topRight,
|
alignment: Alignment.topRight,
|
||||||
child: PopupMenuButton(
|
child: IconButton.ghost(
|
||||||
child: const Padding(
|
icon: const Icon(Icons.more_vert),
|
||||||
padding: EdgeInsets.all(3),
|
size: ButtonSize.small,
|
||||||
child: Icon(Icons.more_vert),
|
onPressed: () {
|
||||||
),
|
showDropdown(
|
||||||
itemBuilder: (context) {
|
context: context,
|
||||||
return [
|
builder: (context) {
|
||||||
PopupMenuItem(
|
return DropdownMenu(
|
||||||
child: ListTile(
|
children: [
|
||||||
leading: const Icon(SpotubeIcons.folderRemove),
|
MenuButton(
|
||||||
iconColor: colorScheme.error,
|
leading: Icon(SpotubeIcons.folderRemove,
|
||||||
title:
|
color: colorScheme.destructive),
|
||||||
|
child:
|
||||||
Text(context.l10n.remove_library_location),
|
Text(context.l10n.remove_library_location),
|
||||||
onTap: () {
|
onPressed: (context) {
|
||||||
final libraryLocations = ref
|
final libraryLocations = ref
|
||||||
.read(userPreferencesProvider)
|
.read(userPreferencesProvider)
|
||||||
.localLibraryLocation;
|
.localLibraryLocation;
|
||||||
@ -163,44 +178,19 @@ class LocalFolderItem extends HookConsumerWidget {
|
|||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
)
|
)
|
||||||
];
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Wrap(
|
|
||||||
spacing: 2,
|
|
||||||
runSpacing: 2,
|
|
||||||
children: [
|
|
||||||
for (final MapEntry(key: index, value: segment)
|
|
||||||
in segments.asMap().entries)
|
|
||||||
Text.rich(
|
|
||||||
TextSpan(
|
|
||||||
children: [
|
|
||||||
if (index != 0)
|
|
||||||
TextSpan(
|
|
||||||
text: "/ ",
|
|
||||||
style: TextStyle(color: colorScheme.primary),
|
|
||||||
),
|
|
||||||
TextSpan(text: segment),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
color: colorScheme.tertiary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:file_selector/file_selector.dart';
|
import 'package:file_selector/file_selector.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/modules/library/local_folder/local_folder_item.dart';
|
import 'package:spotube/modules/library/local_folder/local_folder_item.dart';
|
||||||
@ -58,17 +57,23 @@ class UserLocalTracks extends HookConsumerWidget {
|
|||||||
// For now, this gets all of them.
|
// For now, this gets all of them.
|
||||||
ref.watch(localTracksProvider);
|
ref.watch(localTracksProvider);
|
||||||
|
|
||||||
return LayoutBuilder(builder: (context, constrains) {
|
final locations = [
|
||||||
return Padding(
|
preferences.downloadLocation,
|
||||||
|
if (cacheDir.hasData) cacheDir.data!,
|
||||||
|
...preferences.localLibraryLocation,
|
||||||
|
];
|
||||||
|
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constrains) => Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: TextButton.icon(
|
child: Button.secondary(
|
||||||
icon: const Icon(SpotubeIcons.folderAdd),
|
leading: const Icon(SpotubeIcons.folderAdd),
|
||||||
label: Text(context.l10n.add_library_location),
|
|
||||||
onPressed: addLocalLibraryLocation,
|
onPressed: addLocalLibraryLocation,
|
||||||
|
child: Text(context.l10n.add_library_location),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -84,23 +89,16 @@ class UserLocalTracks extends HookConsumerWidget {
|
|||||||
crossAxisSpacing: 10,
|
crossAxisSpacing: 10,
|
||||||
mainAxisSpacing: 10,
|
mainAxisSpacing: 10,
|
||||||
),
|
),
|
||||||
itemCount: preferences.localLibraryLocation.length +
|
itemCount: locations.length,
|
||||||
1 +
|
|
||||||
(cacheDir.hasData ? 1 : 0),
|
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return LocalFolderItem(
|
return LocalFolderItem(
|
||||||
folder: index == 0
|
folder: locations[index],
|
||||||
? preferences.downloadLocation
|
|
||||||
: index == 1 && cacheDir.hasData
|
|
||||||
? cacheDir.data!
|
|
||||||
: preferences.localLibraryLocation[index - 1],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,16 @@ import 'dart:math';
|
|||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/components/button/back_button.dart';
|
||||||
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/string.dart';
|
import 'package:spotube/extensions/string.dart';
|
||||||
import 'package:spotube/modules/library/local_folder/cache_export_dialog.dart';
|
import 'package:spotube/modules/library/local_folder/cache_export_dialog.dart';
|
||||||
import 'package:spotube/modules/library/user_local_tracks.dart';
|
import 'package:spotube/modules/library/user_local_tracks.dart';
|
||||||
@ -65,7 +68,7 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final ThemeData(:textTheme) = Theme.of(context);
|
final scale = context.theme.scaling;
|
||||||
|
|
||||||
final sortBy = useState<SortBy>(SortBy.none);
|
final sortBy = useState<SortBy>(SortBy.none);
|
||||||
final playlist = ref.watch(audioPlayerProvider);
|
final playlist = ref.watch(audioPlayerProvider);
|
||||||
@ -93,7 +96,13 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
return SafeArea(
|
return SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: TitleBar(
|
headers: [
|
||||||
|
TitleBar(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 0,
|
||||||
|
),
|
||||||
|
surfaceBlur: 0,
|
||||||
leading: const [BackButton()],
|
leading: const [BackButton()],
|
||||||
title: Column(
|
title: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -105,47 +114,43 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
: isCache
|
: isCache
|
||||||
? context.l10n.cache_folder.capitalize()
|
? context.l10n.cache_folder.capitalize()
|
||||||
: location,
|
: location,
|
||||||
style: textTheme.titleLarge,
|
|
||||||
),
|
),
|
||||||
FutureBuilder<String>(
|
FutureBuilder<String>(
|
||||||
future: directorySize,
|
future: directorySize,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
return Text(
|
return Text(
|
||||||
"${(snapshot.data ?? 0)} GB",
|
"${(snapshot.data ?? 0)} GB",
|
||||||
style: textTheme.labelSmall,
|
).xSmall().muted();
|
||||||
);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
|
trailingGap: 10,
|
||||||
trailing: [
|
trailing: [
|
||||||
if (isCache) ...[
|
if (isCache) ...[
|
||||||
IconButton(
|
IconButton.outline(
|
||||||
iconSize: 16,
|
size: ButtonSize.small,
|
||||||
icon: Column(
|
icon: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(SpotubeIcons.delete),
|
const Icon(SpotubeIcons.delete),
|
||||||
Text(
|
Text(context.l10n.clear_cache)
|
||||||
context.l10n.clear_cache,
|
|
||||||
style: textTheme.labelSmall,
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
).xSmall().iconSmall(),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final accepted = await showDialog<bool>(
|
final accepted = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog.adaptive(
|
builder: (context) => AlertDialog(
|
||||||
title: Text(context.l10n.clear_cache_confirmation),
|
title: Text(context.l10n.clear_cache_confirmation),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
Button.outline(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop(false);
|
Navigator.of(context).pop(false);
|
||||||
},
|
},
|
||||||
child: Text(context.l10n.decline),
|
child: Text(context.l10n.decline),
|
||||||
),
|
),
|
||||||
TextButton(
|
Button.destructive(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.of(context).pop(true);
|
Navigator.of(context).pop(true);
|
||||||
},
|
},
|
||||||
@ -166,18 +171,17 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton.outline(
|
||||||
iconSize: 16,
|
size: ButtonSize.small,
|
||||||
icon: Column(
|
icon: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(SpotubeIcons.export),
|
const Icon(SpotubeIcons.export),
|
||||||
Text(
|
Text(
|
||||||
context.l10n.export,
|
context.l10n.export,
|
||||||
style: textTheme.labelSmall,
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
).xSmall().iconSmall(),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final exportPath =
|
final exportPath =
|
||||||
await FilePicker.platform.getDirectoryPath();
|
await FilePicker.platform.getDirectoryPath();
|
||||||
@ -207,54 +211,69 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Column(
|
],
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) => Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 5),
|
const Gap(5),
|
||||||
FilledButton(
|
Button.primary(
|
||||||
onPressed: trackSnapshot.asData?.value != null
|
onPressed: trackSnapshot.asData?.value != null
|
||||||
? () async {
|
? () async {
|
||||||
if (trackSnapshot.asData?.value.isNotEmpty ==
|
if (trackSnapshot
|
||||||
|
.asData?.value.isNotEmpty ==
|
||||||
true) {
|
true) {
|
||||||
if (!isPlaylistPlaying) {
|
if (!isPlaylistPlaying) {
|
||||||
await playLocalTracks(
|
await playLocalTracks(
|
||||||
ref,
|
ref,
|
||||||
trackSnapshot.asData!.value[location] ?? [],
|
trackSnapshot
|
||||||
|
.asData!.value[location] ??
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
child: Row(
|
leading: Icon(
|
||||||
children: [
|
|
||||||
Text(context.l10n.play),
|
|
||||||
Icon(
|
|
||||||
isPlaylistPlaying
|
isPlaylistPlaying
|
||||||
? SpotubeIcons.stop
|
? SpotubeIcons.stop
|
||||||
: SpotubeIcons.play,
|
: SpotubeIcons.play,
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
child: Text(context.l10n.play),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
if (constraints.smAndDown)
|
||||||
ExpandableSearchButton(
|
ExpandableSearchButton(
|
||||||
isFiltering: isFiltering.value,
|
isFiltering: isFiltering.value,
|
||||||
onPressed: (value) => isFiltering.value = value,
|
onPressed: (value) => isFiltering.value = value,
|
||||||
searchFocus: searchFocus,
|
searchFocus: searchFocus,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: 300 * scale,
|
||||||
|
maxHeight: 38 * scale,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
child: ExpandableSearchField(
|
||||||
|
isFiltering: true,
|
||||||
|
onChangeFiltering: (value) {},
|
||||||
|
searchController: searchController,
|
||||||
|
searchFocus: searchFocus,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(5),
|
||||||
SortTracksDropdown(
|
SortTracksDropdown(
|
||||||
value: sortBy.value,
|
value: sortBy.value,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
sortBy.value = value;
|
sortBy.value = value;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 5),
|
const Gap(5),
|
||||||
FilledButton(
|
IconButton.outline(
|
||||||
child: const Icon(SpotubeIcons.refresh),
|
icon: const Icon(SpotubeIcons.refresh),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ref.invalidate(localTracksProvider);
|
ref.invalidate(localTracksProvider);
|
||||||
},
|
},
|
||||||
@ -268,11 +287,13 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
isFiltering: isFiltering.value,
|
isFiltering: isFiltering.value,
|
||||||
onChangeFiltering: (value) => isFiltering.value = value,
|
onChangeFiltering: (value) => isFiltering.value = value,
|
||||||
),
|
),
|
||||||
trackSnapshot.when(
|
HookBuilder(builder: (context) {
|
||||||
|
return trackSnapshot.when(
|
||||||
data: (tracks) {
|
data: (tracks) {
|
||||||
final sortedTracks = useMemoized(() {
|
final sortedTracks = useMemoized(() {
|
||||||
return ServiceUtils.sortTracks(
|
return ServiceUtils.sortTracks(
|
||||||
tracks[location] ?? <LocalTrack>[], sortBy.value);
|
tracks[location] ?? <LocalTrack>[],
|
||||||
|
sortBy.value);
|
||||||
}, [sortBy.value, tracks]);
|
}, [sortBy.value, tracks]);
|
||||||
|
|
||||||
final filteredTracks = useMemoized(() {
|
final filteredTracks = useMemoized(() {
|
||||||
@ -297,7 +318,8 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
.toList();
|
.toList();
|
||||||
}, [searchController.text, sortedTracks]);
|
}, [searchController.text, sortedTracks]);
|
||||||
|
|
||||||
if (!trackSnapshot.isLoading && filteredTracks.isEmpty) {
|
if (!trackSnapshot.isLoading &&
|
||||||
|
filteredTracks.isEmpty) {
|
||||||
return const Expanded(
|
return const Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@ -307,9 +329,9 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: RefreshIndicator(
|
child: RefreshTrigger(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
ref.invalidate(localTracksProvider);
|
// ref.invalidate(localTracksProvider);
|
||||||
},
|
},
|
||||||
child: InterScrollbar(
|
child: InterScrollbar(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
@ -317,7 +339,8 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
enabled: trackSnapshot.isLoading,
|
enabled: trackSnapshot.isLoading,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics:
|
||||||
|
const AlwaysScrollableScrollPhysics(),
|
||||||
itemCount: trackSnapshot.isLoading
|
itemCount: trackSnapshot.isLoading
|
||||||
? 5
|
? 5
|
||||||
: filteredTracks.length,
|
: filteredTracks.length,
|
||||||
@ -366,9 +389,10 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
error: (error, stackTrace) =>
|
error: (error, stackTrace) =>
|
||||||
Text(error.toString() + stackTrace.toString()),
|
Text(error.toString() + stackTrace.toString()),
|
||||||
)
|
);
|
||||||
|
})
|
||||||
],
|
],
|
||||||
)),
|
))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user