feat(blocker): post-cooldown disable shows a11y-settings notice (DiGA — user must be able to fully exit)
After the cooldown elapses and forceDisable() runs (VPN off + tamper-lock disarmed), Android's a11y service can't deactivate itself — surface a friendly Alert routing the user to Settings → Accessibility so they can finish removing protection. Wired into both the fetchState cooldown active→inactive transition and the AppState 'active' check; idempotent via ref. (Native side — disable() also disarms the tamper-lock, RebreakAccessibilityService goes fully passive when neither tamper-locked nor enabled, syncBlocklist no longer re-starts the VpnService when disabled — lives in the gitignored module/android dir, not committed here.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
aac709ec41
commit
33f411ab55
@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { AppState, type AppStateStatus } from 'react-native';
|
||||
import { Alert, AppState, type AppStateStatus } from 'react-native';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
protection,
|
||||
type ProtectionState,
|
||||
@ -39,6 +40,7 @@ type UseProtectionStateReturn = {
|
||||
* - Layer-Change-Listener vom Native-Modul (Bypass-Detection)
|
||||
*/
|
||||
export function useProtectionState(): UseProtectionStateReturn {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState<ProtectionState | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@ -47,6 +49,30 @@ export function useProtectionState(): UseProtectionStateReturn {
|
||||
const pollTimer = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const tickTimer = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const prevCooldownActiveRef = useRef<boolean | null>(null);
|
||||
// Verhindert Mehrfach-Alert wenn fetchState + AppState-Listener beide kurz
|
||||
// hintereinander applyCooldownDisableIfElapsed → true sehen.
|
||||
const cooldownDisabledNoticeShownRef = useRef(false);
|
||||
|
||||
// Freundlicher Hinweis nachdem der Cooldown abgelaufen ist und der Schutz
|
||||
// (inkl. Tamper-Lock) abgeschaltet wurde. Android: a11y-Service kann sich
|
||||
// nicht selbst deaktivieren → User zu den Einstellungen leiten.
|
||||
const showCooldownElapsedNotice = useCallback(() => {
|
||||
if (cooldownDisabledNoticeShownRef.current) return;
|
||||
cooldownDisabledNoticeShownRef.current = true;
|
||||
Alert.alert(
|
||||
t('blocker.cooldown_elapsed_title'),
|
||||
t('blocker.cooldown_elapsed_message'),
|
||||
[
|
||||
{ text: t('common.ok'), style: 'cancel' },
|
||||
{
|
||||
text: t('blocker.cooldown_elapsed_open_settings'),
|
||||
onPress: () => {
|
||||
protection.openSystemSettings('accessibility').catch(() => {});
|
||||
},
|
||||
},
|
||||
],
|
||||
);
|
||||
}, [t]);
|
||||
|
||||
const fetchState = useCallback(async (showLoading = false) => {
|
||||
if (showLoading) setLoading(true);
|
||||
@ -59,6 +85,7 @@ export function useProtectionState(): UseProtectionStateReturn {
|
||||
if (prevActive === true && !next.cooldown.active) {
|
||||
const didDisable = await protection.applyCooldownDisableIfElapsed();
|
||||
if (didDisable) {
|
||||
showCooldownElapsedNotice();
|
||||
// Nativer State hat sich geändert → ein weiterer Fetch für konsistenten State.
|
||||
const afterDisable = await protection.getCombinedState();
|
||||
setState(afterDisable);
|
||||
@ -76,7 +103,7 @@ export function useProtectionState(): UseProtectionStateReturn {
|
||||
} finally {
|
||||
if (showLoading) setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
}, [showCooldownElapsedNotice]);
|
||||
|
||||
// Initial fetch
|
||||
useEffect(() => {
|
||||
@ -117,11 +144,12 @@ export function useProtectionState(): UseProtectionStateReturn {
|
||||
useEffect(() => {
|
||||
const sub = AppState.addEventListener('change', async (status: AppStateStatus) => {
|
||||
if (status !== 'active') return;
|
||||
await protection.applyCooldownDisableIfElapsed();
|
||||
const didDisable = await protection.applyCooldownDisableIfElapsed();
|
||||
if (didDisable) showCooldownElapsedNotice();
|
||||
await fetchState(false);
|
||||
});
|
||||
return () => sub.remove();
|
||||
}, [fetchState]);
|
||||
}, [fetchState, showCooldownElapsedNotice]);
|
||||
|
||||
// Native Layer-Change-Listener (User schaltet VPN extern aus etc.)
|
||||
useEffect(() => {
|
||||
|
||||
@ -295,7 +295,10 @@
|
||||
"faq3_a": "Ja. Über die Domain-Liste auf der Blocker-Seite kannst du eigene Domains hinzufügen, die zusätzlich zur globalen Liste blockiert werden.",
|
||||
"faq4_q": "Warum kann ich den Schutz nicht sofort abschalten?",
|
||||
"faq4_a": "Wenn du im Drang bist, willst du oft schnell deaktivieren — und es danach bereuen. Der 24-Stunden-Cooldown gibt dir Zeit, den Drang abklingen zu lassen. Du kannst den Cooldown jederzeit abbrechen — der Schutz bleibt dann einfach an.",
|
||||
"more_info_title": "Schutz deaktivieren"
|
||||
"more_info_title": "Schutz deaktivieren",
|
||||
"cooldown_elapsed_title": "Schutz ist aus",
|
||||
"cooldown_elapsed_message": "Der Cooldown ist abgelaufen — der Schutz wurde deaktiviert. Du kannst den ReBreak-Bedienungshilfe-Dienst jetzt in den Einstellungen ausschalten.",
|
||||
"cooldown_elapsed_open_settings": "Einstellungen öffnen"
|
||||
},
|
||||
"mail": {
|
||||
"title": "Mail-Schutz",
|
||||
|
||||
@ -295,7 +295,10 @@
|
||||
"faq3_a": "Yes. From the domain list on the blocker page you can add custom domains that get blocked in addition to the global list.",
|
||||
"faq4_q": "Why can't I turn protection off immediately?",
|
||||
"faq4_a": "In the moment of urge, you often want to disable fast — and regret it after. The 24-hour cooldown gives you time for the urge to pass. You can cancel the cooldown anytime — protection then simply stays on.",
|
||||
"more_info_title": "Disable protection"
|
||||
"more_info_title": "Disable protection",
|
||||
"cooldown_elapsed_title": "Protection is off",
|
||||
"cooldown_elapsed_message": "The cooldown has elapsed — protection was disabled. You can now turn off the ReBreak accessibility service in Settings.",
|
||||
"cooldown_elapsed_open_settings": "Open Settings"
|
||||
},
|
||||
"mail": {
|
||||
"title": "Mail Shield",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user