import { useEffect, useMemo, useState } from 'react'; import { Box, Typography, Paper, Switch, FormControlLabel, Slider, Select, MenuItem, FormControl, InputLabel, Button, Divider, Chip, } from '@mui/material'; import CustomNotification from '../components/Notifications/CustomNotification'; import type { NotificationPosition } from '../components/Notifications/CustomNotification'; import { isNotificationsEnabled, getNotifPositionFromSettings } from '../utils/notifications'; type SettingsState = { // UI uiScale: number; // 80..120 reduceMotion: boolean; blurEffects: boolean; // Launcher / app autoUpdate: boolean; startInTray: boolean; // Game autoRotateSkinViewer: boolean; walkingSpeed: number; // 0..1 // Notifications notifications: boolean; notificationPosition: 'top-right' | 'top-center' | 'top-left' | 'bottom-right' | 'bottom-center' | 'bottom-left'; }; const STORAGE_KEY = 'launcher_settings'; const GRADIENT = 'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)'; const defaultSettings: SettingsState = { uiScale: 100, reduceMotion: false, blurEffects: true, autoUpdate: true, startInTray: false, autoRotateSkinViewer: true, walkingSpeed: 0.5, notifications: true, notificationPosition: 'top-right', }; function safeParseSettings(raw: string | null): SettingsState | null { if (!raw) return null; try { const obj = JSON.parse(raw); return { ...defaultSettings, ...obj, } as SettingsState; } catch { return null; } } // 🔽 ВСТАВИТЬ СЮДА (выше Settings) const NotificationPositionPicker = ({ value, disabled, onChange, }: { value: SettingsState['notificationPosition']; disabled?: boolean; onChange: (v: SettingsState['notificationPosition']) => void; }) => { const POSITIONS = [ { key: 'top-left', label: 'Сверху слева', align: 'flex-start', justify: 'flex-start' }, { key: 'top-center', label: 'Сверху по-центру', align: 'flex-start', justify: 'center' }, { key: 'top-right', label: 'Сверху справа', align: 'flex-start', justify: 'flex-end' }, { key: 'bottom-left', label: 'Снизу слева', align: 'flex-end', justify: 'flex-start' }, { key: 'bottom-center', label: 'Снизу по-центру', align: 'flex-end', justify: 'center' }, { key: 'bottom-right', label: 'Снизу справа', align: 'flex-end', justify: 'flex-end' }, ] as const; return ( Позиция уведомлений {POSITIONS.map((p) => { const selected = value === p.key; return ( onChange(p.key)} sx={{ cursor: 'pointer', //borderRadius: '0.9vw', border: selected ? '1px solid rgba(233,64,205,0.55)' : '1px solid rgba(255,255,255,0.10)', background: selected ? 'linear-gradient(120deg, rgba(242,113,33,0.12), rgba(233,64,205,0.10))' : 'rgba(255,255,255,0.04)', display: 'flex', alignItems: p.align, justifyContent: p.justify, p: '0.6vw', transition: 'all 0.18s ease', }} > {/* мини-уведомление */} ); })} ); }; const mapNotifPosition = ( p: SettingsState['notificationPosition'], ): NotificationPosition => { const [vertical, horizontal] = p.split('-') as ['top' | 'bottom', 'left' | 'center' | 'right']; return { vertical, horizontal }; }; const Settings = () => { const [lastSavedSettings, setLastSavedSettings] = useState(() => { if (typeof window === 'undefined') return defaultSettings; return safeParseSettings(localStorage.getItem(STORAGE_KEY)) ?? defaultSettings; }); const [notifOpen, setNotifOpen] = useState(false); const [notifMsg, setNotifMsg] = useState(''); const [notifSeverity, setNotifSeverity] = useState< 'success' | 'info' | 'warning' | 'error' >('info'); const [notifPos, setNotifPos] = useState({ vertical: 'bottom', horizontal: 'center', }); const [settings, setSettings] = useState(() => { if (typeof window === 'undefined') return defaultSettings; return safeParseSettings(localStorage.getItem(STORAGE_KEY)) ?? defaultSettings; }); const dirty = useMemo(() => { return JSON.stringify(settings) !== JSON.stringify(lastSavedSettings); }, [settings, lastSavedSettings]); const save = () => { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); setLastSavedSettings(settings); window.dispatchEvent(new CustomEvent('settings-updated')); // если уведомления выключены — НЕ показываем нотификацию if (!isNotificationsEnabled()) return; setNotifMsg('Настройки успешно сохранены!'); setNotifSeverity('info'); setNotifPos(mapNotifPosition(settings.notificationPosition)); setNotifOpen(true); } catch (e) { console.error('Не удалось сохранить настройки', e); } }; const reset = () => { setSettings(defaultSettings); setLastSavedSettings(defaultSettings); try { localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultSettings)); } catch (e) { console.error('Не удалось сбросить настройки', e); } }; const checkNotif = () => { if (!settings.notifications) return; // если выключены — не показываем setNotifMsg('Проверка уведомления!'); setNotifSeverity('info'); setNotifPos(mapNotifPosition(settings.notificationPosition)); // 👈 важно setNotifOpen(true); }; useEffect(() => { if (typeof document === 'undefined') return; const scale = settings.uiScale / 100; document.documentElement.style.setProperty('--ui-scale', String(scale)); document.body.classList.toggle('reduce-motion', settings.reduceMotion); document.body.classList.toggle('no-blur', !settings.blurEffects); }, [settings.uiScale, settings.reduceMotion, settings.blurEffects]); const SectionTitle = ({ children }: { children: string }) => ( {children} ); const Glass = ({ children }: { children: React.ReactNode }) => ( {children} ); const controlSx = { '& .MuiFormControlLabel-label': { fontFamily: 'Benzin-Bold', color: 'rgba(255,255,255,0.88)', }, '& .MuiSwitch-switchBase.Mui-checked': { color: 'rgba(242,113,33,0.95)', }, '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { backgroundColor: 'rgba(233,64,205,0.55)', }, '& .MuiSwitch-track': { backgroundColor: 'rgba(255,255,255,0.20)', }, } as const; return ( setNotifOpen(false)} autoHideDuration={2500} /> {/* header */} {dirty && ( )} {/* LEFT */} Интерфейс Масштаб интерфейса: {settings.uiScale}% setSettings((s) => ({ ...s, uiScale: v as number }))} sx={{ mt: 0.4, '& .MuiSlider-thumb': { boxShadow: '0 10px 22px rgba(0,0,0,0.45)' }, }} /> setSettings((s) => ({ ...s, reduceMotion: e.target.checked })) } /> } label="Уменьшить анимации" sx={controlSx} /> setSettings((s) => ({ ...s, blurEffects: e.target.checked })) } /> } label="Эффекты размытия (blur)" sx={controlSx} /> Уведомления setSettings((s) => ({ ...s, notifications: e.target.checked })) } /> } label="Включить уведомления" sx={controlSx} /> setSettings((s) => ({ ...s, notificationPosition: pos, })) } /> Нажмите сюда, чтобы проверить уведомление. {/* RIGHT */} Игра setSettings((s) => ({ ...s, autoRotateSkinViewer: e.target.checked })) } /> } label="Автоповорот персонажа в профиле" sx={controlSx} /> Скорость ходьбы в просмотрщике: {settings.walkingSpeed.toFixed(2)} setSettings((s) => ({ ...s, walkingSpeed: v as number }))} sx={{ mt: 0.4 }} /> Эти значения можно прокинуть в Profile: autoRotate и walkingSpeed. Лаунчер setSettings((s) => ({ ...s, autoUpdate: e.target.checked }))} /> } label="Автообновление данных (где поддерживается)" sx={controlSx} /> setSettings((s) => ({ ...s, startInTray: e.target.checked }))} /> } label="Запускать свернутым (в трей)" sx={controlSx} /> Кнопка-заглушка: можно подключить к вашим реальным ключам localStorage. ); }; export default Settings;