feat: add full screen audio player and radio station management
- Implemented FullScreenPlayer component for enhanced audio playback experience. - Added functionality to toggle full screen mode in AudioPlayer. - Introduced NavidromeConfigContext for managing Navidrome server configurations. - Created RadioStationsPage for managing internet radio stations, including adding, deleting, and playing stations. - Enhanced SettingsPage to configure Navidrome server connection with validation and feedback. - Updated NavidromeAPI to support fetching and managing radio stations. - Integrated lyrics fetching and display in FullScreenPlayer using LrcLibClient.
This commit is contained in:
@@ -1,13 +1,111 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '@/components/ui/select';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useTheme } from '@/app/components/ThemeProvider';
|
||||
import { useNavidromeConfig } from '@/app/components/NavidromeConfigContext';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { FaServer, FaUser, FaLock, FaCheck, FaTimes } from 'react-icons/fa';
|
||||
|
||||
const SettingsPage = () => {
|
||||
const { theme, setTheme } = useTheme();
|
||||
const { config, updateConfig, isConnected, testConnection, clearConfig } = useNavidromeConfig();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
serverUrl: config.serverUrl,
|
||||
username: config.username,
|
||||
password: config.password
|
||||
});
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
setHasUnsavedChanges(true);
|
||||
};
|
||||
|
||||
const handleTestConnection = async () => {
|
||||
if (!formData.serverUrl || !formData.username || !formData.password) {
|
||||
toast({
|
||||
title: "Missing Information",
|
||||
description: "Please fill in all fields before testing the connection.",
|
||||
variant: "destructive"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsTesting(true);
|
||||
try {
|
||||
const success = await testConnection({
|
||||
serverUrl: formData.serverUrl,
|
||||
username: formData.username,
|
||||
password: formData.password
|
||||
});
|
||||
|
||||
if (success) {
|
||||
toast({
|
||||
title: "Connection Successful",
|
||||
description: "Successfully connected to Navidrome server.",
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: "Connection Failed",
|
||||
description: "Could not connect to the server. Please check your settings.",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Connection Error",
|
||||
description: "An error occurred while testing the connection.",
|
||||
variant: "destructive"
|
||||
});
|
||||
} finally {
|
||||
setIsTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveConfig = async () => {
|
||||
if (!formData.serverUrl || !formData.username || !formData.password) {
|
||||
toast({
|
||||
title: "Missing Information",
|
||||
description: "Please fill in all fields.",
|
||||
variant: "destructive"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
updateConfig({
|
||||
serverUrl: formData.serverUrl,
|
||||
username: formData.username,
|
||||
password: formData.password
|
||||
});
|
||||
|
||||
setHasUnsavedChanges(false);
|
||||
toast({
|
||||
title: "Settings Saved",
|
||||
description: "Navidrome configuration has been saved.",
|
||||
});
|
||||
};
|
||||
|
||||
const handleClearConfig = () => {
|
||||
clearConfig();
|
||||
setFormData({
|
||||
serverUrl: '',
|
||||
username: '',
|
||||
password: ''
|
||||
});
|
||||
setHasUnsavedChanges(false);
|
||||
toast({
|
||||
title: "Configuration Cleared",
|
||||
description: "Navidrome configuration has been cleared.",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6 max-w-2xl">
|
||||
@@ -17,6 +115,93 @@ const SettingsPage = () => {
|
||||
<p className="text-muted-foreground">Customize your music experience</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<FaServer className="w-5 h-5" />
|
||||
Navidrome Server
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Configure connection to your Navidrome music server
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="server-url">Server URL</Label>
|
||||
<Input
|
||||
id="server-url"
|
||||
type="url"
|
||||
placeholder="https://your-navidrome-server.com"
|
||||
value={formData.serverUrl}
|
||||
onChange={(e) => handleInputChange('serverUrl', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="username">Username</Label>
|
||||
<Input
|
||||
id="username"
|
||||
type="text"
|
||||
placeholder="Your username"
|
||||
value={formData.username}
|
||||
onChange={(e) => handleInputChange('username', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="Your password"
|
||||
value={formData.password}
|
||||
onChange={(e) => handleInputChange('password', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 rounded-lg bg-muted">
|
||||
{isConnected ? (
|
||||
<>
|
||||
<FaCheck className="w-4 h-4 text-green-600" />
|
||||
<span className="text-sm text-green-600">Connected to server</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FaTimes className="w-4 h-4 text-red-600" />
|
||||
<span className="text-sm text-red-600">Not connected</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={handleTestConnection}
|
||||
disabled={isTesting}
|
||||
variant="outline"
|
||||
>
|
||||
{isTesting ? 'Testing...' : 'Test Connection'}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSaveConfig}
|
||||
disabled={!hasUnsavedChanges}
|
||||
>
|
||||
Save Configuration
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleClearConfig}
|
||||
variant="destructive"
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<p><strong>Note:</strong> Your credentials are stored locally in your browser</p>
|
||||
<p><strong>Security:</strong> Always use HTTPS for your Navidrome server</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Appearance</CardTitle>
|
||||
|
||||
Reference in New Issue
Block a user