mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
feat: improve loading animations
This commit is contained in:
parent
2ceb6a8e53
commit
b92583d0df
161
lib/collections/fake.dart
Normal file
161
lib/collections/fake.dart
Normal file
@ -0,0 +1,161 @@
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/extensions/track.dart';
|
||||
|
||||
abstract class FakeData {
|
||||
static final Image image = Image()
|
||||
..height = 1
|
||||
..width = 1
|
||||
..url = "url";
|
||||
|
||||
static final Followers followers = Followers()
|
||||
..href = "text"
|
||||
..total = 1;
|
||||
|
||||
static final Artist artist = Artist()
|
||||
..id = "1"
|
||||
..name = "Wow artist Good!"
|
||||
..images = [image]
|
||||
..popularity = 1
|
||||
..type = "type"
|
||||
..uri = "uri"
|
||||
..externalUrls = externalUrls
|
||||
..genres = ["genre"]
|
||||
..href = "text"
|
||||
..followers = followers;
|
||||
|
||||
static final externalIds = ExternalIds()
|
||||
..isrc = "text"
|
||||
..ean = "text"
|
||||
..upc = "text";
|
||||
|
||||
static final externalUrls = ExternalUrls()..spotify = "text";
|
||||
|
||||
static final Album album = Album()
|
||||
..id = "1"
|
||||
..genres = ["genre"]
|
||||
..label = "label"
|
||||
..popularity = 1
|
||||
..albumType = AlbumType.album
|
||||
..artists = [artist]
|
||||
..availableMarkets = [Market.BD]
|
||||
..externalUrls = externalUrls
|
||||
..href = "text"
|
||||
..images = [image]
|
||||
..name = "Another good album"
|
||||
..releaseDate = "2021-01-01"
|
||||
..releaseDatePrecision = DatePrecision.day
|
||||
..tracks = [track]
|
||||
..type = "type"
|
||||
..uri = "uri"
|
||||
..externalIds = externalIds
|
||||
..copyrights = [
|
||||
Copyright()
|
||||
..type = CopyrightType.C
|
||||
..text = "text",
|
||||
];
|
||||
|
||||
static final ArtistSimple artistSimple = ArtistSimple()
|
||||
..id = "1"
|
||||
..name = "What an artist"
|
||||
..type = "type"
|
||||
..uri = "uri"
|
||||
..externalUrls = externalUrls;
|
||||
|
||||
static final AlbumSimple albumSimple = AlbumSimple()
|
||||
..id = "1"
|
||||
..albumType = AlbumType.album
|
||||
..artists = [artistSimple]
|
||||
..availableMarkets = [Market.BD]
|
||||
..externalUrls = externalUrls
|
||||
..href = "text"
|
||||
..images = [image]
|
||||
..name = "A good album"
|
||||
..releaseDate = "2021-01-01"
|
||||
..releaseDatePrecision = DatePrecision.day
|
||||
..type = "type"
|
||||
..uri = "uri";
|
||||
|
||||
static final Track track = Track()
|
||||
..id = "1"
|
||||
..artists = [artist, artist, artist]
|
||||
..album = albumSimple
|
||||
..availableMarkets = [Market.BD]
|
||||
..discNumber = 1
|
||||
..durationMs = 50000
|
||||
..explicit = false
|
||||
..externalUrls = externalUrls
|
||||
..href = "text"
|
||||
..name = "A Track Name"
|
||||
..popularity = 1
|
||||
..previewUrl = "url"
|
||||
..trackNumber = 1
|
||||
..type = "type"
|
||||
..uri = "uri"
|
||||
..isPlayable = true
|
||||
..explicit = false
|
||||
..linkedFrom = trackLink;
|
||||
|
||||
static final TrackLink trackLink = TrackLink()
|
||||
..id = "1"
|
||||
..type = "type"
|
||||
..uri = "uri"
|
||||
..externalUrls = {"spotify": "text"}
|
||||
..href = "text";
|
||||
|
||||
static final Paging<Track> paging = Paging()
|
||||
..href = "text"
|
||||
..itemsNative = [track.toJson()]
|
||||
..limit = 1
|
||||
..next = "text"
|
||||
..offset = 1
|
||||
..previous = "text"
|
||||
..total = 1;
|
||||
|
||||
static final User user = User()
|
||||
..id = "1"
|
||||
..displayName = "Your Name"
|
||||
..birthdate = "2021-01-01"
|
||||
..country = Market.BD
|
||||
..email = "test@email.com"
|
||||
..followers = followers
|
||||
..href = "text"
|
||||
..images = [image]
|
||||
..type = "type"
|
||||
..uri = "uri";
|
||||
|
||||
static final TracksLink tracksLink = TracksLink()
|
||||
..href = "text"
|
||||
..total = 1;
|
||||
|
||||
static final Playlist playlist = Playlist()
|
||||
..id = "1"
|
||||
..collaborative = false
|
||||
..description = "A very good playlist description"
|
||||
..externalUrls = externalUrls
|
||||
..followers = followers
|
||||
..href = "text"
|
||||
..images = [image]
|
||||
..name = "A good playlist"
|
||||
..owner = user
|
||||
..public = true
|
||||
..snapshotId = "text"
|
||||
..tracks = paging
|
||||
..tracksLink = tracksLink
|
||||
..type = "type"
|
||||
..uri = "uri";
|
||||
|
||||
static final PlaylistSimple playlistSimple = PlaylistSimple()
|
||||
..id = "1"
|
||||
..collaborative = false
|
||||
..externalUrls = externalUrls
|
||||
..href = "text"
|
||||
..images = [image]
|
||||
..name = "A good playlist"
|
||||
..owner = user
|
||||
..public = true
|
||||
..snapshotId = "text"
|
||||
..tracksLink = tracksLink
|
||||
..type = "type"
|
||||
..description = "A very good playlist description"
|
||||
..uri = "uri";
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||
@ -91,6 +92,7 @@ class ArtistCard extends HookConsumerWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(50)),
|
||||
child: Skeleton.ignore(
|
||||
child: Text(
|
||||
context.l10n.artist,
|
||||
style: const TextStyle(
|
||||
@ -101,6 +103,7 @@ class ArtistCard extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
@ -3,12 +3,13 @@ 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:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/fake.dart';
|
||||
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/album/album_card.dart';
|
||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
|
||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||
import 'package:spotube/components/shared/waypoint.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
@ -82,6 +83,8 @@ class UserAlbums extends HookConsumerWidget {
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
controller: controller,
|
||||
child: Skeletonizer(
|
||||
enabled: albums.isEmpty,
|
||||
child: Wrap(
|
||||
runSpacing: 20,
|
||||
alignment: WrapAlignment.center,
|
||||
@ -89,21 +92,20 @@ class UserAlbums extends HookConsumerWidget {
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
if (albums.isEmpty)
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: const ShimmerPlaybuttonCard(count: 4),
|
||||
...List.generate(
|
||||
10,
|
||||
(index) => AlbumCard(FakeData.album),
|
||||
),
|
||||
for (final album in albums)
|
||||
AlbumCard(
|
||||
TypeConversionUtils.simpleAlbum_X_Album(album),
|
||||
),
|
||||
if (albumsQuery.hasNextPage)
|
||||
if (albums.isNotEmpty && albumsQuery.hasNextPage)
|
||||
Waypoint(
|
||||
controller: controller,
|
||||
isGrid: true,
|
||||
onTouchEdge: albumsQuery.fetchNext,
|
||||
child: const ShimmerPlaybuttonCard(count: 1),
|
||||
child: AlbumCard(FakeData.album),
|
||||
)
|
||||
],
|
||||
),
|
||||
@ -112,6 +114,7 @@ class UserAlbums extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ 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:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotube/collections/fake.dart';
|
||||
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||
@ -87,11 +89,17 @@ class UserArtists extends HookConsumerWidget {
|
||||
width: double.infinity,
|
||||
child: SafeArea(
|
||||
child: Center(
|
||||
child: Skeletonizer(
|
||||
enabled: artistQuery.isLoading,
|
||||
child: Wrap(
|
||||
spacing: 15,
|
||||
runSpacing: 5,
|
||||
children: filteredArtists
|
||||
.mapIndexed((index, artist) => ArtistCard(artist))
|
||||
children: artistQuery.isLoading
|
||||
? List.generate(
|
||||
10, (index) => ArtistCard(FakeData.artist))
|
||||
: filteredArtists
|
||||
.mapIndexed(
|
||||
(index, artist) => ArtistCard(artist))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
@ -100,6 +108,7 @@ class UserArtists extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -11,12 +11,13 @@ import 'package:metadata_god/metadata_god.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/fake.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/shared/expandable_search/expandable_search.dart';
|
||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart';
|
||||
import 'package:spotube/components/shared/sort_tracks_dropdown.dart';
|
||||
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
@ -261,11 +262,18 @@ class UserLocalTracks extends HookConsumerWidget {
|
||||
},
|
||||
child: InterScrollbar(
|
||||
controller: controller,
|
||||
child: Skeletonizer(
|
||||
enabled: trackSnapshot.isLoading,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount: filteredTracks.length,
|
||||
itemCount:
|
||||
trackSnapshot.isLoading ? 5 : filteredTracks.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (trackSnapshot.isLoading) {
|
||||
return TrackTile(track: FakeData.track, index: index);
|
||||
}
|
||||
|
||||
final track = filteredTracks[index];
|
||||
return TrackTile(
|
||||
index: index,
|
||||
@ -283,10 +291,19 @@ class UserLocalTracks extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () =>
|
||||
const Expanded(child: ShimmerTrackTileGroup(noSliver: true)),
|
||||
loading: () => Expanded(
|
||||
child: Skeletonizer(
|
||||
enabled: true,
|
||||
child: ListView.builder(
|
||||
itemCount: 5,
|
||||
itemBuilder: (context, index) =>
|
||||
TrackTile(track: FakeData.track, index: index),
|
||||
),
|
||||
),
|
||||
),
|
||||
error: (error, stackTrace) =>
|
||||
Text(error.toString() + stackTrace.toString()),
|
||||
)
|
||||
|
@ -1,16 +1,16 @@
|
||||
import 'package:flutter/material.dart' hide Image;
|
||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/fake.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/playlist/playlist_create_dialog.dart';
|
||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
|
||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||
import 'package:spotube/components/playlist/playlist_card.dart';
|
||||
import 'package:spotube/components/shared/waypoint.dart';
|
||||
@ -123,7 +123,7 @@ class UserPlaylists extends HookConsumerWidget {
|
||||
),
|
||||
SliverLayoutBuilder(builder: (context, constrains) {
|
||||
return SliverGrid.builder(
|
||||
itemCount: playlists.length + 1,
|
||||
itemCount: playlists.isEmpty ? 6 : playlists.length + 1,
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 200,
|
||||
mainAxisExtent: constrains.smAndDown ? 225 : 250,
|
||||
@ -131,7 +131,7 @@ class UserPlaylists extends HookConsumerWidget {
|
||||
mainAxisSpacing: 8,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == playlists.length) {
|
||||
if (playlists.isNotEmpty && index == playlists.length) {
|
||||
if (!playlistsQuery.hasNextPage) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
@ -140,11 +140,17 @@ class UserPlaylists extends HookConsumerWidget {
|
||||
controller: controller,
|
||||
isGrid: true,
|
||||
onTouchEdge: playlistsQuery.fetchNext,
|
||||
child: const ShimmerPlaybuttonCard(count: 1),
|
||||
child: Skeletonizer(
|
||||
enabled: true,
|
||||
child: PlaylistCard(FakeData.playlistSimple),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return PlaylistCard(playlists[index]);
|
||||
return PlaylistCard(
|
||||
playlists.elementAtOrNull(index) ??
|
||||
FakeData.playlistSimple,
|
||||
);
|
||||
},
|
||||
);
|
||||
})
|
||||
|
@ -2,11 +2,12 @@ import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/fake.dart';
|
||||
import 'package:spotube/components/album/album_card.dart';
|
||||
import 'package:spotube/components/artist/artist_card.dart';
|
||||
import 'package:spotube/components/playlist/playlist_card.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
|
||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||
|
||||
@ -61,25 +62,36 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
|
||||
PointerDeviceKind.mouse,
|
||||
},
|
||||
),
|
||||
child: InfiniteList(
|
||||
child: items.isEmpty
|
||||
? ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: 5,
|
||||
itemBuilder: (context, index) {
|
||||
return AlbumCard(FakeData.albumSimple);
|
||||
},
|
||||
)
|
||||
: InfiniteList(
|
||||
scrollController: scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
itemCount: items.length,
|
||||
onFetchData: onFetchMore,
|
||||
loadingBuilder: (context) => const ShimmerPlaybuttonCard(),
|
||||
emptyBuilder: (context) =>
|
||||
const ShimmerPlaybuttonCard(count: 5),
|
||||
loadingBuilder: (context) => Skeletonizer(
|
||||
enabled: true,
|
||||
child: AlbumCard(FakeData.albumSimple),
|
||||
),
|
||||
isLoading: isLoadingNextPage,
|
||||
hasReachedMax: !hasNextPage,
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
|
||||
return switch (item.runtimeType) {
|
||||
PlaylistSimple => PlaylistCard(item as PlaylistSimple),
|
||||
PlaylistSimple =>
|
||||
PlaylistCard(item as PlaylistSimple),
|
||||
Album => AlbumCard(item as Album),
|
||||
Artist => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: ArtistCard(item as Artist),
|
||||
),
|
||||
_ => const SizedBox.shrink(),
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
import 'package:spotube/collections/assets.gen.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
@ -146,7 +147,8 @@ class PlaybuttonCard extends HookWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!isPlaying)
|
||||
IconButton(
|
||||
Skeleton.keep(
|
||||
child: IconButton(
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.background,
|
||||
foregroundColor: theme.colorScheme.primary,
|
||||
@ -155,6 +157,7 @@ class PlaybuttonCard extends HookWidget {
|
||||
icon: const Icon(SpotubeIcons.queueAdd),
|
||||
onPressed: isLoading ? null : onAddToQueuePressed,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
IconButton(
|
||||
style: IconButton.styleFrom(
|
||||
@ -162,7 +165,8 @@ class PlaybuttonCard extends HookWidget {
|
||||
foregroundColor: theme.colorScheme.primary,
|
||||
minimumSize: const Size.square(10),
|
||||
),
|
||||
icon: isLoading
|
||||
icon: Skeleton.keep(
|
||||
child: isLoading
|
||||
? SizedBox.fromSize(
|
||||
size: const Size.square(15),
|
||||
child: const CircularProgressIndicator(
|
||||
@ -171,6 +175,7 @@ class PlaybuttonCard extends HookWidget {
|
||||
: isPlaying
|
||||
? const Icon(SpotubeIcons.pause)
|
||||
: const Icon(SpotubeIcons.play),
|
||||
),
|
||||
onPressed: isLoading ? null : onPlaybuttonPressed,
|
||||
),
|
||||
],
|
||||
|
@ -1,57 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
import 'package:skeleton_text/skeleton_text.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart';
|
||||
import 'package:spotube/extensions/theme.dart';
|
||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||
|
||||
class ShimmerArtistProfile extends HookWidget {
|
||||
const ShimmerArtistProfile({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final shimmerTheme = ShimmerColorTheme(
|
||||
shimmerBackgroundColor: isDark ? Colors.grey[700] : Colors.grey[200],
|
||||
shimmerColor: isDark ? Colors.grey[800] : Colors.grey[300],
|
||||
);
|
||||
final shimmerColor = shimmerTheme.shimmerColor ?? Colors.white;
|
||||
final shimmerBackgroundColor =
|
||||
shimmerTheme.shimmerBackgroundColor ?? Colors.grey;
|
||||
|
||||
final avatarWidth = useBreakpointValue(
|
||||
xs: MediaQuery.of(context).size.width * 0.80,
|
||||
sm: MediaQuery.of(context).size.width * 0.80,
|
||||
md: MediaQuery.of(context).size.width * 0.50,
|
||||
lg: MediaQuery.of(context).size.width * 0.30,
|
||||
xl: MediaQuery.of(context).size.width * 0.30,
|
||||
xxl: MediaQuery.of(context).size.width * 0.30,
|
||||
) ??
|
||||
0;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: SkeletonAnimation(
|
||||
shimmerColor: shimmerColor,
|
||||
borderRadius: BorderRadius.circular(avatarWidth),
|
||||
shimmerDuration: 1000,
|
||||
child: Container(
|
||||
width: avatarWidth,
|
||||
height: avatarWidth,
|
||||
decoration: BoxDecoration(
|
||||
color: shimmerBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(avatarWidth),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const Flexible(child: ShimmerTrackTileGroup(noSliver: true)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
|
||||
import 'package:spotube/extensions/theme.dart';
|
||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||
|
||||
class ShimmerCategories extends HookWidget {
|
||||
const ShimmerCategories({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final shimmerTheme = ShimmerColorTheme(
|
||||
shimmerBackgroundColor: isDark ? Colors.grey[700] : Colors.grey[200],
|
||||
);
|
||||
final shimmerBackgroundColor =
|
||||
shimmerTheme.shimmerBackgroundColor ?? Colors.grey;
|
||||
|
||||
final shimmerCount = useBreakpointValue(
|
||||
xs: 2,
|
||||
sm: 2,
|
||||
md: 3,
|
||||
lg: 3,
|
||||
xl: 6,
|
||||
xxl: 8,
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.only(left: 15),
|
||||
height: 10,
|
||||
width: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: shimmerBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: ShimmerPlaybuttonCard(count: shimmerCount),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,69 +1,38 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
|
||||
import 'package:skeleton_text/skeleton_text.dart';
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
import 'package:spotube/extensions/theme.dart';
|
||||
|
||||
const widths = [20, 56, 89, 60, 25, 69];
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
class ShimmerLyrics extends HookWidget {
|
||||
const ShimmerLyrics({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final shimmerTheme = ShimmerColorTheme(
|
||||
shimmerBackgroundColor: isDark ? Colors.grey[700] : Colors.grey[200],
|
||||
shimmerColor: isDark ? Colors.grey[800] : Colors.grey[300],
|
||||
);
|
||||
final shimmerColor = shimmerTheme.shimmerColor ?? Colors.white;
|
||||
final shimmerBackgroundColor =
|
||||
shimmerTheme.shimmerBackgroundColor ?? Colors.grey;
|
||||
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: 20,
|
||||
shrinkWrap: true,
|
||||
return Skeletonizer(
|
||||
enabled: true,
|
||||
child: ListView.builder(
|
||||
itemCount: 30,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
final widthsCp = [...widths];
|
||||
if (mediaQuery.isMd) {
|
||||
widthsCp.removeLast();
|
||||
}
|
||||
if (mediaQuery.smAndDown) {
|
||||
widthsCp.removeLast();
|
||||
widthsCp.removeLast();
|
||||
}
|
||||
widthsCp.shuffle();
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: Row(
|
||||
final texts = [
|
||||
"Lorem ipsum",
|
||||
"consectetur.",
|
||||
"Sed",
|
||||
"Sed non risus",
|
||||
]..shuffle();
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: widthsCp.map(
|
||||
(width) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: SkeletonAnimation(
|
||||
shimmerColor: shimmerColor,
|
||||
shimmerDuration: 1000,
|
||||
child: Container(
|
||||
height: 10,
|
||||
width: width.toDouble(),
|
||||
decoration: BoxDecoration(
|
||||
color: shimmerBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
),
|
||||
),
|
||||
children: [
|
||||
for (final text in texts) ...[
|
||||
Text(text),
|
||||
if (text != texts.last) const Gap(10),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,119 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||
|
||||
class ShimmerPlaybuttonCardPainter extends CustomPainter {
|
||||
final Color background;
|
||||
final Color foreground;
|
||||
ShimmerPlaybuttonCardPainter({
|
||||
required this.background,
|
||||
required this.foreground,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
const radius = Radius.circular(15);
|
||||
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(0, 0, size.width, size.height),
|
||||
radius,
|
||||
),
|
||||
Paint()..color = background,
|
||||
);
|
||||
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(8, 8, size.width - 16, size.height - 90),
|
||||
radius,
|
||||
),
|
||||
Paint()..color = foreground,
|
||||
);
|
||||
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(12, size.height - 67, size.width / 2, 10),
|
||||
radius,
|
||||
),
|
||||
Paint()..color = foreground,
|
||||
);
|
||||
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(12, size.height - 45, size.width - 24, 8),
|
||||
radius,
|
||||
),
|
||||
Paint()..color = foreground,
|
||||
);
|
||||
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(12, size.height - 30, size.width * .4, 8),
|
||||
radius,
|
||||
),
|
||||
Paint()..color = foreground,
|
||||
);
|
||||
|
||||
canvas.drawCircle(
|
||||
Offset(size.width * .85, size.height * .50),
|
||||
17,
|
||||
Paint()..color = background,
|
||||
);
|
||||
|
||||
canvas.drawCircle(
|
||||
Offset(size.width * .85, size.height * .67),
|
||||
17,
|
||||
Paint()..color = background,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class ShimmerPlaybuttonCard extends HookWidget {
|
||||
final int count;
|
||||
|
||||
const ShimmerPlaybuttonCard({
|
||||
Key? key,
|
||||
this.count = 1,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final Size size = useBreakpointValue<Size>(
|
||||
xs: const Size(130, 200),
|
||||
sm: const Size(130, 200),
|
||||
md: const Size(150, 220),
|
||||
others: const Size(170, 240),
|
||||
);
|
||||
|
||||
final isDark = theme.brightness == Brightness.dark;
|
||||
final bgColor = theme.colorScheme.surfaceVariant.withOpacity(.2);
|
||||
final fgColor = Color.lerp(
|
||||
theme.colorScheme.surfaceVariant,
|
||||
isDark ? Colors.black : Colors.white,
|
||||
.4,
|
||||
);
|
||||
|
||||
return Wrap(
|
||||
spacing: 20,
|
||||
runSpacing: 20,
|
||||
children: [
|
||||
for (var i = 0; i < count; i++) ...[
|
||||
CustomPaint(
|
||||
size: size,
|
||||
painter: ShimmerPlaybuttonCardPainter(
|
||||
background: bgColor,
|
||||
foreground: fgColor!,
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:spotube/extensions/theme.dart';
|
||||
|
||||
class ShimmerTrackTilePainter extends CustomPainter {
|
||||
final Color background;
|
||||
final Color foreground;
|
||||
ShimmerTrackTilePainter({
|
||||
required this.background,
|
||||
required this.foreground,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = background
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(0, 0, size.width, size.height),
|
||||
const Radius.circular(5),
|
||||
),
|
||||
paint,
|
||||
);
|
||||
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(0, 0, size.height, size.height),
|
||||
const Radius.circular(5),
|
||||
),
|
||||
Paint()..color = foreground,
|
||||
);
|
||||
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
const Rect.fromLTWH(70, 10, 100, 10),
|
||||
const Radius.circular(5),
|
||||
),
|
||||
Paint()..color = foreground,
|
||||
);
|
||||
|
||||
// draw Icons.play
|
||||
const icon = Icons.play_arrow_outlined;
|
||||
TextPainter textPainter = TextPainter(textDirection: TextDirection.rtl);
|
||||
textPainter.text = TextSpan(
|
||||
text: String.fromCharCode(icon.codePoint),
|
||||
style: TextStyle(
|
||||
fontSize: 40.0,
|
||||
fontFamily: icon.fontFamily,
|
||||
color: background,
|
||||
),
|
||||
);
|
||||
textPainter.layout();
|
||||
textPainter.paint(canvas, const Offset(10, 10));
|
||||
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
const Rect.fromLTWH(70, 30, 170, 7),
|
||||
const Radius.circular(5),
|
||||
),
|
||||
Paint()..color = foreground,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class ShimmerTrackTile extends StatelessWidget {
|
||||
const ShimmerTrackTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final isDark = theme.brightness == Brightness.dark;
|
||||
final shimmerTheme = ShimmerColorTheme(
|
||||
shimmerBackgroundColor: isDark ? Colors.grey[700] : Colors.grey[200],
|
||||
shimmerColor: isDark ? Colors.grey[800] : Colors.grey[300],
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0, left: 8, right: 8),
|
||||
child: CustomPaint(
|
||||
size: const Size(double.infinity, 60),
|
||||
painter: ShimmerTrackTilePainter(
|
||||
background: shimmerTheme.shimmerBackgroundColor ??
|
||||
theme.scaffoldBackgroundColor,
|
||||
foreground: shimmerTheme.shimmerColor ?? theme.cardColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ShimmerTrackTileGroup extends StatelessWidget {
|
||||
final bool noSliver;
|
||||
final int count;
|
||||
const ShimmerTrackTileGroup({
|
||||
super.key,
|
||||
this.noSliver = false,
|
||||
this.count = 5,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (noSliver) {
|
||||
return ListView.builder(
|
||||
itemCount: 5,
|
||||
itemBuilder: (context, index) => const ShimmerTrackTile(),
|
||||
);
|
||||
}
|
||||
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) => const ShimmerTrackTile(),
|
||||
childCount: count,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -4,9 +4,10 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/fake.dart';
|
||||
import 'package:spotube/components/shared/expandable_search/expandable_search.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart';
|
||||
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
||||
import 'package:spotube/components/shared/tracks_view/sections/body/track_view_body_headers.dart';
|
||||
import 'package:spotube/components/shared/tracks_view/sections/body/use_is_user_playlist.dart';
|
||||
@ -84,7 +85,22 @@ class TrackViewBodySection extends HookConsumerWidget {
|
||||
onFetchData: props.pagination.onFetchMore,
|
||||
isLoading: props.pagination.isLoading,
|
||||
hasReachedMax: !props.pagination.hasNextPage,
|
||||
loadingBuilder: (context) => const ShimmerTrackTile(),
|
||||
loadingBuilder: (context) => Skeletonizer(
|
||||
enabled: true,
|
||||
child: TrackTile(
|
||||
track: FakeData.track,
|
||||
index: 0,
|
||||
),
|
||||
),
|
||||
emptyBuilder: (context) => Skeletonizer(
|
||||
enabled: true,
|
||||
child: Column(
|
||||
children: List.generate(
|
||||
10,
|
||||
(index) => TrackTile(track: FakeData.track, index: index),
|
||||
),
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final track = tracks[index];
|
||||
return TrackTile(
|
||||
|
@ -3,7 +3,6 @@ import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:sliver_tools/sliver_tools.dart';
|
||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart';
|
||||
import 'package:spotube/components/shared/tracks_view/sections/header/flexible_header.dart';
|
||||
import 'package:spotube/components/shared/tracks_view/sections/body/track_view_body.dart';
|
||||
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
||||
@ -30,14 +29,12 @@ class TrackView extends HookConsumerWidget {
|
||||
extendBodyBehindAppBar: true,
|
||||
body: RefreshIndicator(
|
||||
onRefresh: props.pagination.onRefresh,
|
||||
child: CustomScrollView(
|
||||
child: const CustomScrollView(
|
||||
slivers: [
|
||||
const TrackViewFlexHeader(),
|
||||
TrackViewFlexHeader(),
|
||||
SliverAnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: props.tracks.isEmpty
|
||||
? const ShimmerTrackTileGroup()
|
||||
: const TrackViewBodySection(),
|
||||
duration: Duration(milliseconds: 500),
|
||||
child: TrackViewBodySection(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||
import 'package:spotube/components/artist/artist_album_list.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_artist_profile.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/models/logger.dart';
|
||||
import 'package:spotube/pages/artist/section/footer.dart';
|
||||
@ -35,12 +35,12 @@ class ArtistPage extends HookConsumerWidget {
|
||||
),
|
||||
extendBodyBehindAppBar: true,
|
||||
body: Builder(builder: (context) {
|
||||
if (artistQuery.isLoading || !artistQuery.hasData) {
|
||||
const ShimmerArtistProfile();
|
||||
} else if (artistQuery.hasError) {
|
||||
if (artistQuery.hasError) {
|
||||
return Center(child: Text(artistQuery.error.toString()));
|
||||
}
|
||||
return CustomScrollView(
|
||||
return Skeletonizer(
|
||||
enabled: artistQuery.isLoading,
|
||||
child: CustomScrollView(
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
@ -74,6 +74,7 @@ class ArtistPage extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
|
@ -4,7 +4,9 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/fake.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
@ -25,7 +27,7 @@ class ArtistPageHeader extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, ref) {
|
||||
final queryClient = useQueryClient();
|
||||
final artistQuery = useQueries.artist.get(ref, artistId);
|
||||
final artist = artistQuery.data;
|
||||
final artist = artistQuery.data ?? FakeData.artist;
|
||||
|
||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
@ -41,10 +43,6 @@ class ArtistPageHeader extends HookConsumerWidget {
|
||||
xxl: textTheme.titleMedium,
|
||||
);
|
||||
|
||||
if (artist == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final spotify = ref.read(spotifyProvider);
|
||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||
final blacklist = ref.watch(BlackListNotifier.provider);
|
||||
@ -96,6 +94,7 @@ class ArtistPageHeader extends HookConsumerWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(50)),
|
||||
child: Skeleton.keep(
|
||||
child: Text(
|
||||
artist.type!.toUpperCase(),
|
||||
style: chipTextVariant.copyWith(
|
||||
@ -103,6 +102,7 @@ class ArtistPageHeader extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isBlackListed) ...[
|
||||
const SizedBox(width: 5),
|
||||
Container(
|
||||
@ -138,7 +138,8 @@ class ArtistPageHeader extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
const Gap(20),
|
||||
Row(
|
||||
Skeleton.keep(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (auth != null)
|
||||
@ -245,6 +246,7 @@ class ArtistPageHeader extends HookConsumerWidget {
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/fake.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
@ -28,11 +30,7 @@ class ArtistPageTopTracks extends HookConsumerWidget {
|
||||
topTracksQuery.data ?? <Track>[],
|
||||
);
|
||||
|
||||
if (topTracksQuery.isLoading || !topTracksQuery.hasData) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
} else if (topTracksQuery.hasError) {
|
||||
if (topTracksQuery.hasError) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: Text(topTracksQuery.error.toString()),
|
||||
@ -40,7 +38,8 @@ class ArtistPageTopTracks extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
final topTracks = topTracksQuery.data!;
|
||||
final topTracks =
|
||||
topTracksQuery.data ?? List.generate(10, (index) => FakeData.track);
|
||||
|
||||
void playPlaylist(List<Track> tracks, {Track? currentTrack}) async {
|
||||
currentTrack ??= tracks.first;
|
||||
@ -92,10 +91,12 @@ class ArtistPageTopTracks extends HookConsumerWidget {
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
icon: Skeleton.keep(
|
||||
child: Icon(
|
||||
isPlaylistPlaying ? SpotubeIcons.stop : SpotubeIcons.play,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
),
|
||||
|
@ -3,11 +3,12 @@ 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:skeletonizer/skeletonizer.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/genre/category_card.dart';
|
||||
import 'package:spotube/components/shared/expandable_search/expandable_search.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
|
||||
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||
import 'package:spotube/components/shared/waypoint.dart';
|
||||
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
@ -77,7 +78,23 @@ class GenrePage extends HookConsumerWidget {
|
||||
),
|
||||
if (!categoriesQuery.hasPageData &&
|
||||
!categoriesQuery.isLoadingNextPage)
|
||||
const ShimmerCategories()
|
||||
Expanded(
|
||||
child: Skeletonizer(
|
||||
enabled: true,
|
||||
child: ListView.builder(
|
||||
itemCount: 5,
|
||||
itemBuilder: (context, index) {
|
||||
return HorizontalPlaybuttonCardView<PlaylistSimple>(
|
||||
title: const Text("Loading"),
|
||||
items: const [],
|
||||
hasNextPage: true,
|
||||
isLoadingNextPage: false,
|
||||
onFetchMore: () {},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: InfiniteList(
|
||||
@ -86,7 +103,16 @@ class GenrePage extends HookConsumerWidget {
|
||||
onFetchData: categoriesQuery.fetchNext,
|
||||
isLoading: categoriesQuery.isLoadingNextPage,
|
||||
hasReachedMax: !categoriesQuery.hasNextPage,
|
||||
loadingBuilder: (context) => const ShimmerCategories(),
|
||||
loadingBuilder: (context) => Skeletonizer(
|
||||
enabled: true,
|
||||
child: HorizontalPlaybuttonCardView<PlaylistSimple>(
|
||||
title: const Text("Loading"),
|
||||
items: const [],
|
||||
hasNextPage: true,
|
||||
isLoadingNextPage: false,
|
||||
onFetchMore: () {},
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
return CategoryCard(categories[index]);
|
||||
},
|
||||
|
@ -4,11 +4,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/services/queries/queries.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
class PersonalizedPage extends HookConsumerWidget {
|
||||
const PersonalizedPage({Key? key}) : super(key: key);
|
||||
@ -46,39 +46,35 @@ class PersonalizedPage extends HookConsumerWidget {
|
||||
[newReleases.pages],
|
||||
);
|
||||
|
||||
final hasNewReleases = newReleases.hasPageData &&
|
||||
userArtistsQuery.hasData &&
|
||||
!newReleases.isLoadingNextPage;
|
||||
|
||||
final isLoadingFeaturedPlaylists = !featuredPlaylistsQuery.hasPageData &&
|
||||
!featuredPlaylistsQuery.isLoadingNextPage;
|
||||
|
||||
return CustomScrollView(
|
||||
controller: controller,
|
||||
slivers: [
|
||||
SliverList.list(
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: !featuredPlaylistsQuery.hasPageData &&
|
||||
!featuredPlaylistsQuery.isLoadingNextPage
|
||||
? const ShimmerCategories()
|
||||
: HorizontalPlaybuttonCardView<PlaylistSimple>(
|
||||
Skeletonizer(
|
||||
enabled: isLoadingFeaturedPlaylists,
|
||||
child: HorizontalPlaybuttonCardView<PlaylistSimple>(
|
||||
items: playlists.toList(),
|
||||
title: Text(context.l10n.featured),
|
||||
isLoadingNextPage:
|
||||
featuredPlaylistsQuery.isLoadingNextPage,
|
||||
isLoadingNextPage: featuredPlaylistsQuery.isLoadingNextPage,
|
||||
hasNextPage: featuredPlaylistsQuery.hasNextPage,
|
||||
onFetchMore: featuredPlaylistsQuery.fetchNext,
|
||||
),
|
||||
),
|
||||
if (auth != null)
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: newReleases.hasPageData &&
|
||||
userArtistsQuery.hasData &&
|
||||
!newReleases.isLoadingNextPage
|
||||
? HorizontalPlaybuttonCardView<Album>(
|
||||
if (auth != null || hasNewReleases)
|
||||
HorizontalPlaybuttonCardView<Album>(
|
||||
items: albums,
|
||||
title: Text(context.l10n.new_releases),
|
||||
isLoadingNextPage: newReleases.isLoadingNextPage,
|
||||
hasNextPage: newReleases.hasNextPage,
|
||||
onFetchMore: newReleases.fetchNext,
|
||||
)
|
||||
: const ShimmerCategories(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -77,7 +77,7 @@ class SyncedLyrics extends HookConsumerWidget {
|
||||
: textTheme.headlineMedium?.copyWith(fontSize: 25))
|
||||
?.copyWith(color: palette.titleTextColor);
|
||||
|
||||
var bodyTextTheme = textTheme.bodyLarge?.copyWith(
|
||||
final bodyTextTheme = textTheme.bodyLarge?.copyWith(
|
||||
color: palette.bodyTextColor,
|
||||
);
|
||||
return Stack(
|
||||
@ -184,7 +184,9 @@ class SyncedLyrics extends HookConsumerWidget {
|
||||
),
|
||||
if (playlist.activeTrack != null &&
|
||||
(timedLyricsQuery.isLoading || timedLyricsQuery.isRefreshing))
|
||||
const Expanded(child: ShimmerLyrics())
|
||||
const Expanded(
|
||||
child: ShimmerLyrics(),
|
||||
)
|
||||
else if (playlist.activeTrack != null &&
|
||||
(timedLyricsQuery.hasError))
|
||||
Text(
|
||||
|
@ -1832,6 +1832,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
skeletonizer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: skeletonizer
|
||||
sha256: ff4c36e826efd5288d7a84e7619a6e9be8185d3064cecf101a9133762f3b401b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -118,6 +118,7 @@ dependencies:
|
||||
url: https://github.com/Tommypop2/dart_discord_rpc.git
|
||||
html_unescape: ^2.0.0
|
||||
wikipedia_api: ^0.1.0
|
||||
skeletonizer: ^0.8.0
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.3.2
|
||||
|
Loading…
Reference in New Issue
Block a user