chahinebrini b23bd6d29f feat(onboarding,protection): Duo-style flow + cooldown auto-disable fix + Family Controls live
## Duo-Style Onboarding (Foundation + alle Slides)

Self-contained Onboarding-Flow mit Lyra-Mascot ersetzt das Spotlight-POC vom
vorherigen Iteration. Slides leben unter `components/onboarding/slides/`.

- Foundation: OnboardingShell (Progress + ScrollView + sticky CTABar),
  LyraBubble (Rive-Avatar + animierte Speech-Bubble), SlideProgress, CTABar
- Slides: Welcome, Privacy (4 Versprechen), Nickname (inline + PATCH /me),
  DigaChoice (Ja/Nein-Branch), DigaCode (redeem-Endpoint + inline-Errors),
  Plan (Pro/Legend cards, monthly/yearly toggle, 2 Monate gratis, Härtefall-
  Mailto), Payment (RevenueCat-Dev-Stub bis Phase-0), Protection (activate +
  PermissionDeniedSheet-Wiring), Done (animierter Checkmark + Streak-Day-1)
- State-Machine in app/onboarding/index.tsx: 9 Slides, DiGA-Branch, Resume-
  on-launch via slideFromStep(me.onboardingStep)
- Routing-gate in (app)/_layout.tsx: step != 'done' → /onboarding
- Backend Profile.onboardingStep enum extended:
  welcome | account | plan | pre_protection | done (+ legacy nickname/block)
- Backend diga redeem: step='pre_protection' (NICHT 'done') — User muss noch
  durch Protection-Slide für NEFilter/VPN-Aktivierung
- Locale-Keys (de/en/fr/ar): onboarding.lyra.<slide>.body, .cta_primary,
  Plan-Tier-Details (3,99/7,99 €/Mo, 39,90/79,90 €/Jahr mit 2 Monaten gratis),
  Härtefall-Link, DiGA-Code-Errors, Protection-Feat-Descriptions

## Cooldown Auto-Disable Race-Fix

Bug: nach Cooldown-Ablauf bleib URL-Filter installiert (NEFilter in iOS-
Settings sichtbar als "Läuft..."). Root-cause: `/api/cooldown/status` GET
auto-resolved beim ersten expired-Hit; zweiter Call in
applyCooldownDisableIfElapsed sah cooldownEndsAt=null → bail → forceDisable
nie aufgerufen.

- useProtectionState.fetchState: lokalen next.cooldown.endsAt state nutzen
  statt redundantem API-Call. Atomarer, race-frei.
- AppState-Listener-Path unverändert (dort ist es der erste API-Call, kein
  Race).
- lib/protection.forceDisable: console.log für Debug-Visibility.

## iOS NEFilter Robust-Disable (Native)

`removeFromPreferences()` alleine ist auf iOS 18+ unzuverlässig — Settings-
UI zeigt "Läuft..." obwohl Provider beendet sein sollte. 2-Step-Pattern:

  1. loadFromPreferences
  2. isEnabled = false + saveToPreferences (stoppt Filter-Daemon)
  3. removeFromPreferences (Config-Eintrag aus Settings)

Quelle: Apple-Developer-Forums + eigene Empirie. Pattern wird auch in
PermissionDeniedSheet's resetUrlFilter genutzt (analog).

## Family Controls jetzt immer aktiv

Apple-Entitlement seit 2026-05 für ReBreak approved (TestFlight-akzeptiert).
`familyControlsEnabled: true` hart in app.config.ts (kein Env-Var-Gating mehr).
"Bald verfügbar"-Placeholder in blocker.tsx entfernt — App-Lock-Toggle ist
jetzt voll funktional auf iOS.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 17:48:05 +02:00

154 lines
5.4 KiB
TypeScript

