import { useState, useEffect } from 'react'; import { settingsStore, getDefaultSources, type SortOption, type SearchDisplayMode, type ProxyMode, type LocaleOption, DEFAULT_SEEK_STEP_SECONDS, normalizeSeekStepSeconds, } from '@/lib/store/settings-store'; import type { VideoSource, SourceSubscription } from '@/lib/types'; import { type ImportResult, mergeSources, parseSourcesFromJson, fetchSourcesFromUrl } from '@/lib/utils/source-import-utils'; export function useSettingsPage() { const [sources, setSources] = useState([]); const [subscriptions, setSubscriptions] = useState([]); const [sortBy, setSortBy] = useState('default'); const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [isExportModalOpen, setIsExportModalOpen] = useState(false); const [isImportModalOpen, setIsImportModalOpen] = useState(false); const [isResetDialogOpen, setIsResetDialogOpen] = useState(false); const [isRestoreDefaultsDialogOpen, setIsRestoreDefaultsDialogOpen] = useState(false); const [editingSource, setEditingSource] = useState(null); // Display settings const [realtimeLatency, setRealtimeLatency] = useState(false); const [searchDisplayMode, setSearchDisplayMode] = useState('normal'); const [fullscreenType, setFullscreenType] = useState<'auto' | 'native' | 'window'>('auto'); const [proxyMode, setProxyMode] = useState('retry'); const [seekStepSeconds, setSeekStepSeconds] = useState(DEFAULT_SEEK_STEP_SECONDS); const [rememberScrollPosition, setRememberScrollPosition] = useState(true); const [locale, setLocale] = useState('zh-CN'); const [videoTogetherEnabled, setVideoTogetherEnabled] = useState(false); // Danmaku settings const [danmakuApiUrl, setDanmakuApiUrl] = useState(''); const [danmakuOpacity, setDanmakuOpacity] = useState(0.7); const [danmakuFontSize, setDanmakuFontSize] = useState(20); const [danmakuDisplayArea, setDanmakuDisplayArea] = useState(0.5); // Content filter const [blockedCategories, setBlockedCategories] = useState([]); useEffect(() => { const syncFromStore = () => { const settings = settingsStore.getSettings(); setSources(settings.sources || []); setSubscriptions(settings.subscriptions || []); setSortBy(settings.sortBy); setRealtimeLatency(settings.realtimeLatency); setSearchDisplayMode(settings.searchDisplayMode); setFullscreenType(settings.fullscreenType); setProxyMode(settings.proxyMode); setSeekStepSeconds(settings.seekStepSeconds); setRememberScrollPosition(settings.rememberScrollPosition); setLocale(settings.locale); setVideoTogetherEnabled(settings.videoTogetherEnabled); setDanmakuApiUrl(settings.danmakuApiUrl); setDanmakuOpacity(settings.danmakuOpacity); setDanmakuFontSize(settings.danmakuFontSize); setDanmakuDisplayArea(settings.danmakuDisplayArea); setBlockedCategories(settings.blockedCategories || []); }; syncFromStore(); return settingsStore.subscribe(syncFromStore); }, []); const handleSourcesChange = (newSources: VideoSource[]) => { setSources(newSources); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, sources: newSources, sortBy, subscriptions, }); }; const handleAddSource = (source: VideoSource) => { const exists = sources.some(s => s.id === source.id); const updated = exists ? sources.map(s => s.id === source.id ? source : s) : [...sources, source]; handleSourcesChange(updated); setEditingSource(null); }; const handleEditSource = (source: VideoSource) => { setEditingSource(source); setIsAddModalOpen(true); }; const handleSortChange = (newSort: SortOption) => { setSortBy(newSort); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, sources, sortBy: newSort, }); }; const handleExport = (includeSearchHistory: boolean, includeWatchHistory: boolean) => { const data = settingsStore.exportSettings(includeSearchHistory || includeWatchHistory); const blob = new Blob([data], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `kvideo-settings-${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); }; const handleImportFile = (jsonString: string): boolean => { // 1. Try to import as full settings backup const asBackupSuccess = settingsStore.importSettings(jsonString); if (asBackupSuccess) { const settings = settingsStore.getSettings(); setSources(settings.sources); setSortBy(settings.sortBy); setSubscriptions(settings.subscriptions || []); // Reload to apply changes setTimeout(() => window.location.reload(), 1000); return true; } // 2. Try to import as source list (JSON format) try { const result = parseSourcesFromJson(jsonString); if (result.totalCount > 0) { return handleImportLink(result, false); // Reuse link import logic } } catch { return false; } return false; }; const handleImportLink = (result: ImportResult, isSync: boolean = false): boolean => { try { // Merge normal sources const updatedSources = mergeSources(sources, result.normalSources); // Merge premium sources if needed const currentSettings = settingsStore.getSettings(); const updatedPremiumSources = mergeSources(currentSettings.premiumSources, result.premiumSources); // Save everything settingsStore.saveSettings({ ...currentSettings, sources: updatedSources, premiumSources: updatedPremiumSources, }); setSources(updatedSources); // Update local state // If strictly creating/editing subscription, we don't reload page usually, but here we might want to refresh UI if (!isSync) { setTimeout(() => window.location.reload(), 1000); } return true; } catch (e) { console.error("Import error:", e); return false; } }; // Subscription Handlers const handleAddSubscription = async (sub: SourceSubscription): Promise => { // Verify we can fetch it try { const result = await fetchSourcesFromUrl(sub.url); // Import the content handleImportLink(result, true); // Add subscription to store const newSubscriptions = [...subscriptions, sub]; setSubscriptions(newSubscriptions); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, subscriptions: newSubscriptions }); return true; } catch (e) { console.error(e); throw new Error('无法连接到订阅链接或格式错误'); } }; const handleRemoveSubscription = (id: string) => { const newSubscriptions = subscriptions.filter(s => s.id !== id); setSubscriptions(newSubscriptions); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, subscriptions: newSubscriptions }); }; const handleRefreshSubscription = async (sub: SourceSubscription) => { try { const result = await fetchSourcesFromUrl(sub.url); handleImportLink(result, true); // Update last updated timestamp const updatedSubscriptions = subscriptions.map(s => s.id === sub.id ? { ...s, lastUpdated: Date.now() } : s ); setSubscriptions(updatedSubscriptions); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, subscriptions: updatedSubscriptions }); } catch (e) { console.error(e); // Optionally notify user of failure } }; const handleRealtimeLatencyChange = (enabled: boolean) => { setRealtimeLatency(enabled); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, realtimeLatency: enabled, }); }; const handleSearchDisplayModeChange = (mode: SearchDisplayMode) => { setSearchDisplayMode(mode); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, searchDisplayMode: mode, }); }; const handleFullscreenTypeChange = (type: 'auto' | 'native' | 'window') => { setFullscreenType(type); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, fullscreenType: type, }); }; const handleProxyModeChange = (mode: ProxyMode) => { setProxyMode(mode); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, proxyMode: mode, }); }; const handleSeekStepSecondsChange = (value: number) => { const normalized = normalizeSeekStepSeconds(value); setSeekStepSeconds(normalized); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, seekStepSeconds: normalized, }); }; const handleRememberScrollPositionChange = (enabled: boolean) => { setRememberScrollPosition(enabled); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, rememberScrollPosition: enabled, }); }; const handleVideoTogetherEnabledChange = (enabled: boolean) => { setVideoTogetherEnabled(enabled); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, videoTogetherEnabled: enabled, }); }; const handleLocaleChange = (newLocale: LocaleOption) => { setLocale(newLocale); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, locale: newLocale, }); }; const handleDanmakuApiUrlChange = (url: string) => { setDanmakuApiUrl(url); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, danmakuApiUrl: url, }); }; const handleDanmakuOpacityChange = (value: number) => { const clamped = Math.max(0.1, Math.min(1, value)); setDanmakuOpacity(clamped); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, danmakuOpacity: clamped, }); }; const handleDanmakuFontSizeChange = (value: number) => { setDanmakuFontSize(value); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, danmakuFontSize: value, }); }; const handleDanmakuDisplayAreaChange = (value: number) => { setDanmakuDisplayArea(value); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, danmakuDisplayArea: value, }); }; const handleBlockedCategoriesChange = (categories: string[]) => { setBlockedCategories(categories); const currentSettings = settingsStore.getSettings(); settingsStore.saveSettings({ ...currentSettings, blockedCategories: categories, }); }; const handleRestoreDefaults = () => { const defaults = getDefaultSources(); handleSourcesChange(defaults); setIsRestoreDefaultsDialogOpen(false); }; const handleResetAll = () => { settingsStore.resetToDefaults(); setIsResetDialogOpen(false); window.location.reload(); }; return { sources, subscriptions, sortBy, realtimeLatency, searchDisplayMode, fullscreenType, proxyMode, seekStepSeconds, isAddModalOpen, isExportModalOpen, isImportModalOpen, isResetDialogOpen, isRestoreDefaultsDialogOpen, setIsAddModalOpen, setIsExportModalOpen, setIsImportModalOpen, setIsResetDialogOpen, setIsRestoreDefaultsDialogOpen, setEditingSource, handleSourcesChange, handleAddSource, handleSortChange, handleExport, handleImportFile, handleImportLink, handleAddSubscription, handleRemoveSubscription, handleRefreshSubscription, handleRestoreDefaults, handleResetAll, editingSource, handleEditSource, handleRealtimeLatencyChange, handleSearchDisplayModeChange, handleFullscreenTypeChange, handleProxyModeChange, handleSeekStepSecondsChange, rememberScrollPosition, handleRememberScrollPositionChange, locale, handleLocaleChange, videoTogetherEnabled, handleVideoTogetherEnabledChange, danmakuApiUrl, handleDanmakuApiUrlChange, danmakuOpacity, handleDanmakuOpacityChange, danmakuFontSize, handleDanmakuFontSizeChange, danmakuDisplayArea, handleDanmakuDisplayAreaChange, blockedCategories, handleBlockedCategoriesChange, }; }