import React, { useCallback, useEffect, useState } from 'react'; import FocusTrap from 'focus-trap-react'; import { Dialog, Overlay, OverlayCenter, OverlayBackdrop, Header, config, Box, Text, IconButton, Icon, Icons, Button, } from 'folds'; import { useTranslation } from 'react-i18next'; import { isNativePlatform } from '../../utils/capacitor'; import { canUseFullScreenIntent, openFullScreenIntentSettings, } from '../../plugins/fullScreenIntent'; import { usePushEnabled } from '../../hooks/usePushNotifications'; import { stopPropagation } from '../../utils/keyboard'; // Mirrors PushPermissionPrompt's cooldown. 7 days is long enough that "not now" // isn't nagging, short enough that a user who changes their mind doesn't have // to dig through Settings to find the toggle. const DISMISS_KEY = 'vojo_fsi_prompt_dismissed_at'; const DISMISS_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000; const INITIAL_DELAY_MS = 1500; const wasRecentlyDismissed = (): boolean => { const raw = localStorage.getItem(DISMISS_KEY); if (!raw) return false; const ts = Number(raw); if (!Number.isFinite(ts)) return false; return Date.now() - ts < DISMISS_COOLDOWN_MS; }; const markDismissed = (): void => { localStorage.setItem(DISMISS_KEY, String(Date.now())); }; export function FullScreenIntentPrompt() { const { t } = useTranslation(); const pushEnabled = usePushEnabled(); const [visible, setVisible] = useState(false); useEffect(() => { // Only relevant when push is on — FSI wakeup only matters if we're actually // going to show CallStyle notifications. Showing this before push is enabled // would be out-of-context: user doesn't yet know what "incoming call screen" // means in our UX. The PushPermissionPrompt comes first. if (!isNativePlatform()) return undefined; if (!pushEnabled) { setVisible(false); return undefined; } if (wasRecentlyDismissed()) return undefined; let cancelled = false; canUseFullScreenIntent().then((allowed) => { if (cancelled) return; if (allowed) return; // Delay matches PushPermissionPrompt so the two never try to stack on // top of each other at exact startup — PushPermissionPrompt goes first // since its effect depends on `status === 'prompt'`; by the time that's // resolved to 'granted' (and pushEnabled flips true), the PushPrompt is // already gone and this one has room. setTimeout(() => { if (!cancelled) setVisible(true); }, INITIAL_DELAY_MS); }); return () => { cancelled = true; }; }, [pushEnabled]); const handleLater = useCallback(() => { markDismissed(); setVisible(false); }, []); const handleEnable = useCallback(() => { // Opens Settings; the user returns to the app via the back button. We don't // re-check canUseFullScreenIntent() on resume because the prompt will // simply not re-show (7-day cooldown would apply if we markDismissed — // we deliberately DO NOT mark dismissed here so if the user declines in // Settings or forgets, the next startup still reminds them). openFullScreenIntentSettings().catch(() => { /* plugin missing — nothing to do */ }); setVisible(false); }, []); if (!visible) return null; return ( }>
{t('Settings.fsi_prompt_title')}
{t('Settings.fsi_prompt_body')}
); }