mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-12 04:30:23 +00:00
337 lines
11 KiB
TypeScript
337 lines
11 KiB
TypeScript
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
import { View, Text, StyleSheet, TouchableOpacity, StatusBar, Platform, Animated, Easing, TextInput, ActivityIndicator } from 'react-native';
|
|
import FastImage from '@d11/react-native-fast-image';
|
|
import { useNavigation } from '@react-navigation/native';
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
import { MaterialIcons } from '@expo/vector-icons';
|
|
import { useAccount } from '../contexts/AccountContext';
|
|
import { useTheme } from '../contexts/ThemeContext';
|
|
import { LinearGradient } from 'expo-linear-gradient';
|
|
import * as Haptics from 'expo-haptics';
|
|
import CustomAlert from '../components/CustomAlert';
|
|
|
|
const AccountManageScreen: React.FC = () => {
|
|
const navigation = useNavigation();
|
|
const insets = useSafeAreaInsets();
|
|
const { user, signOut, updateProfile } = useAccount();
|
|
const { currentTheme } = useTheme();
|
|
|
|
const headerOpacity = useRef(new Animated.Value(0)).current;
|
|
const headerTranslateY = useRef(new Animated.Value(8)).current;
|
|
const contentOpacity = useRef(new Animated.Value(0)).current;
|
|
const contentTranslateY = useRef(new Animated.Value(8)).current;
|
|
|
|
useEffect(() => {
|
|
Animated.parallel([
|
|
Animated.timing(headerOpacity, { toValue: 1, duration: 260, easing: Easing.out(Easing.cubic), useNativeDriver: true }),
|
|
Animated.timing(headerTranslateY, { toValue: 0, duration: 260, easing: Easing.out(Easing.cubic), useNativeDriver: true }),
|
|
Animated.timing(contentOpacity, { toValue: 1, duration: 320, delay: 80, easing: Easing.out(Easing.cubic), useNativeDriver: true }),
|
|
Animated.timing(contentTranslateY, { toValue: 0, duration: 320, delay: 80, easing: Easing.out(Easing.cubic), useNativeDriver: true }),
|
|
]).start();
|
|
}, [headerOpacity, headerTranslateY, contentOpacity, contentTranslateY]);
|
|
|
|
const initial = useMemo(() => (user?.email?.[0]?.toUpperCase() || 'U'), [user?.email]);
|
|
const [displayName, setDisplayName] = useState(user?.displayName || '');
|
|
const [avatarUrl, setAvatarUrl] = useState(user?.avatarUrl || '');
|
|
const [saving, setSaving] = useState(false);
|
|
const [avatarError, setAvatarError] = useState(false);
|
|
const [alertVisible, setAlertVisible] = useState(false);
|
|
const [alertTitle, setAlertTitle] = useState('');
|
|
const [alertMessage, setAlertMessage] = useState('');
|
|
const [alertActions, setAlertActions] = useState<any[]>([]);
|
|
|
|
useEffect(() => {
|
|
// Reset image error state when URL changes
|
|
setAvatarError(false);
|
|
}, [avatarUrl]);
|
|
|
|
const handleSave = async () => {
|
|
if (saving) return;
|
|
setSaving(true);
|
|
const err = await updateProfile({ displayName: displayName.trim() || undefined, avatarUrl: avatarUrl.trim() || undefined });
|
|
if (err) {
|
|
setAlertTitle('Error');
|
|
setAlertMessage(err);
|
|
setAlertActions([{ label: 'OK', onPress: () => {} }]);
|
|
setAlertVisible(true);
|
|
}
|
|
setSaving(false);
|
|
};
|
|
|
|
const handleSignOut = () => {
|
|
setAlertTitle('Sign out');
|
|
setAlertMessage('Are you sure you want to sign out?');
|
|
setAlertActions([
|
|
{ label: 'Cancel', onPress: () => {} },
|
|
{
|
|
label: 'Sign out',
|
|
onPress: async () => {
|
|
try {
|
|
await signOut();
|
|
// @ts-ignore
|
|
navigation.goBack();
|
|
} catch (_) {}
|
|
},
|
|
style: { opacity: 1 },
|
|
},
|
|
]);
|
|
setAlertVisible(true);
|
|
};
|
|
|
|
return (
|
|
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
|
<StatusBar translucent barStyle="light-content" backgroundColor="transparent" />
|
|
|
|
{/* Header */}
|
|
<Animated.View
|
|
style={[
|
|
styles.header,
|
|
{
|
|
paddingTop: (Platform.OS === 'android' ? (StatusBar.currentHeight || 0) : insets.top) + 12,
|
|
opacity: headerOpacity,
|
|
transform: [{ translateY: headerTranslateY }],
|
|
},
|
|
]}
|
|
>
|
|
<LinearGradient
|
|
colors={[currentTheme.colors.darkBackground, '#111318']}
|
|
style={StyleSheet.absoluteFill}
|
|
/>
|
|
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.headerBack} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
|
|
<MaterialIcons name="arrow-back" size={22} color={currentTheme.colors.white} />
|
|
</TouchableOpacity>
|
|
<Text style={[styles.headerTitle, { color: currentTheme.colors.white }]}>Account</Text>
|
|
<View style={{ width: 22, height: 22 }} />
|
|
</Animated.View>
|
|
|
|
{/* Content */}
|
|
<Animated.View style={[styles.content, { opacity: contentOpacity, transform: [{ translateY: contentTranslateY }] }]}>
|
|
{/* Profile Badge */}
|
|
<View style={styles.profileContainer}>
|
|
{avatarUrl && !avatarError ? (
|
|
<View style={[styles.avatar, { overflow: 'hidden' }]}>
|
|
<FastImage
|
|
source={{ uri: avatarUrl }}
|
|
style={styles.avatarImage}
|
|
resizeMode={FastImage.resizeMode.cover}
|
|
onError={() => setAvatarError(true)}
|
|
/>
|
|
</View>
|
|
) : (
|
|
<View style={[styles.avatar, { backgroundColor: currentTheme.colors.elevation2 }]}>
|
|
<Text style={styles.avatarText}>{(displayName?.[0] || initial)}</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
|
|
{/* Account details card */}
|
|
<View style={[styles.card, { backgroundColor: currentTheme.colors.elevation1, borderColor: currentTheme.colors.elevation2 }]}>
|
|
<View style={styles.itemRow}>
|
|
<View style={styles.itemLeft}>
|
|
<MaterialIcons name="badge" size={20} color={currentTheme.colors.primary} />
|
|
<Text style={[styles.itemTitle, { color: currentTheme.colors.highEmphasis }]}>Display name</Text>
|
|
</View>
|
|
<TextInput
|
|
placeholder="Add a display name"
|
|
placeholderTextColor={currentTheme.colors.mediumEmphasis}
|
|
style={[styles.input, { color: currentTheme.colors.white }]}
|
|
value={displayName}
|
|
onChangeText={setDisplayName}
|
|
numberOfLines={1}
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.divider} />
|
|
|
|
<View style={[styles.itemRow, Platform.OS === 'android' && styles.itemRowCompact]}>
|
|
<View style={styles.itemLeft}>
|
|
<MaterialIcons name="image" size={20} color={currentTheme.colors.primary} />
|
|
<Text style={[styles.itemTitle, { color: currentTheme.colors.highEmphasis }]}>Avatar URL</Text>
|
|
</View>
|
|
<TextInput
|
|
placeholder="https://..."
|
|
placeholderTextColor={currentTheme.colors.mediumEmphasis}
|
|
style={[styles.input, { color: currentTheme.colors.white }]}
|
|
value={avatarUrl}
|
|
onChangeText={setAvatarUrl}
|
|
autoCapitalize="none"
|
|
numberOfLines={1}
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.divider} />
|
|
|
|
<View style={styles.itemRow}>
|
|
<View style={styles.itemLeft}>
|
|
<MaterialIcons name="account-circle" size={20} color={currentTheme.colors.primary} />
|
|
<Text style={[styles.itemTitle, { color: currentTheme.colors.highEmphasis }]}>Email</Text>
|
|
</View>
|
|
<Text style={[styles.itemValue, { color: currentTheme.colors.mediumEmphasis }]} numberOfLines={1}>
|
|
{user?.email || '—'}
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.divider} />
|
|
|
|
<View style={styles.itemRow}>
|
|
<View style={styles.itemLeft}>
|
|
<MaterialIcons name="fingerprint" size={20} color={currentTheme.colors.primary} />
|
|
<Text style={[styles.itemTitle, { color: currentTheme.colors.highEmphasis }]}>User ID</Text>
|
|
</View>
|
|
<Text style={[styles.itemValue, { color: currentTheme.colors.mediumEmphasis }]} numberOfLines={1}>
|
|
{user?.id}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Save and Sign out */}
|
|
<TouchableOpacity
|
|
activeOpacity={0.85}
|
|
style={[styles.saveButton, { backgroundColor: currentTheme.colors.elevation2, borderColor: currentTheme.colors.elevation2 }]}
|
|
onPress={handleSave}
|
|
disabled={saving}
|
|
>
|
|
{saving ? (
|
|
<ActivityIndicator color={currentTheme.colors.white} />
|
|
) : (
|
|
<>
|
|
<MaterialIcons name="save-alt" size={18} color={currentTheme.colors.white} style={{ marginRight: 8 }} />
|
|
<Text style={styles.saveText}>Save changes</Text>
|
|
</>
|
|
)}
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity
|
|
activeOpacity={0.85}
|
|
style={[
|
|
styles.signOutButton,
|
|
{ backgroundColor: currentTheme.colors.primary },
|
|
]}
|
|
onPress={handleSignOut}
|
|
>
|
|
<MaterialIcons name="logout" size={18} color="#fff" style={{ marginRight: 8 }} />
|
|
<Text style={styles.signOutText}>Sign out</Text>
|
|
</TouchableOpacity>
|
|
</Animated.View>
|
|
<CustomAlert
|
|
visible={alertVisible}
|
|
title={alertTitle}
|
|
message={alertMessage}
|
|
actions={alertActions}
|
|
onClose={() => setAlertVisible(false)}
|
|
/>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
},
|
|
header: {
|
|
paddingHorizontal: 16,
|
|
paddingBottom: 14,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
},
|
|
headerBack: {
|
|
padding: 4,
|
|
},
|
|
headerTitle: {
|
|
fontSize: 18,
|
|
fontWeight: '800',
|
|
},
|
|
content: {
|
|
flex: 1,
|
|
paddingHorizontal: 16,
|
|
paddingTop: 10,
|
|
},
|
|
profileContainer: {
|
|
alignItems: 'center',
|
|
marginBottom: 12,
|
|
},
|
|
avatar: {
|
|
width: 72,
|
|
height: 72,
|
|
borderRadius: 36,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
avatarImage: {
|
|
width: '100%',
|
|
height: '100%',
|
|
},
|
|
avatarText: {
|
|
color: '#fff',
|
|
fontWeight: '800',
|
|
fontSize: 24,
|
|
},
|
|
card: {
|
|
borderRadius: 14,
|
|
borderWidth: StyleSheet.hairlineWidth,
|
|
paddingHorizontal: 14,
|
|
paddingVertical: 10,
|
|
},
|
|
itemRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingVertical: 10,
|
|
},
|
|
itemRowCompact: {
|
|
paddingVertical: 6,
|
|
},
|
|
input: {
|
|
flex: 1,
|
|
textAlign: 'right',
|
|
paddingVertical: 6,
|
|
marginLeft: 12,
|
|
},
|
|
itemLeft: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
},
|
|
itemTitle: {
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
},
|
|
itemValue: {
|
|
fontSize: 14,
|
|
maxWidth: '65%',
|
|
},
|
|
divider: {
|
|
height: StyleSheet.hairlineWidth,
|
|
backgroundColor: 'rgba(255,255,255,0.08)',
|
|
},
|
|
signOutButton: {
|
|
marginTop: 16,
|
|
height: 48,
|
|
borderRadius: 12,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
flexDirection: 'row',
|
|
},
|
|
signOutText: {
|
|
color: '#fff',
|
|
fontWeight: '700',
|
|
},
|
|
saveButton: {
|
|
marginTop: 12,
|
|
height: 46,
|
|
borderRadius: 12,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
flexDirection: 'row',
|
|
borderWidth: StyleSheet.hairlineWidth,
|
|
},
|
|
saveText: {
|
|
color: '#fff',
|
|
fontWeight: '700',
|
|
},
|
|
});
|
|
|
|
export default AccountManageScreen;
|
|
|
|
|