chahinebrini 4a013bc43b feat(android-protection): präzise Tamper-Lock + a11y-Onboarding-Guide
Tamper-Lock von Keyword-Scanning auf präzise Einzel-Surfaces umgebaut:
blockt nur ReBreaks eigene Screens (Admin-Deaktivierung via DeviceAdminAdd,
a11y-Ausschalten, VPN-Trennen/Surface), nie Listen oder fremde Apps.

- Deny-Removal = Admin-only: OS graut Uninstall+Force-Stop für aktiven
  Device-Admin aus; einziger Bypass (Admin deaktivieren) bleibt a11y-gesperrt.
  Andere Apps verwalten/force-stoppen/deinstallieren bleibt komplett frei.
- a11y-Onboarding: passiver Bottom-Overlay-Hinweis + Settings-Reset auf
  Startseite nach Aktivierung + 1s-Delay vor App-Rückkehr.
- VPN-Trennen-Dialog + a11y-Ausschalten neu abgedeckt.
- a11y-Service-Icon im Plugin (klar als ReBreak erkennbar).

Verifiziert auf A50 per logcat: alle 4 Surfaces blocken, Listen + fremde
Apps frei, keine False-Positives.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 04:05:41 +02:00

190 lines
5.8 KiB
TypeScript

import { useEffect, useState } from 'react';
import { Image, Text, TouchableOpacity, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useTranslation } from 'react-i18next';
import { useColors } from '../../lib/theme';
import { FormSheet } from '../FormSheet';
/**
* Anti-Blind-Klick-Gate fürs Onboarding: zeigt VOR der eigentlichen Permission
* eine kurze Instruktion (welcher Button im gleich folgenden System-Dialog) und
* verlangt ein bewusstes Häkchen, bevor der „Weiter"-Button aktiv wird. Bricht
* das „weiter-weiter"-Muster genau dort, wo User sonst den falschen Button tippen
* (v.a. iOS-Family-Controls: zwei Buttons, der blaue ist die Falle).
*
* Onboarding-only — der Blocker-Screen aktiviert weiterhin direkt (kein Gate).
*/
export function PermissionConfirmSheet({
visible,
title,
body,
steps,
screenshot,
indicatorCaption,
onConfirm,
onClose,
}: {
visible: boolean;
title: string;
body: string;
/** Optional: nummerierte Schritte (z.B. a11y: erst Overlay erlauben, dann Schalter an). */
steps?: string[];
/** Optional: Screenshot (require-Handle) — zeigt dem User wie der Ziel-Screen aussieht. */
screenshot?: number;
/** Optional: Caption unter dem Screenshot — der Indikator „hier tippen". */
indicatorCaption?: string;
onConfirm: () => void;
onClose: () => void;
}) {
const { t } = useTranslation();
const colors = useColors();
const [checked, setChecked] = useState(false);
// Häkchen bei jedem Öffnen zurücksetzen — sonst klickt man beim nächsten Step
// mit schon-gesetztem Haken blind durch (genau das wollen wir verhindern).
useEffect(() => {
if (visible) setChecked(false);
}, [visible]);
return (
<FormSheet visible={visible} onClose={onClose} title={title}>
{/* FormSheet padded nur den Titel, nicht die children → hier selbst polstern. */}
<View style={{ paddingHorizontal: 20, paddingTop: 4, paddingBottom: 28 }}>
<View
style={{
backgroundColor: colors.surfaceElevated,
borderRadius: 14,
padding: 16,
flexDirection: 'row',
gap: 12,
}}
>
<Ionicons name="information-circle" size={22} color={colors.brandOrange} style={{ marginTop: 1 }} />
<Text
style={{
flex: 1,
fontSize: 15,
lineHeight: 22,
fontFamily: 'Nunito_600SemiBold',
color: colors.text,
}}
>
{body}
</Text>
</View>
{steps && steps.length > 0 && (
<View style={{ marginTop: 12, gap: 8 }}>
{steps.map((step, i) => (
<View key={i} style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 10 }}>
<View
style={{
width: 22,
height: 22,
borderRadius: 11,
backgroundColor: colors.brandOrange,
alignItems: 'center',
justifyContent: 'center',
marginTop: 1,
}}
>
<Text style={{ fontSize: 12, fontFamily: 'Nunito_700Bold', color: '#fff' }}>{i + 1}</Text>
</View>
<Text
style={{
flex: 1,
fontSize: 14,
lineHeight: 20,
fontFamily: 'Nunito_600SemiBold',
color: colors.text,
}}
>
{step}
</Text>
</View>
))}
</View>
)}
{screenshot != null && (
<View style={{ marginTop: 14, alignItems: 'center' }}>
<View
style={{
height: 240,
aspectRatio: 0.9,
borderRadius: 12,
overflow: 'hidden',
borderWidth: 1,
borderColor: colors.border,
}}
>
<Image source={screenshot} style={{ width: '100%', height: '100%' }} resizeMode="contain" />
</View>
{!!indicatorCaption && (
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, marginTop: 8 }}>
<Ionicons name="arrow-up" size={16} color={colors.brandOrange} />
<Text
style={{
fontSize: 13,
fontFamily: 'Nunito_700Bold',
color: colors.brandOrange,
textAlign: 'center',
}}
>
{indicatorCaption}
</Text>
</View>
)}
</View>
)}
<TouchableOpacity
activeOpacity={0.7}
onPress={() => setChecked((c) => !c)}
style={{
flexDirection: 'row',
alignItems: 'center',
gap: 12,
marginTop: 20,
paddingVertical: 4,
}}
>
<Ionicons
name={checked ? 'checkbox' : 'square-outline'}
size={26}
color={checked ? colors.brandOrange : colors.textMuted}
/>
<Text
style={{
flex: 1,
fontSize: 15,
fontFamily: 'Nunito_700Bold',
color: colors.text,
}}
>
{t('onboarding.protection_confirm.checkbox')}
</Text>
</TouchableOpacity>
<TouchableOpacity
disabled={!checked}
activeOpacity={0.85}
onPress={onConfirm}
style={{
backgroundColor: colors.brandOrange,
borderRadius: 12,
paddingVertical: 15,
alignItems: 'center',
marginTop: 18,
opacity: checked ? 1 : 0.4,
}}
>
<Text style={{ fontSize: 16, fontFamily: 'Nunito_700Bold', color: '#fff' }}>
{t('onboarding.protection_confirm.cta')}
</Text>
</TouchableOpacity>
</View>
</FormSheet>
);
}