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:auto_size_text/auto_size_text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
|
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
@ -91,6 +92,7 @@ class ArtistCard extends HookConsumerWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
borderRadius: BorderRadius.circular(50)),
|
borderRadius: BorderRadius.circular(50)),
|
||||||
|
child: Skeleton.ignore(
|
||||||
child: Text(
|
child: Text(
|
||||||
context.l10n.artist,
|
context.l10n.artist,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@ -101,6 +103,7 @@ class ArtistCard extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
@ -3,12 +3,13 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.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:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.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/album/album_card.dart';
|
import 'package:spotube/components/album/album_card.dart';
|
||||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.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/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/components/shared/waypoint.dart';
|
import 'package:spotube/components/shared/waypoint.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
@ -82,6 +83,8 @@ class UserAlbums extends HookConsumerWidget {
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
child: Skeletonizer(
|
||||||
|
enabled: albums.isEmpty,
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
runSpacing: 20,
|
runSpacing: 20,
|
||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
@ -89,21 +92,20 @@ class UserAlbums extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (albums.isEmpty)
|
if (albums.isEmpty)
|
||||||
Container(
|
...List.generate(
|
||||||
alignment: Alignment.topLeft,
|
10,
|
||||||
padding: const EdgeInsets.all(16.0),
|
(index) => AlbumCard(FakeData.album),
|
||||||
child: const ShimmerPlaybuttonCard(count: 4),
|
|
||||||
),
|
),
|
||||||
for (final album in albums)
|
for (final album in albums)
|
||||||
AlbumCard(
|
AlbumCard(
|
||||||
TypeConversionUtils.simpleAlbum_X_Album(album),
|
TypeConversionUtils.simpleAlbum_X_Album(album),
|
||||||
),
|
),
|
||||||
if (albumsQuery.hasNextPage)
|
if (albums.isNotEmpty && albumsQuery.hasNextPage)
|
||||||
Waypoint(
|
Waypoint(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
isGrid: true,
|
isGrid: true,
|
||||||
onTouchEdge: albumsQuery.fetchNext,
|
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:collection/collection.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:skeletonizer/skeletonizer.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/shared/fallbacks/anonymous_fallback.dart';
|
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||||
@ -87,11 +89,17 @@ class UserArtists extends HookConsumerWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
|
child: Skeletonizer(
|
||||||
|
enabled: artistQuery.isLoading,
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
spacing: 15,
|
spacing: 15,
|
||||||
runSpacing: 5,
|
runSpacing: 5,
|
||||||
children: filteredArtists
|
children: artistQuery.isLoading
|
||||||
.mapIndexed((index, artist) => ArtistCard(artist))
|
? List.generate(
|
||||||
|
10, (index) => ArtistCard(FakeData.artist))
|
||||||
|
: filteredArtists
|
||||||
|
.mapIndexed(
|
||||||
|
(index, artist) => ArtistCard(artist))
|
||||||
.toList(),
|
.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:mime/mime.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
|
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.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/shared/expandable_search/expandable_search.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/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/sort_tracks_dropdown.dart';
|
||||||
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
@ -261,11 +262,18 @@ class UserLocalTracks extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
child: InterScrollbar(
|
child: InterScrollbar(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
child: Skeletonizer(
|
||||||
|
enabled: trackSnapshot.isLoading,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
itemCount: filteredTracks.length,
|
itemCount:
|
||||||
|
trackSnapshot.isLoading ? 5 : filteredTracks.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
if (trackSnapshot.isLoading) {
|
||||||
|
return TrackTile(track: FakeData.track, index: index);
|
||||||
|
}
|
||||||
|
|
||||||
final track = filteredTracks[index];
|
final track = filteredTracks[index];
|
||||||
return TrackTile(
|
return TrackTile(
|
||||||
index: index,
|
index: index,
|
||||||
@ -283,10 +291,19 @@ class UserLocalTracks extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
loading: () =>
|
loading: () => Expanded(
|
||||||
const Expanded(child: ShimmerTrackTileGroup(noSliver: true)),
|
child: Skeletonizer(
|
||||||
|
enabled: true,
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: 5,
|
||||||
|
itemBuilder: (context, index) =>
|
||||||
|
TrackTile(track: FakeData.track, index: index),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
error: (error, stackTrace) =>
|
error: (error, stackTrace) =>
|
||||||
Text(error.toString() + stackTrace.toString()),
|
Text(error.toString() + stackTrace.toString()),
|
||||||
)
|
)
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import 'package:flutter/material.dart' hide Image;
|
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:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.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:skeletonizer/skeletonizer.dart';
|
||||||
|
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.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/playlist/playlist_create_dialog.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/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/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/components/playlist/playlist_card.dart';
|
import 'package:spotube/components/playlist/playlist_card.dart';
|
||||||
import 'package:spotube/components/shared/waypoint.dart';
|
import 'package:spotube/components/shared/waypoint.dart';
|
||||||
@ -123,7 +123,7 @@ class UserPlaylists extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
SliverLayoutBuilder(builder: (context, constrains) {
|
SliverLayoutBuilder(builder: (context, constrains) {
|
||||||
return SliverGrid.builder(
|
return SliverGrid.builder(
|
||||||
itemCount: playlists.length + 1,
|
itemCount: playlists.isEmpty ? 6 : playlists.length + 1,
|
||||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
maxCrossAxisExtent: 200,
|
maxCrossAxisExtent: 200,
|
||||||
mainAxisExtent: constrains.smAndDown ? 225 : 250,
|
mainAxisExtent: constrains.smAndDown ? 225 : 250,
|
||||||
@ -131,7 +131,7 @@ class UserPlaylists extends HookConsumerWidget {
|
|||||||
mainAxisSpacing: 8,
|
mainAxisSpacing: 8,
|
||||||
),
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index == playlists.length) {
|
if (playlists.isNotEmpty && index == playlists.length) {
|
||||||
if (!playlistsQuery.hasNextPage) {
|
if (!playlistsQuery.hasNextPage) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
@ -140,11 +140,17 @@ class UserPlaylists extends HookConsumerWidget {
|
|||||||
controller: controller,
|
controller: controller,
|
||||||
isGrid: true,
|
isGrid: true,
|
||||||
onTouchEdge: playlistsQuery.fetchNext,
|
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/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/collections/fake.dart';
|
||||||
import 'package:spotube/components/album/album_card.dart';
|
import 'package:spotube/components/album/album_card.dart';
|
||||||
import 'package:spotube/components/artist/artist_card.dart';
|
import 'package:spotube/components/artist/artist_card.dart';
|
||||||
import 'package:spotube/components/playlist/playlist_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:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
@ -61,25 +62,36 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
|
|||||||
PointerDeviceKind.mouse,
|
PointerDeviceKind.mouse,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
child: InfiniteList(
|
child: items.isEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: 5,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return AlbumCard(FakeData.albumSimple);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: InfiniteList(
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
itemCount: items.length,
|
itemCount: items.length,
|
||||||
onFetchData: onFetchMore,
|
onFetchData: onFetchMore,
|
||||||
loadingBuilder: (context) => const ShimmerPlaybuttonCard(),
|
loadingBuilder: (context) => Skeletonizer(
|
||||||
emptyBuilder: (context) =>
|
enabled: true,
|
||||||
const ShimmerPlaybuttonCard(count: 5),
|
child: AlbumCard(FakeData.albumSimple),
|
||||||
|
),
|
||||||
isLoading: isLoadingNextPage,
|
isLoading: isLoadingNextPage,
|
||||||
hasReachedMax: !hasNextPage,
|
hasReachedMax: !hasNextPage,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = items[index];
|
final item = items[index];
|
||||||
|
|
||||||
return switch (item.runtimeType) {
|
return switch (item.runtimeType) {
|
||||||
PlaylistSimple => PlaylistCard(item as PlaylistSimple),
|
PlaylistSimple =>
|
||||||
|
PlaylistCard(item as PlaylistSimple),
|
||||||
Album => AlbumCard(item as Album),
|
Album => AlbumCard(item as Album),
|
||||||
Artist => Padding(
|
Artist => Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
child: ArtistCard(item as Artist),
|
child: ArtistCard(item as Artist),
|
||||||
),
|
),
|
||||||
_ => const SizedBox.shrink(),
|
_ => const SizedBox.shrink(),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:auto_size_text/auto_size_text.dart';
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
import 'package:spotube/collections/assets.gen.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
@ -146,7 +147,8 @@ class PlaybuttonCard extends HookWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (!isPlaying)
|
if (!isPlaying)
|
||||||
IconButton(
|
Skeleton.keep(
|
||||||
|
child: IconButton(
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
backgroundColor: theme.colorScheme.background,
|
backgroundColor: theme.colorScheme.background,
|
||||||
foregroundColor: theme.colorScheme.primary,
|
foregroundColor: theme.colorScheme.primary,
|
||||||
@ -155,6 +157,7 @@ class PlaybuttonCard extends HookWidget {
|
|||||||
icon: const Icon(SpotubeIcons.queueAdd),
|
icon: const Icon(SpotubeIcons.queueAdd),
|
||||||
onPressed: isLoading ? null : onAddToQueuePressed,
|
onPressed: isLoading ? null : onAddToQueuePressed,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
IconButton(
|
IconButton(
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
@ -162,7 +165,8 @@ class PlaybuttonCard extends HookWidget {
|
|||||||
foregroundColor: theme.colorScheme.primary,
|
foregroundColor: theme.colorScheme.primary,
|
||||||
minimumSize: const Size.square(10),
|
minimumSize: const Size.square(10),
|
||||||
),
|
),
|
||||||
icon: isLoading
|
icon: Skeleton.keep(
|
||||||
|
child: isLoading
|
||||||
? SizedBox.fromSize(
|
? SizedBox.fromSize(
|
||||||
size: const Size.square(15),
|
size: const Size.square(15),
|
||||||
child: const CircularProgressIndicator(
|
child: const CircularProgressIndicator(
|
||||||
@ -171,6 +175,7 @@ class PlaybuttonCard extends HookWidget {
|
|||||||
: isPlaying
|
: isPlaying
|
||||||
? const Icon(SpotubeIcons.pause)
|
? const Icon(SpotubeIcons.pause)
|
||||||
: const Icon(SpotubeIcons.play),
|
: const Icon(SpotubeIcons.play),
|
||||||
|
),
|
||||||
onPressed: isLoading ? null : onPlaybuttonPressed,
|
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/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
|
||||||
import 'package:skeleton_text/skeleton_text.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
|
||||||
import 'package:spotube/extensions/theme.dart';
|
|
||||||
|
|
||||||
const widths = [20, 56, 89, 60, 25, 69];
|
|
||||||
|
|
||||||
class ShimmerLyrics extends HookWidget {
|
class ShimmerLyrics extends HookWidget {
|
||||||
const ShimmerLyrics({Key? key}) : super(key: key);
|
const ShimmerLyrics({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
return Skeletonizer(
|
||||||
final shimmerTheme = ShimmerColorTheme(
|
enabled: true,
|
||||||
shimmerBackgroundColor: isDark ? Colors.grey[700] : Colors.grey[200],
|
child: ListView.builder(
|
||||||
shimmerColor: isDark ? Colors.grey[800] : Colors.grey[300],
|
itemCount: 30,
|
||||||
);
|
|
||||||
final shimmerColor = shimmerTheme.shimmerColor ?? Colors.white;
|
|
||||||
final shimmerBackgroundColor =
|
|
||||||
shimmerTheme.shimmerBackgroundColor ?? Colors.grey;
|
|
||||||
|
|
||||||
final mediaQuery = MediaQuery.of(context);
|
|
||||||
|
|
||||||
return ListView.builder(
|
|
||||||
itemCount: 20,
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
shrinkWrap: true,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final widthsCp = [...widths];
|
final texts = [
|
||||||
if (mediaQuery.isMd) {
|
"Lorem ipsum",
|
||||||
widthsCp.removeLast();
|
"consectetur.",
|
||||||
}
|
"Sed",
|
||||||
if (mediaQuery.smAndDown) {
|
"Sed non risus",
|
||||||
widthsCp.removeLast();
|
]..shuffle();
|
||||||
widthsCp.removeLast();
|
return Row(
|
||||||
}
|
|
||||||
widthsCp.shuffle();
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 5),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: widthsCp.map(
|
children: [
|
||||||
(width) {
|
for (final text in texts) ...[
|
||||||
return Padding(
|
Text(text),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
if (text != texts.last) const Gap(10),
|
||||||
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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).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:fuzzywuzzy/fuzzywuzzy.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotify/spotify.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/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/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/track_view_body_headers.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/sections/body/use_is_user_playlist.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,
|
onFetchData: props.pagination.onFetchMore,
|
||||||
isLoading: props.pagination.isLoading,
|
isLoading: props.pagination.isLoading,
|
||||||
hasReachedMax: !props.pagination.hasNextPage,
|
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) {
|
itemBuilder: (context, index) {
|
||||||
final track = tracks[index];
|
final track = tracks[index];
|
||||||
return TrackTile(
|
return TrackTile(
|
||||||
|
@ -3,7 +3,6 @@ import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:sliver_tools/sliver_tools.dart';
|
import 'package:sliver_tools/sliver_tools.dart';
|
||||||
import 'package:spotube/components/shared/page_window_title_bar.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/header/flexible_header.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/sections/body/track_view_body.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';
|
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
||||||
@ -30,14 +29,12 @@ class TrackView extends HookConsumerWidget {
|
|||||||
extendBodyBehindAppBar: true,
|
extendBodyBehindAppBar: true,
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: props.pagination.onRefresh,
|
onRefresh: props.pagination.onRefresh,
|
||||||
child: CustomScrollView(
|
child: const CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
const TrackViewFlexHeader(),
|
TrackViewFlexHeader(),
|
||||||
SliverAnimatedSwitcher(
|
SliverAnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 500),
|
duration: Duration(milliseconds: 500),
|
||||||
child: props.tracks.isEmpty
|
child: TrackViewBodySection(),
|
||||||
? const ShimmerTrackTileGroup()
|
|
||||||
: const TrackViewBodySection(),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -2,10 +2,10 @@ 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:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/shared/page_window_title_bar.dart';
|
||||||
import 'package:spotube/components/artist/artist_album_list.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/extensions/context.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/pages/artist/section/footer.dart';
|
import 'package:spotube/pages/artist/section/footer.dart';
|
||||||
@ -35,12 +35,12 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
extendBodyBehindAppBar: true,
|
extendBodyBehindAppBar: true,
|
||||||
body: Builder(builder: (context) {
|
body: Builder(builder: (context) {
|
||||||
if (artistQuery.isLoading || !artistQuery.hasData) {
|
if (artistQuery.hasError) {
|
||||||
const ShimmerArtistProfile();
|
|
||||||
} else if (artistQuery.hasError) {
|
|
||||||
return Center(child: Text(artistQuery.error.toString()));
|
return Center(child: Text(artistQuery.error.toString()));
|
||||||
}
|
}
|
||||||
return CustomScrollView(
|
return Skeletonizer(
|
||||||
|
enabled: artistQuery.isLoading,
|
||||||
|
child: CustomScrollView(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverToBoxAdapter(
|
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:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.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/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
@ -25,7 +27,7 @@ class ArtistPageHeader extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final queryClient = useQueryClient();
|
final queryClient = useQueryClient();
|
||||||
final artistQuery = useQueries.artist.get(ref, artistId);
|
final artistQuery = useQueries.artist.get(ref, artistId);
|
||||||
final artist = artistQuery.data;
|
final artist = artistQuery.data ?? FakeData.artist;
|
||||||
|
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
@ -41,10 +43,6 @@ class ArtistPageHeader extends HookConsumerWidget {
|
|||||||
xxl: textTheme.titleMedium,
|
xxl: textTheme.titleMedium,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (artist == null) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
final spotify = ref.read(spotifyProvider);
|
final spotify = ref.read(spotifyProvider);
|
||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
final blacklist = ref.watch(BlackListNotifier.provider);
|
final blacklist = ref.watch(BlackListNotifier.provider);
|
||||||
@ -96,6 +94,7 @@ class ArtistPageHeader extends HookConsumerWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
borderRadius: BorderRadius.circular(50)),
|
borderRadius: BorderRadius.circular(50)),
|
||||||
|
child: Skeleton.keep(
|
||||||
child: Text(
|
child: Text(
|
||||||
artist.type!.toUpperCase(),
|
artist.type!.toUpperCase(),
|
||||||
style: chipTextVariant.copyWith(
|
style: chipTextVariant.copyWith(
|
||||||
@ -103,6 +102,7 @@ class ArtistPageHeader extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
if (isBlackListed) ...[
|
if (isBlackListed) ...[
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
Container(
|
Container(
|
||||||
@ -138,7 +138,8 @@ class ArtistPageHeader extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(20),
|
const Gap(20),
|
||||||
Row(
|
Skeleton.keep(
|
||||||
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (auth != null)
|
if (auth != null)
|
||||||
@ -245,6 +246,7 @@ class ArtistPageHeader extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.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/shared/track_tile/track_tile.dart';
|
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
@ -28,11 +30,7 @@ class ArtistPageTopTracks extends HookConsumerWidget {
|
|||||||
topTracksQuery.data ?? <Track>[],
|
topTracksQuery.data ?? <Track>[],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (topTracksQuery.isLoading || !topTracksQuery.hasData) {
|
if (topTracksQuery.hasError) {
|
||||||
return const SliverToBoxAdapter(
|
|
||||||
child: Center(child: CircularProgressIndicator()),
|
|
||||||
);
|
|
||||||
} else if (topTracksQuery.hasError) {
|
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(topTracksQuery.error.toString()),
|
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 {
|
void playPlaylist(List<Track> tracks, {Track? currentTrack}) async {
|
||||||
currentTrack ??= tracks.first;
|
currentTrack ??= tracks.first;
|
||||||
@ -92,10 +91,12 @@ class ArtistPageTopTracks extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
icon: Skeleton.keep(
|
||||||
|
child: Icon(
|
||||||
isPlaylistPlaying ? SpotubeIcons.stop : SpotubeIcons.play,
|
isPlaylistPlaying ? SpotubeIcons.stop : SpotubeIcons.play,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
backgroundColor: theme.colorScheme.primary,
|
backgroundColor: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
|
@ -3,11 +3,12 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.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:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/genre/category_card.dart';
|
import 'package:spotube/components/genre/category_card.dart';
|
||||||
import 'package:spotube/components/shared/expandable_search/expandable_search.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/components/shared/waypoint.dart';
|
||||||
|
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
@ -77,7 +78,23 @@ class GenrePage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
if (!categoriesQuery.hasPageData &&
|
if (!categoriesQuery.hasPageData &&
|
||||||
!categoriesQuery.isLoadingNextPage)
|
!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
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: InfiniteList(
|
child: InfiniteList(
|
||||||
@ -86,7 +103,16 @@ class GenrePage extends HookConsumerWidget {
|
|||||||
onFetchData: categoriesQuery.fetchNext,
|
onFetchData: categoriesQuery.fetchNext,
|
||||||
isLoading: categoriesQuery.isLoadingNextPage,
|
isLoading: categoriesQuery.isLoadingNextPage,
|
||||||
hasReachedMax: !categoriesQuery.hasNextPage,
|
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) {
|
itemBuilder: (context, index) {
|
||||||
return CategoryCard(categories[index]);
|
return CategoryCard(categories[index]);
|
||||||
},
|
},
|
||||||
|
@ -4,11 +4,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
|
|
||||||
import 'package:spotify/spotify.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/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/extensions/context.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
|
|
||||||
class PersonalizedPage extends HookConsumerWidget {
|
class PersonalizedPage extends HookConsumerWidget {
|
||||||
const PersonalizedPage({Key? key}) : super(key: key);
|
const PersonalizedPage({Key? key}) : super(key: key);
|
||||||
@ -46,39 +46,35 @@ class PersonalizedPage extends HookConsumerWidget {
|
|||||||
[newReleases.pages],
|
[newReleases.pages],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final hasNewReleases = newReleases.hasPageData &&
|
||||||
|
userArtistsQuery.hasData &&
|
||||||
|
!newReleases.isLoadingNextPage;
|
||||||
|
|
||||||
|
final isLoadingFeaturedPlaylists = !featuredPlaylistsQuery.hasPageData &&
|
||||||
|
!featuredPlaylistsQuery.isLoadingNextPage;
|
||||||
|
|
||||||
return CustomScrollView(
|
return CustomScrollView(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverList.list(
|
SliverList.list(
|
||||||
children: [
|
children: [
|
||||||
AnimatedSwitcher(
|
Skeletonizer(
|
||||||
duration: const Duration(milliseconds: 300),
|
enabled: isLoadingFeaturedPlaylists,
|
||||||
child: !featuredPlaylistsQuery.hasPageData &&
|
child: HorizontalPlaybuttonCardView<PlaylistSimple>(
|
||||||
!featuredPlaylistsQuery.isLoadingNextPage
|
|
||||||
? const ShimmerCategories()
|
|
||||||
: HorizontalPlaybuttonCardView<PlaylistSimple>(
|
|
||||||
items: playlists.toList(),
|
items: playlists.toList(),
|
||||||
title: Text(context.l10n.featured),
|
title: Text(context.l10n.featured),
|
||||||
isLoadingNextPage:
|
isLoadingNextPage: featuredPlaylistsQuery.isLoadingNextPage,
|
||||||
featuredPlaylistsQuery.isLoadingNextPage,
|
|
||||||
hasNextPage: featuredPlaylistsQuery.hasNextPage,
|
hasNextPage: featuredPlaylistsQuery.hasNextPage,
|
||||||
onFetchMore: featuredPlaylistsQuery.fetchNext,
|
onFetchMore: featuredPlaylistsQuery.fetchNext,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (auth != null)
|
if (auth != null || hasNewReleases)
|
||||||
AnimatedSwitcher(
|
HorizontalPlaybuttonCardView<Album>(
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: newReleases.hasPageData &&
|
|
||||||
userArtistsQuery.hasData &&
|
|
||||||
!newReleases.isLoadingNextPage
|
|
||||||
? HorizontalPlaybuttonCardView<Album>(
|
|
||||||
items: albums,
|
items: albums,
|
||||||
title: Text(context.l10n.new_releases),
|
title: Text(context.l10n.new_releases),
|
||||||
isLoadingNextPage: newReleases.isLoadingNextPage,
|
isLoadingNextPage: newReleases.isLoadingNextPage,
|
||||||
hasNextPage: newReleases.hasNextPage,
|
hasNextPage: newReleases.hasNextPage,
|
||||||
onFetchMore: newReleases.fetchNext,
|
onFetchMore: newReleases.fetchNext,
|
||||||
)
|
|
||||||
: const ShimmerCategories(),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -77,7 +77,7 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
: textTheme.headlineMedium?.copyWith(fontSize: 25))
|
: textTheme.headlineMedium?.copyWith(fontSize: 25))
|
||||||
?.copyWith(color: palette.titleTextColor);
|
?.copyWith(color: palette.titleTextColor);
|
||||||
|
|
||||||
var bodyTextTheme = textTheme.bodyLarge?.copyWith(
|
final bodyTextTheme = textTheme.bodyLarge?.copyWith(
|
||||||
color: palette.bodyTextColor,
|
color: palette.bodyTextColor,
|
||||||
);
|
);
|
||||||
return Stack(
|
return Stack(
|
||||||
@ -184,7 +184,9 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
if (playlist.activeTrack != null &&
|
if (playlist.activeTrack != null &&
|
||||||
(timedLyricsQuery.isLoading || timedLyricsQuery.isRefreshing))
|
(timedLyricsQuery.isLoading || timedLyricsQuery.isRefreshing))
|
||||||
const Expanded(child: ShimmerLyrics())
|
const Expanded(
|
||||||
|
child: ShimmerLyrics(),
|
||||||
|
)
|
||||||
else if (playlist.activeTrack != null &&
|
else if (playlist.activeTrack != null &&
|
||||||
(timedLyricsQuery.hasError))
|
(timedLyricsQuery.hasError))
|
||||||
Text(
|
Text(
|
||||||
|
@ -1832,6 +1832,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
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:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -118,6 +118,7 @@ dependencies:
|
|||||||
url: https://github.com/Tommypop2/dart_discord_rpc.git
|
url: https://github.com/Tommypop2/dart_discord_rpc.git
|
||||||
html_unescape: ^2.0.0
|
html_unescape: ^2.0.0
|
||||||
wikipedia_api: ^0.1.0
|
wikipedia_api: ^0.1.0
|
||||||
|
skeletonizer: ^0.8.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.3.2
|
build_runner: ^2.3.2
|
||||||
|
Loading…
Reference in New Issue
Block a user