This commit is contained in:
2026-07-27 16:42:04 +02:00
parent 45a7704ea6
commit 018b751723
23 changed files with 2327 additions and 635 deletions

View File

@@ -69,20 +69,31 @@ const ensureInstallConsistency = async (): Promise<boolean> => {
const sqliteMarker = AppMetaDb.get('install_marker_v2');
const secureMarker = await SecureStore.getItemAsync(SECURE_INSTALL_MARKER).catch(() => null);
if (sqliteMarker === '1' && secureMarker === '1') {
return false; // Alles gut, keine Neuinstallation
}
if (sqliteMarker === '1' || secureMarker === '1') {
// Teilweise vorhanden -> heilen, nicht löschen
AppMetaDb.set('install_marker_v2', '1');
await SecureStore.setItemAsync(SECURE_INSTALL_MARKER, '1');
// WICHTIG: Nur der SQLite-Marker taugt als Neuinstallations-Erkennung.
// SecureStore liegt auf iOS im Keychain, und Keychain-Eintraege ueberleben
// das Loeschen der App. Nach einer Neuinstallation gilt also immer
// sqliteMarker === null && secureMarker === '1' - das ist der Normalfall,
// nicht ein halb geschriebener Zustand.
//
// Vorher lief genau dieser Fall in den "heilen"-Zweig: die Session blieb
// bestehen, der Nutzer war nach der Neuinstallation weiter eingeloggt und
// hat das Onboarding nie wieder gesehen (Gate: !session && !hasCompletedOnboarding).
if (sqliteMarker === '1') {
// App-Daten vorhanden -> laufende Installation. Keychain-Marker nachziehen,
// falls er fehlt (z. B. nach einem Keychain-Reset).
if (secureMarker !== '1') {
await SecureStore.setItemAsync(SECURE_INSTALL_MARKER, '1');
}
return false;
}
// Fresh Install: Alles zurücksetzen
// Fresh Install: Alles zurücksetzen. Der Nutzer landet danach auf dem
// Welcome-Screen und durchläuft das Onboarding erneut - inklusive
// A/B-Zuweisung, die ebenfalls im geloeschten App-Storage lag.
await AuthService.logout();
await AsyncStorage.removeItem('greenlens_show_tour');
// Experiment-Zuweisungen gehoeren zur Installation, nicht zum Keychain.
await AsyncStorage.removeItem('greenlens_experiment_welcome_headline_v1');
AppMetaDb.set('install_marker_v2', '1');
await SecureStore.setItemAsync(SECURE_INSTALL_MARKER, '1');
return true;
@@ -152,6 +163,7 @@ function RootLayoutInner() {
<Stack.Screen name="onboarding" options={{ animation: 'none' }} />
<Stack.Screen name="onboarding/slides" options={{ animation: 'slide_from_right' }} />
<Stack.Screen name="onboarding/source" options={{ animation: 'slide_from_right' }} />
<Stack.Screen name="onboarding/health-check" options={{ animation: 'slide_from_right' }} />
<Stack.Screen name="onboarding/goal" options={{ animation: 'slide_from_right' }} />
<Stack.Screen name="onboarding/experience" options={{ animation: 'slide_from_right' }} />
<Stack.Screen name="onboarding/customize" options={{ animation: 'slide_from_right' }} />
@@ -182,6 +194,7 @@ function RootLayoutInner() {
<Stack.Screen name="onboarding" options={{ animation: 'none' }} />
<Stack.Screen name="onboarding/slides" options={{ animation: 'slide_from_right' }} />
<Stack.Screen name="onboarding/source" options={{ animation: 'slide_from_right' }} />
<Stack.Screen name="onboarding/health-check" options={{ animation: 'slide_from_right' }} />
<Stack.Screen name="onboarding/goal" options={{ animation: 'slide_from_right' }} />
<Stack.Screen name="onboarding/experience" options={{ animation: 'slide_from_right' }} />
<Stack.Screen name="onboarding/customize" options={{ animation: 'slide_from_right' }} />