diff --git a/lib/collections/formatters.dart b/lib/collections/formatters.dart index 2f823f56..0aed9e9f 100644 --- a/lib/collections/formatters.dart +++ b/lib/collections/formatters.dart @@ -1,3 +1,8 @@ import 'package:intl/intl.dart'; final compactNumberFormatter = NumberFormat.compact(); +final usdFormatter = NumberFormat.compactCurrency( + locale: 'en-US', + symbol: r"$", + decimalDigits: 2, +); diff --git a/lib/components/stats/summary/summary.dart b/lib/components/stats/summary/summary.dart index 41b4c872..b793d2a7 100644 --- a/lib/components/stats/summary/summary.dart +++ b/lib/components/stats/summary/summary.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/collections/formatters.dart'; import 'package:spotube/components/stats/summary/summary_card.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/provider/history/summary.dart'; @@ -32,7 +33,7 @@ class StatsPageSummarySection extends HookConsumerWidget { title: summary.duration.inMinutes.toDouble(), unit: "minutes", description: 'Listened to music', - color: Colors.green, + color: Colors.purple, ), SummaryCard( title: summary.tracks.toDouble(), @@ -40,6 +41,12 @@ class StatsPageSummarySection extends HookConsumerWidget { description: 'Streamed overall', color: Colors.lightBlue, ), + SummaryCard.unformatted( + title: usdFormatter.format(summary.fees.toDouble()), + unit: "", + description: 'Worth of streams', + color: Colors.green, + ), SummaryCard( title: summary.artists.toDouble(), unit: "artist's", diff --git a/lib/components/stats/summary/summary_card.dart b/lib/components/stats/summary/summary_card.dart index 2601f9dd..b9ef5448 100644 --- a/lib/components/stats/summary/summary_card.dart +++ b/lib/components/stats/summary/summary_card.dart @@ -4,13 +4,21 @@ import 'package:gap/gap.dart'; import 'package:spotube/collections/formatters.dart'; class SummaryCard extends StatelessWidget { - final double title; + final String title; final String unit; final String description; final MaterialColor color; - const SummaryCard({ + SummaryCard({ + super.key, + required double title, + required this.unit, + required this.description, + required this.color, + }) : title = compactNumberFormatter.format(title); + + const SummaryCard.unformatted({ super.key, required this.title, required this.unit, @@ -35,7 +43,7 @@ class SummaryCard extends StatelessWidget { TextSpan( children: [ TextSpan( - text: compactNumberFormatter.format(title), + text: title, style: textTheme.headlineLarge?.copyWith( color: color.shade900, ), diff --git a/lib/models/connect/connect.dart b/lib/models/connect/connect.dart index efb37315..28386050 100644 --- a/lib/models/connect/connect.dart +++ b/lib/models/connect/connect.dart @@ -5,7 +5,6 @@ import 'dart:convert'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:spotify/spotify.dart'; -import 'package:spotube/extensions/track.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; import 'package:spotube/services/audio_player/loop_mode.dart'; diff --git a/lib/models/current_playlist.dart b/lib/models/current_playlist.dart index 53ea2799..7e55e393 100644 --- a/lib/models/current_playlist.dart +++ b/lib/models/current_playlist.dart @@ -1,6 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotify/spotify.dart'; -import 'package:spotube/extensions/track.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; class CurrentPlaylist { diff --git a/lib/provider/connect/server.dart b/lib/provider/connect/server.dart index 23fbada8..9c4e6466 100644 --- a/lib/provider/connect/server.dart +++ b/lib/provider/connect/server.dart @@ -82,7 +82,7 @@ final connectServerProvider = FutureProvider((ref) async { .toJson(), ); channel.sink.add( - WebSocketShuffleEvent(await audioPlayer.isShuffled).toJson(), + WebSocketShuffleEvent(audioPlayer.isShuffled).toJson(), ); channel.sink.add( WebSocketLoopEvent(audioPlayer.loopMode).toJson(), diff --git a/lib/provider/history/summary.dart b/lib/provider/history/summary.dart index 1cc316a7..2aa86ac9 100644 --- a/lib/provider/history/summary.dart +++ b/lib/provider/history/summary.dart @@ -1,6 +1,8 @@ import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/provider/history/history.dart'; +import 'package:spotube/provider/history/state.dart'; +import 'package:spotube/provider/history/top.dart'; final playbackHistorySummaryProvider = Provider((ref) { final (:tracks, :albums, :playlists) = @@ -43,10 +45,17 @@ final playbackHistorySummaryProvider = Provider((ref) { ) .length; + final tracksThisMonth = ref.watch( + playbackHistoryTopProvider(HistoryDuration.days30).select((s) => s.tracks), + ); + + final streams = tracksThisMonth.fold(0, (acc, el) => acc + el.count); + return ( duration: totalDurationListened, tracks: totalTracksListened, artists: totalArtistsListened, + fees: streams * 0.005, // Spotify pays $0.003 to $0.005 albums: totalAlbumsListened, playlists: totalPlaylistsListened, ); diff --git a/lib/provider/history/top.dart b/lib/provider/history/top.dart index 46c86033..bee2bf29 100644 --- a/lib/provider/history/top.dart +++ b/lib/provider/history/top.dart @@ -6,6 +6,7 @@ import 'package:spotube/provider/history/state.dart'; final playbackHistoryTopDurationProvider = StateProvider((ref) => HistoryDuration.days7); + final playbackHistoryTopProvider = Provider.family((ref, HistoryDuration durationState) { final grouped = ref.watch(playbackHistoryGroupedProvider); diff --git a/lib/services/audio_services/mobile_audio_service.dart b/lib/services/audio_services/mobile_audio_service.dart index 3bb88447..62cc8552 100644 --- a/lib/services/audio_services/mobile_audio_service.dart +++ b/lib/services/audio_services/mobile_audio_service.dart @@ -11,7 +11,7 @@ class MobileAudioService extends BaseAudioHandler { AudioSession? session; final ProxyPlaylistNotifier playlistNotifier; - // ignore: invalid_use_of_protected_member + // ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member ProxyPlaylist get playlist => playlistNotifier.state; MobileAudioService(this.playlistNotifier) { @@ -135,7 +135,7 @@ class MobileAudioService extends BaseAudioHandler { playing: audioPlayer.isPlaying, updatePosition: position, bufferedPosition: await audioPlayer.bufferedPosition ?? Duration.zero, - shuffleMode: await audioPlayer.isShuffled == true + shuffleMode: audioPlayer.isShuffled == true ? AudioServiceShuffleMode.all : AudioServiceShuffleMode.none, repeatMode: (audioPlayer.loopMode).toAudioServiceRepeatMode(),