Commit Graph
425 Commits
Author SHA1 Message Date
vicrodh d5f2ec9030 Fix: Add public reload_settings() method to Player
COMPILATION ERROR FIXED:
- audio_settings field was private
- Can't access from commands module

SOLUTION:
- Added public method: Player::reload_settings(settings)
- Encapsulation preserved
- Commands use public API

VERIFIED: cargo check passes
2026-01-17 15:04:43 -06:00
vicrodh e74c642831 CRITICAL FIX: ReinitDevice now uses backend system
PROBLEMA ENCONTRADO:
1. Frontend guardaba backend_type correctamente ✅
2. Player leía settings al inicio (backend_type = None) ✅
3. Usuario cambiaba backend en UI → se guardaba en BD ✅
4. Pero Player en memoria tenía settings VIEJOS ❌
5. reinit_audio_device usaba legacy CPAL sin backend ❌

SÍNTOMAS:
- "Backend system not configured, using legacy CPAL path"
- "Device '...' not found, using default"
- Audio salía por laptop speakers, no por DAC

SOLUCIÓN IMPLEMENTADA:

1. init_device() ahora intenta backend system primero:
   - Lee thread_settings.backend_type
   - Si configurado, llama try_init_stream_with_backend()
   - Fallback a legacy CPAL si falla

2. reinit_audio_device() recarga settings desde BD:
   - Lee AudioSettings desde database
   - Actualiza Player.audio_settings en memoria
   - Luego llama reinit_device()
   - Garantiza que Player siempre tiene config fresca

AHORA FUNCIONARÁ:
- Cambiar backend → se guarda → se recarga → se usa
- Cambiar device → se guarda → se recarga → se usa
- PULSE_SINK se setea correctamente
- Audio debe salir por Cambridge DAC

TESTING REQUERIDO:
Ir a Settings → Audio → Backend: PipeWire → Device: DacMagic Plus
Reproducir música → debería salir por DAC
2026-01-17 15:02:14 -06:00
vicrodh 196b824999 Fase 4: Integrate backend system into player
PLAYER INTEGRATION:
- Added try_init_stream_with_backend() helper function
- Modified AudioCommand::Play to use backend system when configured
- Falls back to legacy CPAL path if backend_type is None
- Preserves existing behavior for users without backend config

BACKEND PRIORITY:
1. Try backend system (if backend_type is set in audio_settings)
2. Fall back to legacy CPAL direct access (backward compatible)

VERIFIED:
- cargo check passes
- Backward compatible with existing installations
- Ready for Phase 5 (UI components)
2026-01-17 13:06:51 -06:00
vicrodh d5a2991e2b Fase 3: Add backend and plugin persistence to audio settings
AUDIO SETTINGS EXTENDED:
- Added backend_type field (PipeWire/ALSA/Pulse, None=auto)
- Added alsa_plugin field (Hw/PlugHw/Pcm for ALSA backend)
- Default: backend_type=None (auto-detect), alsa_plugin=Hw

DATABASE MIGRATION:
- ALTER TABLE for existing databases (backward compatible)
- Stores enums as JSON strings for flexibility

NEW TAURI COMMANDS:
- set_audio_backend_type(backend_type)
- set_audio_alsa_plugin(plugin)

VERIFIED:
- cargo check passes
- Migration handles existing databases gracefully
- Ready for Phase 4 (player integration)
2026-01-17 13:04:30 -06:00
vicrodh 67ff0cab5a Fase 2: Add Tauri commands for audio backend system
COMMANDS CREATED (audio_backends.rs):
- get_available_backends() → List backends with availability status
- get_devices_for_backend(type) → Enumerate devices for selected backend
- get_alsa_plugins() → List ALSA plugin options (hw/plughw/pcm)

INTEGRATION:
- Registered commands in commands/mod.rs
- Added to invoke_handler in lib.rs
- All commands return pretty names for UI display

