179 lines
5.0 KiB
TypeScript
179 lines
5.0 KiB
TypeScript
import { useState, useCallback, useEffect } from 'react'
|
|
import { AppState, Linking, View, Text, StyleSheet, Pressable, Switch } from 'react-native'
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
import { useRouter, useFocusEffect } from 'expo-router'
|
|
import { ChevronLeft } from 'lucide-react-native'
|
|
import { colors, spacing, typography } from '../src/theme/mobile-theme'
|
|
import {
|
|
loadPushNotificationsEnabled,
|
|
savePushNotificationsEnabled
|
|
} from '../src/storage/preferences'
|
|
import {
|
|
ensureNotificationPermissions,
|
|
getNotificationPermissionState,
|
|
type NotificationPermissionState
|
|
} from '../src/notifications/mobile-notifications'
|
|
|
|
const DEFAULT_PERMISSION_STATE: NotificationPermissionState = {
|
|
granted: false,
|
|
status: 'undetermined',
|
|
canAskAgain: true,
|
|
authorizationReflectsUserChoice: false
|
|
}
|
|
|
|
export default function NotificationsScreen() {
|
|
const router = useRouter()
|
|
const insets = useSafeAreaInsets()
|
|
const [pushEnabled, setPushEnabled] = useState(false)
|
|
const [permissionState, setPermissionState] = useState(DEFAULT_PERMISSION_STATE)
|
|
|
|
const refreshSettings = useCallback(async () => {
|
|
const [enabled, permission] = await Promise.all([
|
|
loadPushNotificationsEnabled(),
|
|
getNotificationPermissionState()
|
|
])
|
|
setPushEnabled(enabled)
|
|
setPermissionState(permission)
|
|
}, [])
|
|
|
|
useFocusEffect(
|
|
useCallback(() => {
|
|
void refreshSettings()
|
|
}, [refreshSettings])
|
|
)
|
|
|
|
useEffect(() => {
|
|
const subscription = AppState.addEventListener('change', (state) => {
|
|
if (state === 'active') {
|
|
void refreshSettings()
|
|
}
|
|
})
|
|
return () => subscription.remove()
|
|
}, [refreshSettings])
|
|
|
|
const togglePush = async (value: boolean) => {
|
|
if (value) {
|
|
const granted = await ensureNotificationPermissions()
|
|
const permission = await getNotificationPermissionState()
|
|
setPermissionState(permission)
|
|
if (!granted) {
|
|
setPushEnabled(false)
|
|
await savePushNotificationsEnabled(false)
|
|
return
|
|
}
|
|
}
|
|
setPushEnabled(value)
|
|
await savePushNotificationsEnabled(value)
|
|
}
|
|
|
|
const switchEnabled = pushEnabled && permissionState.granted
|
|
const notificationsBlocked = permissionState.status === 'denied'
|
|
const hint = notificationsBlocked
|
|
? 'Notifications are disabled in system settings.'
|
|
: 'Get notified on this device when an agent needs your input or finishes a task.'
|
|
|
|
return (
|
|
<View style={[styles.container, { paddingTop: insets.top + spacing.sm }]}>
|
|
<View style={styles.topRow}>
|
|
<Pressable style={styles.backButton} onPress={() => router.back()}>
|
|
<ChevronLeft size={22} color={colors.textSecondary} />
|
|
</Pressable>
|
|
<Text style={styles.heading}>Notifications</Text>
|
|
</View>
|
|
|
|
<View style={styles.section}>
|
|
<View style={styles.row}>
|
|
<Text style={styles.rowLabel}>Agent notifications</Text>
|
|
<Switch
|
|
value={switchEnabled}
|
|
disabled={notificationsBlocked}
|
|
onValueChange={(v) => void togglePush(v)}
|
|
trackColor={{ false: colors.bgRaised, true: colors.textSecondary }}
|
|
thumbColor={colors.textPrimary}
|
|
/>
|
|
</View>
|
|
<Text style={styles.hint}>{hint}</Text>
|
|
{notificationsBlocked && (
|
|
<Pressable
|
|
style={({ pressed }) => [
|
|
styles.settingsButton,
|
|
pressed && styles.settingsButtonPressed
|
|
]}
|
|
onPress={() => void Linking.openSettings()}
|
|
>
|
|
<Text style={styles.settingsButtonText}>Open Settings</Text>
|
|
</Pressable>
|
|
)}
|
|
</View>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: colors.bgBase,
|
|
padding: spacing.lg
|
|
},
|
|
topRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginBottom: spacing.xl
|
|
},
|
|
backButton: {
|
|
width: 36,
|
|
height: 36,
|
|
borderRadius: 18,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
marginRight: spacing.sm
|
|
},
|
|
heading: {
|
|
fontSize: 20,
|
|
fontWeight: '700',
|
|
color: colors.textPrimary
|
|
},
|
|
section: {
|
|
backgroundColor: colors.bgPanel,
|
|
borderRadius: 12,
|
|
overflow: 'hidden'
|
|
},
|
|
row: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: spacing.sm + 2,
|
|
paddingVertical: spacing.md,
|
|
paddingHorizontal: spacing.md + 2
|
|
},
|
|
rowLabel: {
|
|
flex: 1,
|
|
fontSize: typography.bodySize,
|
|
fontWeight: '500',
|
|
color: colors.textPrimary
|
|
},
|
|
hint: {
|
|
fontSize: typography.metaSize,
|
|
color: colors.textMuted,
|
|
lineHeight: 18,
|
|
paddingHorizontal: spacing.md + 2,
|
|
paddingBottom: spacing.md
|
|
},
|
|
settingsButton: {
|
|
alignSelf: 'flex-start',
|
|
marginHorizontal: spacing.md + 2,
|
|
marginBottom: spacing.md,
|
|
paddingVertical: spacing.xs,
|
|
paddingHorizontal: spacing.sm,
|
|
borderRadius: 8,
|
|
backgroundColor: colors.bgRaised
|
|
},
|
|
settingsButtonPressed: {
|
|
opacity: 0.6
|
|
},
|
|
settingsButtonText: {
|
|
color: colors.textPrimary,
|
|
fontSize: typography.metaSize,
|
|
fontWeight: '600'
|
|
}
|
|
})
|