Skip to content

Commit

Permalink
feat(home): personalized section
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Feb 6, 2023
1 parent 0c54f2d commit 9080441
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 45 deletions.
5 changes: 2 additions & 3 deletions lib/collections/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import 'package:catcher/catcher.dart';
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import 'package:spotify/spotify.dart' hide Search;
import 'package:spotube/pages/home/home.dart';
import 'package:spotube/pages/settings/blacklist.dart';
import 'package:spotube/pages/settings/about.dart';
import 'package:spotube/utils/platform.dart';
import 'package:spotube/components/shared/spotube_page_route.dart';
import 'package:spotube/pages/album/album.dart';
import 'package:spotube/pages/artist/artist.dart';
import 'package:spotube/pages/genre/genres.dart';
import 'package:spotube/pages/library/library.dart';
import 'package:spotube/pages/desktop_login/login_tutorial.dart';
import 'package:spotube/pages/desktop_login/desktop_login.dart';
Expand All @@ -31,8 +31,7 @@ final router = GoRouter(
routes: [
GoRoute(
path: "/",
pageBuilder: (context, state) =>
SpotubePage(child: const GenrePage()),
pageBuilder: (context, state) => SpotubePage(child: const HomePage()),
),
GoRoute(
path: "/search",
Expand Down
2 changes: 2 additions & 0 deletions lib/collections/spotube_icons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,6 @@ abstract class SpotubeIcons {
static const userRemove = FeatherIcons.userX;
static const close = FeatherIcons.x;
static const minimize = FeatherIcons.chevronDown;
static const personalized = FeatherIcons.star;
static const genres = FeatherIcons.music;
}
23 changes: 22 additions & 1 deletion lib/components/album/album_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:uuid/uuid.dart';

enum AlbumType {
album,
single,
compilation;

factory AlbumType.from(String? type) {
switch (type) {
case "album":
return AlbumType.album;
case "single":
return AlbumType.single;
case "compilation":
return AlbumType.compilation;
default:
return AlbumType.album;
}
}

String get formatted => name.replaceFirst(name[0], name[0].toUpperCase());
}

class AlbumCard extends HookConsumerWidget {
final Album album;
final PlaybuttonCardViewType viewType;
Expand Down Expand Up @@ -48,7 +69,7 @@ class AlbumCard extends HookConsumerWidget {
isLoading: isPlaylistPlaying && playlist?.isLoading == true,
title: album.name!,
description:
"Album • ${TypeConversionUtils.artists_X_String<ArtistSimple>(album.artists ?? [])}",
"${AlbumType.from(album.albumType!).formatted} • ${TypeConversionUtils.artists_X_String<ArtistSimple>(album.artists ?? [])}",
onTap: () {
ServiceUtils.navigate(context, "/album/${album.id}", extra: album);
},
Expand Down
2 changes: 0 additions & 2 deletions lib/components/genre/category_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ import 'package:spotube/services/queries/queries.dart';

class CategoryCard extends HookConsumerWidget {
final Category category;
final Iterable<PlaylistSimple>? playlists;
CategoryCard(
this.category, {
Key? key,
this.playlists,
}) : super(key: key);

final logger = getLogger(CategoryCard);
Expand Down
48 changes: 14 additions & 34 deletions lib/pages/genre/genres.dart → lib/pages/home/genres.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:collection/collection.dart';
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:platform_ui/platform_ui.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/genre/category_card.dart';
import 'package:spotube/components/shared/compact_search.dart';
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/components/shared/waypoint.dart';
import 'package:spotube/provider/auth_provider.dart';
import 'package:spotube/provider/spotify_provider.dart';

import 'package:spotube/provider/user_preferences_provider.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/platform.dart';
import 'package:tuple/tuple.dart';

class GenrePage extends HookConsumerWidget {
Expand Down Expand Up @@ -59,16 +56,11 @@ class GenrePage extends HookConsumerWidget {
final searchText = useState("");
final categories = useMemoized(
() {
final categories = [
Category()
..id = "user-featured-playlists"
..name = "Featured",
...categoriesQuery.pages
.expand<Category>(
(page) => page?.items ?? const Iterable.empty(),
)
.toList()
];
final categories = categoriesQuery.pages
.expand<Category>(
(page) => page?.items ?? const Iterable.empty(),
)
.toList();
if (searchText.value.isEmpty) {
return categories;
}
Expand Down Expand Up @@ -117,27 +109,15 @@ class GenrePage extends HookConsumerWidget {
),
),
);
return PlatformScaffold(
appBar: kIsDesktop
? PageWindowTitleBar(
actions: [searchbar, const SizedBox(width: 10)],
)
: null,
backgroundColor: PlatformProperty.all(
PlatformTheme.of(context).scaffoldBackgroundColor!,
),
body: (platform == TargetPlatform.windows && kIsDesktop) || kIsMobile
? Stack(
children: [
Positioned.fill(child: list),
Positioned(
top: kIsMobile ? 30 : 10,
right: kIsMobile ? 5 : 20,
child: searchbar,
),
],
)
: list,
return Stack(
children: [
Positioned.fill(child: list),
Positioned(
top: 0,
right: 10,
child: searchbar,
),
],
);
});
}
Expand Down
69 changes: 69 additions & 0 deletions lib/pages/home/home.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:platform_ui/platform_ui.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/pages/home/genres.dart';
import 'package:spotube/pages/home/personalized.dart';

class HomePage extends HookConsumerWidget {
const HomePage({Key? key}) : super(key: key);

@override
Widget build(BuildContext context, ref) {
final index = useState(0);
final tabbar = PlatformTabBar(
androidIsScrollable: true,
selectedIndex: index.value,
onSelectedIndexChanged: (value) => index.value = value,
isNavigational:
PlatformProperty.byPlatformGroup(mobile: false, desktop: true),
tabs: [
PlatformTab(
label: 'Genres',
icon: PlatformProperty.only(
android: const SizedBox.shrink(),
other: const Icon(SpotubeIcons.genres),
).resolve(platform!),
),
PlatformTab(
label: 'Personalized',
icon: PlatformProperty.only(
android: const SizedBox.shrink(),
other: const Icon(SpotubeIcons.personalized),
).resolve(platform!),
),
],
);

return PlatformScaffold(
appBar: platform == TargetPlatform.windows
? PreferredSize(
preferredSize: const Size.fromHeight(40),
child: tabbar,
)
: PageWindowTitleBar(
titleWidth: 347,
centerTitle: true,
center: tabbar,
),
body: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) => SlideTransition(
position: animation.drive(
Tween<Offset>(
begin: const Offset(1, 0),
end: const Offset(0, 0),
),
),
child: child,
),
child: [
const GenrePage(),
const PersonalizedPage(),
][index.value],
),
);
}
}
150 changes: 150 additions & 0 deletions lib/pages/home/personalized.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import 'package:fl_query_hooks/fl_query_hooks.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' hide Page;
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:platform_ui/platform_ui.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/album/album_card.dart';
import 'package:spotube/components/playlist/playlist_card.dart';
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
import 'package:spotube/components/shared/waypoint.dart';
import 'package:spotube/models/logger.dart';
import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/type_conversion_utils.dart';

class PersonalizedItemCard extends HookWidget {
final Iterable<Page<PlaylistSimple>>? playlists;
final Iterable<Page<AlbumSimple>>? albums;
final String title;
final bool hasNextPage;
final void Function() onFetchMore;

PersonalizedItemCard({
this.playlists,
this.albums,
required this.title,
required this.hasNextPage,
required this.onFetchMore,
Key? key,
}) : assert(playlists == null || albums == null),
super(key: key);

final logger = getLogger(PersonalizedItemCard);

@override
Widget build(BuildContext context) {
final scrollController = useScrollController();

final playlistItems = playlists
?.expand(
(page) => page.items ?? const Iterable<PlaylistSimple>.empty(),
)
.toList();

final albumItems = albums
?.expand(
(page) => page.items ?? const Iterable<AlbumSimple>.empty(),
)
.toList();

return Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
PlatformText.headline(title),
],
),
),
SizedBox(
height: playlists != null ? 245 : 285,
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
},
),
child: Scrollbar(
controller: scrollController,
interactive: false,
child: Waypoint(
controller: scrollController,
onTouchEdge: hasNextPage ? onFetchMore : null,
child: ListView(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
controller: scrollController,
physics: const AlwaysScrollableScrollPhysics(),
children: [
...?playlistItems
?.map((playlist) => PlaylistCard(playlist)),
...?albumItems?.map(
(album) => AlbumCard(
TypeConversionUtils.simpleAlbum_X_Album(album),
),
),
if (hasNextPage) const ShimmerPlaybuttonCard(count: 1),
],
),
),
),
),
),
],
);
}
}

