Skip to content

Commit

Permalink
feat: personalized stats based on local music history (#1522)
Browse files Browse the repository at this point in the history
* feat: add playback history provider

* feat: implement recently played section

* refactor: use route names

* feat: add stats summary and top tracks/artists/albums

* feat: add top date based filtering

* feat: add stream money calculation

* refactor: place search in mobile navbar and settings in home appbar

* feat: add individual minutes and streams page

* feat(stats): add individual minutes and streams page

* chore: default period to 1 month

* feat: add text to explain user how hypothetical fees are calculated

* chore: ensure usage of route names instead of direct paths

* cd: add cache key

* cd: remove media_kit_event_loop from git
  • Loading branch information
KRTirtho authored Jun 1, 2024
1 parent fc5bfa0 commit 82307bc
Show file tree
Hide file tree
Showing 114 changed files with 3,366 additions and 607 deletions.
1 change: 1 addition & 0 deletions .github/workflows/spotube-release-binary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ jobs:
- uses: subosito/flutter-action@v2.12.0
with:
cache: true
cache-key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.yaml') }}
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Setup Java
if: ${{matrix.platform == 'android'}}
Expand Down
7 changes: 6 additions & 1 deletion build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ targets:
$default:
sources:
exclude:
- bin/*.dart
- bin/*.dart
builders:
json_serializable:
options:
any_map: true
explicit_to_json: true
1 change: 0 additions & 1 deletion lib/collections/fake.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:spotify/spotify.dart';
import 'package:spotube/extensions/track.dart';
import 'package:spotube/models/spotify/home_feed.dart';
import 'package:spotube/models/spotify_friends.dart';

Expand Down
8 changes: 8 additions & 0 deletions lib/collections/formatters.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'package:intl/intl.dart';

final compactNumberFormatter = NumberFormat.compact();
final usdFormatter = NumberFormat.compactCurrency(
locale: 'en-US',
symbol: r"$",
decimalDigits: 2,
);
12 changes: 8 additions & 4 deletions lib/collections/intents.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import 'package:go_router/go_router.dart';
import 'package:spotube/collections/routes.dart';
import 'package:spotube/components/player/player_controls.dart';
import 'package:spotube/models/logger.dart';
import 'package:spotube/pages/home/home.dart';
import 'package:spotube/pages/library/library.dart';
import 'package:spotube/pages/lyrics/lyrics.dart';
import 'package:spotube/pages/search/search.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/utils/platform.dart';
Expand Down Expand Up @@ -67,16 +71,16 @@ class HomeTabAction extends Action<HomeTabIntent> {
final router = intent.ref.read(routerProvider);
switch (intent.tab) {
case HomeTabs.browse:
router.go("/");
router.goNamed(HomePage.name);
break;
case HomeTabs.search:
router.go("/search");
router.goNamed(SearchPage.name);
break;
case HomeTabs.library:
router.go("/library");
router.goNamed(LibraryPage.name);
break;
case HomeTabs.lyrics:
router.go("/lyrics");
router.goNamed(LyricsPage.name);
break;
}
return null;
Expand Down
114 changes: 98 additions & 16 deletions lib/collections/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ import 'package:spotube/pages/search/search.dart';
import 'package:spotube/pages/settings/blacklist.dart';
import 'package:spotube/pages/settings/about.dart';
import 'package:spotube/pages/settings/logs.dart';
import 'package:spotube/pages/stats/albums/albums.dart';
import 'package:spotube/pages/stats/artists/artists.dart';
import 'package:spotube/pages/stats/fees/fees.dart';
import 'package:spotube/pages/stats/minutes/minutes.dart';
import 'package:spotube/pages/stats/playlists/playlists.dart';
import 'package:spotube/pages/stats/stats.dart';
import 'package:spotube/pages/stats/streams/streams.dart';
import 'package:spotube/pages/track/track.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/services/kv_store/kv_store.dart';
Expand All @@ -51,6 +58,7 @@ final routerProvider = Provider((ref) {
routes: [
GoRoute(
path: "/",
name: HomePage.name,
redirect: (context, state) async {
final authNotifier = ref.read(authenticationProvider.notifier);
final json = await authNotifier.box.get(authNotifier.cacheKey);
Expand All @@ -67,11 +75,13 @@ final routerProvider = Provider((ref) {
routes: [
GoRoute(
path: "genres",
name: GenrePage.name,
pageBuilder: (context, state) =>
const SpotubePage(child: GenrePage()),
),
GoRoute(
path: "genre/:categoryId",
name: GenrePlaylistsPage.name,
pageBuilder: (context, state) => SpotubePage(
child: GenrePlaylistsPage(
category: state.extra as Category,
Expand All @@ -80,6 +90,7 @@ final routerProvider = Provider((ref) {
),
GoRoute(
path: "feeds/:feedId",
name: HomeFeedSectionPage.name,
pageBuilder: (context, state) => SpotubePage(
child: HomeFeedSectionPage(
sectionUri: state.pathParameters["feedId"] as String,
Expand All @@ -90,69 +101,77 @@ final routerProvider = Provider((ref) {
),
GoRoute(
path: "/search",
name: "Search",
name: SearchPage.name,
pageBuilder: (context, state) =>
const SpotubePage(child: SearchPage()),
),
GoRoute(
path: "/library",
name: "Library",
name: LibraryPage.name,
pageBuilder: (context, state) =>
const SpotubePage(child: LibraryPage()),
routes: [
GoRoute(
path: "generate",
pageBuilder: (context, state) =>
const SpotubePage(child: PlaylistGeneratorPage()),
routes: [
GoRoute(
path: "result",
pageBuilder: (context, state) => SpotubePage(
child: PlaylistGenerateResultPage(
state: state.extra as GeneratePlaylistProviderInput,
),
path: "generate",
name: PlaylistGeneratorPage.name,
pageBuilder: (context, state) =>
const SpotubePage(child: PlaylistGeneratorPage()),
routes: [
GoRoute(
path: "result",
name: PlaylistGenerateResultPage.name,
pageBuilder: (context, state) => SpotubePage(
child: PlaylistGenerateResultPage(
state: state.extra as GeneratePlaylistProviderInput,
),
),
]),
)
],
),
GoRoute(
path: "local",
name: LocalLibraryPage.name,
pageBuilder: (context, state) {
assert(state.extra is String);
return SpotubePage(
child: LocalLibraryPage(state.extra as String,
isDownloads: state.uri.queryParameters["downloads"] != null
),
isDownloads:
state.uri.queryParameters["downloads"] != null),
);
},
),
]),
GoRoute(
path: "/lyrics",
name: "Lyrics",
name: LyricsPage.name,
pageBuilder: (context, state) =>
const SpotubePage(child: LyricsPage()),
),
GoRoute(
path: "/settings",
name: SettingsPage.name,
pageBuilder: (context, state) => const SpotubePage(
child: SettingsPage(),
),
routes: [
GoRoute(
path: "blacklist",
name: BlackListPage.name,
pageBuilder: (context, state) => SpotubeSlidePage(
child: const BlackListPage(),
),
),
if (!kIsWeb)
GoRoute(
path: "logs",
name: LogsPage.name,
pageBuilder: (context, state) => SpotubeSlidePage(
child: const LogsPage(),
),
),
GoRoute(
path: "about",
name: AboutSpotube.name,
pageBuilder: (context, state) => SpotubeSlidePage(
child: const AboutSpotube(),
),
Expand All @@ -161,6 +180,7 @@ final routerProvider = Provider((ref) {
),
GoRoute(
path: "/album/:id",
name: AlbumPage.name,
pageBuilder: (context, state) {
assert(state.extra is AlbumSimple);
return SpotubePage(
Expand All @@ -170,6 +190,7 @@ final routerProvider = Provider((ref) {
),
GoRoute(
path: "/artist/:id",
name: ArtistPage.name,
pageBuilder: (context, state) {
assert(state.pathParameters["id"] != null);
return SpotubePage(
Expand All @@ -178,6 +199,7 @@ final routerProvider = Provider((ref) {
),
GoRoute(
path: "/playlist/:id",
name: PlaylistPage.name,
pageBuilder: (context, state) {
assert(state.extra is PlaylistSimple);
return SpotubePage(
Expand All @@ -189,6 +211,7 @@ final routerProvider = Provider((ref) {
),
GoRoute(
path: "/track/:id",
name: TrackPage.name,
pageBuilder: (context, state) {
final id = state.pathParameters["id"]!;
return SpotubePage(
Expand All @@ -198,12 +221,14 @@ final routerProvider = Provider((ref) {
),
GoRoute(
path: "/connect",
name: ConnectPage.name,
pageBuilder: (context, state) => const SpotubePage(
child: ConnectPage(),
),
routes: [
GoRoute(
path: "control",
name: ConnectControlPage.name,
pageBuilder: (context, state) {
return const SpotubePage(
child: ConnectControlPage(),
Expand All @@ -214,41 +239,98 @@ final routerProvider = Provider((ref) {
),
GoRoute(
path: "/profile",
name: ProfilePage.name,
pageBuilder: (context, state) =>
const SpotubePage(child: ProfilePage()),
),
GoRoute(
path: "/stats",
name: StatsPage.name,
pageBuilder: (context, state) => const SpotubePage(
child: StatsPage(),
),
routes: [
GoRoute(
path: "minutes",
name: StatsMinutesPage.name,
pageBuilder: (context, state) => const SpotubePage(
child: StatsMinutesPage(),
),
),
GoRoute(
path: "streams",
name: StatsStreamsPage.name,
pageBuilder: (context, state) => const SpotubePage(
child: StatsStreamsPage(),
),
),
GoRoute(
path: "fees",
name: StatsStreamFeesPage.name,
pageBuilder: (context, state) => const SpotubePage(
child: StatsStreamFeesPage(),
),
),
GoRoute(
path: "artists",
name: StatsArtistsPage.name,
pageBuilder: (context, state) => const SpotubePage(
child: StatsArtistsPage(),
),
),
GoRoute(
path: "albums",
name: StatsAlbumsPage.name,
pageBuilder: (context, state) => const SpotubePage(
child: StatsAlbumsPage(),
),
),
GoRoute(
path: "playlists",
name: StatsPlaylistsPage.name,
pageBuilder: (context, state) => const SpotubePage(
child: StatsPlaylistsPage(),
),
),
],
)
],
),
GoRoute(
path: "/mini-player",
name: MiniLyricsPage.name,
parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) => SpotubePage(
child: MiniLyricsPage(prevSize: state.extra as Size),
),
),
GoRoute(
path: "/getting-started",
name: GettingStarting.name,
parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) => const SpotubePage(
child: GettingStarting(),
),
),
GoRoute(
path: "/login",
name: WebViewLogin.name,
parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) => SpotubePage(
child: kIsMobile ? const WebViewLogin() : const DesktopLoginPage(),
),
),
GoRoute(
path: "/login-tutorial",
name: LoginTutorial.name,
parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) => const SpotubePage(
child: LoginTutorial(),
),
),
GoRoute(
path: "/lastfm-login",
name: LastFMLoginPage.name,
parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) =>
const SpotubePage(child: LastFMLoginPage()),
Expand Down
Loading

0 comments on commit 82307bc

Please sign in to comment.