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;