VERIFIED:
- cargo check passes successfully
- Ready for Phase 3 (settings persistence)
2026-01-17 12:59:18 -06:00
vicrodh 513a7f9ba9 Fase 1: Implement audio backend abstraction system
ARCHITECTURE:
- Created trait AudioBackend for unified interface
- Backends: PipeWire, ALSA (stub), PulseAudio

PIPEWIRE BACKEND (complete):
- Enumerates devices via pactl (pretty names)
- Sets PULSE_SINK env var for device routing
- Uses CPAL 'pulse'/'pipewire' device
- Does NOT change system default
- Only affects QBZ process

ALSA BACKEND (stub):
- To be implemented by Copilot
- Will provide direct hw access
- True exclusive mode

PULSE BACKEND (complete):
- Reuses PipeWire implementation
- Legacy fallback

BENEFITS:
- User can choose audio stack
- Pretty names from pactl
- Device selection only affects QBZ
- Preparation for Strawberry-style UI

Next: Tauri commands for enumeration
2026-01-17 12:55:21 -06:00
vicrodh cb696aca60 Add detailed logging for CPAL device enumeration
Logs now show:
- Device index
- Device name
- Is default
- Supported configs (channels/sample rates)

This helps identify devices when pretty names aren't clear.
2026-01-17 12:03:13 -06:00
vicrodh 1f13370073 Fix device selection: Use CPAL for enumeration AND playback
PREVIOUS WRONG APPROACH (commit 212aae5):
- Used pactl to enumerate devices (PipeWire sink names)
- Tried to open those sink names with CPAL
- CPAL couldn't find PipeWire sink names → fell back to default
- Attempted workaround: change system default with set_pipewire_default_sink
- UNACCEPTABLE: Changed system-wide default, affecting all apps

ROOT CAUSE (identified by ChatGPT research):
- CPAL can open specific devices by name (research was correct)
- BUT: Name mismatch between pactl sinks and CPAL device names
- pactl returns: "alsa_output.usb-Cambridge_Audio..." (PipeWire sink)
- CPAL sees: "DacMagic Plus Analog Stereo" (user-friendly name)

CORRECT SOLUTION:
- Use CPAL for BOTH enumeration AND opening devices
- CPAL device names are consistent: enumerate → save → find → open
- No need to change system default
- Device selection only affects QBZ

CHANGES:
1. Rewrote get_pipewire_sinks() on Linux to use CPAL instead of pactl
2. Removed all calls to set_pipewire_default_sink()
3. Pass CPAL device names directly to reinit_audio_device()
4. CPAL can now find and open devices because names match

BENEFITS:
- ✅ Device selection only affects QBZ (not system-wide)
- ✅ No regresions
- ✅ No features lost
- ✅ Works exactly like librespot (Spotify client in Rust)
- ✅ Consistent with rodio/CPAL architecture

TESTING REQUIRED:
1. Select Cambridge DAC in QBZ → audio plays through Cambridge
2. Open Firefox/other app → uses its own device (unaffected)
3. Toggle devices in QBZ → changes correctly
4. Restart QBZ → restores saved device
5. DAC passthrough bit-perfect → works
2026-01-17 11:51:25 -06:00
vicrodh 732a489a3e Improve Discogs artwork selection with multi-release images
- Added /release/{id} endpoint to Cloudflare proxy for detailed release info
- Modified search_artwork_options to fetch full image sets from top 2 releases
- Interleave images: up to 4 from release #1, 4 from #2, 2 from others
- Added release_title and release_year metadata to each image option
- UI now displays release title and year for each artwork option
- Better image diversity and context for users

Users now see which release variant each artwork comes from,
making it easier to choose the correct edition.
2026-01-17 00:42:18 -06:00
vicrodh 279d433b77 Add catalog number support for precise Discogs searches
- Added catalog_number field to LocalTrack and LocalAlbum models
- Extract catalog_number tag from audio files using lofty
- Database migration to add catalog_number column to local_tracks table
- Modified Discogs search to prefer catalog number over artist/album
- Updated UI to display catalog number in album details
- Pass catalog_number to discogs_search_artwork command

