looood / src /hooks /use-settings.ts
looda3131's picture
Clean push without any binary history
cc276cc
"use client";
import { useState, useCallback, useEffect, useRef } from 'react';
import { useLocalStorage } from '@/hooks/use-local-storage';
import { useToast } from '@/hooks/use-toast';
import { usePresence } from '@/hooks/use-presence';
import type { User, DataMode, FontStyle, Language, PrivacySettings } from '@/lib/types';
import { formatDistanceToNow as formatDistanceToNowFn } from 'date-fns';
import { ar as arLocale, enUS as enLocale } from 'date-fns/locale';
import enTranslations from '@/lib/locales/en.json';
import arTranslations from '@/lib/locales/ar.json';
import { useFirebase } from '@/contexts/firebase-context';
import { getMessaging, getToken } from "firebase/messaging";
import { doc, updateDoc } from 'firebase/firestore';
const SYNC_INTERVAL = 30;
const defaultPrivacySettings: PrivacySettings = {
showOnline: true,
showTyping: true,
readReceipts: true,
whoCanAdd: 'everyone',
whoCanViewProfile: 'everyone',
};
const translations = { en: enTranslations, ar: arTranslations };
const locales = { ar: arLocale, en: enLocale };
interface UseSettingsProps {
currentUser: User | null;
}
export const useSettingsCore = ({ currentUser }: UseSettingsProps) => {
const { app, db, rtdb } = useFirebase();
const { toast } = useToast();
const soundsRef = useRef<Record<string, HTMLAudioElement> | null>(null);
const [fontStyle, setFontStyle] = useLocalStorage<FontStyle>('fontStyle', 'default');
const [language, setLanguage] = useLocalStorage<Language>('appLanguage', 'en');
const [isLanguageLoading, setIsLanguageLoading] = useState(true);
const [isSoundEnabled, setIsSoundEnabled] = useLocalStorage<boolean>('soundEnabled', false);
const [dataMode, setDataMode] = useLocalStorage<DataMode>('dataMode', 'normal');
const [privacySettings, setPrivacySettings] = useLocalStorage<PrivacySettings>('privacySettings', defaultPrivacySettings);
const [accentColor, setAccentColor] = useLocalStorage<string>('accentColor', '221.2 83.2% 53.3%');
const [syncCountdown, setSyncCountdown] = useState(SYNC_INTERVAL);
const [notificationPermission, setNotificationPermission] = useState<NotificationPermission>('default');
const [fcmToken, setFcmToken] = useState<string | null>(null);
usePresence({ currentUser, rtdb, privacySettings });
const addToast = useCallback((message: string, options?: { variant?: 'default' | 'destructive' }) => {
toast({ title: message, variant: options?.variant });
}, [toast]);
const playSound = useCallback((sound: 'send' | 'receive' | 'touch' | 'typing') => {
if (!isSoundEnabled || !soundsRef.current) return;
const s = soundsRef.current[sound];
if (s) {
s.currentTime = 0;
s.play().catch(e => console.error("Sound play failed:", e));
}
}, [isSoundEnabled]);
const stopSound = useCallback((sound: 'typing') => {
if (!soundsRef.current) return;
const s = soundsRef.current[sound];
if (s) {
s.pause();
s.currentTime = 0;
}
}, []);
const toggleSound = () => setIsSoundEnabled(prev => !prev);
useEffect(() => {
if (typeof window !== 'undefined' && 'Notification' in window) {
setNotificationPermission(Notification.permission);
}
soundsRef.current = {
send: new Audio('/sounds/send.mp3'),
receive: new Audio('/sounds/receive.mp3'),
touch: new Audio('/sounds/touch.mp3'),
typing: new Audio('/sounds/typing.mp3'),
};
Object.values(soundsRef.current).forEach(sound => {
if(sound) {
sound.preload = 'auto';
sound.volume = 0.7;
}
});
if(soundsRef.current.typing) {
soundsRef.current.typing.loop = true;
soundsRef.current.typing.volume = 0.3;
}
const storedLang = localStorage.getItem('appLanguage');
const lang = storedLang && (storedLang === 'en' || storedLang === 'ar') ? storedLang : (typeof navigator !== 'undefined' && navigator.language.split('-')[0] === 'ar' ? 'ar' : 'en');
setLanguage(lang);
setIsLanguageLoading(false);
}, [setLanguage]);
const requestNotificationPermission = useCallback(async () => {
if (typeof window === 'undefined' || !('Notification' in window) || !currentUser) {
addToast("This browser does not support desktop notification or user is not logged in.", { variant: 'destructive' });
return;
}
try {
const permission = await Notification.requestPermission();
setNotificationPermission(permission);
if (permission === 'granted') {
const messaging = getMessaging(app);
// Get registration token. Initially this makes a network call, once retrieved
// subsequent calls to getToken will return from cache.
const currentToken = await getToken(messaging, { vapidKey: "BD6ckIt46u2iNjS-1MZ09WoWg2HP5WkZelZr8EK4cTtoU4i_JbEAJq4EsprfpaMs8JBRtCZiph3lFGSW7TsmjVM" });
if (currentToken) {
setFcmToken(currentToken);
await updateDoc(doc(db, 'users', currentUser.uid), { fcmToken: currentToken });
addToast("Notifications enabled and token registered!", { variant: 'default' });
} else {
addToast("Could not get notification token.", { variant: 'destructive' });
}
} else if (permission === 'denied') {
addToast("Notifications blocked. You can enable them from browser settings.", { variant: 'destructive' });
}
} catch (error) {
console.error("Error during notification permission request:", error);
addToast("Failed to request notification permission.", { variant: 'destructive' });
}
}, [addToast, app, currentUser, db]);
const t = useCallback((key: string, options?: Record<string, string | number>) => {
const translationsTyped = translations as Record<Language, Record<string, string>>;
let text = translationsTyped[language]?.[key] || translations['en']?.[key] || key;
if (options) {
Object.keys(options).forEach(k => {
text = text.replace(new RegExp(`{{${k}}}`, 'g'), String(options[k]));
});
}
return text;
}, [language]);
const formatDistanceToNow = useCallback((date: number | Date, options?: { addSuffix?: boolean }) => {
return formatDistanceToNowFn(date, { ...options, locale: locales[language] });
}, [language]);
const updatePrivacySettings = useCallback(async (newSettings: Partial<PrivacySettings>) => {
setPrivacySettings(prev => ({ ...prev, ...newSettings }));
}, [setPrivacySettings]);
useEffect(() => {
if (!isLanguageLoading) {
document.documentElement.lang = language;
document.documentElement.dir = language === 'ar' ? 'rtl' : 'ltr';
document.documentElement.style.setProperty('--primary', accentColor);
document.documentElement.style.setProperty('--ring', accentColor);
// Safer body class manipulation
document.body.classList.remove('font-body', 'font-casual-body');
document.body.classList.add(fontStyle === 'casual' ? 'font-casual-body' : 'font-body');
}
}, [language, fontStyle, isLanguageLoading, accentColor]);
useEffect(() => {
if (dataMode !== 'ultra') return;
const timer = setInterval(() => {
setSyncCountdown(prev => (prev <= 1 ? SYNC_INTERVAL : prev - 1));
}, 1000);
return () => clearInterval(timer);
}, [dataMode]);
return {
dataMode, setDataMode,
isSoundEnabled, toggleSound,
privacySettings, updatePrivacySettings,
fontStyle, setFontStyle,
language, setLanguage,
accentColor, setAccentColor,
isLanguageLoading, syncCountdown,
notificationPermission, requestNotificationPermission,
fcmToken, setFcmToken,
playSound, stopSound,
t, formatDistanceToNow, addToast
};
}