Skip to content

Commit

Permalink
feat: search/filter tracks inside playlist or album
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Jan 5, 2023
1 parent 8a6ba3b commit a06cd0d
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 17 deletions.
2 changes: 1 addition & 1 deletion lib/components/library/user_artists.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class UserArtists extends HookConsumerWidget {
),
),
backgroundColor: PlatformTheme.of(context).scaffoldBackgroundColor,
body: (artistQuery.isLoading || !artistQuery.hasData)
body: artistQuery.pages.isEmpty
? Padding(
padding: const EdgeInsets.all(20),
child: Row(
Expand Down
93 changes: 78 additions & 15 deletions lib/components/shared/track_table/track_collection_view.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:fl_query/fl_query.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:hooks_riverpod/hooks_riverpod.dart';
import 'package:platform_ui/platform_ui.dart';
Expand All @@ -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/track_table/tracks_table_view.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_palette_color.dart';
import 'package:spotube/models/logger.dart';
import 'package:flutter/material.dart';
import 'package:spotify/spotify.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 {
final logger = getLogger(TrackCollectionView);
Expand Down Expand Up @@ -97,6 +100,28 @@ class TrackCollectionView<T> extends HookConsumerWidget {

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(
color?.color ?? PlatformTheme.of(context).scaffoldBackgroundColor!,
GoRouter.of(context).location == routePath,
Expand All @@ -116,13 +141,51 @@ class TrackCollectionView<T> extends HookConsumerWidget {
return () => controller.removeListener(listener);
}, [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(
child: PlatformScaffold(
appBar: kIsDesktop
? PageWindowTitleBar(
backgroundColor: color?.color,
foregroundColor: color?.titleTextColor,
leading: PlatformBackButton(color: color?.titleTextColor),
leading: leading,
)
: null,
body: CustomScrollView(
Expand All @@ -134,9 +197,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
pinned: true,
expandedHeight: 400,
automaticallyImplyLeading: kIsMobile,
leading: kIsMobile
? PlatformBackButton(color: color?.titleTextColor)
: null,
leading: kIsMobile ? leading : null,
iconTheme: IconThemeData(color: color?.titleTextColor),
primary: true,
backgroundColor: color?.color,
Expand Down Expand Up @@ -239,17 +300,19 @@ class TrackCollectionView<T> extends HookConsumerWidget {
child: PlatformText("Error ${tracksSnapshot.error}"));
}

final tracks = tracksSnapshot.data!;
return TracksTableView(
tracks is! List<Track>
? tracks
.map(
(track) =>
TypeConversionUtils.simpleTrack_X_Track(
track, album!),
)
.toList()
: tracks,
List.from(
(filteredTracks ?? []).map(
(e) {
if (e is Track) {
return e;
} else {
return TypeConversionUtils.simpleTrack_X_Track(
e, album!);
}
},
),
),
onTrackPlayButtonPressed: onPlay,
playlistId: id,
userPlaylist: isOwned,
Expand Down
4 changes: 3 additions & 1 deletion lib/components/shared/track_table/track_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ class TrackTile extends HookConsumerWidget {
height: 20,
width: 35,
child: Center(
child: AutoSizeText((track.key + 1).toString()),
child: AutoSizeText(
(track.key + 1).toString(),
),
),
),
Padding(
Expand Down
49 changes: 49 additions & 0 deletions lib/extensions/list.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:collection/collection.dart';
import 'package:tuple/tuple.dart';

extension MultiSortListMap on List<Map> {
/// [preference] - List of properties in which you want to sort the list
Expand Down Expand Up @@ -46,3 +47,51 @@ extension MultiSortListMap on List<Map> {
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));
}
}

0 comments on commit a06cd0d

Please sign in to comment.