For audiophile users with well-tagged libraries, catalog numbers
provide more precise Discogs search results than artist + album.
2026-01-17 00:34:27 -06:00
vicrodh 2a5c181618 Fix Discogs artwork selection to show unique images
- Modified search_artwork_options to return unique images from different releases
- Only take one image per release (prefer cover_image, fallback to thumb)
- Use HashSet to track and filter duplicate URLs
- Process up to 20 results to get more diverse options
- Adjusted grid layout from minmax(100px, 1fr) to minmax(80px, 1fr) to fit at least 4 items per row
2026-01-17 00:21:23 -06:00
vicrodh dab5c28727 Add Discogs artwork selection UI in album edit modal
Backend:
- Add DiscogsImageOption struct for artwork options
- Add discogs_search_artwork command to search multiple images
- Add discogs_download_artwork command to download selected image
- Extend DiscogsClient with search_artwork_options() method
- Extend DiscogsClient with download_artwork_from_url() method
- Register new commands in lib.rs

Frontend:
- Add Discogs image selection to album edit modal
- Add 'Fetch from Discogs' button with Discogs logo
- Display grid of artwork options from Discogs search results
- Allow user to select preferred artwork option
- Download and save selected artwork on Save
- Add comprehensive CSS styling for Discogs UI elements
- Adjust Discogs logo color filter for proper contrast

User can now click 'Fetch from Discogs', browse multiple artwork
options, select one, and save it as the album's cover art.
2026-01-17 00:12:14 -06:00
vicrodh dd70f0a8c4 Refactor Discogs integration to use Cloudflare Workers proxy
- Update DISCOGS_API_URL to point to proxy endpoint
- Remove embedded CONSUMER_KEY and CONSUMER_SECRET constants
- Simplify DiscogsClient to only handle HTTP requests
- Update search_release() to use proxy /search endpoint
- Update search_artist() to use proxy /search endpoint
- Update download_image() to use proxy /image endpoint
- Add User-Agent 'QBZ/1.0.0' for proxy validation
- Maintain backward compatibility with with_user_credentials (no-op)
2026-01-16 21:31:10 -06:00
vicrodh 9099123091 Refactor Spotify and Tidal playlist import to use proxy
- Update Spotify to use Cloudflare Workers proxy endpoint
- Update Tidal to use Cloudflare Workers proxy endpoint
- Remove embedded CLIENT_ID and CLIENT_SECRET constants
- Remove Base64 encoding and client credentials flow (proxy handles it)
- Add User-Agent 'QBZ/1.0.0' for proxy validation
- Maintain backward compatibility with user_creds parameter (ignored)
2026-01-16 21:19:51 -06:00
vicrodh c810663c6f feat: implement 3-state DAC badge and hardware sample rate detection
Backend changes:
- Add audio_diagnostics module to read actual hardware sample rate from /proc/asound
- New command get_hardware_audio_status() parses hw_params for real-time monitoring
- Add glob dependency for file pattern matching

Frontend (AudioOutputBadges):
- Implement 3-state DAC badge system:
  - Green: DAC passthrough working correctly (rates match)
  - Yellow: DAC passthrough ON but resampling detected (warning)
  - Gray: DAC passthrough OFF (intentionally disabled)
- Dynamic tooltips showing actual hardware sample rate
- Compare file sample rate vs hardware sample rate
- Increase tooltip z-index to 9999 to prevent occlusion issues
- Add pointer-events: none to tooltip to prevent interaction issues

Frontend (SettingsView):
- Replace hardcoded "192 kHz" with dynamic hardware sample rate
- Load and display actual hardware audio status
- Show format details (e.g., S32_LE) when available
- Display "No active playback" when no audio is playing
- Add muted styling for inactive state

Frontend (QualityBadge):
- Add native tooltip showing tier and format
- Add cursor: help to indicate interactivity