class PersonalizedPage extends HookConsumerWidget {
const PersonalizedPage({Key? key}) : super(key: key);

@override
Widget build(BuildContext context, ref) {
final spotify = ref.watch(spotifyProvider);

final featuredPlaylistsQuery = useInfiniteQuery(
job: Queries.playlist.featured,
externalData: spotify,
);

final newReleases = useInfiniteQuery(
job: Queries.album.newReleases,
externalData: spotify,
);

useEffect(() {
if (featuredPlaylistsQuery.hasError &&
featuredPlaylistsQuery.pages.first == null) {
featuredPlaylistsQuery.setExternalData(spotify);
featuredPlaylistsQuery.refetch();
}
if (newReleases.hasError && newReleases.pages.first == null) {
newReleases.setExternalData(spotify);
newReleases.refetch();
}
return null;
}, [spotify]);

return ListView(
children: [
PersonalizedItemCard(
playlists:
featuredPlaylistsQuery.pages.whereType<Page<PlaylistSimple>>(),
title: 'Featured',
hasNextPage: featuredPlaylistsQuery.hasNextPage,
onFetchMore: featuredPlaylistsQuery.fetchNextPage,
),
PersonalizedItemCard(
albums: newReleases.pages.whereType<Page<AlbumSimple>>(),
title: 'New Releases',
hasNextPage: newReleases.hasNextPage,
onFetchMore: newReleases.fetchNextPage,
),
],
);
}
}
2 changes: 1 addition & 1 deletion lib/provider/downloader_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'package:http/http.dart';
import 'package:metadata_god/metadata_god.dart';
import 'package:queue/queue.dart';
import 'package:path/path.dart' as path;
import 'package:spotify/spotify.dart' hide Image;
import 'package:spotify/spotify.dart' hide Image, Queue;
import 'package:spotube/components/shared/dialogs/replace_downloaded_dialog.dart';

import 'package:spotube/models/logger.dart';
Expand Down
Loading

0 comments on commit 9080441

Please sign in to comment.