diff --git a/packages/app/src/components/ui/alert.tsx b/packages/app/src/components/ui/alert.tsx index a672c1c89..ef0f3e248 100644 --- a/packages/app/src/components/ui/alert.tsx +++ b/packages/app/src/components/ui/alert.tsx @@ -31,10 +31,11 @@ export function Alert({ }: AlertProps) { const { theme } = useUnistyles(); const accentColor = resolveAccentColor(variant, theme); + const borderColor = variant === "success" ? theme.colors.border : accentColor; const containerStyle = useMemo( - () => [styles.container, accentColor ? { borderColor: accentColor } : null], - [accentColor], + () => [styles.container, borderColor ? { borderColor } : null], + [borderColor], ); const titleStyle = useMemo( @@ -73,7 +74,7 @@ function resolveAccentColor( theme: ReturnType["theme"], ): string | null { if (variant === "info") return theme.colors.palette.blue[300]; - if (variant === "success") return theme.colors.palette.green[400]; + if (variant === "success") return theme.colors.statusSuccess; if (variant === "warning") return theme.colors.palette.amber[500]; if (variant === "error") return theme.colors.destructive; return null; diff --git a/packages/app/src/desktop/components/desktop-notifications-section.tsx b/packages/app/src/desktop/components/desktop-notifications-section.tsx new file mode 100644 index 000000000..a45d64744 --- /dev/null +++ b/packages/app/src/desktop/components/desktop-notifications-section.tsx @@ -0,0 +1,150 @@ +import { useCallback, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { Text, View } from "react-native"; +import { withUnistyles } from "react-native-unistyles"; +import { RotateCw } from "lucide-react-native"; +import { Alert } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; +import { DesktopPermissionRow } from "@/desktop/components/desktop-permission-row"; +import { useDesktopPermissions } from "@/desktop/permissions/use-desktop-permissions"; +import { useDesktopSettings } from "@/desktop/settings/desktop-settings"; +import { SettingsSection } from "@/screens/settings/settings-section"; +import { settingsStyles } from "@/styles/settings"; + +const ThemedRotateCw = withUnistyles(RotateCw, (theme) => ({ + size: theme.iconSize.md, + color: theme.colors.foregroundMuted, +})); + +export function DesktopNotificationsSection() { + const { t } = useTranslation(); + const { settings, isSaving, updateSettings } = useDesktopSettings(); + const { + isDesktopApp, + snapshot, + isRefreshing, + requestingPermission, + testNotificationState, + refreshPermissions, + requestPermission, + sendTestNotification, + } = useDesktopPermissions(); + + const handleRefreshPress = useCallback(() => { + void refreshPermissions(); + }, [refreshPermissions]); + + const handleRequestNotifications = useCallback(() => { + void requestPermission("notifications"); + }, [requestPermission]); + + const handlePlaySoundChange = useCallback( + (playSound: boolean) => { + void updateSettings({ notifications: { playSound } }).catch(() => { + // useDesktopSettings owns the user-visible IPC error. + }); + }, + [updateSettings], + ); + + const handleSendTestNotification = useCallback(() => { + void sendTestNotification(); + }, [sendTestNotification]); + + const isPermissionBusy = isRefreshing || requestingPermission !== null; + const isSendingTestNotification = testNotificationState.status === "sending"; + const refreshIcon = useMemo(() => , []); + const refreshButton = useMemo( + () => ( + + ), + [handleRefreshPress, isPermissionBusy, isRefreshing, refreshIcon, t], + ); + const permissionLabels = useMemo( + () => ({ + granted: t("settings.permissions.actions.granted"), + request: t("settings.permissions.actions.request"), + requesting: t("settings.permissions.actions.requesting"), + }), + [t], + ); + + if (!isDesktopApp) { + return null; + } + + const notificationsGranted = snapshot?.notifications.state === "granted"; + + return ( + + + + + + {t("settings.notifications.playSound")} + {t("settings.notifications.playSoundHint")} + + + + + + {t("settings.notifications.test")} + + {notificationsGranted + ? t("settings.notifications.testHint") + : t("settings.notifications.permissionRequired")} + + + + + + {testNotificationState.status === "success" ? ( + + ) : null} + {testNotificationState.status === "error" ? ( + + ) : null} + + ); +} diff --git a/packages/app/src/desktop/components/desktop-permission-row.tsx b/packages/app/src/desktop/components/desktop-permission-row.tsx index f97050b7d..950d9c6c1 100644 --- a/packages/app/src/desktop/components/desktop-permission-row.tsx +++ b/packages/app/src/desktop/components/desktop-permission-row.tsx @@ -1,6 +1,6 @@ import { useMemo } from "react"; import { View, Text } from "react-native"; -import { StyleSheet, useUnistyles } from "react-native-unistyles"; +import { StyleSheet, withUnistyles } from "react-native-unistyles"; import { Check } from "lucide-react-native"; import { Button } from "@/components/ui/button"; import { settingsStyles } from "@/styles/settings"; @@ -14,16 +14,16 @@ export interface DesktopPermissionRowProps { granted: string; request: string; requesting: string; - busyExtraAction: (label: string) => string; }; showBorder?: boolean; onRequest: () => void; - extraActionLabel?: string; - isExtraActionBusy?: boolean; - isExtraActionDisabled?: boolean; - onExtraAction?: () => void; } +const ThemedCheck = withUnistyles(Check, (theme) => ({ + size: theme.iconSize.sm, + color: theme.colors.foregroundMuted, +})); + export function DesktopPermissionRow({ title, status, @@ -31,12 +31,7 @@ export function DesktopPermissionRow({ labels, showBorder, onRequest, - extraActionLabel, - isExtraActionBusy = false, - isExtraActionDisabled = false, - onExtraAction, }: DesktopPermissionRowProps) { - const { theme } = useUnistyles(); const state = status?.state ?? "unknown"; const isGranted = state === "granted"; const shouldShowDetail = @@ -58,21 +53,9 @@ export function DesktopPermissionRow({ {isGranted ? ( - - - - {labels.granted} - - {extraActionLabel && onExtraAction ? ( - - ) : null} + + + {labels.granted} ) : (