i18n:
- Add "noActivePlayback" translations (EN/ES)

This allows users to verify bit-perfect playback is working and detect
resampling issues that would compromise audio quality.
2026-01-16 21:14:22 -06:00
vicrodh 179a6b7c54 Refactor Last.fm integration to use Cloudflare Workers proxy
- Update LASTFM_API_URL to point to proxy endpoint
- Remove MD5 signature generation (proxy handles it)
- Remove api_key and api_secret from LastFmClient (proxy manages credentials)
- Simplify client to only handle session_key
- Update get_token() to return (token, authUrl) tuple from proxy
- Update all methods (get_session, scrobble, update_now_playing) to use proxy
- Add User-Agent header 'QBZ/1.0.0' for proxy validation
- Update bootstrap.ts to skip credential restoration (proxy handles it)
- Maintain backward compatibility with set_credentials as no-op
2026-01-16 21:09:18 -06:00
vicrodh 3a49e3f6dd Remove embedded Last.fm API secret for Flathub compliance
- Remove DEFAULT_API_SECRET constant and get_api_secret() function
- Update Default impl to require user-provided secret
- Update has_embedded_credentials() to only check API key
- API secret will be handled by Cloudflare Workers proxy
2026-01-16 20:46:01 -06:00
vicrodh 791ff7a73b feat: implement mutual exclusion for incompatible audio settings
Add auto-disable logic to prevent incompatible settings from being enabled simultaneously:

Frontend (SettingsView.svelte):
- DAC Passthrough ON → auto-disables Gapless Playback and Crossfade
- Gapless Playback ON → auto-disables DAC Passthrough
- Crossfade > 0 → auto-disables DAC Passthrough
- Added handleGaplessPlaybackChange() and handleCrossfadeChange() handlers
- Modified handleDacPassthroughChange() to disable incompatible settings

Backend (player/mod.rs):
- Only recreate OutputStream on format change when DAC passthrough is enabled
- When DAC passthrough is OFF: reuse existing stream for gapless transitions
- When DAC passthrough is ON: recreate stream for bit-perfect playback
- Added detailed logging for stream recreation decisions

This fixes the issue where Gapless Playback was enabled but gaps still occurred
due to DAC passthrough forcing stream recreation on sample rate changes.
2026-01-16 20:41:53 -06:00
vicrodh 20fc254769 Merge branch 'bugfix/pretty-device-names' 2026-01-16 19:53:11 -06:00
vicrodh 33c5e00da0 Fix audio device selection to show friendly PipeWire names
The previous implementation tried to map ALSA device names (from cpal)
to PipeWire descriptions, but the naming schemes are completely different:
- cpal returns names like "plughw:1,0"
- pactl returns sink names like "alsa_output.usb-Cambridge_Audio_..."

This fix changes the approach:
1. Use PipeWire sinks directly for the device dropdown (already filtered
   to real outputs and have friendly descriptions like "DacMagic Plus")
2. Store the PipeWire sink name when user selects a device
3. Set the PipeWire default sink using pactl when device is selected
4. Use rodio's default device (which follows PipeWire's default)

This ensures users see 4-5 meaningful devices with friendly names
instead of 30+ raw ALSA device names.

Backend changes:
- Add set_pipewire_default_sink command (pactl set-default-sink)
- Register new command in lib.rs

Frontend changes:
- Use pipewireSinks directly for device dropdown
- Display sink description (friendly name)
- Store sink name (for pactl)
- Call set_pipewire_default_sink when device changes
- Remove unused AudioDevice interface and getDevicePrettyName import
2026-01-16 19:52:51 -06:00
vicrodh 6e36b56486 fix: make offline detection less aggressive
- Increase network check timeout from 5s to 15s
- Add retry (2 attempts with 2s delay) before declaring offline
- Add logging for failed network check attempts

