import { createContext, type Dispatch, useEffect, useState, type SetStateAction } from 'react'; import { type Page } from '../basic/NavBar'; import { type OpenDialogs } from '../basic/MainDialogs'; import { Settings, type Version, type Origin, type Favorites } from '../models'; import { getClientVersion, getHost } from '../utils'; import defaultFederation from '../../static/federation.json'; import { createTheme, type Theme } from '@mui/material/styles'; import i18n from '../i18n/Web'; const getWindowSize = function (fontSize: number): { width: number; height: number } { // returns window size in EM units return { width: window.innerWidth / fontSize, height: window.innerHeight / fontSize, }; }; export interface SlideDirection { in: 'left' | 'right' | undefined; out: 'left' | 'right' | undefined; } export type TorStatus = 'NOTINIT' | 'STARTING' | '"Done"' | 'DONE'; export const isNativeRoboSats = !(window.NativeRobosats === undefined); const pageFromPath = window.location.pathname.split('/')[1]; const isPagePathEmpty = pageFromPath === ''; const entryPage: Page = !isNativeRoboSats ? ((isPagePathEmpty ? 'robot' : pageFromPath) as Page) : 'robot'; export const closeAll: OpenDialogs = { more: false, learn: false, community: false, info: false, coordinator: '', warning: false, exchange: false, client: false, update: false, profile: false, }; const makeTheme = function (settings: Settings): Theme { const theme: Theme = createTheme({ palette: { mode: settings.mode, background: { default: settings.mode === 'dark' ? '#070707' : '#fff', }, }, typography: { fontSize: settings.fontSize }, }); return theme; }; const getHostUrl = (network = 'mainnet'): string => { let host = ''; let protocol = ''; if (window.NativeRobosats === undefined) { host = getHost(); protocol = location.protocol; } else { host = defaultFederation.exp[network].Onion; protocol = 'http:'; } const hostUrl = `${protocol}//${host}`; return hostUrl; }; const getOrigin = (network = 'mainnet'): Origin => { const host = getHostUrl(network); let origin: Origin = 'onion'; if (window.NativeRobosats !== undefined || host.includes('.onion')) { origin = 'onion'; } else if (host.includes('i2p')) { origin = 'i2p'; } else { origin = 'clearnet'; } return origin; }; export interface WindowSize { width: number; height: number; } export interface UseAppStoreType { theme?: Theme; torStatus: TorStatus; settings: Settings; setSettings: Dispatch>; page: Page; setPage: Dispatch>; slideDirection: SlideDirection; setSlideDirection: Dispatch>; navbarHeight: number; open: OpenDialogs; setOpen: Dispatch>; windowSize?: WindowSize; acknowledgedWarning: boolean; setAcknowledgedWarning: Dispatch>; clientVersion: { semver: Version; short: string; long: string; }; origin: Origin; hostUrl: string; fav: Favorites; setFav: Dispatch>; } export const initialAppContext: UseAppStoreType = { theme: undefined, torStatus: 'NOTINIT', settings: new Settings(), setSettings: () => {}, page: entryPage, setPage: () => {}, slideDirection: { in: undefined, out: undefined, }, setSlideDirection: () => {}, navbarHeight: 2.5, open: closeAll, setOpen: () => {}, windowSize: undefined, origin: getOrigin(), hostUrl: getHostUrl(), clientVersion: getClientVersion(), acknowledgedWarning: false, fav: { type: null, currency: 0, mode: 'fiat' }, setFav: () => {}, }; export const AppContext = createContext(initialAppContext); export const useAppStore = (): UseAppStoreType => { // State provided right at the top level of the app. A chaotic bucket of everything. // Contains app-wide state and functions. Triggers re-renders on the full tree often. // All app data structured const navbarHeight = initialAppContext.navbarHeight; const clientVersion = initialAppContext.clientVersion; const hostUrl = initialAppContext.hostUrl; const origin = initialAppContext.origin; const [settings, setSettings] = useState(initialAppContext.settings); const [theme, setTheme] = useState(() => { return makeTheme(settings); }); const [torStatus, setTorStatus] = useState(initialAppContext.torStatus); const [page, setPage] = useState(initialAppContext.page); const [slideDirection, setSlideDirection] = useState( initialAppContext.slideDirection, ); const [open, setOpen] = useState(initialAppContext.open); const [windowSize, setWindowSize] = useState(() => getWindowSize(theme.typography.fontSize), ); const [fav, setFav] = useState(initialAppContext.fav); const [acknowledgedWarning, setAcknowledgedWarning] = useState( initialAppContext.acknowledgedWarning, ); useEffect(() => { setTheme(makeTheme(settings)); }, [settings.fontSize, settings.mode, settings.lightQRs]); useEffect(() => { void i18n.changeLanguage(settings.language); }, []); useEffect(() => { window.addEventListener('torStatus', (event) => { // Trick to improve UX on Android webview: delay the "Connected to TOR" status by 5 secs to avoid long waits on the first request. setTimeout( () => { setTorStatus(event?.detail); }, event?.detail === '"Done"' ? 5000 : 0, ); }); }, []); useEffect(() => { if (window !== undefined) { window.addEventListener('resize', onResize); } return () => { if (window !== undefined) { window.removeEventListener('resize', onResize); } }; }, []); useEffect(() => { setWindowSize(getWindowSize(theme.typography.fontSize)); }, [theme.typography.fontSize]); const onResize = function (): void { setWindowSize(getWindowSize(theme.typography.fontSize)); }; return { theme, torStatus, settings, setSettings, page, setPage, slideDirection, setSlideDirection, navbarHeight, open, setOpen, windowSize, clientVersion, acknowledgedWarning, setAcknowledgedWarning, hostUrl, origin, fav, setFav, }; };