211 lines
7.6 KiB
TypeScript
211 lines
7.6 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
TouchableOpacity,
|
|
Switch,
|
|
StyleSheet,
|
|
Alert,
|
|
Share,
|
|
ScrollView,
|
|
} from 'react-native';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { MaterialIcons } from '@expo/vector-icons';
|
|
import { kv } from '../../src/lib/kv';
|
|
import { useHouseholdStore } from '../../src/hooks/useHousehold';
|
|
import { regenerateInviteToken, buildInviteLink } from '../../src/services/household';
|
|
import { updateFcmToken, requestNotificationPermission } from '../../src/services/notifications';
|
|
import { COLORS } from '../../src/constants';
|
|
import Toast from 'react-native-toast-message';
|
|
|
|
const NOTIF_KEY = 'houseorg_notifications_enabled';
|
|
|
|
export default function SettingsScreen() {
|
|
const household = useHouseholdStore((s) => s.household);
|
|
const setHousehold = useHouseholdStore((s) => s.setHousehold);
|
|
const [notificationsEnabled, setNotificationsEnabled] = useState<boolean | null>(null);
|
|
|
|
useEffect(() => {
|
|
kv.getItem(NOTIF_KEY).then((val) => {
|
|
setNotificationsEnabled(val !== 'false');
|
|
});
|
|
}, []);
|
|
|
|
const handleShareInvite = async () => {
|
|
if (!household) return;
|
|
const link = buildInviteLink(household.id, household.inviteToken);
|
|
const text = `Tritt unserem Haushalt bei: ${link}`;
|
|
if (typeof navigator !== 'undefined' && (navigator as any).share) {
|
|
await (navigator as any).share({ text, title: 'HouseOrg Einladung' });
|
|
} else if (typeof navigator !== 'undefined' && (navigator as any).clipboard) {
|
|
await (navigator as any).clipboard.writeText(text);
|
|
Toast.show({ type: 'success', text1: 'Link kopiert' });
|
|
} else {
|
|
await Share.share({ message: text, title: 'HouseOrg Einladung' });
|
|
}
|
|
};
|
|
|
|
const handleRegenerateLink = async () => {
|
|
if (!household) return;
|
|
Alert.alert(
|
|
'Link erneuern',
|
|
'Der alte Link wird ungültig. Alle Mitglieder können sich damit nicht mehr neu anmelden.',
|
|
[
|
|
{ text: 'Abbrechen', style: 'cancel' },
|
|
{
|
|
text: 'Erneuern',
|
|
style: 'destructive',
|
|
onPress: async () => {
|
|
try {
|
|
const newToken = await regenerateInviteToken(household.id);
|
|
setHousehold({ ...household, inviteToken: newToken });
|
|
Toast.show({ type: 'success', text1: 'Neuer Einladungslink erstellt' });
|
|
} catch {
|
|
Toast.show({ type: 'error', text1: 'Fehler beim Erneuern' });
|
|
}
|
|
},
|
|
},
|
|
]
|
|
);
|
|
};
|
|
|
|
const handleNotificationToggle = async (enabled: boolean) => {
|
|
if (!household) return;
|
|
if (enabled) {
|
|
const granted = await requestNotificationPermission();
|
|
if (!granted) {
|
|
Toast.show({ type: 'error', text1: 'Benachrichtigungen nicht erlaubt' });
|
|
return;
|
|
}
|
|
}
|
|
setNotificationsEnabled(enabled);
|
|
await kv.setItem(NOTIF_KEY, String(enabled));
|
|
await updateFcmToken(household.id, enabled);
|
|
};
|
|
|
|
return (
|
|
<SafeAreaView style={styles.container} edges={['top']}>
|
|
<ScrollView showsVerticalScrollIndicator={false}>
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Haushalt</Text>
|
|
<View style={styles.card}>
|
|
<View style={styles.row}>
|
|
<View style={[styles.iconWrap, { backgroundColor: '#E8F5E9' }]}>
|
|
<MaterialIcons name="home" size={18} color={COLORS.primary} />
|
|
</View>
|
|
<View style={styles.rowContent}>
|
|
<Text style={styles.rowLabel}>Name</Text>
|
|
<Text style={styles.rowValue}>{household?.name}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.divider} />
|
|
|
|
<TouchableOpacity style={styles.row} onPress={handleShareInvite}>
|
|
<View style={[styles.iconWrap, { backgroundColor: '#E3F2FD' }]}>
|
|
<MaterialIcons name="share" size={18} color="#1E88E5" />
|
|
</View>
|
|
<View style={styles.rowContent}>
|
|
<Text style={styles.rowLabel}>Einladungslink teilen</Text>
|
|
<Text style={styles.rowHint}>Mitglieder einladen</Text>
|
|
</View>
|
|
<MaterialIcons name="chevron-right" size={20} color={COLORS.textSecondary} />
|
|
</TouchableOpacity>
|
|
|
|
<View style={styles.divider} />
|
|
|
|
<TouchableOpacity style={styles.row} onPress={handleRegenerateLink}>
|
|
<View style={[styles.iconWrap, { backgroundColor: '#FFF3E0' }]}>
|
|
<MaterialIcons name="refresh" size={18} color={COLORS.warning} />
|
|
</View>
|
|
<View style={styles.rowContent}>
|
|
<Text style={[styles.rowLabel, { color: COLORS.warning }]}>
|
|
Einladungslink erneuern
|
|
</Text>
|
|
<Text style={styles.rowHint}>Alter Link wird ungültig</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Benachrichtigungen</Text>
|
|
<View style={styles.card}>
|
|
<View style={styles.row}>
|
|
<View style={[styles.iconWrap, { backgroundColor: '#F3E5F5' }]}>
|
|
<MaterialIcons name="notifications" size={18} color="#8E24AA" />
|
|
</View>
|
|
<View style={styles.rowContent}>
|
|
<Text style={styles.rowLabel}>Push-Benachrichtigungen</Text>
|
|
<Text style={styles.rowHint}>Einkaufsliste & MHD-Warnungen</Text>
|
|
</View>
|
|
<Switch
|
|
value={notificationsEnabled ?? false}
|
|
disabled={notificationsEnabled === null}
|
|
onValueChange={handleNotificationToggle}
|
|
trackColor={{ false: COLORS.border, true: COLORS.primaryLight }}
|
|
thumbColor={notificationsEnabled ? COLORS.primary : COLORS.white}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>App</Text>
|
|
<View style={styles.card}>
|
|
<View style={styles.row}>
|
|
<View style={[styles.iconWrap, { backgroundColor: COLORS.surface }]}>
|
|
<MaterialIcons name="info-outline" size={18} color={COLORS.textSecondary} />
|
|
</View>
|
|
<View style={styles.rowContent}>
|
|
<Text style={styles.rowLabel}>Version</Text>
|
|
<Text style={styles.rowValue}>1.0.0</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</ScrollView>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: COLORS.surface },
|
|
section: { marginTop: 28, paddingHorizontal: 16 },
|
|
sectionTitle: {
|
|
fontSize: 12,
|
|
fontWeight: '600',
|
|
color: COLORS.textSecondary,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.6,
|
|
marginBottom: 10,
|
|
},
|
|
card: {
|
|
backgroundColor: COLORS.white,
|
|
borderRadius: 16,
|
|
overflow: 'hidden',
|
|
shadowColor: '#000',
|
|
shadowOpacity: 0.05,
|
|
shadowRadius: 8,
|
|
elevation: 1,
|
|
},
|
|
row: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
padding: 14,
|
|
gap: 12,
|
|
},
|
|
iconWrap: {
|
|
width: 34,
|
|
height: 34,
|
|
borderRadius: 10,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
rowContent: { flex: 1 },
|
|
rowLabel: { fontSize: 15, fontWeight: '500', color: COLORS.text },
|
|
rowValue: { fontSize: 13, color: COLORS.textSecondary, marginTop: 2 },
|
|
rowHint: { fontSize: 12, color: COLORS.textSecondary, marginTop: 1 },
|
|
divider: { height: StyleSheet.hairlineWidth, backgroundColor: COLORS.border, marginLeft: 60 },
|
|
});
|