Skip to content

Commit

Permalink
feat(stats): add lazy loading support
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Jun 30, 2024
1 parent 4c5564f commit 3bdc46d
Show file tree
Hide file tree
Showing 14 changed files with 610 additions and 319 deletions.
3 changes: 2 additions & 1 deletion lib/models/database/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'dart:convert';
import 'dart:io';

import 'package:drift/drift.dart';
import 'package:drift/extensions/json1.dart';
import 'package:encrypt/encrypt.dart';
import 'package:media_kit/media_kit.dart' hide Track;
import 'package:path/path.dart';
Expand All @@ -13,7 +14,7 @@ import 'package:spotube/models/lyrics.dart';
import 'package:spotube/services/kv_store/encrypted_kv_store.dart';
import 'package:spotube/services/kv_store/kv_store.dart';
import 'package:spotube/services/sourced_track/enums.dart';
import 'package:flutter/material.dart' hide Table, Key;
import 'package:flutter/material.dart' hide Table, Key, View;
import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart';
import 'package:drift/native.dart';
import 'package:sqlite3/sqlite3.dart';
Expand Down
22 changes: 16 additions & 6 deletions lib/modules/stats/top/albums.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,31 @@ import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotube/collections/formatters.dart';
import 'package:spotube/modules/stats/common/album_item.dart';
import 'package:spotube/provider/history/top.dart';
import 'package:spotube/provider/history/top/albums.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';

