diff --git a/App.tsx b/App.tsx index 43586f7..6a713c7 100644 --- a/App.tsx +++ b/App.tsx @@ -4,6 +4,7 @@ import * as Notifications from 'expo-notifications'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { AppNavigator } from './src/navigation/AppNavigator'; import { requestNotificationPermissions } from './src/services/gps'; +import { promptBatteryOptimizationIfNeeded } from './src/services/batteryOptimization'; // Show notifications even when the app is in the foreground (iOS suppresses by default) Notifications.setNotificationHandler({ @@ -18,6 +19,7 @@ Notifications.setNotificationHandler({ export default function App() { useEffect(() => { requestNotificationPermissions(); + promptBatteryOptimizationIfNeeded(); }, []); return ( diff --git a/CLAUDE.md b/CLAUDE.md index 4c75f68..da873a1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -117,9 +117,9 @@ Items below are what remains before v1 is shippable. - [x] **Permission request** — `requestNotificationPermissions()` called in `App.tsx` on mount; foreground notification handler set at module level so iOS shows banners while app is active - [x] **Milestone tracker** — module-level `runningDistanceMeters` / `lastNotifiedKm` / `prevPoint` in `gps.ts`, reset on each `startGpsRecording()`; each incoming location adds haversine distance and fires a notification when a new km is crossed, gated on the `kmNotifications` AsyncStorage setting -### 4 — Android battery optimization prompt (1–2 hours) +### 4 — Android battery optimization prompt ✅ -- [ ] On first launch, detect if the app is affected by battery optimization (`expo-intent-launcher`) and show a one-time prompt directing the user to whitelist bincio-rec; persist dismissal in AsyncStorage so it only shows once +- [x] `src/services/batteryOptimization.ts` — Android-only, one-time prompt (dismissed flag in AsyncStorage); uses `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` intent to open the system dialog for bincio-rec directly; falls back to `IGNORE_BATTERY_OPTIMIZATION_SETTINGS` (general page) on OEMs that block the direct intent; called from `App.tsx` on mount alongside notification permission request ### 5 — Map (optional upgrade) diff --git a/src/services/batteryOptimization.ts b/src/services/batteryOptimization.ts new file mode 100644 index 0000000..ad1dc9a --- /dev/null +++ b/src/services/batteryOptimization.ts @@ -0,0 +1,35 @@ +import { Platform, Alert } from 'react-native'; +import * as IntentLauncher from 'expo-intent-launcher'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const PROMPT_SHOWN_KEY = 'batteryOptPromptShown'; + +export async function promptBatteryOptimizationIfNeeded(): Promise { + if (Platform.OS !== 'android') return; + + const shown = await AsyncStorage.getItem(PROMPT_SHOWN_KEY); + if (shown) return; + + await AsyncStorage.setItem(PROMPT_SHOWN_KEY, 'true'); + + Alert.alert( + 'Allow background GPS', + 'To record accurately while the screen is off, bincio-rec needs to be excluded from battery optimization.\n\nTap "Open settings", find bincio-rec, and select "Don\'t optimize".', + [ + { + text: 'Open settings', + onPress: () => + IntentLauncher.startActivityAsync( + IntentLauncher.ActivityAction.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + { data: 'package:com.bincio.rec' }, + ).catch(() => + // Fallback: some OEMs don't support the direct intent — open the general page + IntentLauncher.startActivityAsync( + IntentLauncher.ActivityAction.IGNORE_BATTERY_OPTIMIZATION_SETTINGS, + ), + ), + }, + { text: 'Later', style: 'cancel' }, + ], + ); +}