mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 00:26:42 +02:00
Move inactive projects out of active workspace: - bauntown (community website) - maerchenzauber (AI story generation) - memoro (voice memo app) - news (news aggregation) - nutriphi (nutrition tracking) - reader (reading app) - uload (URL shortener) - wisekeep (AI wisdom extraction) Update CLAUDE.md documentation: - Add presi to active projects - Document archived projects section - Update workspace configuration Archived apps can be re-activated by moving back to apps/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1153 lines
35 KiB
TypeScript
1153 lines
35 KiB
TypeScript
import React, { useState, useCallback } from 'react';
|
||
import { View, ScrollView, StyleSheet, Platform, TouchableOpacity, Pressable } from 'react-native';
|
||
import * as Clipboard from 'expo-clipboard';
|
||
import Alert from '~/components/atoms/Alert';
|
||
import Text from '~/components/atoms/Text';
|
||
import Button from '~/components/atoms/Button';
|
||
import { Stack, useRouter, useFocusEffect } from 'expo-router';
|
||
import { ThemeSettings } from '~/features/theme/ThemeSettings';
|
||
import { useTheme } from '~/features/theme/ThemeProvider';
|
||
import colors from '~/tailwind.config.js';
|
||
import { useAuth } from '~/features/auth';
|
||
|
||
import SettingsToggle from '~/components/organisms/SettingsToggle';
|
||
import { useHeader } from '~/features/menus/HeaderContext';
|
||
import { useTranslation } from 'react-i18next';
|
||
import { useLanguage } from '~/features/i18n/LanguageContext';
|
||
import { useLocation } from '~/features/location/LocationContext';
|
||
import LanguageSelector from '~/features/i18n/LanguageSelector';
|
||
import MultiLanguageSelector from '~/components/molecules/MultiLanguageSelector';
|
||
import { useRecordingLanguage } from '~/features/audioRecordingV2';
|
||
import { usePageOnboarding } from '~/features/onboarding/contexts/OnboardingContext';
|
||
import { useSettingsStore } from '~/features/settings';
|
||
import { useAnalytics } from '~/features/analytics';
|
||
import { EmailNewsletterSettings } from '~/features/settings/components/EmailNewsletterSettings';
|
||
import { useToast } from '~/features/toast';
|
||
import MigrationNotificationModal from '~/features/migration/components/MigrationNotificationModal';
|
||
import Constants from 'expo-constants';
|
||
import * as Device from 'expo-device';
|
||
import { supabaseUrl, supabaseAnonKey } from '~/features/auth/lib/supabaseClient';
|
||
import { MaterialIcons } from '@expo/vector-icons';
|
||
import { openSupportEmail } from '~/features/support/utils/emailSupport';
|
||
import config from '~/config';
|
||
import EasterEggModal from '~/components/organisms/EasterEggModal';
|
||
import { useRating } from '~/features/rating/hooks/useRating';
|
||
|
||
// Abschnitts-Header-Komponente
|
||
function SectionHeader({
|
||
title,
|
||
isFirst = false,
|
||
collapsible = false,
|
||
isCollapsed = false,
|
||
onPress,
|
||
}: {
|
||
title: string;
|
||
isFirst?: boolean;
|
||
collapsible?: boolean;
|
||
isCollapsed?: boolean;
|
||
onPress?: () => void;
|
||
}) {
|
||
const { isDark, themeVariant } = useTheme();
|
||
|
||
// Zugriff auf die Theme-Farben
|
||
const themeColors = (colors as any).theme?.extend?.colors;
|
||
|
||
// Hintergrundfarbe für den Container aus der Tailwind-Konfiguration
|
||
const containerBgColor = isDark
|
||
? themeColors?.dark?.[themeVariant]?.contentBackground || '#1E1E1E'
|
||
: themeColors?.[themeVariant]?.contentBackground || '#FFFFFF';
|
||
|
||
// Randfarbe für den Container aus der Tailwind-Konfiguration
|
||
const containerBorderColor = isDark
|
||
? themeColors?.dark?.[themeVariant]?.border || '#424242'
|
||
: themeColors?.[themeVariant]?.border || '#e6e6e6';
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
marginTop: isFirst ? 8 : collapsible ? 4 : 24,
|
||
marginBottom: collapsible ? 12 : 16,
|
||
paddingHorizontal: 16,
|
||
paddingVertical: collapsible ? 20 : 0,
|
||
},
|
||
collapsedContainer: {
|
||
backgroundColor: containerBgColor,
|
||
borderColor: containerBorderColor,
|
||
borderWidth: 1,
|
||
borderRadius: 16,
|
||
},
|
||
title: {
|
||
fontSize: 22,
|
||
fontWeight: 'bold',
|
||
color: isDark ? '#FFFFFF' : '#000000',
|
||
},
|
||
collapsibleTitle: {
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
color: isDark ? '#FFFFFF' : '#000000',
|
||
},
|
||
chevron: {
|
||
marginLeft: 8,
|
||
},
|
||
});
|
||
|
||
if (collapsible && onPress) {
|
||
return (
|
||
<TouchableOpacity
|
||
style={[styles.container, isCollapsed && styles.collapsedContainer]}
|
||
onPress={onPress}
|
||
activeOpacity={0.7}
|
||
>
|
||
<Text style={collapsible ? styles.collapsibleTitle : styles.title}>{title}</Text>
|
||
<MaterialIcons
|
||
name={isCollapsed ? 'chevron-right' : 'expand-more'}
|
||
size={28}
|
||
color={isDark ? '#FFFFFF' : '#000000'}
|
||
style={styles.chevron}
|
||
/>
|
||
</TouchableOpacity>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Text
|
||
style={[styles.title, { marginTop: isFirst ? 8 : 24, marginBottom: 16, paddingLeft: 16 }]}
|
||
>
|
||
{title}
|
||
</Text>
|
||
);
|
||
}
|
||
|
||
// Hauptkomponente
|
||
export default function SettingsScreen() {
|
||
const router = useRouter();
|
||
const { isDark, themeVariant } = useTheme();
|
||
const { signOut, user } = useAuth();
|
||
const { saveLocation, setSaveLocation } = useLocation();
|
||
const { track } = useAnalytics();
|
||
const { showSuccess } = useToast();
|
||
const { requestRating } = useRating();
|
||
|
||
// Settings from store
|
||
const {
|
||
enableAnalytics,
|
||
enableNewsletter,
|
||
developerMode,
|
||
showDebugBorders,
|
||
enableDiarization,
|
||
showRecordingInstruction,
|
||
showLanguageButton,
|
||
showBlueprints,
|
||
showManaBadge,
|
||
setEnableAnalytics,
|
||
setEnableNewsletter,
|
||
setDeveloperMode,
|
||
setShowDebugBorders,
|
||
setEnableDiarization,
|
||
setShowRecordingInstruction,
|
||
setShowLanguageButton,
|
||
setShowBlueprints,
|
||
setShowManaBadge,
|
||
} = useSettingsStore();
|
||
const [isLanguageSelectorVisible, setIsLanguageSelectorVisible] = useState(false);
|
||
const [isRecordingLanguageSelectorVisible, setIsRecordingLanguageSelectorVisible] =
|
||
useState(false);
|
||
const [showMoreSettings, setShowMoreSettings] = useState(false);
|
||
const [showMigrationModal, setShowMigrationModal] = useState(false);
|
||
const [showUIElements, setShowUIElements] = useState(false);
|
||
const [showEasterEgg, setShowEasterEgg] = useState(false);
|
||
|
||
// i18n Hooks
|
||
const { t } = useTranslation();
|
||
const { currentLanguage, languages } = useLanguage();
|
||
const { recordingLanguages, toggleRecordingLanguage, supportedAzureLanguages } =
|
||
useRecordingLanguage();
|
||
|
||
// Page onboarding
|
||
const { showPageOnboardingToast, cleanupPageToast, resetOnboardingForTesting } =
|
||
usePageOnboarding();
|
||
|
||
// Direkter Zugriff auf die Farben aus der Tailwind-Konfiguration
|
||
const themeColors = (colors as any).theme?.extend?.colors;
|
||
const pageBackgroundColor = isDark
|
||
? themeColors?.dark?.[themeVariant]?.pageBackground || '#121212'
|
||
: themeColors?.[themeVariant]?.pageBackground || '#F5F5F5';
|
||
|
||
// Styles für die Seite
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
backgroundColor: pageBackgroundColor,
|
||
},
|
||
content: {
|
||
paddingHorizontal: 16,
|
||
paddingTop: 0,
|
||
paddingBottom: 16,
|
||
},
|
||
settingGroup: {
|
||
marginBottom: 12,
|
||
},
|
||
infoText: {
|
||
fontSize: 14,
|
||
marginBottom: 16,
|
||
lineHeight: 20,
|
||
color: isDark ? '#CCCCCC' : '#666666',
|
||
},
|
||
appInfoContainer: {
|
||
marginBottom: 24,
|
||
},
|
||
infoCard: {
|
||
backgroundColor: isDark ? '#1E1E1E' : '#FFFFFF',
|
||
borderRadius: 16,
|
||
borderWidth: 1,
|
||
borderColor: isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.05)',
|
||
shadowColor: '#000',
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 2,
|
||
},
|
||
shadowOpacity: isDark ? 0.3 : 0.1,
|
||
shadowRadius: 4,
|
||
elevation: 3,
|
||
},
|
||
infoSection: {
|
||
padding: 20,
|
||
},
|
||
copyButtonContainer: {
|
||
position: 'absolute',
|
||
top: 20,
|
||
right: 20,
|
||
padding: 8,
|
||
borderRadius: 8,
|
||
backgroundColor: isDark ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.02)',
|
||
},
|
||
infoCardHeader: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
marginBottom: 16,
|
||
},
|
||
infoIcon: {
|
||
marginRight: 12,
|
||
},
|
||
infoCardTitle: {
|
||
fontSize: 18,
|
||
fontWeight: '600',
|
||
color: isDark ? '#FFFFFF' : '#000000',
|
||
},
|
||
infoCardContent: {
|
||
gap: 12,
|
||
},
|
||
infoRow: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
paddingVertical: 8,
|
||
borderBottomWidth: 1,
|
||
borderBottomColor: isDark ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)',
|
||
},
|
||
infoLabel: {
|
||
fontSize: 14,
|
||
color: isDark ? 'rgba(255, 255, 255, 0.6)' : 'rgba(0, 0, 0, 0.6)',
|
||
flex: 1,
|
||
},
|
||
infoValue: {
|
||
fontSize: 14,
|
||
fontWeight: '500',
|
||
color: isDark ? '#FFFFFF' : '#000000',
|
||
textAlign: 'right',
|
||
flex: 1,
|
||
},
|
||
logoutButton: {
|
||
marginTop: 16,
|
||
},
|
||
deleteButton: {
|
||
marginTop: 8,
|
||
},
|
||
userInfo: {
|
||
marginBottom: 24,
|
||
},
|
||
userEmailLabel: {
|
||
fontSize: 14,
|
||
color: isDark ? 'rgba(255, 255, 255, 0.6)' : 'rgba(0, 0, 0, 0.6)',
|
||
marginBottom: 4,
|
||
},
|
||
userEmail: {
|
||
fontSize: 16,
|
||
fontWeight: '500',
|
||
marginBottom: 4,
|
||
color: isDark ? '#FFFFFF' : '#000000',
|
||
},
|
||
userId: {
|
||
fontSize: 12,
|
||
color: isDark ? '#AAAAAA' : '#666666',
|
||
},
|
||
moreSettingsButton: {
|
||
width: '100%',
|
||
marginBottom: 16,
|
||
height: 56,
|
||
justifyContent: 'center',
|
||
},
|
||
moreSettingsSection: {
|
||
backgroundColor: isDark ? '#1E1E1E' : '#FFFFFF',
|
||
borderRadius: 16,
|
||
padding: 16,
|
||
marginBottom: 24,
|
||
borderWidth: 1,
|
||
borderColor: isDark ? '#424242' : '#e6e6e6',
|
||
},
|
||
sectionSubtitle: {
|
||
fontSize: 18,
|
||
fontWeight: '600',
|
||
marginBottom: 12,
|
||
color: isDark ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.7)',
|
||
},
|
||
versionContainer: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'center',
|
||
marginTop: 60,
|
||
marginBottom: 24,
|
||
paddingHorizontal: 16,
|
||
},
|
||
versionText: {
|
||
fontSize: 14,
|
||
color: isDark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.5)',
|
||
},
|
||
selectedLanguagesList: {
|
||
flexDirection: 'row',
|
||
flexWrap: 'wrap',
|
||
marginTop: 8,
|
||
},
|
||
selectedLanguageChip: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
backgroundColor: isDark ? '#333333' : '#E0E0E0',
|
||
borderRadius: 16,
|
||
paddingHorizontal: 12,
|
||
paddingVertical: 6,
|
||
marginRight: 8,
|
||
marginBottom: 8,
|
||
},
|
||
languageEmoji: {
|
||
fontSize: 16,
|
||
marginRight: 4,
|
||
},
|
||
languageCode: {
|
||
fontSize: 14,
|
||
color: isDark ? '#FFFFFF' : '#000000',
|
||
},
|
||
creditsContainer: {
|
||
alignItems: 'center',
|
||
marginVertical: 24,
|
||
paddingBottom: Platform.OS === 'ios' ? 20 : 16,
|
||
},
|
||
creditsText: {
|
||
fontSize: 14,
|
||
color: isDark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.5)',
|
||
textAlign: 'center',
|
||
marginBottom: 4,
|
||
},
|
||
creditsSubtext: {
|
||
fontSize: 12,
|
||
color: isDark ? 'rgba(255, 255, 255, 0.4)' : 'rgba(0, 0, 0, 0.4)',
|
||
textAlign: 'center',
|
||
},
|
||
});
|
||
|
||
// Header-Konfiguration mit dem useHeader-Hook aktualisieren
|
||
const { updateConfig } = useHeader();
|
||
|
||
// Copy app info to clipboard
|
||
const copyAppInfo = async () => {
|
||
const appVersion = Constants.expoConfig?.version || 'N/A';
|
||
const buildNumber =
|
||
Platform.OS === 'ios'
|
||
? Constants.expoConfig?.ios?.buildNumber || 'N/A'
|
||
: Constants.expoConfig?.android?.versionCode || 'N/A';
|
||
const deviceModel = Device.modelName || 'N/A';
|
||
const osVersion = `${Device.osName} ${Device.osVersion}`;
|
||
const deviceType =
|
||
Device.deviceType === Device.DeviceType.PHONE
|
||
? t('settings.phone', 'Smartphone')
|
||
: Device.deviceType === Device.DeviceType.TABLET
|
||
? t('settings.tablet', 'Tablet')
|
||
: t('settings.unknown', 'Unbekannt');
|
||
|
||
const infoText = `Memoro App Information
|
||
${t('settings.app_version', 'App-Version')}: ${appVersion}
|
||
${t('settings.build_number', 'Build-Nummer')}: ${buildNumber}
|
||
${t('settings.device_model', 'Modell')}: ${deviceModel}
|
||
${t('settings.os_version', 'System')}: ${osVersion}
|
||
${t('settings.device_type', 'Typ')}: ${deviceType}`;
|
||
|
||
await Clipboard.setStringAsync(infoText);
|
||
showSuccess(t('settings.info_copied', 'App-Informationen kopiert'));
|
||
};
|
||
|
||
// Header-Konfiguration aktualisieren, wenn die Seite fokussiert wird
|
||
useFocusEffect(
|
||
useCallback(() => {
|
||
console.debug('Settings page focused, updating header config');
|
||
|
||
// Settings page doesn't need onboarding toast - it's self-explanatory
|
||
// showPageOnboardingToast('settings');
|
||
|
||
updateConfig({
|
||
title: 'settings.title',
|
||
showBackButton: true,
|
||
showMenu: true,
|
||
rightIcons: [],
|
||
selectedTags: [],
|
||
});
|
||
|
||
// Header-Konfiguration zurücksetzen, wenn die Komponente unfokussiert wird
|
||
return () => {
|
||
// No cleanup needed since we don't show onboarding toast on settings
|
||
// cleanupPageToast('settings');
|
||
|
||
console.debug('Settings page unfocused');
|
||
};
|
||
}, [t])
|
||
);
|
||
|
||
// Handler für die Sprachauswahl
|
||
const handleOpenLanguageSelector = () => {
|
||
setIsLanguageSelectorVisible(true);
|
||
};
|
||
|
||
const handleCloseLanguageSelector = () => {
|
||
setIsLanguageSelectorVisible(false);
|
||
};
|
||
|
||
const handleOpenRecordingLanguageSelector = () => {
|
||
setIsRecordingLanguageSelectorVisible(true);
|
||
};
|
||
|
||
const handleCloseRecordingLanguageSelector = () => {
|
||
setIsRecordingLanguageSelectorVisible(false);
|
||
};
|
||
|
||
// Wrapper functions for toggles with toast feedback
|
||
const handleLocationToggle = async (value: boolean) => {
|
||
await setSaveLocation(value);
|
||
showSuccess(
|
||
t('settings.location_updated', 'Location Setting Updated'),
|
||
t(
|
||
value ? 'settings.location_enabled_message' : 'settings.location_disabled_message',
|
||
value ? 'Location saving has been enabled.' : 'Location saving has been disabled.'
|
||
)
|
||
);
|
||
};
|
||
|
||
const handleAnalyticsToggle = (value: boolean) => {
|
||
setEnableAnalytics(value);
|
||
showSuccess(
|
||
t('settings.analytics_updated', 'Analytics Setting Updated'),
|
||
t(
|
||
value ? 'settings.analytics_enabled_message' : 'settings.analytics_disabled_message',
|
||
value ? 'Analytics have been enabled.' : 'Analytics have been disabled.'
|
||
)
|
||
);
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<Stack.Screen options={{ headerShown: false }} />
|
||
<LanguageSelector
|
||
isVisible={isLanguageSelectorVisible}
|
||
onClose={handleCloseLanguageSelector}
|
||
/>
|
||
<MultiLanguageSelector
|
||
isVisible={isRecordingLanguageSelectorVisible}
|
||
onClose={handleCloseRecordingLanguageSelector}
|
||
languages={supportedAzureLanguages}
|
||
selectedLanguages={recordingLanguages}
|
||
onToggleLanguage={toggleRecordingLanguage}
|
||
title={t('settings.recording_languages', 'Bevorzugte Transkriptionssprachen')}
|
||
/>
|
||
<ScrollView style={styles.container}>
|
||
<View style={styles.content}>
|
||
{/* Design-Einstellungen */}
|
||
<View style={[styles.settingGroup, { marginTop: 0 }]}>
|
||
<ThemeSettings />
|
||
|
||
<View style={{ marginTop: 16 }}>
|
||
<SettingsToggle
|
||
title={t('settings.interface_language', 'Oberflächen-Sprache')}
|
||
description={`${languages[currentLanguage as keyof typeof languages]?.emoji} ${languages[currentLanguage as keyof typeof languages]?.nativeName}`}
|
||
type="button"
|
||
onPress={handleOpenLanguageSelector}
|
||
/>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Benutzeroberfläche */}
|
||
<SectionHeader
|
||
title={t('settings.user_interface', 'Aufnahme Seite Elemente')}
|
||
collapsible={true}
|
||
isCollapsed={!showUIElements}
|
||
onPress={() => setShowUIElements(!showUIElements)}
|
||
/>
|
||
{showUIElements && (
|
||
<View style={styles.settingGroup}>
|
||
<SettingsToggle
|
||
title={t('settings.show_language_button', 'Sprachen-Button anzeigen')}
|
||
description={t(
|
||
'settings.show_language_button_description',
|
||
'Zeigt den Sprachen-Auswahl-Button neben dem Aufnahme-Button'
|
||
)}
|
||
type="toggle"
|
||
isOn={showLanguageButton}
|
||
onToggle={setShowLanguageButton}
|
||
/>
|
||
|
||
<View style={{ marginTop: 16 }}>
|
||
<SettingsToggle
|
||
title={t('settings.show_recording_instruction', 'Aufnahme-Anleitung anzeigen')}
|
||
description={t(
|
||
'settings.show_recording_instruction_description',
|
||
'Zeigt "Aufnahme starten" Text mit Pfeil beim Recording-Button'
|
||
)}
|
||
type="toggle"
|
||
isOn={showRecordingInstruction}
|
||
onToggle={setShowRecordingInstruction}
|
||
/>
|
||
</View>
|
||
|
||
<View style={{ marginTop: 16 }}>
|
||
<SettingsToggle
|
||
title={t('settings.show_blueprints', 'Blueprints anzeigen')}
|
||
description={t(
|
||
'settings.show_blueprints_description',
|
||
'Zeigt die Blueprint-Auswahl am unteren Bildschirmrand'
|
||
)}
|
||
type="toggle"
|
||
isOn={showBlueprints}
|
||
onToggle={setShowBlueprints}
|
||
/>
|
||
</View>
|
||
|
||
<View style={{ marginTop: 16 }}>
|
||
<SettingsToggle
|
||
title={t('settings.show_mana_badge', 'Mana-Anzeige im Header')}
|
||
description={t(
|
||
'settings.show_mana_badge_description',
|
||
'Zeigt den Mana-Zähler oben im Header an'
|
||
)}
|
||
type="toggle"
|
||
isOn={showManaBadge}
|
||
onToggle={setShowManaBadge}
|
||
/>
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
{/* Aufnahme */}
|
||
<SectionHeader title={t('settings.recording', 'Aufnahme')} />
|
||
<View style={styles.settingGroup}>
|
||
<SettingsToggle
|
||
title={t('settings.recording_languages', 'Bevorzugte Transkriptionssprachen')}
|
||
description={t(
|
||
'settings.recording_languages_description',
|
||
'Wähle die Sprachen, die bei der Transkription bevorzugt erkannt werden sollen'
|
||
)}
|
||
type="button"
|
||
onPress={handleOpenRecordingLanguageSelector}
|
||
secondaryText={
|
||
recordingLanguages.length > 0
|
||
? recordingLanguages.map((lang) => supportedAzureLanguages[lang]?.emoji).join(' ')
|
||
: t('settings.recording_languages_auto', 'Automatisch')
|
||
}
|
||
/>
|
||
|
||
<View style={{ marginTop: 16 }}>
|
||
<SettingsToggle
|
||
title={t('settings.enable_diarization', 'Enable Speaker Diarization')}
|
||
description={t(
|
||
'settings.enable_diarization_description',
|
||
'Automatically identify and separate different speakers in recordings'
|
||
)}
|
||
type="toggle"
|
||
isOn={enableDiarization}
|
||
onToggle={setEnableDiarization}
|
||
/>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Daten */}
|
||
<SectionHeader title={t('settings.data', 'Daten')} />
|
||
<View style={styles.settingGroup}>
|
||
<SettingsToggle
|
||
title={t('settings.save_location', 'Standort speichern')}
|
||
description={t(
|
||
'settings.save_location_description',
|
||
'Erlaube der App, deinen Standort zu speichern, um standortbezogene Funktionen zu ermöglichen'
|
||
)}
|
||
type="toggle"
|
||
isOn={saveLocation}
|
||
onToggle={handleLocationToggle}
|
||
/>
|
||
|
||
<View style={{ marginTop: 16 }}>
|
||
<SettingsToggle
|
||
title={t('settings.enable_analytics', 'Analytics aktivieren')}
|
||
description={t(
|
||
'settings.enable_analytics_description',
|
||
'Ich stimme der Verwendung von Analytics zu, um meine Benutzererfahrung zu verbessern'
|
||
)}
|
||
type="toggle"
|
||
isOn={enableAnalytics}
|
||
onToggle={handleAnalyticsToggle}
|
||
/>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Kommunikation */}
|
||
<SectionHeader title={t('settings.communication', 'Kommunikation')} />
|
||
<View style={styles.settingGroup}>
|
||
<EmailNewsletterSettings />
|
||
</View>
|
||
|
||
{/* Support */}
|
||
<SectionHeader title={t('settings.support', 'Support')} />
|
||
<View style={styles.settingGroup}>
|
||
<SettingsToggle
|
||
title={t('settings.contact_support', 'Support kontaktieren')}
|
||
description={t(
|
||
'settings.contact_support_description',
|
||
'Benötigst du Hilfe? Kontaktiere unser Support-Team'
|
||
)}
|
||
type="button"
|
||
onPress={() => {
|
||
openSupportEmail({ userId: user?.id, t });
|
||
}}
|
||
/>
|
||
|
||
<View style={{ marginTop: 16 }}>
|
||
<SettingsToggle
|
||
title={t('settings.rate_app', 'App bewerten')}
|
||
description={t(
|
||
'settings.rate_app_description',
|
||
'Gefällt dir Memoro? Bewerte uns im App Store'
|
||
)}
|
||
type="button"
|
||
onPress={requestRating}
|
||
icon="star-outline"
|
||
/>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Anzeige-Abschnitt wurde entfernt und in den Darstellung-Abschnitt verschoben */}
|
||
|
||
{/* Mehr Einstellungen Button */}
|
||
<Button
|
||
title={t('settings.show_more_settings', 'Erweiterte Einstellungen anzeigen')}
|
||
variant="secondary"
|
||
iconName={showMoreSettings ? 'chevron-up-outline' : 'chevron-down-outline'}
|
||
onPress={() => setShowMoreSettings(!showMoreSettings)}
|
||
style={styles.moreSettingsButton}
|
||
/>
|
||
|
||
{/* Erweiterte Einstellungen Bereich */}
|
||
{showMoreSettings && (
|
||
<View style={styles.moreSettingsSection}>
|
||
{/* Konto löschen */}
|
||
<View style={styles.settingGroup}>
|
||
<Text style={styles.sectionSubtitle}>
|
||
{t('settings.delete_account', 'Konto löschen')}
|
||
</Text>
|
||
<Text style={styles.infoText}>
|
||
{t(
|
||
'settings.delete_account_warning',
|
||
'Wenn du dein Konto löschst, werden alle deine Daten dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.'
|
||
)}
|
||
</Text>
|
||
<Button
|
||
title={t('settings.delete_account_button', 'Konto löschen')}
|
||
variant="danger"
|
||
onPress={() => {
|
||
// Hier würde normalerweise ein Dialog angezeigt werden
|
||
console.debug('Konto löschen angefordert');
|
||
}}
|
||
style={styles.deleteButton}
|
||
/>
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
{developerMode && (
|
||
<>
|
||
<SectionHeader title={t('settings.developer_settings', 'Entwickler-Einstellungen')} />
|
||
<View style={styles.settingGroup}>
|
||
<SettingsToggle
|
||
title={t('settings.debug_borders', 'Debug-Rahmen')}
|
||
description={t(
|
||
'settings.debug_borders_description',
|
||
'Zeige Rahmen um UI-Elemente für eine bessere Entwicklung'
|
||
)}
|
||
type="toggle"
|
||
isOn={showDebugBorders}
|
||
onToggle={setShowDebugBorders}
|
||
/>
|
||
|
||
<Button
|
||
title={t('settings.show_onboarding', 'Onboarding anzeigen')}
|
||
variant="secondary"
|
||
iconName="information-circle-outline"
|
||
onPress={() => router.push('/(public)/login?showOnboarding=true')}
|
||
style={{
|
||
marginTop: 16,
|
||
marginBottom: 8,
|
||
}}
|
||
/>
|
||
|
||
<Button
|
||
title={t('settings.reset_onboarding_toasts', 'Onboarding-Toasts zurücksetzen')}
|
||
variant="secondary"
|
||
iconName="bug-outline"
|
||
onPress={() => {
|
||
console.log('User object:', user);
|
||
console.log('App metadata:', user?.app_metadata);
|
||
console.log('User metadata:', user?.user_metadata);
|
||
Alert.alert(
|
||
'User Debug Info',
|
||
JSON.stringify(
|
||
{
|
||
email: user?.email,
|
||
app_metadata: user?.app_metadata,
|
||
user_metadata: user?.user_metadata,
|
||
},
|
||
null,
|
||
2
|
||
)
|
||
);
|
||
}}
|
||
style={{
|
||
marginTop: 8,
|
||
marginBottom: 8,
|
||
}}
|
||
/>
|
||
|
||
<Button
|
||
title={t('settings.reset_onboarding_toasts', 'Onboarding-Toasts zurücksetzen')}
|
||
variant="secondary"
|
||
iconName="refresh-outline"
|
||
onPress={() => {
|
||
resetOnboardingForTesting();
|
||
Alert.alert(
|
||
t('settings.reset_complete', 'Zurücksetzung abgeschlossen'),
|
||
t(
|
||
'settings.reset_onboarding_message',
|
||
'Alle Onboarding-Toasts werden wieder angezeigt, wenn du die entsprechenden Seiten besuchst.'
|
||
)
|
||
);
|
||
}}
|
||
style={{
|
||
marginTop: 8,
|
||
marginBottom: 8,
|
||
}}
|
||
/>
|
||
|
||
<Button
|
||
title="Test Analytics Event"
|
||
variant="secondary"
|
||
iconName="analytics-outline"
|
||
onPress={() => {
|
||
track('test_analytics_button_pressed', {
|
||
timestamp: new Date().toISOString(),
|
||
screen: 'settings',
|
||
user_id: user?.id,
|
||
});
|
||
Alert.alert(
|
||
'Analytics Test',
|
||
'Event gesendet! Überprüfe die Konsole und dein PostHog Dashboard.'
|
||
);
|
||
}}
|
||
style={{
|
||
marginTop: 8,
|
||
marginBottom: 8,
|
||
}}
|
||
/>
|
||
|
||
<Button
|
||
title="Migration Modal anzeigen"
|
||
variant="secondary"
|
||
iconName="cloud-upload-outline"
|
||
onPress={() => setShowMigrationModal(true)}
|
||
style={{
|
||
marginTop: 8,
|
||
marginBottom: 8,
|
||
}}
|
||
/>
|
||
</View>
|
||
</>
|
||
)}
|
||
|
||
{/* Konto */}
|
||
<SectionHeader title={t('settings.account', 'Konto')} />
|
||
<View style={styles.moreSettingsSection}>
|
||
{user && (
|
||
<View style={styles.userInfo}>
|
||
<Text style={styles.userEmailLabel}>
|
||
{t('settings.email_label', 'E-Mail-Adresse')}
|
||
</Text>
|
||
<Text style={styles.userEmail}>
|
||
{user.email || t('settings.no_email', 'Keine E-Mail verfügbar')}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
|
||
<Button
|
||
title={t('settings.logout', 'Abmelden')}
|
||
variant="danger"
|
||
iconName="log-out-outline"
|
||
onPress={() => {
|
||
Alert.alert(
|
||
t('settings.logout_confirm_title', 'Abmelden bestätigen'),
|
||
t('settings.logout_confirm_message', 'Möchtest du dich wirklich abmelden?'),
|
||
[
|
||
{
|
||
text: t('common.cancel', 'Abbrechen'),
|
||
style: 'cancel',
|
||
},
|
||
{
|
||
text: t('settings.logout', 'Abmelden'),
|
||
style: 'destructive',
|
||
onPress: signOut,
|
||
},
|
||
]
|
||
);
|
||
}}
|
||
style={styles.logoutButton}
|
||
/>
|
||
</View>
|
||
|
||
{/* App-Informationen */}
|
||
|
||
{/* App & Device Information */}
|
||
<SectionHeader title={t('settings.app_info', 'App-Informationen')} />
|
||
<View>
|
||
<View style={styles.infoCard}>
|
||
{/* App Version Section */}
|
||
<View style={styles.infoSection}>
|
||
<View style={styles.infoCardHeader}>
|
||
<MaterialIcons
|
||
name="info-outline"
|
||
size={24}
|
||
color={isDark ? '#4A90E2' : '#2E7CD6'}
|
||
style={styles.infoIcon}
|
||
/>
|
||
<Text style={styles.infoCardTitle}>{t('settings.version_info', 'Version')}</Text>
|
||
</View>
|
||
<View style={styles.infoCardContent}>
|
||
<View style={styles.infoRow}>
|
||
<Text style={styles.infoLabel}>{t('settings.app_version', 'App-Version')}</Text>
|
||
<Text style={styles.infoValue}>{Constants.expoConfig?.version || 'N/A'}</Text>
|
||
</View>
|
||
<View style={[styles.infoRow, { borderBottomWidth: 0 }]}>
|
||
<Text style={styles.infoLabel}>
|
||
{t('settings.build_number', 'Build-Nummer')}
|
||
</Text>
|
||
<Text style={styles.infoValue}>
|
||
{Platform.OS === 'ios'
|
||
? Constants.expoConfig?.ios?.buildNumber || 'N/A'
|
||
: Constants.expoConfig?.android?.versionCode || 'N/A'}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Copy Button */}
|
||
<TouchableOpacity onPress={copyAppInfo} style={styles.copyButtonContainer}>
|
||
<MaterialIcons
|
||
name="content-copy"
|
||
size={20}
|
||
color={isDark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.5)'}
|
||
/>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Environment Variables (Developer Mode Only) */}
|
||
{developerMode && (
|
||
<>
|
||
<SectionHeader title={t('settings.environment_variables', 'Environment Variables')} />
|
||
<View style={styles.infoCard}>
|
||
<View style={styles.infoSection}>
|
||
<View style={styles.infoCardHeader}>
|
||
<MaterialIcons
|
||
name="code"
|
||
size={24}
|
||
color={isDark ? '#4A90E2' : '#2E7CD6'}
|
||
style={styles.infoIcon}
|
||
/>
|
||
<Text style={styles.infoCardTitle}>
|
||
{t('settings.env_config', 'Environment Configuration')}
|
||
</Text>
|
||
</View>
|
||
<View style={styles.infoCardContent}>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start' },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>SUPABASE_URL</Text>
|
||
<Text style={[styles.infoValue, { fontSize: 12, marginTop: 4 }]}>
|
||
{config.SUPABASE_URL}
|
||
</Text>
|
||
</View>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start' },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>SUPABASE_ANON_KEY</Text>
|
||
<Text style={[styles.infoValue, { fontSize: 12, marginTop: 4 }]}>
|
||
{config.SUPABASE_ANON_KEY}
|
||
</Text>
|
||
</View>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start' },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>MEMORO_MIDDLEWARE_URL</Text>
|
||
<Text style={[styles.infoValue, { fontSize: 12, marginTop: 4 }]}>
|
||
{config.MEMORO_MIDDLEWARE_URL}
|
||
</Text>
|
||
</View>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start' },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>MANA_MIDDLEWARE_URL</Text>
|
||
<Text style={[styles.infoValue, { fontSize: 12, marginTop: 4 }]}>
|
||
{config.MANA_MIDDLEWARE_URL}
|
||
</Text>
|
||
</View>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start' },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>MIDDLEWARE_APP_ID</Text>
|
||
<Text style={[styles.infoValue, { fontSize: 12, marginTop: 4 }]}>
|
||
{config.MIDDLEWARE_APP_ID}
|
||
</Text>
|
||
</View>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start' },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>STORAGE_BUCKET</Text>
|
||
<Text style={[styles.infoValue, { fontSize: 12, marginTop: 4 }]}>
|
||
{config.STORAGE_BUCKET}
|
||
</Text>
|
||
</View>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start' },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>REVENUECAT_IOS_KEY</Text>
|
||
<Text style={[styles.infoValue, { fontSize: 12, marginTop: 4 }]}>
|
||
{config.REVENUECAT_IOS_KEY || 'Not set'}
|
||
</Text>
|
||
</View>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start' },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>GOOGLE_CLIENT_ID</Text>
|
||
<Text style={[styles.infoValue, { fontSize: 12, marginTop: 4 }]}>
|
||
{config.GOOGLE_CLIENT_ID || 'Not set'}
|
||
</Text>
|
||
</View>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start', borderBottomWidth: 0 },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>POSTHOG_KEY</Text>
|
||
<Text style={[styles.infoValue, { fontSize: 12, marginTop: 4 }]}>
|
||
{config.POSTHOG_KEY || 'Not set'}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Supabase Client Configuration */}
|
||
<SectionHeader
|
||
title={t('settings.supabase_config', 'Supabase Client Configuration')}
|
||
/>
|
||
<View style={styles.infoCard}>
|
||
<View style={styles.infoSection}>
|
||
<View style={styles.infoCardHeader}>
|
||
<MaterialIcons
|
||
name="storage"
|
||
size={24}
|
||
color={isDark ? '#4A90E2' : '#2E7CD6'}
|
||
style={styles.infoIcon}
|
||
/>
|
||
<Text style={styles.infoCardTitle}>
|
||
{t('settings.supabase_init', 'Supabase Initialization')}
|
||
</Text>
|
||
</View>
|
||
<View style={styles.infoCardContent}>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start' },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>Main Supabase Client URL</Text>
|
||
<Text style={[styles.infoValue, { fontSize: 12, marginTop: 4 }]}>
|
||
{supabaseUrl}
|
||
</Text>
|
||
</View>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start' },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>Main Supabase Anon Key</Text>
|
||
<Text style={[styles.infoValue, { fontSize: 12, marginTop: 4 }]}>
|
||
{supabaseAnonKey}
|
||
</Text>
|
||
</View>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start' },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>Key Format</Text>
|
||
<Text style={[styles.infoValue, { fontSize: 12, marginTop: 4 }]}>
|
||
{supabaseAnonKey.startsWith('eyJ')
|
||
? 'JWT Format (Legacy)'
|
||
: 'Publishable Key Format (New)'}
|
||
</Text>
|
||
</View>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start' },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>URL Match Status</Text>
|
||
<Text
|
||
style={[
|
||
styles.infoValue,
|
||
{
|
||
fontSize: 12,
|
||
marginTop: 4,
|
||
color: supabaseUrl === config.SUPABASE_URL ? '#4CAF50' : '#FF5252',
|
||
},
|
||
]}
|
||
>
|
||
{supabaseUrl === config.SUPABASE_URL
|
||
? '✓ URLs Match'
|
||
: '✗ URLs Do Not Match'}
|
||
</Text>
|
||
</View>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start' },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>Key Match Status</Text>
|
||
<Text
|
||
style={[
|
||
styles.infoValue,
|
||
{
|
||
fontSize: 12,
|
||
marginTop: 4,
|
||
color:
|
||
supabaseAnonKey === config.SUPABASE_ANON_KEY ? '#4CAF50' : '#FF5252',
|
||
},
|
||
]}
|
||
>
|
||
{supabaseAnonKey === config.SUPABASE_ANON_KEY
|
||
? '✓ Keys Match'
|
||
: '✗ Keys Do Not Match'}
|
||
</Text>
|
||
</View>
|
||
<View
|
||
style={[
|
||
styles.infoRow,
|
||
{ flexDirection: 'column', alignItems: 'flex-start', borderBottomWidth: 0 },
|
||
]}
|
||
>
|
||
<Text style={styles.infoLabel}>Environment Source</Text>
|
||
<Text style={[styles.infoValue, { fontSize: 12, marginTop: 4 }]}>
|
||
{process.env.EXPO_PUBLIC_SUPABASE_URL
|
||
? 'Environment Variable'
|
||
: 'Fallback Default'}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</>
|
||
)}
|
||
|
||
{/* Credits */}
|
||
<Pressable
|
||
style={styles.creditsContainer}
|
||
onLongPress={() => setShowEasterEgg(true)}
|
||
delayLongPress={1500}
|
||
>
|
||
<Text style={styles.creditsText}>© 2025 Memoro GmbH</Text>
|
||
<Text style={styles.creditsSubtext}>Made with ❤️ in Germany</Text>
|
||
</Pressable>
|
||
</View>
|
||
</ScrollView>
|
||
<MigrationNotificationModal
|
||
isVisible={showMigrationModal}
|
||
onClose={() => setShowMigrationModal(false)}
|
||
subscriptionPlanId={
|
||
user?.app_metadata?.subscription_plan_id ||
|
||
user?.user_metadata?.subscription ||
|
||
'Pro User'
|
||
}
|
||
/>
|
||
<EasterEggModal
|
||
isVisible={showEasterEgg}
|
||
onClose={() => setShowEasterEgg(false)}
|
||
onLongPress={() => {
|
||
setShowEasterEgg(false);
|
||
const newMode = !developerMode;
|
||
setDeveloperMode(newMode);
|
||
showSuccess(
|
||
t(
|
||
newMode ? 'settings.developer_mode_enabled' : 'settings.developer_mode_disabled',
|
||
newMode ? 'Developer Mode aktiviert' : 'Developer Mode deaktiviert'
|
||
),
|
||
t(
|
||
newMode ? 'settings.developer_mode_message' : 'settings.developer_mode_hidden',
|
||
newMode
|
||
? 'Entwicklereinstellungen sind jetzt sichtbar'
|
||
: 'Entwicklereinstellungen wurden ausgeblendet'
|
||
)
|
||
);
|
||
}}
|
||
/>
|
||
</>
|
||
);
|
||
}
|