import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Alert, TextInput, Modal, KeyboardAvoidingView, Platform, } from 'react-native'; import { router } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useAuth } from '@/contexts/AuthContext'; import { useVoice } from '@/contexts/VoiceContext'; import { api } from '@/services/api'; import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme'; interface MenuItemProps { icon: keyof typeof Ionicons.glyphMap; iconColor?: string; iconBgColor?: string; title: string; subtitle?: string; onPress?: () => void; showChevron?: boolean; } function MenuItem({ icon, iconColor = AppColors.primary, iconBgColor = '#DBEAFE', title, subtitle, onPress, showChevron = true, }: MenuItemProps) { return ( {title} {subtitle && {subtitle}} {showChevron && ( )} ); } export default function ProfileScreen() { const { user, logout } = useAuth(); const { updateVoiceApiType } = useVoice(); const [deploymentId, setDeploymentId] = useState(''); const [deploymentName, setDeploymentName] = useState(''); const [showDeploymentModal, setShowDeploymentModal] = useState(false); const [tempDeploymentId, setTempDeploymentId] = useState(''); const [isValidating, setIsValidating] = useState(false); const [validationError, setValidationError] = useState(null); // Voice API Type state const [voiceApiType, setVoiceApiType] = useState<'voice_ask' | 'ask_wellnuo_ai'>('ask_wellnuo_ai'); const [showVoiceApiModal, setShowVoiceApiModal] = useState(false); const [tempVoiceApiType, setTempVoiceApiType] = useState<'voice_ask' | 'ask_wellnuo_ai'>('ask_wellnuo_ai'); // Load saved deployment ID or auto-populate from first available useEffect(() => { const loadDeploymentId = async () => { const saved = await api.getDeploymentId(); if (saved) { // Use saved deployment ID setDeploymentId(saved); // Validate to get the deployment name const result = await api.validateDeploymentId(saved); if (result.ok && result.data?.valid && result.data.name) { setDeploymentName(result.data.name); } } else { // No saved ID - auto-populate from first available deployment const firstResult = await api.getFirstDeploymentId(); if (firstResult.ok && firstResult.data) { setDeploymentId(firstResult.data.deploymentId); setDeploymentName(firstResult.data.name); // Also save it so it persists await api.setDeploymentId(firstResult.data.deploymentId); } } }; loadDeploymentId(); }, []); // Load saved Voice API type useEffect(() => { const loadVoiceApiType = async () => { const saved = await api.getVoiceApiType(); setVoiceApiType(saved); }; loadVoiceApiType(); }, []); const openDeploymentModal = useCallback(() => { setTempDeploymentId(deploymentId); setValidationError(null); setShowDeploymentModal(true); }, [deploymentId]); const openVoiceApiModal = useCallback(() => { setTempVoiceApiType(voiceApiType); setShowVoiceApiModal(true); }, [voiceApiType]); const saveDeploymentId = useCallback(async () => { const trimmed = tempDeploymentId.trim(); setValidationError(null); if (trimmed) { setIsValidating(true); try { const result = await api.validateDeploymentId(trimmed); if (result.ok && result.data?.valid) { await api.setDeploymentId(trimmed); if (result.data.name) { await api.setDeploymentName(result.data.name); } setDeploymentId(trimmed); setDeploymentName(result.data.name || ''); setShowDeploymentModal(false); } else if (result.ok && !result.data?.valid) { setValidationError('Invalid Deployment ID. Please check and try again.'); } else { setValidationError(result.error?.message || 'Failed to validate Deployment ID'); } } catch { setValidationError('Network error. Please try again.'); } finally { setIsValidating(false); } } else { await api.clearDeploymentId(); setDeploymentId(''); setDeploymentName(''); setShowDeploymentModal(false); } }, [tempDeploymentId]); const saveVoiceApiType = useCallback(async () => { await api.setVoiceApiType(tempVoiceApiType); setVoiceApiType(tempVoiceApiType); updateVoiceApiType(tempVoiceApiType); setShowVoiceApiModal(false); }, [tempVoiceApiType, updateVoiceApiType]); const openTerms = () => { router.push('/terms'); }; const openPrivacy = () => { router.push('/privacy'); }; const handleLogout = () => { Alert.alert( 'Logout', 'Are you sure you want to logout?', [ { text: 'Cancel', style: 'cancel' }, { text: 'Logout', style: 'destructive', onPress: async () => { await logout(); router.replace('/(auth)/login'); }, }, ], { cancelable: true } ); }; return ( {/* Header */} Profile {/* User Info */} {user?.user_name?.charAt(0).toUpperCase() || 'U'} {user?.user_name || 'User'} {/* Settings */} Settings {/* Legal - Required for App Store */} Legal {/* Logout Button */} Logout {/* Version */} WellNuo v1.0.0 {/* Deployment ID Modal */} setShowDeploymentModal(false)} > Deployment ID Enter the deployment ID to connect to a specific device. Leave empty for automatic detection. { setTempDeploymentId(text); setValidationError(null); }} keyboardType="numeric" autoFocus editable={!isValidating} /> {validationError && ( {validationError} )} setShowDeploymentModal(false)} disabled={isValidating} > Cancel {isValidating ? 'Validating...' : 'Save'} {/* Voice API Modal */} setShowVoiceApiModal(false)} > Voice API Choose which API function to use for voice requests. {/* Radio buttons */} setTempVoiceApiType('ask_wellnuo_ai')} > {tempVoiceApiType === 'ask_wellnuo_ai' && } ask_wellnuo_ai LLaMA with WellNuo data setTempVoiceApiType('voice_ask')} > {tempVoiceApiType === 'voice_ask' && } voice_ask Alternative voice API setShowVoiceApiModal(false)} > Cancel Save ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: AppColors.surface, }, header: { paddingHorizontal: Spacing.lg, paddingVertical: Spacing.md, backgroundColor: AppColors.background, borderBottomWidth: 1, borderBottomColor: AppColors.border, }, headerTitle: { fontSize: FontSizes.xl, fontWeight: '700', color: AppColors.textPrimary, }, userCard: { flexDirection: 'row', alignItems: 'center', backgroundColor: AppColors.background, padding: Spacing.lg, marginBottom: Spacing.md, }, avatarContainer: { width: 64, height: 64, borderRadius: BorderRadius.full, backgroundColor: AppColors.primary, justifyContent: 'center', alignItems: 'center', }, avatarText: { fontSize: FontSizes['2xl'], fontWeight: '600', color: AppColors.white, }, userInfo: { flex: 1, marginLeft: Spacing.md, }, userName: { fontSize: FontSizes.lg, fontWeight: '600', color: AppColors.textPrimary, }, editButton: { width: 40, height: 40, borderRadius: BorderRadius.full, backgroundColor: AppColors.surface, justifyContent: 'center', alignItems: 'center', }, section: { marginBottom: Spacing.md, }, sectionTitle: { fontSize: FontSizes.sm, fontWeight: '600', color: AppColors.textSecondary, paddingHorizontal: Spacing.lg, paddingVertical: Spacing.sm, textTransform: 'uppercase', }, menuCard: { backgroundColor: AppColors.background, }, menuItem: { flexDirection: 'row', alignItems: 'center', paddingVertical: Spacing.md, paddingHorizontal: Spacing.lg, }, menuIconContainer: { width: 36, height: 36, borderRadius: BorderRadius.md, justifyContent: 'center', alignItems: 'center', }, menuTextContainer: { flex: 1, marginLeft: Spacing.md, }, menuTitle: { fontSize: FontSizes.base, fontWeight: '500', color: AppColors.textPrimary, }, menuSubtitle: { fontSize: FontSizes.xs, color: AppColors.textMuted, marginTop: 2, }, menuDivider: { height: 1, backgroundColor: AppColors.border, marginLeft: Spacing.lg + 36 + Spacing.md, }, logoutButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: AppColors.background, paddingVertical: Spacing.md, marginHorizontal: Spacing.lg, borderRadius: BorderRadius.lg, }, logoutText: { fontSize: FontSizes.base, fontWeight: '600', color: AppColors.error, marginLeft: Spacing.sm, }, version: { textAlign: 'center', fontSize: FontSizes.xs, color: AppColors.textMuted, paddingVertical: Spacing.xl, }, // Modal styles modalOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'center', alignItems: 'center', padding: Spacing.lg, }, modalContent: { backgroundColor: AppColors.background, borderRadius: BorderRadius.lg, padding: Spacing.lg, width: '100%', maxWidth: 400, }, modalTitle: { fontSize: FontSizes.lg, fontWeight: '600', color: AppColors.textPrimary, marginBottom: Spacing.sm, }, modalDescription: { fontSize: FontSizes.sm, color: AppColors.textSecondary, marginBottom: Spacing.md, }, modalInput: { backgroundColor: AppColors.surface, borderRadius: BorderRadius.md, paddingHorizontal: Spacing.md, paddingVertical: Spacing.sm + 4, fontSize: FontSizes.base, color: AppColors.textPrimary, borderWidth: 1, borderColor: AppColors.border, marginBottom: Spacing.md, }, modalInputError: { borderColor: AppColors.error, marginBottom: Spacing.xs, }, errorText: { color: AppColors.error, fontSize: FontSizes.sm, marginBottom: Spacing.md, }, modalButtons: { flexDirection: 'row', justifyContent: 'flex-end', gap: Spacing.sm, }, modalButtonCancel: { paddingHorizontal: Spacing.md, paddingVertical: Spacing.sm, }, modalButtonCancelText: { fontSize: FontSizes.base, color: AppColors.textSecondary, }, modalButtonSave: { backgroundColor: AppColors.primary, paddingHorizontal: Spacing.lg, paddingVertical: Spacing.sm, borderRadius: BorderRadius.md, }, modalButtonSaveText: { fontSize: FontSizes.base, fontWeight: '600', color: AppColors.white, }, modalButtonDisabled: { backgroundColor: AppColors.textMuted, }, disabledText: { opacity: 0.5, }, // Radio button styles radioOption: { flexDirection: 'row', alignItems: 'center', paddingVertical: Spacing.sm + 4, marginBottom: Spacing.xs, }, radioCircle: { width: 24, height: 24, borderRadius: 12, borderWidth: 2, borderColor: AppColors.primary, alignItems: 'center', justifyContent: 'center', marginRight: Spacing.md, }, radioCircleSelected: { width: 12, height: 12, borderRadius: 6, backgroundColor: AppColors.primary, }, radioTextContainer: { flex: 1, }, radioLabel: { fontSize: FontSizes.base, fontWeight: '500', color: AppColors.textPrimary, marginBottom: 2, }, radioDescription: { fontSize: FontSizes.xs, color: AppColors.textSecondary, }, });