class TopAlbums extends HookConsumerWidget {
const TopAlbums({super.key});

@override
Widget build(BuildContext context, ref) {
final historyDuration = ref.watch(playbackHistoryTopDurationProvider);
final albums = ref.watch(playbackHistoryTopProvider(historyDuration)
.select((value) => value.whenData((s) => s.albums)));
final topAlbums = ref.watch(historyTopAlbumsProvider(historyDuration));
final topAlbumsNotifier =
ref.watch(historyTopAlbumsProvider(historyDuration).notifier);

final albumsData = albums.asData?.value ?? [];
final albumsData = topAlbums.asData?.value.items ?? [];

return Skeletonizer(
enabled: albums.isLoading,
child: SliverList.builder(
return Skeletonizer.sliver(
enabled: topAlbums.isLoading && !topAlbums.isLoadingNextPage,
child: SliverInfiniteList(
onFetchData: () async {
await topAlbumsNotifier.fetchMore();
},
hasError: topAlbums.hasError,
isLoading: topAlbums.isLoading && !topAlbums.isLoadingNextPage,
hasReachedMax: topAlbums.asData?.value.hasMore ?? true,
itemCount: albumsData.length,
itemBuilder: (context, index) {
final album = albumsData[index];
Expand Down
42 changes: 30 additions & 12 deletions lib/modules/stats/top/artists.dart
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotube/collections/formatters.dart';
import 'package:spotube/modules/stats/common/artist_item.dart';
import 'package:spotube/provider/history/top.dart';
import 'package:spotube/provider/history/top/tracks.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';

class TopArtists extends HookConsumerWidget {
const TopArtists({super.key});

@override
Widget build(BuildContext context, ref) {
final historyDuration = ref.watch(playbackHistoryTopDurationProvider);
final artists = ref.watch(playbackHistoryTopProvider(historyDuration)
.select((value) => value.whenData((s) => s.artists)));
final topTracks = ref.watch(
historyTopTracksProvider(historyDuration),
);
final topTracksNotifier =
ref.watch(historyTopTracksProvider(historyDuration).notifier);

final artistsData = artists.asData?.value ?? [];
final artistsData = useMemoized(
() => topTracks.asData?.value.artists ?? [], [topTracks.asData?.value]);

return SliverList.builder(
itemCount: artistsData.length,
itemBuilder: (context, index) {
final artist = artistsData[index];
return StatsArtistItem(
artist: artist.artist,
info: Text("${compactNumberFormatter.format(artist.count)} plays"),
);
},
return Skeletonizer.sliver(
enabled: topTracks.isLoading && !topTracks.isLoadingNextPage,
child: SliverInfiniteList(
onFetchData: () async {
await topTracksNotifier.fetchMore();
},
hasError: topTracks.hasError,
isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage,
hasReachedMax: topTracks.asData?.value.hasMore ?? true,
itemCount: artistsData.length,
itemBuilder: (context, index) {
final artist = artistsData[index];
return StatsArtistItem(
artist: artist.artist,
info: Text("${compactNumberFormatter.format(artist.count)} plays"),
);
},
),
);
}
}
44 changes: 29 additions & 15 deletions lib/modules/stats/top/tracks.dart
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotube/collections/formatters.dart';
import 'package:spotube/modules/stats/common/track_item.dart';
import 'package:spotube/provider/history/top.dart';
import 'package:spotube/provider/history/top/tracks.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';

class TopTracks extends HookConsumerWidget {
const TopTracks({super.key});

@override
Widget build(BuildContext context, ref) {
final historyDuration = ref.watch(playbackHistoryTopDurationProvider);
final tracks = ref.watch(
playbackHistoryTopProvider(historyDuration)
.select((value) => value.whenData((s) => s.tracks)),
final topTracks = ref.watch(
historyTopTracksProvider(historyDuration),
);
final topTracksNotifier =
ref.watch(historyTopTracksProvider(historyDuration).notifier);

final tracksData = tracks.asData?.value ?? [];
final tracksData = topTracks.asData?.value.items ?? [];

return SliverList.builder(
itemCount: tracksData.length,
itemBuilder: (context, index) {
final track = tracksData[index];
return StatsTrackItem(
track: track.track,
info: Text(
"${compactNumberFormatter.format(track.count)} plays",
),
);
},
return Skeletonizer.sliver(
enabled: topTracks.isLoading && !topTracks.isLoadingNextPage,
child: SliverInfiniteList(
onFetchData: () async {
await topTracksNotifier.fetchMore();
},
hasError: topTracks.hasError,
isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage,
hasReachedMax: topTracks.asData?.value.hasMore ?? true,
itemCount: tracksData.length,
itemBuilder: (context, index) {
final track = tracksData[index];
return StatsTrackItem(
track: track.track,
info: Text(
"${compactNumberFormatter.format(track.count)} plays",
),
);
},
),
);
}
}
41 changes: 29 additions & 12 deletions lib/pages/stats/albums/albums.dart
Original file line number Diff line number Diff line change
@@ -1,37 +1,54 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotube/collections/formatters.dart';
import 'package:spotube/components/titlebar/titlebar.dart';
import 'package:spotube/modules/stats/common/album_item.dart';

import 'package:spotube/provider/history/top.dart';
import 'package:spotube/provider/history/top/albums.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';

class StatsAlbumsPage extends HookConsumerWidget {
static const name = "stats_albums";
const StatsAlbumsPage({super.key});

@override
Widget build(BuildContext context, ref) {
final albums = ref.watch(playbackHistoryTopProvider(HistoryDuration.allTime)
.select((value) => value.whenData((s) => s.albums)));
final topAlbums =
ref.watch(historyTopAlbumsProvider(HistoryDuration.allTime));
final topAlbumsNotifier =
ref.watch(historyTopAlbumsProvider(HistoryDuration.allTime).notifier);

final albumsData = albums.asData?.value ?? [];
final albumsData = topAlbums.asData?.value.items ?? [];

return Scaffold(
appBar: const PageWindowTitleBar(
automaticallyImplyLeading: true,
centerTitle: false,
title: Text("Albums"),
),
body: ListView.builder(
itemCount: albumsData.length,
itemBuilder: (context, index) {
final album = albumsData[index];
return StatsAlbumItem(
album: album.album,
info: Text("${compactNumberFormatter.format(album.count)} plays"),
);
},
body: Skeletonizer(
enabled: topAlbums.isLoading && !topAlbums.isLoadingNextPage,
child: InfiniteList(
onFetchData: () async {
await topAlbumsNotifier.fetchMore();
},
hasError: topAlbums.hasError,
isLoading: topAlbums.isLoading && !topAlbums.isLoadingNextPage,
hasReachedMax: topAlbums.asData?.value.hasMore ?? true,
itemCount: albumsData.length,
itemBuilder: (context, index) {
final album = albumsData[index];
return StatsAlbumItem(
album: album.album,
info: Text(
"${compactNumberFormatter.format(album.count)} plays",
),
);
},
),
),
);
}
Expand Down
43 changes: 30 additions & 13 deletions lib/pages/stats/artists/artists.dart
Original file line number Diff line number Diff line change
@@ -1,39 +1,56 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotube/collections/formatters.dart';
import 'package:spotube/components/titlebar/titlebar.dart';
import 'package:spotube/modules/stats/common/artist_item.dart';

import 'package:spotube/provider/history/top.dart';
import 'package:spotube/provider/history/top/tracks.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';

class StatsArtistsPage extends HookConsumerWidget {
static const name = "stats_artists";
const StatsArtistsPage({super.key});

@override
Widget build(BuildContext context, ref) {
final artists = ref.watch(
playbackHistoryTopProvider(HistoryDuration.allTime)
.select((s) => s.whenData((s) => s.artists)),
final topTracks = ref.watch(
historyTopTracksProvider(HistoryDuration.allTime),
);
final topTracksNotifier =
ref.watch(historyTopTracksProvider(HistoryDuration.allTime).notifier);

final artistsData = artists.asData?.value ?? [];
final artistsData = useMemoized(
() => topTracks.asData?.value.artists ?? [], [topTracks.asData?.value]);

return Scaffold(
appBar: const PageWindowTitleBar(
automaticallyImplyLeading: true,
centerTitle: false,
title: Text("Artists"),
),
body: ListView.builder(
itemCount: artistsData.length,
itemBuilder: (context, index) {
final artist = artistsData[index];
return StatsArtistItem(
artist: artist.artist,
info: Text("${compactNumberFormatter.format(artist.count)} plays"),
);
},
body: Skeletonizer(
enabled: topTracks.isLoading && !topTracks.isLoadingNextPage,
child: InfiniteList(
onFetchData: () async {
await topTracksNotifier.fetchMore();
},
hasError: topTracks.hasError,
isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage,
hasReachedMax: topTracks.asData?.value.hasMore ?? true,
itemCount: artistsData.length,
itemBuilder: (context, index) {
final artist = artistsData[index];
return StatsArtistItem(
artist: artist.artist,
info:
Text("${compactNumberFormatter.format(artist.count)} plays"),
);
},
),
),
);
}
Expand Down
42 changes: 29 additions & 13 deletions lib/pages/stats/fees/fees.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:sliver_tools/sliver_tools.dart';
import 'package:spotube/collections/formatters.dart';
import 'package:spotube/components/titlebar/titlebar.dart';
import 'package:spotube/modules/stats/common/artist_item.dart';

import 'package:spotube/provider/history/top.dart';
import 'package:spotube/provider/history/top/tracks.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';

class StatsStreamFeesPage extends HookConsumerWidget {
static const name = "stats_stream_fees";
Expand All @@ -16,12 +21,14 @@ class StatsStreamFeesPage extends HookConsumerWidget {
Widget build(BuildContext context, ref) {
final ThemeData(:textTheme, :hintColor) = Theme.of(context);

final artists = ref.watch(
playbackHistoryTopProvider(HistoryDuration.days30)
.select((value) => value.whenData((s) => s.artists)),
final topTracks = ref.watch(
historyTopTracksProvider(HistoryDuration.allTime),
);
final topTracksNotifier =
ref.watch(historyTopTracksProvider(HistoryDuration.allTime).notifier);

final artistsData = artists.asData?.value ?? [];
final artistsData = useMemoized(
() => topTracks.asData?.value.artists ?? [], [topTracks.asData?.value]);

return Scaffold(
appBar: const PageWindowTitleBar(
Expand Down Expand Up @@ -50,15 +57,24 @@ class StatsStreamFeesPage extends HookConsumerWidget {
),
),
),
SliverList.builder(
itemCount: artistsData.length,
itemBuilder: (context, index) {
final artist = artistsData[index];
return StatsArtistItem(
artist: artist.artist,
info: Text(usdFormatter.format(artist.count * 0.005)),
);
},
Skeletonizer.sliver(
enabled: topTracks.isLoading && !topTracks.isLoadingNextPage,
child: SliverInfiniteList(
onFetchData: () async {
await topTracksNotifier.fetchMore();
},
hasError: topTracks.hasError,
isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage,
hasReachedMax: topTracks.asData?.value.hasMore ?? true,
itemCount: artistsData.length,
itemBuilder: (context, index) {
final artist = artistsData[index];
return StatsArtistItem(
artist: artist.artist,
info: Text(usdFormatter.format(artist.count * 0.005)),
);
},
),
),
],
),
Expand Down
Loading

0 comments on commit 3bdc46d

Please sign in to comment.