This prevents false offline mode triggers from temporary latency spikes.
2026-01-16 19:24:35 -06:00
vicrodh c5a1d14822 fix: use fast header-only metadata extraction instead of full decode
The play_data function was calling decode_with_symphonia which decoded
the entire audio file (271MB+) just to extract sample_rate and channels.
This caused 30-35 second delays between download complete and playback.

New extract_audio_metadata function:
- For FLAC: uses rodio decoder (reads headers only)
- For isomp4: uses symphonia probe to read codec_params without decoding

This restores the fast playback we had before.
2026-01-16 18:32:16 -06:00
vicrodh eebf440f84 Merge remote-tracking branch 'origin/main' into feature/favorite-playlists 2026-01-16 17:12:34 -06:00
vicrodh 573d818cf5 Merge branch 'research/dac-passthrough' 2026-01-16 17:12:19 -06:00
vicrodh 8f32eec782 refactor(wayland): harden NVIDIA fix with detection and user overrides
Improvements to the Wayland compatibility fix:

Detection:
- Add NVIDIA GPU detection via /proc and kernel modules
- Apply DMA-BUF workaround only on NVIDIA systems
- Preserve full acceleration on Intel/AMD GPUs

User Overrides:
- QBZ_FORCE_DMABUF=1: Force-enable DMA-BUF (bypass workaround)
- QBZ_DISABLE_DMABUF=1: Force-disable DMA-BUF
- QBZ_FORCE_X11=1: Force X11 backend on Wayland

Diagnostics:
- Clear logging of display server type
- NVIDIA detection status
- Applied workarounds and reasons
- User override notifications

This makes the fix selective and transparent while protecting
NVIDIA + Wayland users from fatal protocol errors.

Related: #6

cc
2026-01-16 14:57:19 -06:00
vicrodh 837fe0bb7a fix(wayland): add environment variables for Wayland and WebKit compatibility
Resolves protocol errors and GBM buffer issues on Wayland by:
- Detecting Wayland display and forcing GDK backend
- Disabling WebKit compositing mode for transparent windows
- Disabling DMA-BUF renderer to fix GPU driver compatibility
- Setting client-side decorations preference

This addresses issue #6 where users on Wayland experienced crashes
with "Error 71 (Protocol error)" and GBM buffer creation failures.

cc
2026-01-16 14:57:14 -06:00
vicrodh 27431d2ea9 Add backend support for playlist favorites
- Add is_favorite column migration to playlist_settings table
- Update PlaylistSettings struct with is_favorite field
- Add set_playlist_favorite() and get_favorite_playlist_ids() methods
- Add playlist_set_favorite and playlist_get_favorites Tauri commands
2026-01-16 14:40:47 -06:00
vicrodh 88b1f959ae Implement DAC passthrough and sample rate switching
This commit implements proper sample rate passthrough for high-quality
audio playback, addressing issue #10 where DAC sample rates were locked
at 44.1kHz regardless of file quality.

Core Changes:
- Modified Player to accept and store AudioSettings (exclusive mode, DAC passthrough)
- Updated AudioCommand::Play to include sample_rate and channels metadata
- Created create_output_stream_with_config() to build custom StreamConfig
- Added sample rate tracking to detect changes between tracks
- Modified Play handler to recreate OutputStream when sample rate changes
- Updated play_data() to extract audio metadata before playback
- Modified lib.rs to read AudioSettings on startup and pass to Player

