goosemusic 1.2.0

A music player with YouTube search, local playback, and OS media controls
use serde::{Deserialize, Serialize};

use super::{JsonStore, StoreLocation};
#[cfg(test)]
use crate::app::ViewKind;
use crate::{app::ViewData, types::PlayQueue};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionState {
    pub data: ViewData,
    pub queue: PlayQueue,
    pub show_queue: bool,
    pub volume: f32,
    pub repeat: bool,
    pub library_expanded: bool,
    #[serde(default)]
    pub search_scope: crate::providers::SearchScope,
    #[serde(default)]
    pub search_provider: crate::providers::ProviderId,
    pub lyrics_provider: crate::lyrics::LyricsProvider,
}

impl Default for SessionState {
    fn default() -> Self {
        Self {
            data: ViewData::default(),
            queue: PlayQueue::new(),
            show_queue: false,
            volume: 0.8,
            repeat: false,
            library_expanded: true,
            search_scope: crate::providers::SearchScope::Songs,
            search_provider: crate::providers::ProviderId::YouTube,
            lyrics_provider: crate::lyrics::LyricsProvider::default(),
        }
    }
}

impl JsonStore for SessionState {
    const FILE: &'static str = "session.json";
    const LOCATION: StoreLocation = StoreLocation::Cache;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn session_state_default() {
        let state = SessionState::default();
        assert!(matches!(state.data.kind, ViewKind::Search { .. }));
        assert!(state.queue.tracks.is_empty());
        assert!(!state.show_queue);
        assert!((state.volume - 0.8).abs() < f32::EPSILON);
        assert_eq!(state.lyrics_provider, crate::lyrics::LyricsProvider::LrcLib);
    }

    #[test]
    fn session_state_round_trip() {
        let state = SessionState {
            data: ViewData {
                kind: ViewKind::ArtistRadio("Test Radio".into()),
                content: crate::load_state::LoadState::Ready(Vec::new()),
                selection: vec![2],
                scroll: 42.0,
                request_id: 0,
            },
            queue: PlayQueue::default(),
            show_queue: true,
            volume: 0.5,
            repeat: true,
            library_expanded: false,
            search_scope: crate::providers::SearchScope::Songs,
            search_provider: crate::providers::ProviderId::YouTube,
            lyrics_provider: crate::lyrics::LyricsProvider::LrcLib,
        };
        let json = serde_json::to_string(&state).unwrap();
        let restored: SessionState = serde_json::from_str(&json).unwrap();
        assert!(matches!(
            restored.data.kind,
            ViewKind::SongRadio(_) | ViewKind::ArtistRadio(_)
        ));
        assert!(restored.show_queue);
        assert!((restored.volume - 0.5).abs() < f32::EPSILON);
        assert!(restored.repeat);
        assert!(!restored.library_expanded);
        assert_eq!(
            restored.lyrics_provider,
            crate::lyrics::LyricsProvider::LrcLib
        );
        if let ViewKind::ArtistRadio(label) = &restored.data.kind {
            assert_eq!(label, "Test Radio");
            assert_eq!(restored.data.selection, vec![2]);
            assert!((restored.data.scroll - 42.0).abs() < f32::EPSILON);
        } else {
            panic!("expected Radio data");
        }
    }
}