Wire settings: account info, themes, and cache management
- Account section now shows real user name and subscription - Logout button connected to logout handler - Theme switching with persistence (Dark/Light/OLED Black/Warm) - Theme loads on app startup from localStorage - Cache stats display (size, max, track count) - Clear cache functionality with disabled state
This commit is contained in:
1 parent
ef8feebc45
commit
9b8e1352eb
5 files changed
+163
-11
No files matched your search
@@ -0,0 +1,21 @@
|
|||||||
|
//! Cache management commands
|
||||||
|
|
||||||
|
use tauri::State;
|
||||||
|
|
||||||
|
use crate::cache::CacheStats;
|
||||||
|
use crate::AppState;
|
||||||
|
|
||||||
|
/// Get cache statistics
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_cache_stats(state: State<'_, AppState>) -> CacheStats {
|
||||||
|
log::info!("Command: get_cache_stats");
|
||||||
|
state.audio_cache.stats()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear the audio cache
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn clear_cache(state: State<'_, AppState>) -> Result<(), String> {
|
||||||
|
log::info!("Command: clear_cache");
|
||||||
|
state.audio_cache.clear();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
//! Exposes backend functionality to the frontend via IPC
|
//! Exposes backend functionality to the frontend via IPC
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod cache;
|
||||||
pub mod favorites;
|
pub mod favorites;
|
||||||
pub mod notification;
|
pub mod notification;
|
||||||
pub mod playback;
|
pub mod playback;
|
||||||
@@ -11,6 +12,7 @@ pub mod queue;
|
|||||||
pub mod search;
|
pub mod search;
|
||||||
|
|
||||||
pub use auth::*;
|
pub use auth::*;
|
||||||
|
pub use cache::*;
|
||||||
pub use favorites::*;
|
pub use favorites::*;
|
||||||
pub use notification::*;
|
pub use notification::*;
|
||||||
pub use playback::*;
|
pub use playback::*;
|
||||||
|
|||||||
@@ -127,6 +127,9 @@ pub fn run() {
|
|||||||
// Notification commands
|
// Notification commands
|
||||||
commands::show_track_notification,
|
commands::show_track_notification,
|
||||||
commands::show_notification,
|
commands::show_notification,
|
||||||
|
// Cache commands
|
||||||
|
commands::get_cache_stats,
|
||||||
|
commands::clear_cache,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { ArrowLeft } from 'lucide-svelte';
|
import { ArrowLeft } from 'lucide-svelte';
|
||||||
import Toggle from '../Toggle.svelte';
|
import Toggle from '../Toggle.svelte';
|
||||||
import Dropdown from '../Dropdown.svelte';
|
import Dropdown from '../Dropdown.svelte';
|
||||||
@@ -7,9 +9,39 @@
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onBack?: () => void;
|
onBack?: () => void;
|
||||||
|
onLogout?: () => void;
|
||||||
|
userName?: string;
|
||||||
|
userEmail?: string;
|
||||||
|
subscription?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { onBack }: Props = $props();
|
interface CacheStats {
|
||||||
|
cached_tracks: number;
|
||||||
|
current_size_bytes: number;
|
||||||
|
max_size_bytes: number;
|
||||||
|
fetching_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { onBack, onLogout, userName = 'User', userEmail = '', subscription = 'Qobuz' }: Props = $props();
|
||||||
|
|
||||||
|
// Cache state
|
||||||
|
let cacheStats = $state<CacheStats | null>(null);
|
||||||
|
let isClearing = $state(false);
|
||||||
|
|
||||||
|
// Theme mapping: display name -> data-theme value
|
||||||
|
const themeMap: Record<string, string> = {
|
||||||
|
'Dark': '',
|
||||||
|
'Light': 'light',
|
||||||
|
'OLED Black': 'oled',
|
||||||
|
'Warm': 'warm'
|
||||||
|
};
|
||||||
|
|
||||||
|
const themeReverseMap: Record<string, string> = {
|
||||||
|
'': 'Dark',
|
||||||
|
'light': 'Light',
|
||||||
|
'oled': 'OLED Black',
|
||||||
|
'warm': 'Warm'
|
||||||
|
};
|
||||||
|
|
||||||
// Audio settings
|
// Audio settings
|
||||||
let streamingQuality = $state('Hi-Res');
|
let streamingQuality = $state('Hi-Res');
|
||||||
@@ -30,6 +62,60 @@
|
|||||||
// Integrations
|
// Integrations
|
||||||
let lastfmConnected = $state(false);
|
let lastfmConnected = $state(false);
|
||||||
let scrobbling = $state(false);
|
let scrobbling = $state(false);
|
||||||
|
|
||||||
|
// Load saved settings on mount
|
||||||
|
onMount(() => {
|
||||||
|
const savedTheme = localStorage.getItem('qbz-theme') || '';
|
||||||
|
theme = themeReverseMap[savedTheme] || 'Dark';
|
||||||
|
applyTheme(savedTheme);
|
||||||
|
|
||||||
|
// Load cache stats
|
||||||
|
loadCacheStats();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadCacheStats() {
|
||||||
|
try {
|
||||||
|
cacheStats = await invoke<CacheStats>('get_cache_stats');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load cache stats:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleClearCache() {
|
||||||
|
if (isClearing) return;
|
||||||
|
isClearing = true;
|
||||||
|
try {
|
||||||
|
await invoke('clear_cache');
|
||||||
|
await loadCacheStats();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to clear cache:', err);
|
||||||
|
} finally {
|
||||||
|
isClearing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBytes(bytes: number): string {
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTheme(themeValue: string) {
|
||||||
|
if (themeValue) {
|
||||||
|
document.documentElement.setAttribute('data-theme', themeValue);
|
||||||
|
} else {
|
||||||
|
document.documentElement.removeAttribute('data-theme');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleThemeChange(newTheme: string) {
|
||||||
|
theme = newTheme;
|
||||||
|
const themeValue = themeMap[newTheme] || '';
|
||||||
|
applyTheme(themeValue);
|
||||||
|
localStorage.setItem('qbz-theme', themeValue);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="settings-view">
|
<div class="settings-view">
|
||||||
@@ -48,13 +134,15 @@
|
|||||||
<section class="section">
|
<section class="section">
|
||||||
<h3 class="section-title">Account</h3>
|
<h3 class="section-title">Account</h3>
|
||||||
<div class="account-card">
|
<div class="account-card">
|
||||||
<div class="avatar">U</div>
|
<div class="avatar">{userName.charAt(0).toUpperCase()}</div>
|
||||||
<div class="account-info">
|
<div class="account-info">
|
||||||
<div class="username">Username</div>
|
<div class="username">{userName}</div>
|
||||||
<div class="email">email@example.com</div>
|
{#if userEmail}
|
||||||
<div class="subscription">Studio Premier</div>
|
<div class="email">{userEmail}</div>
|
||||||
|
{/if}
|
||||||
|
<div class="subscription">{subscription}</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="logout-btn">Logout</button>
|
<button class="logout-btn" onclick={onLogout}>Logout</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -128,7 +216,7 @@
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
value={theme}
|
value={theme}
|
||||||
options={['Dark', 'Light', 'OLED Black', 'Warm']}
|
options={['Dark', 'Light', 'OLED Black', 'Warm']}
|
||||||
onchange={(v) => (theme = v)}
|
onchange={handleThemeChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row last">
|
<div class="setting-row last">
|
||||||
@@ -163,11 +251,33 @@
|
|||||||
<h3 class="section-title">Storage</h3>
|
<h3 class="section-title">Storage</h3>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<span class="setting-label">Cache Size</span>
|
<span class="setting-label">Cache Size</span>
|
||||||
<span class="setting-value">2.4 GB</span>
|
<span class="setting-value">
|
||||||
|
{#if cacheStats}
|
||||||
|
{formatBytes(cacheStats.current_size_bytes)} / {formatBytes(cacheStats.max_size_bytes)}
|
||||||
|
{:else}
|
||||||
|
Loading...
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<span class="setting-label">Cached Tracks</span>
|
||||||
|
<span class="setting-value">
|
||||||
|
{#if cacheStats}
|
||||||
|
{cacheStats.cached_tracks} tracks
|
||||||
|
{:else}
|
||||||
|
-
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row last">
|
<div class="setting-row last">
|
||||||
<span class="setting-label">Clear Cache</span>
|
<span class="setting-label">Clear Cache</span>
|
||||||
<button class="clear-btn">Clear</button>
|
<button
|
||||||
|
class="clear-btn"
|
||||||
|
onclick={handleClearCache}
|
||||||
|
disabled={isClearing || !cacheStats || cacheStats.current_size_bytes === 0}
|
||||||
|
>
|
||||||
|
{isClearing ? 'Clearing...' : 'Clear'}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@@ -345,7 +455,12 @@
|
|||||||
transition: background-color 150ms ease;
|
transition: background-color 150ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clear-btn:hover {
|
.clear-btn:hover:not(:disabled) {
|
||||||
background-color: rgba(255, 107, 107, 0.1);
|
background-color: rgba(255, 107, 107, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clear-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
+12
-1
@@ -839,6 +839,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
// Load saved theme
|
||||||
|
const savedTheme = localStorage.getItem('qbz-theme');
|
||||||
|
if (savedTheme) {
|
||||||
|
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('keydown', handleKeydown);
|
document.addEventListener('keydown', handleKeydown);
|
||||||
return () => document.removeEventListener('keydown', handleKeydown);
|
return () => document.removeEventListener('keydown', handleKeydown);
|
||||||
});
|
});
|
||||||
@@ -888,7 +894,12 @@
|
|||||||
onTrackPlay={handleTrackPlay}
|
onTrackPlay={handleTrackPlay}
|
||||||
/>
|
/>
|
||||||
{:else if activeView === 'settings'}
|
{:else if activeView === 'settings'}
|
||||||
<SettingsView onBack={goBack} />
|
<SettingsView
|
||||||
|
onBack={goBack}
|
||||||
|
onLogout={handleLogout}
|
||||||
|
userName={userInfo?.userName}
|
||||||
|
subscription={userInfo?.subscription}
|
||||||
|
/>
|
||||||
{:else if activeView === 'album' && selectedAlbum}
|
{:else if activeView === 'album' && selectedAlbum}
|
||||||
<AlbumDetailView
|
<AlbumDetailView
|
||||||
album={selectedAlbum}
|
album={selectedAlbum}
|
||||||
|
|||||||
Reference in new issue
Block a user