import { ExpoConfig, ConfigContext } from "expo/config";
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: "ReBreak",
slug: "rebreak",
version: "0.2.1",
orientation: "portrait",
icon: "./assets/icon.png",
scheme: "rebreak",
userInterfaceStyle: "automatic",
newArchEnabled: true,
splash: {
image: "./assets/icon.png",
resizeMode: "contain",
backgroundColor: "#0f172a",
},
ios: {
supportsTablet: true,
bundleIdentifier: "org.rebreak.app",
buildNumber: "9",
config: {
usesNonExemptEncryption: false,
},
infoPlist: {
ITSAppUsesNonExemptEncryption: false,
NSMicrophoneUsageDescription:
"Rebreak nutzt das Mikrofon für Sprachnachrichten an Lyra.",
NSPhotoLibraryUsageDescription:
"Rebreak greift auf Fotos zu, damit du sie in deinen Posts teilen kannst.",
NSPhotoLibraryAddUsageDescription:
"Rebreak speichert Bilder in deine Foto-Mediathek.",
NSFaceIDUsageDescription:
"Rebreak nutzt Face ID, um die App zu entsperren — damit niemand außer dir sie öffnen kann.",
},
},
android: {
package: "org.rebreak.app",
versionCode: 9,
adaptiveIcon: {
// Foreground muss in der ~66%-Safe-Zone bleiben (Launcher-Mask clippt den
// Außenring) → adaptive-foreground.png ist das Logo auf transparentem
// 1024er-Canvas mit ~19% Padding. Hintergrund weiss → matcht den
// Play-Console-Look (dunkles Logo auf Weiss), statt dunkel-auf-dunkel.
foregroundImage: "./assets/adaptive-foreground.png",
backgroundColor: "#ffffff",
},
permissions: [
"INTERNET",
"ACCESS_NETWORK_STATE",
"BIND_VPN_SERVICE",
"FOREGROUND_SERVICE",
"POST_NOTIFICATIONS",
"BIND_ACCESSIBILITY_SERVICE",
"RECORD_AUDIO",
],
},
plugins: [
"expo-router",
"expo-localization",
"expo-font",
"expo-web-browser",
[
"expo-build-properties",
{
ios: {
deploymentTarget: "15.1",
useFrameworks: "static",
},
android: {
minSdkVersion: 26,
compileSdkVersion: 36,
targetSdkVersion: 36,
},
},
],
// Xcode 16 + RN 0.79 fmt consteval workaround
"./plugins/with-fmt-consteval-fix",
// Xcode 14+ resource-bundle-signing fix (needed because useFrameworks: static)
"./plugins/with-resource-bundle-signing-fix",
// Phase 5: NEFilter Extension + Family Controls Entitlements (iOS)
"./plugins/with-rebreak-protection-ios",
// Phase 5: VpnService + AccessibilityService (Android)
"./plugins/with-rebreak-protection-android",
// Rive-Asset (lyra-avatar.riv) als Android raw-resource bundlen
"./plugins/with-rive-asset-android",
// MaterialComponents-Theme-Fix für BadgeDrawable in react-native-bottom-tabs
"./plugins/with-material-theme-android",
// Release-Signing-Block in build.gradle (liest android/key.properties — nicht committen)
"./plugins/with-release-signing-android",
],
experiments: {
typedRoutes: true,
},
extra: {
eas: {
projectId: "a4f2186e-8ca5-4d38-921d-82ae96c9c086",
// EAS muss VOR dem Build wissen, dass es eine App-Extension gibt — sonst
// generiert es nur Credentials für die Haupt-App und der Xcode-Build kippt
// mit "No profiles for 'org.rebreak.app.RebreakURLFilter' were found".
// Bundle-ID + Entitlements müssen exakt zu plugins/with-rebreak-protection-ios.js
// und modules/rebreak-protection/ios/RebreakURLFilter/RebreakURLFilter.entitlements passen.
build: {
experimental: {
ios: {
appExtensions: [
{
targetName: "RebreakURLFilter",
bundleIdentifier: "org.rebreak.app.RebreakURLFilter",
entitlements: {
"com.apple.developer.networking.networkextension": [
"content-filter-provider",
],
"com.apple.security.application-groups": [
"group.org.rebreak.app",
],
},
},
],
},
},
},
},
// Family Controls Entitlement (denyAppRemoval via ManagedSettings) ist seit
// 2026-05 für ReBreak via Apple-Entitlement-Request approved und in TestFlight-
// sowie production-Builds aktiv. Daher hart auf `true` — keine Build-Flag-Gating
// mehr nötig. Legacy `REBREAK_ENABLE_FAMILY_CONTROLS=1` aus dev-builds wird
// ignoriert (das war Übergangs-Gating vor Apple-Approval).
familyControlsEnabled: true,
apiUrl:
process.env.EXPO_PUBLIC_API_URL ||
process.env.API_URL ||
"https://staging.rebreak.org",
// TEMP: Staging Anon-Key + URL hardcoded für lokales Dev-Testing.
// Anon-Key ist designed für Client-Ship (RLS protectiert DB). Trotzdem:
// BFF-Migration kommt in Phase 5 — dann fliegen diese 2 Zeilen wieder raus.
supabaseUrl:
process.env.EXPO_PUBLIC_SUPABASE_URL ||
process.env.SUPABASE_URL ||
"https://db-staging.rebreak.org",
supabaseAnonKey:
process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY ||
process.env.SUPABASE_KEY ||
process.env.SUPABASE_ANON_KEY ||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsImF1ZCI6ImF1dGhlbnRpY2F0ZWQiLCJyb2xlIjoiYW5vbiIsImV4cCI6MjA5MTAxODk1NSwiaWF0IjoxNzc1NjU4OTU1fQ.93d2r3pft2E-alf1JezqueD0l0n1dim7dGvhBN0l1Cs",
},
});