mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-20 12:40:15 +02:00
add stats tracking + stats page + fix scrolling bug in dynamic playlists tray
This commit is contained in:
@@ -123,6 +123,7 @@ interface LibraryStore {
|
||||
loadPreviousArtists: () => Promise<void>;
|
||||
jumpToSection: (cursor: string) => Promise<boolean>;
|
||||
recordTrackPlayed: (path: string) => Promise<void>;
|
||||
refreshRecentlyPlayed: () => Promise<void>;
|
||||
recomputeArtists: () => void;
|
||||
recomputeAlbums: () => void;
|
||||
setViewMode: (mode: ViewMode) => void;
|
||||
@@ -907,6 +908,10 @@ export const useLibraryStore = create<LibraryStore>((set, get) => {
|
||||
set({ recentlyPlayedTracks: await AstraLibraryData.getRecentlyPlayed<DbTrack>(20) });
|
||||
},
|
||||
|
||||
refreshRecentlyPlayed: async () => {
|
||||
set({ recentlyPlayedTracks: await AstraLibraryData.getRecentlyPlayed<DbTrack>(20) });
|
||||
},
|
||||
|
||||
recomputeArtists: () => {
|
||||
void resetArtists();
|
||||
},
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { create } from 'zustand';
|
||||
import { AstraLibraryData } from '../../modules/astra-library-scanner';
|
||||
import { useSettingsStore } from './settingsStore';
|
||||
import type {
|
||||
ListeningStatsCategory,
|
||||
ListeningStatsDashboard,
|
||||
ListeningStatsRange,
|
||||
ListeningStatsRankingMetric,
|
||||
} from '@/types/listeningStats';
|
||||
|
||||
interface ListeningStatsStore {
|
||||
range: ListeningStatsRange;
|
||||
rankingMetric: ListeningStatsRankingMetric;
|
||||
category: ListeningStatsCategory;
|
||||
dashboard: ListeningStatsDashboard | null;
|
||||
homePreview: ListeningStatsDashboard | null;
|
||||
loading: boolean;
|
||||
refreshing: boolean;
|
||||
error: string | null;
|
||||
setRange: (range: ListeningStatsRange) => void;
|
||||
setRankingMetric: (metric: ListeningStatsRankingMetric) => void;
|
||||
setCategory: (category: ListeningStatsCategory) => void;
|
||||
loadDashboard: () => Promise<void>;
|
||||
loadHomePreview: () => Promise<void>;
|
||||
}
|
||||
|
||||
let dashboardRequest = 0;
|
||||
let homeRequest = 0;
|
||||
|
||||
function errorMessage(error: unknown): string {
|
||||
return error instanceof Error && error.message ? error.message : 'Listening Stats could not load.';
|
||||
}
|
||||
|
||||
async function queryDashboard(
|
||||
range: ListeningStatsRange,
|
||||
rankingMetric: ListeningStatsRankingMetric,
|
||||
): Promise<ListeningStatsDashboard> {
|
||||
await AstraLibraryData.initialize();
|
||||
return AstraLibraryData.getListeningStatsDashboard<ListeningStatsDashboard>({
|
||||
range,
|
||||
rankingMetric,
|
||||
artistGroupingMode: useSettingsStore.getState().artistGroupingMode,
|
||||
});
|
||||
}
|
||||
|
||||
export const useListeningStatsStore = create<ListeningStatsStore>((set, get) => ({
|
||||
range: '30d',
|
||||
rankingMetric: 'plays',
|
||||
category: 'tracks',
|
||||
dashboard: null,
|
||||
homePreview: null,
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
error: null,
|
||||
|
||||
setRange: (range) => {
|
||||
if (get().range === range) return;
|
||||
set({ range });
|
||||
void get().loadDashboard();
|
||||
},
|
||||
|
||||
setRankingMetric: (rankingMetric) => {
|
||||
if (get().rankingMetric === rankingMetric) return;
|
||||
set({ rankingMetric });
|
||||
void get().loadDashboard();
|
||||
},
|
||||
|
||||
setCategory: (category) => set({ category }),
|
||||
|
||||
loadDashboard: async () => {
|
||||
const request = ++dashboardRequest;
|
||||
set((state) => ({
|
||||
loading: state.dashboard == null,
|
||||
refreshing: state.dashboard != null,
|
||||
error: null,
|
||||
}));
|
||||
try {
|
||||
const { range, rankingMetric } = get();
|
||||
const dashboard = await queryDashboard(range, rankingMetric);
|
||||
if (request !== dashboardRequest) return;
|
||||
set({ dashboard, loading: false, refreshing: false, error: null });
|
||||
} catch (error) {
|
||||
if (request !== dashboardRequest) return;
|
||||
set({ loading: false, refreshing: false, error: errorMessage(error) });
|
||||
}
|
||||
},
|
||||
|
||||
loadHomePreview: async () => {
|
||||
const request = ++homeRequest;
|
||||
try {
|
||||
const homePreview = await queryDashboard('7d', 'plays');
|
||||
if (request === homeRequest) set({ homePreview });
|
||||
} catch {
|
||||
// Home remains quiet on transient stats failures; the full screen has retry UI.
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -9,6 +9,11 @@ import {
|
||||
parseHomeGreetingTextMode,
|
||||
type HomeGreetingTextMode,
|
||||
} from '@/home/homeGreeting';
|
||||
import {
|
||||
pauseListeningHistoryTracking,
|
||||
resumeListeningHistoryTracking,
|
||||
} from '@/audio/listeningHistoryTracker';
|
||||
import { notifyListeningHistoryChanged } from '@/listeningStats/events';
|
||||
|
||||
/**
|
||||
* Persisted app preferences. SQLite (settings table) is the source of truth — this
|
||||
@@ -23,6 +28,7 @@ const SCOPE_STYLE_KEY = 'now_playing_scope_style';
|
||||
const LYRICS_VISIBLE_KEY = 'lyrics_visible';
|
||||
const NOW_PLAYING_COMPANION_KEY = 'now_playing_companion';
|
||||
const HOME_GREETING_TEXT_MODE_KEY = 'home_greeting_text_mode';
|
||||
const LISTENING_HISTORY_ENABLED_KEY = 'listening_history_enabled';
|
||||
|
||||
/** Which visualizer the now-playing scope stage shows. */
|
||||
export type ScopeMode = 'spectrum' | 'scope';
|
||||
@@ -61,6 +67,7 @@ interface SettingsStore {
|
||||
lyricsVisible: boolean;
|
||||
nowPlayingCompanion: NowPlayingCompanion;
|
||||
homeGreetingTextMode: HomeGreetingTextMode;
|
||||
listeningHistoryEnabled: boolean;
|
||||
loaded: boolean;
|
||||
load: () => Promise<void>;
|
||||
setArtistGroupingMode: (mode: ArtistGroupingMode) => Promise<void>;
|
||||
@@ -71,6 +78,7 @@ interface SettingsStore {
|
||||
setLyricsVisible: (visible: boolean) => Promise<void>;
|
||||
setNowPlayingCompanion: (companion: NowPlayingCompanion) => Promise<void>;
|
||||
setHomeGreetingTextMode: (mode: HomeGreetingTextMode) => Promise<void>;
|
||||
setListeningHistoryEnabled: (enabled: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
@@ -82,6 +90,7 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
lyricsVisible: false,
|
||||
nowPlayingCompanion: 'queue',
|
||||
homeGreetingTextMode: 'messages',
|
||||
listeningHistoryEnabled: true,
|
||||
loaded: false,
|
||||
|
||||
load: async () => {
|
||||
@@ -96,6 +105,7 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
LYRICS_VISIBLE_KEY,
|
||||
NOW_PLAYING_COMPANION_KEY,
|
||||
HOME_GREETING_TEXT_MODE_KEY,
|
||||
LISTENING_HISTORY_ENABLED_KEY,
|
||||
]);
|
||||
const grouping = values[ARTIST_GROUPING_KEY] ?? null;
|
||||
const includeSingles = values[INCLUDE_SINGLES_KEY] ?? null;
|
||||
@@ -105,6 +115,7 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
const lyricsVisible = values[LYRICS_VISIBLE_KEY] ?? null;
|
||||
const nowPlayingCompanion = values[NOW_PLAYING_COMPANION_KEY] ?? null;
|
||||
const homeGreetingTextMode = values[HOME_GREETING_TEXT_MODE_KEY] ?? null;
|
||||
const listeningHistoryEnabled = values[LISTENING_HISTORY_ENABLED_KEY] !== '0';
|
||||
set({
|
||||
artistGroupingMode: parseGroupingMode(grouping),
|
||||
includeSingles: parseBoolean(includeSingles),
|
||||
@@ -114,6 +125,7 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
lyricsVisible: parseBoolean(lyricsVisible),
|
||||
nowPlayingCompanion: parseNowPlayingCompanion(nowPlayingCompanion),
|
||||
homeGreetingTextMode: parseHomeGreetingTextMode(homeGreetingTextMode),
|
||||
listeningHistoryEnabled,
|
||||
loaded: true,
|
||||
});
|
||||
},
|
||||
@@ -166,4 +178,20 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
set({ homeGreetingTextMode: nextMode });
|
||||
await AstraLibraryData.setSettings({ [HOME_GREETING_TEXT_MODE_KEY]: nextMode });
|
||||
},
|
||||
|
||||
setListeningHistoryEnabled: async (enabled) => {
|
||||
if (get().listeningHistoryEnabled === enabled) return;
|
||||
if (!enabled) await pauseListeningHistoryTracking();
|
||||
try {
|
||||
await AstraLibraryData.setSettings({
|
||||
[LISTENING_HISTORY_ENABLED_KEY]: enabled ? '1' : '0',
|
||||
});
|
||||
set({ listeningHistoryEnabled: enabled });
|
||||
notifyListeningHistoryChanged();
|
||||
if (enabled) resumeListeningHistoryTracking();
|
||||
} catch (error) {
|
||||
if (!enabled) resumeListeningHistoryTracking();
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user