Device Detection Improvements (Issue #4):
- Added display_name and device_type fields to AudioDevice struct
- Implemented get_pipewire_device_descriptions() to extract friendly names
- Updated get_audio_devices() to map technical names to user-friendly names
- Added device type detection (USB, HDMI, PCI, Bluetooth)

Technical Details:
- Audio files are decoded twice: once for metadata, once for playback
- Stream is recreated only when sample rate or channel count changes
- Falls back to default device config if custom config fails
- Supports both PipeWire and PulseAudio device naming via pactl
- Logs sample rate transitions and OutputStream configuration

Files Modified:
- src-tauri/src/player/mod.rs: Core playback engine changes
- src-tauri/src/lib.rs: Initialize Player with AudioSettings
- src-tauri/src/commands/playback.rs: Device enumeration improvements

Tested: Compiles successfully on Linux with Rust 1.92
2026-01-16 14:16:18 -06:00
vicrodh 73d022ae20 Add two-level audio cache system (L1: memory, L2: disk)
L1 (Memory): 300MB for ~3-4 Hi-Res tracks, instant access
L2 (Disk): 500MB playback cache at ~/.cache/qbz/playback/

Flow:
- When track evicted from memory, saved to disk cache
- When loading: check memory → disk → download cache → network
- Tracks promoted back to memory when accessed from disk

This reduces re-downloads when navigating back to recently played
tracks in mixed playlists.
2026-01-16 08:49:56 -06:00
vicrodh 2bdde3d592 Improve prefetch for mixed playlists
- Look ahead 15 tracks instead of 3 to find Qobuz tracks past local ones
- Prefetch up to 3 Qobuz tracks regardless of local track positions
- Count cached/fetching tracks toward limit to avoid over-prefetching

Example: [Local, Local, Qobuz, Qobuz, Local x4, Qobuz]
Before: Only prefetched tracks 3,4 (next 3 total)
After:  Prefetches tracks 3,4,9 (next 3 Qobuz)
2026-01-16 08:42:17 -06:00
vicrodh 94d7d78a22 Fix buffering UX when transitioning from local to Qobuz tracks
- Stop playback immediately when starting a Qobuz track to prevent
  previous track from continuing during download
- Show buffering toast while downloading Qobuz audio
- Skip buffering UI for local tracks since they load instantly
- Add is_local field to QueueTrack for prefetch optimization
- Update prefetch to skip local tracks (no network needed)
- Update all queue track builders to include is_local flag
2026-01-16 07:58:13 -06:00
vicrodh aeb13b9ffc Connect playlist count callback for single source of truth
Wire onPlaylistCountUpdate from PlaylistDetailView to Sidebar's
updatePlaylistCounts method in +page.svelte. This ensures the
sidebar displays accurate track counts from the database instead
of calculated values that could drift out of sync.
2026-01-16 07:19:34 -06:00
vicrodh 16486e4591 Fix playlist track ordering to respect insertion order
- Add PlaylistLocalTrack struct with playlist_position field
- Add playlist_get_local_tracks_with_position command
- Update PlaylistDetailView to merge tracks by position instead of
  appending local tracks at the end
- Update PlaylistModal to calculate correct position when adding local
  tracks (total count = qobuz + existing local)
- Local tracks now appear in the order they were added, interleaved
  with Qobuz tracks
2026-01-16 07:04:21 -06:00
vicrodh 5f4cfa5888 feat(playlists): include local tracks in counts everywhere
- Add playlist_get_all_local_track_counts command to get local track
  counts for all playlists efficiently
- PlaylistDetailView: show combined track count and duration in header,
  style local count with muted color, remove redundant count below
- Sidebar: load and display local track counts in tooltips
- PlaylistModal: show combined counts when selecting playlist to add to
- PlaylistManagerView: show combined counts in both grid and list views
- TrackRow: move local indicator to download column position, add
  placeholder for favorite column to maintain alignment, update tooltip
- Remove local tracks notice banner (info now in tooltip)

cc
2026-01-16 06:32:13 -06:00
vicrodh 81d2bdcf33 CRITICAL: Fix library clear to preserve Qobuz downloads
- clear_all_tracks now only deletes local library tracks
- Qobuz downloads (source = 'qobuz_download') are preserved
- Local library management is now completely separate from Qobuz downloads
2026-01-16 05:15:18 -06:00
vicrodh 6cabded79a Add network folder detection backfill and folder path editing
- Fix offline settings database migrations to run individually
- Add network detection refresh when loading folder list
- Add network detection during folder scan
- Show folder alias with path tooltip in folder list
- Add folder path change capability in FolderSettingsModal
- Add network content visibility toggle in offline settings
- Add i18n strings for new features (en/es)
2026-01-16 05:00:24 -06:00
vicrodh c0ea039476 Add network folder detection and folder settings
- Add network folder detection module using /proc/self/mountinfo
  - Detect CIFS/SMB, NFS, SSHFS, rclone, WebDAV, GlusterFS, CephFS
  - Classify mount types (local, network, virtual, FUSE unknown)
  - Check mount accessibility

- Add library folder metadata
  - Alias for friendly naming (e.g., "NAS Music", "External Drive")
  - Network folder flag with auto-detection
  - User override for manual network folder marking
  - Network filesystem type (detected or user-defined)
  - Enabled toggle to disable folders without removing

- Add FolderSettingsModal component
  - Edit folder alias, enabled state, network settings
  - Show accessibility status for network folders
  - Scan single folder button

- Update LocalLibraryView folder list
  - Show network icon with connected/disconnected states
  - Display alias with path below if set
  - Edit button (enabled for single selection)
  - Visual badges for disabled/unavailable folders

- Add Tauri commands
  - library_get_folders_with_metadata
  - library_update_folder_settings
  - library_scan_folder (scan single folder)
  - check_network_path, check_mount_accessible
2026-01-16 04:40:06 -06:00
vicrodh f9ca1bd25e Add manual offline mode settings and disable import in offline mode
- Disable import playlists button when offline (shows disabled state)
- Add Cast, immediate scrobbling, and accumulated scrobbling toggles
- These settings only appear when manual offline mode is enabled
- Add time limit note for accumulated scrobbling (Last.fm 2 week limit)
- Persist new settings in SQLite with proper migrations
- Add i18n strings for all new settings (en/es)
2026-01-16 03:15:02 -06:00
vicrodh 6f3bcfe1cb Add offline mode track handling for playlists and queue
- Add batch check command for track local status in Rust
- Show unavailable tracks with grayed-out visual state in PlaylistDetailView
- Disable interactions for unavailable tracks (click, menu, play)
- Add WifiOff indicator for unavailable tracks
- Update QueuePanel to show unavailable track styling
- Implement auto-skip for unavailable tracks during playback
- Add offline mode sync to queue store
- Add offline-specific filter states to PlaylistManager:
  - Available Offline
  - Partially Available
  - Not Available Offline (view-only)
- Disable edit/drag for unavailable playlists in PlaylistManager
- Add i18n strings for offline states (en/es)
2026-01-16 03:01:15 -06:00
vicrodh d5f91db751 Remove NotLoggedIn check from offline detection
The login state check was causing race conditions on app startup because
offline status was being checked before auto_login completed. Since the
frontend already handles showing LoginView when not logged in, the
NotLoggedIn check in offline detection was redundant and problematic.

Now offline detection only checks:
1. Manual offline mode (from settings)
2. Network connectivity (via HTTP HEAD to qobuz.com)
2026-01-16 00:12:32 -06:00
vicrodh 373e61f0bd Fix offline mode toggle and add network verification
- Change OfflinePlaceholder icon to use offline.svg instead of Power icon
- Add flex-shrink protection to Toggle component to prevent collapsing
- Add network verification when disabling offline mode:
  - Check network connectivity before switching to online
  - Show semi-transparent overlay during check
  - Revert to offline and show toast if no internet available
- Add check_network Tauri command to verify connectivity
- Add i18n strings for noNetworkToast and checkingNetwork (en/es)
2026-01-15 23:55:45 -06:00
vicrodh a219a1dcb6 fix(offline): edge cases and code cleanup
- Fix online transition callback not triggering on manual mode disable
- Fix $derived pattern in DownloadButton (use $derived.by)
- Remove unused OptionalExtension import in offline/mod.rs
2026-01-15 23:33:24 -06:00
vicrodh e7f06fcd2f feat(offline): implement Last.fm scrobble queue for offline mode
Research findings:
- Last.fm API supports batch scrobbling (up to 50 tracks)
- Timestamps can be backdated up to 14 days

Implementation:
- Add scrobble_queue table to offline database
- Add queue_scrobble, get_queued_scrobbles, mark_scrobbles_sent commands
- Queue scrobbles when offline instead of skipping
- Flush queue automatically when transitioning to online
- Skip scrobbles older than 14 days (Last.fm limit)
- Cleanup sent scrobbles after 7 days
2026-01-15 23:24:24 -06:00
vicrodh dd5641e36b feat(offline): add pending playlist sync queue
Backend:
- Add PendingPlaylist struct for offline-created playlists
- Create pending_playlist_sync table in SQLite
- Add methods: create_pending_playlist, get_pending_playlists,
  mark_playlist_synced, delete_pending_playlist, get_pending_playlist_count
- Register Tauri commands for pending playlist management

Assets:
- Add offline.svg icon for offline mode indicator

cc
2026-01-15 22:52:02 -06:00
vicrodh 02240f41ac feat(offline): add playlist local content detection
- Add LocalContentStatus enum (unknown, no, some_local, all_local)
- Add has_local_content column to playlist_settings with migration
- Implement local track matching by Qobuz ID and by metadata (title+artist+album)
- Add commands: playlist_analyze_local_content, playlist_get_local_content_status,
  playlist_track_is_local, playlist_get_local_track_id, playlist_get_offline_available
- Add database functions for local content filtering and status updates

cc
2026-01-15 22:11:08 -06:00
vicrodh 21354f9b64 feat(offline): add offline mode detection backend
- Create offline module with OfflineReason enum and OfflineStatus struct
- Add OfflineSettings for manual mode and partial playlist preferences
- Implement network connectivity check via HEAD request to qobuz.com
- Add SQLite persistence for offline settings
- Register Tauri commands: get_offline_status, get_offline_settings,
  set_manual_offline, set_show_partial_playlists
- Add event emission for offline status changes

cc
2026-01-15 22:06:41 -06:00
vicrodh db49168623 feat(search): add carousel for artists and view more cards
- Albums: 30 results with view more card if total > 30
- Artists: 12 results with carousel navigation
- Artists carousel: same pagination logic as albums
- View more cards: 160x220px dashed border, navigate to tab
- Artists gap: reduced to 16px (same as albums)
- Separate page tracking for albums and artists

cp
2026-01-15 15:04:15 -06:00
vicrodh 8a3e8546bd fix(search): single row carousel and homogenize artist cards
- Albums carousel: display flex (one row only)
- Backend: 20 albums (was 16)
- Artist cards: all 160x220px (most popular and grid)
- Carousel buttons work with multiple pages

cp
2026-01-15 14:51:44 -06:00
vicrodh 919e72c0d2 fix(search): final adjustments for unified search view
- Artist cards: fixed spacing with 16px gap, 120x120 circular images
- Layout: Albums and Tracks now in separate full-width rows
- Carousel: Changed to single row (was 2 rows)
- Crown icon: Shimmer gradient effect (no glow/drop-shadow)
- Backend already at 16 albums

cp
2026-01-15 14:27:01 -06:00
vicrodh aa0e44d533 feat(search): add unified search view with all results tab
- Add search_all command to Rust backend (parallel search execution)
- Create SearchAllResults type for unified search results
- Add 'all' tab as new default in search view
- Implement unified results layout:
  - Most Popular section (top artist or album)
  - Artists section (up to 5 artist cards)
  - Albums carousel (horizontal scroll, 10 albums)
  - Tracks section (top 8 tracks)
- Add View all links to navigate to individual tabs
- Update search state store to include allResults
- Maintain existing tab functionality (albums, tracks, artists)
- Add responsive grid layout for unified view

cp
2026-01-15 12:38:41 -06:00