feat: search/filter tracks inside playlist or album

This commit is contained in:
Kingkor Roy Tirtho 2023-01-06 00:48:04 +06:00
parent 8a6ba3b35f
commit a06cd0da84
4 changed files with 131 additions and 17 deletions

View File

@ -64,7 +64,7 @@ class UserArtists extends HookConsumerWidget {
), ),
), ),
backgroundColor: PlatformTheme.of(context).scaffoldBackgroundColor, backgroundColor: PlatformTheme.of(context).scaffoldBackgroundColor,
body: (artistQuery.isLoading || !artistQuery.hasData) body: artistQuery.pages.isEmpty
? Padding( ? Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Row( child: Row(

View File

@ -1,5 +1,7 @@
import 'package:fl_query/fl_query.dart'; import 'package:fl_query/fl_query.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:collection/collection.dart';
import 'package:fuzzywuzzy/fuzzywuzzy.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:platform_ui/platform_ui.dart'; import 'package:platform_ui/platform_ui.dart';
@ -8,13 +10,14 @@ import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/components/shared/track_table/tracks_table_view.dart'; import 'package:spotube/components/shared/track_table/tracks_table_view.dart';
import 'package:spotube/provider/auth_provider.dart'; import 'package:spotube/provider/auth_provider.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:spotube/hooks/use_custom_status_bar_color.dart'; import 'package:spotube/hooks/use_custom_status_bar_color.dart';
import 'package:spotube/hooks/use_palette_color.dart'; import 'package:spotube/hooks/use_palette_color.dart';
import 'package:spotube/models/logger.dart'; import 'package:spotube/models/logger.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:tuple/tuple.dart';
class TrackCollectionView<T> extends HookConsumerWidget { class TrackCollectionView<T> extends HookConsumerWidget {
final logger = getLogger(TrackCollectionView); final logger = getLogger(TrackCollectionView);
@ -97,6 +100,28 @@ class TrackCollectionView<T> extends HookConsumerWidget {
final collapsed = useState(false); final collapsed = useState(false);
final searchText = useState("");
final filteredTracks = useMemoized(() {
return tracksSnapshot.data
?.mapIndexed((i, e) => Tuple2(
searchText.value.isEmpty
? 100
: weightedRatio(
"${e.name} - ${TypeConversionUtils.artists_X_String<Artist>(e.artists ?? [])}",
searchText.value,
),
e..discNumber = i,
))
.toList()
.sorted(
(a, b) => b.item1.compareTo(a.item1),
)
.where((e) => e.item1 > 50)
.map((e) => e.item2)
.toList();
}, [tracksSnapshot.data, searchText.value]);
useCustomStatusBarColor( useCustomStatusBarColor(
color?.color ?? PlatformTheme.of(context).scaffoldBackgroundColor!, color?.color ?? PlatformTheme.of(context).scaffoldBackgroundColor!,
GoRouter.of(context).location == routePath, GoRouter.of(context).location == routePath,
@ -116,13 +141,51 @@ class TrackCollectionView<T> extends HookConsumerWidget {
return () => controller.removeListener(listener); return () => controller.removeListener(listener);
}, [collapsed.value]); }, [collapsed.value]);
final leading = Row(
mainAxisSize: MainAxisSize.min,
children: [
if (platform != TargetPlatform.windows)
PlatformBackButton(color: color?.titleTextColor),
const SizedBox(width: 10),
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 400,
maxHeight: 40,
),
child: PlatformTextField(
onChanged: (value) => searchText.value = value,
placeholder: "Search tracks...",
backgroundColor: Colors.transparent,
prefixIcon: Icons.search_rounded,
),
),
],
);
useEffect(() {
OverlayEntry? entry;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (platform == TargetPlatform.windows) {
entry = OverlayEntry(builder: (context) {
return Positioned(
left: 40,
top: 7,
child: leading,
);
});
Overlay.of(context)!.insert(entry!);
}
});
return () => entry?.remove();
}, [color?.titleTextColor, leading]);
return SafeArea( return SafeArea(
child: PlatformScaffold( child: PlatformScaffold(
appBar: kIsDesktop appBar: kIsDesktop
? PageWindowTitleBar( ? PageWindowTitleBar(
backgroundColor: color?.color, backgroundColor: color?.color,
foregroundColor: color?.titleTextColor, foregroundColor: color?.titleTextColor,
leading: PlatformBackButton(color: color?.titleTextColor), leading: leading,
) )
: null, : null,
body: CustomScrollView( body: CustomScrollView(
@ -134,9 +197,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
pinned: true, pinned: true,
expandedHeight: 400, expandedHeight: 400,
automaticallyImplyLeading: kIsMobile, automaticallyImplyLeading: kIsMobile,
leading: kIsMobile leading: kIsMobile ? leading : null,
? PlatformBackButton(color: color?.titleTextColor)
: null,
iconTheme: IconThemeData(color: color?.titleTextColor), iconTheme: IconThemeData(color: color?.titleTextColor),
primary: true, primary: true,
backgroundColor: color?.color, backgroundColor: color?.color,
@ -239,17 +300,19 @@ class TrackCollectionView<T> extends HookConsumerWidget {
child: PlatformText("Error ${tracksSnapshot.error}")); child: PlatformText("Error ${tracksSnapshot.error}"));
} }
final tracks = tracksSnapshot.data!;
return TracksTableView( return TracksTableView(
tracks is! List<Track> List.from(
? tracks (filteredTracks ?? []).map(
.map( (e) {
(track) => if (e is Track) {
TypeConversionUtils.simpleTrack_X_Track( return e;
track, album!), } else {
) return TypeConversionUtils.simpleTrack_X_Track(
.toList() e, album!);
: tracks, }
},
),
),
onTrackPlayButtonPressed: onPlay, onTrackPlayButtonPressed: onPlay,
playlistId: id, playlistId: id,
userPlaylist: isOwned, userPlaylist: isOwned,

View File

@ -199,7 +199,9 @@ class TrackTile extends HookConsumerWidget {
height: 20, height: 20,
width: 35, width: 35,
child: Center( child: Center(
child: AutoSizeText((track.key + 1).toString()), child: AutoSizeText(
(track.key + 1).toString(),
),
), ),
), ),
Padding( Padding(

View File

@ -1,4 +1,5 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:tuple/tuple.dart';
extension MultiSortListMap on List<Map> { extension MultiSortListMap on List<Map> {
/// [preference] - List of properties in which you want to sort the list /// [preference] - List of properties in which you want to sort the list
@ -46,3 +47,51 @@ extension MultiSortListMap on List<Map> {
return sorted((a, b) => sortAll(a, b)); return sorted((a, b) => sortAll(a, b));
} }
} }
extension MultiSortListTupleMap<V> on List<Tuple2<Map, V>> {
/// [preference] - List of properties in which you want to sort the list
/// i.e.
/// ```
/// List<String> preference = ['property1','property2'];
/// ```
/// This will first sort the list by property1 then by property2
///
/// [criteria] - List of booleans that specifies the criteria of sort
/// i.e., For ascending order `true` and for descending order `false`.
/// ```
/// List<bool> criteria = [true. false];
/// ```
List<Tuple2<Map, V>> sortByProperties(
List<bool> criteria, List<String> preference) {
if (preference.isEmpty || criteria.isEmpty || isEmpty) {
return this;
}
if (preference.length != criteria.length) {
print('Criteria length is not equal to preference');
return this;
}
int compare(int i, Tuple2<Map, V> a, Tuple2<Map, V> b) {
if (a.item1[preference[i]] == b.item1[preference[i]]) {
return 0;
} else if (a.item1[preference[i]] > b.item1[preference[i]]) {
return criteria[i] ? 1 : -1;
} else {
return criteria[i] ? -1 : 1;
}
}
int sortAll(Tuple2<Map, V> a, Tuple2<Map, V> b) {
int i = 0;
int result = 0;
while (i < preference.length) {
result = compare(i, a, b);
if (result != 0) break;
i++;
}
return result;
}
return sorted((a, b) => sortAll(a, b));
}
}