diff --git a/apps/rebreak-native/app/(app)/_layout.tsx b/apps/rebreak-native/app/(app)/_layout.tsx index 1bbd283..3c75e91 100644 --- a/apps/rebreak-native/app/(app)/_layout.tsx +++ b/apps/rebreak-native/app/(app)/_layout.tsx @@ -92,10 +92,12 @@ export default function AppLayout() { bypassNotifiedRef.current = true; const notified = await notifyBypassDetected(); if (!notified) { - // Fallback wenn Notifications nicht erlaubt sind. + // Fallback wenn Notifications nicht erlaubt sind. Reaktivierung setzt + // NUR den Filter/VPN wieder — kein a11y-Prompt (das passiert nur beim + // ersten Einrichten). rearmInFlightRef.current = true; router.replace('/blocker'); - await protection.activateFamilyControls().catch(() => ({ enabled: false })); + await protection.activate().catch(() => null); } } finally { rearmInFlightRef.current = false; @@ -107,7 +109,8 @@ export default function AppLayout() { rearmInFlightRef.current = true; try { router.replace('/blocker'); - await protection.activateFamilyControls().catch(() => ({ enabled: false })); + // Reaktivierung = nur Filter/VPN wieder setzen (a11y nur beim ersten Mal). + await protection.activate().catch(() => null); } finally { rearmInFlightRef.current = false; } diff --git a/apps/rebreak-native/app/(app)/blocker.tsx b/apps/rebreak-native/app/(app)/blocker.tsx index f5e9630..73394bc 100644 --- a/apps/rebreak-native/app/(app)/blocker.tsx +++ b/apps/rebreak-native/app/(app)/blocker.tsx @@ -134,12 +134,17 @@ export default function BlockerScreen() { try { const result = await activateFamilyControls(); console.log('[blocker] activateFamilyControls:', result); - if (!result.enabled) { + // `accessibility_pending` = die a11y-Berechtigung fehlt noch und wir haben + // grad die System-Settings geöffnet → das IST das Feedback. Kein Fehler- + // Modal (sonst Modal-Loop bei jedem Tap). a11y wird nur beim ersten + // Einrichten geholt; danach ist das hier ein 1-Tap-Arm ohne Dialog. + if (!result.enabled && result.error !== 'accessibility_pending') { Alert.alert( t('blocker.activate_app_lock_failed_title'), result.error ?? t('blocker.activate_app_lock_failed_msg'), ); } + return result; } catch (e: any) { console.error('[blocker] activateFamilyControls threw:', e); Alert.alert(t('blocker.activation_failed_title'), e?.message ?? t('common.unknown_error')); @@ -188,16 +193,15 @@ export default function BlockerScreen() { } if (bypassAlertShownRef.current) return; bypassAlertShownRef.current = true; + // Schutz-Filter ist aus, sollte aber an sein → Reaktivierung setzt NUR den + // VPN/Filter wieder (kein a11y-Prompt — das passiert nur beim ersten Mal). Alert.alert( - t('blocker.activate_app_lock_failed_title'), - t('blocker.layers_app_lock_warning'), - [{ - text: t('common.ok'), - onPress: () => { - void handleActivateFamilyControls(); - }, - }], - { cancelable: false }, + t('blocker.protection_off_title'), + t('blocker.protection_off_message'), + [ + { text: t('common.ok'), style: 'cancel' }, + { text: t('blocker.reactivate_btn'), onPress: () => { void handleActivateUrlFilter(); } }, + ], ); }, [state?.phase, t]); diff --git a/apps/rebreak-native/lib/protection.ts b/apps/rebreak-native/lib/protection.ts index 5cdd93f..54de2c7 100644 --- a/apps/rebreak-native/lib/protection.ts +++ b/apps/rebreak-native/lib/protection.ts @@ -243,16 +243,17 @@ export const protection = { } as DeviceLayers) : rawLayers; - const allLayersOn = isAllLayersOn(layers); - const iosLockActive = - layers.appDeletionLock ?? layers.familyControls ?? false; + // "Aktiv" = der eigentliche Schutz (URL-/DNS-Filter) läuft. Der App-Lock + // (familyControls/tamperLock) ist optionales Hardening — er macht den Schutz + // schwerer abschaltbar, ist aber keine Voraussetzung für "geschützt". Er wird + // nur beim ersten Aktivieren eingerichtet; eine Reaktivierung setzt nur den + // Filter wieder. → "recoveringFromBypass" heißt deshalb: Filter ist aus, + // obwohl das Backend sagt er sollte an sein (= jemand hat den VPN extern aus). const phase: ProtectionPhase = cooldown.active ? "cooldownActive" - : backend?.protectionShouldBeActive === true && - layers.urlFilter === true && - iosLockActive !== true + : backend?.protectionShouldBeActive === true && layers.urlFilter !== true ? "recoveringFromBypass" - : allLayersOn + : layers.urlFilter === true ? "active" : "inactive"; diff --git a/apps/rebreak-native/locales/de.json b/apps/rebreak-native/locales/de.json index b4b3663..44cceb5 100644 --- a/apps/rebreak-native/locales/de.json +++ b/apps/rebreak-native/locales/de.json @@ -241,6 +241,9 @@ "activate_url_failed_title": "URL-Filter konnte nicht aktiviert werden", "activate_url_failed_msg": "Unbekannter Fehler.\nDu kannst es nochmal versuchen oder System-Einstellungen prüfen.", "activate_settings_btn": "Einstellungen", + "protection_off_title": "Schutz ist aus", + "protection_off_message": "Der Filter läuft gerade nicht, sollte aber an sein. Willst du ihn wieder einschalten?", + "reactivate_btn": "Wieder einschalten", "activate_app_lock_failed_title": "App-Lock konnte nicht aktiviert werden", "activate_app_lock_failed_msg": "Die nötige Berechtigung wurde verweigert. Du kannst es nochmal versuchen.", "sync_list_failed_title": "Filter-Liste konnte nicht geladen werden", diff --git a/apps/rebreak-native/locales/en.json b/apps/rebreak-native/locales/en.json index e82ed5a..745502a 100644 --- a/apps/rebreak-native/locales/en.json +++ b/apps/rebreak-native/locales/en.json @@ -241,6 +241,9 @@ "activate_url_failed_title": "Could not activate URL filter", "activate_url_failed_msg": "Unknown error.\nYou can try again or check System Settings.", "activate_settings_btn": "Settings", + "protection_off_title": "Protection is off", + "protection_off_message": "The filter isn't running but should be. Want to turn it back on?", + "reactivate_btn": "Turn back on", "activate_app_lock_failed_title": "Could not activate App Lock", "activate_app_lock_failed_msg": "The required permission was denied. You can try again.", "sync_list_failed_title": "Filter list could not be loaded",