/* * File: SettingsScreen.tsx * Description: Main settings screen with profile management and app preferences * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */ import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, ScrollView, Alert, RefreshControl, Image, TouchableOpacity, ActivityIndicator, ActionSheetIOS, Platform, PermissionsAndroid, } from 'react-native'; import { theme } from '../../../theme/theme'; import { SettingsSection, SettingsSectionData, SettingsItem } from '../../../shared/types'; import { SettingsHeader } from '../components/SettingsHeader'; import { SettingsSectionComponent } from '../components/SettingsSectionComponent'; import { ProfileCard } from '../components/ProfileCard'; import { CustomModal } from '../../../shared/components'; import { useAppDispatch, useAppSelector } from '../../../store/hooks'; import { logoutUser } from '../../Auth/redux/authActions'; import { updateUserProfile } from '../../Auth/redux/authSlice'; import { selectUser, selectUserDisplayName, selectUserEmail, selectUserFirstName, selectUserLastName, selectUserProfilePhoto, selectDashboardSettings } from '../../Auth/redux/authSelectors'; import { API_CONFIG } from '../../../shared/utils'; import { authAPI } from '../../Auth/services/authAPI'; import { launchImageLibrary, launchCamera, ImagePickerResponse, MediaType } from 'react-native-image-picker'; import Icon from 'react-native-vector-icons/Feather'; /** * SettingsScreenProps Interface * * Purpose: Defines the props required by the SettingsScreen component * * Props: * - navigation: React Navigation object for screen navigation */ interface SettingsScreenProps { navigation: any; } /** * SettingsScreen Component * * Purpose: Main settings screen for user profile management and app preferences * * Features: * 1. User profile overview and quick access * 2. Comprehensive settings sections * 3. Navigation to detailed settings screens * 4. Pull-to-refresh functionality * 5. Mock data generation for demonstration * * Settings Sections: * - Profile: Personal and professional information * - Notifications: Alert and notification preferences * - Clinical: Clinical workflow preferences * - Privacy: Security and privacy settings * - Accessibility: Accessibility features * - About: App information and help * - Logout: Sign out functionality */ export const SettingsScreen: React.FC = ({ navigation, }) => { // ============================================================================ // STATE MANAGEMENT // ============================================================================ // Settings sections state const [settingsSections, setSettingsSections] = useState([]); // UI state const [refreshing, setRefreshing] = useState(false); // Profile photo state const [uploadingPhoto, setUploadingPhoto] = useState(false); const [tempProfilePhoto, setTempProfilePhoto] = useState(null); // Upload response interface interface UploadPhotoResponse { success: boolean; message?: string; data?: { profile_photo_url: string; }; } // Modal state const [modalVisible, setModalVisible] = useState(false); const [modalConfig, setModalConfig] = useState({ title: '', message: '', type: 'info' as 'success' | 'error' | 'warning' | 'info' | 'confirm', onConfirm: () => {}, showCancel: false, icon: '', }); // Redux dispatch and selectors const dispatch = useAppDispatch(); // User data from Redux const user = useAppSelector(selectUser); const userDisplayName = useAppSelector(selectUserDisplayName); const userEmail = useAppSelector(selectUserEmail); const userFirstName = useAppSelector(selectUserFirstName); const userLastName = useAppSelector(selectUserLastName); const userProfilePhoto = useAppSelector(selectUserProfilePhoto); const dashboardSettings = useAppSelector(selectDashboardSettings); // ============================================================================ // SETTINGS SECTIONS GENERATION // ============================================================================ /** * generateSettingsSections Function * * Purpose: Generate settings sections with items for the settings screen * * Returns: Array of SettingsSectionData with navigation and action items */ const generateSettingsSections = (): SettingsSectionData[] => [ { id: 'PROFILE', title: 'Profile & Account', items: [ { id: 'edit-profile', title: 'Edit Profile', subtitle: 'Update personal and professional information', icon: 'user', type: 'NAVIGATION', onPress: () => handleNavigation('PROFILE'), }, { id: 'change-password', title: 'Change Password', subtitle: 'Update your account password', icon: 'lock', type: 'NAVIGATION', onPress: () => handleNavigation('CHANGE_PASSWORD'), }, ], }, { id: 'ABOUT', title: 'About & Support', items: [ { id: 'app-info', title: 'App Information', subtitle: 'Version, build number, and details', icon: 'smartphone', type: 'NAVIGATION', onPress: () => handleNavigation('APP_INFO'), }, { id: 'help-support', title: 'Help & Support', subtitle: 'Contact support and view documentation', icon: 'help', type: 'NAVIGATION', onPress: () => handleNavigation('HELP'), }, ], }, { id: 'LOGOUT', title: 'Account', items: [ { id: 'logout', title: 'Sign Out', subtitle: 'Sign out of your account', icon: 'log-out', type: 'ACTION', onPress: handleLogout, }, ], }, ]; // ============================================================================ // EFFECTS // ============================================================================ /** * useEffect for initial settings sections generation * * Purpose: Generate settings sections when component mounts or user data changes */ useEffect(() => { setSettingsSections(generateSettingsSections()); }, [user, dashboardSettings]); // ============================================================================ // PERMISSION HANDLERS // ============================================================================ /** * requestCameraPermission Function * * Purpose: Request camera permission for Android devices * * @returns Promise - Whether permission was granted */ const requestCameraPermission = async (): Promise => { if (Platform.OS === 'android') { try { const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.CAMERA, { title: 'Camera Permission', message: 'This app needs camera permission to capture profile photos.', buttonNeutral: 'Ask Me Later', buttonNegative: 'Cancel', buttonPositive: 'OK', } ); return granted === PermissionsAndroid.RESULTS.GRANTED; } catch (err) { console.warn('Camera permission error:', err); return false; } } return true; // iOS permissions are handled via Info.plist }; // ============================================================================ // EVENT HANDLERS // ============================================================================ /** * handleProfilePhotoUpdate Function * * Purpose: Show action sheet with camera and gallery options for profile photo update * * Flow: * 1. Show action sheet with camera and gallery options * 2. Handle user selection * 3. Launch appropriate image picker */ const handleProfilePhotoUpdate = () => { if (Platform.OS === 'ios') { ActionSheetIOS.showActionSheetWithOptions( { options: ['Cancel', 'Take Photo', 'Choose from Gallery'], cancelButtonIndex: 0, userInterfaceStyle: 'light', }, (buttonIndex) => { if (buttonIndex === 1) { handleCameraCapture(); } else if (buttonIndex === 2) { handleGallerySelection(); } } ); } else { // For Android, show custom action sheet or Alert Alert.alert( 'Update Profile Photo', 'Choose an option', [ { text: 'Cancel', style: 'cancel' }, { text: 'Take Photo', onPress: handleCameraCapture }, { text: 'Choose from Gallery', onPress: handleGallerySelection }, ] ); } }; /** * handleCameraCapture Function * * Purpose: Launch camera to capture new profile photo * * Flow: * 1. Check camera permissions * 2. Launch camera with callback * 3. Validate captured image * 4. Upload to server * 5. Update local state */ const handleCameraCapture = async () => { try { // Check camera permission first const hasPermission = await requestCameraPermission(); if (!hasPermission) { setModalConfig({ title: 'Permission Required', message: 'Camera permission is required to capture profile photos.', type: 'error', onConfirm: () => {}, showCancel: false, icon: 'camera', }); setModalVisible(true); return; } // Launch camera with callback const options = { mediaType: 'photo' as MediaType, quality: 0.8 as const, maxWidth: 800, maxHeight: 800, saveToPhotos: false, includeBase64: false, }; launchCamera(options, (response: ImagePickerResponse) => { try { // Handle user cancellation if (response.didCancel) { return; } // Handle errors if (response.errorMessage) { throw new Error(response.errorMessage); } // Validate response and assets if (!response.assets || response.assets.length === 0) { throw new Error('No image captured'); } const asset = response.assets[0]; if (!asset.uri) { throw new Error('Invalid image data'); } // Validate file size (max 5MB) if (asset.fileSize && asset.fileSize > 5 * 1024 * 1024) { throw new Error('Image size must be less than 5MB'); } // Set temporary photo for preview setTempProfilePhoto(asset.uri); // Upload the captured photo uploadProfilePhoto(asset.uri); } catch (error) { console.error('Camera capture processing error:', error); setModalConfig({ title: 'Camera Error', message: error instanceof Error ? error.message : 'Failed to capture photo', type: 'error', onConfirm: () => {}, showCancel: false, icon: 'alert-circle', }); setModalVisible(true); } }); } catch (error) { console.error('Camera launch error:', error); setModalConfig({ title: 'Error', message: 'Failed to launch camera. Please try again.', type: 'error', onConfirm: () => {}, showCancel: false, icon: 'alert-circle', }); setModalVisible(true); } }; /** * handleGallerySelection Function * * Purpose: Launch gallery to select existing profile photo * * Flow: * 1. Launch image picker with callback * 2. Validate selected image * 3. Upload to server * 4. Update local state */ const handleGallerySelection = () => { try { // Launch image picker with callback const options = { mediaType: 'photo' as MediaType, quality: 0.8 as const, maxWidth: 800, maxHeight: 800, includeBase64: false, }; launchImageLibrary(options, (response: ImagePickerResponse) => { try { // Handle user cancellation if (response.didCancel) { return; } // Handle errors if (response.errorMessage) { throw new Error(response.errorMessage); } // Validate response and assets if (!response.assets || response.assets.length === 0) { throw new Error('No image selected'); } const asset = response.assets[0]; if (!asset.uri) { throw new Error('Invalid image data'); } // Validate file size (max 5MB) if (asset.fileSize && asset.fileSize > 5 * 1024 * 1024) { throw new Error('Image size must be less than 5MB'); } // Set temporary photo for preview setTempProfilePhoto(asset.uri); // Upload the selected photo uploadProfilePhoto(asset.uri); } catch (error) { console.error('Gallery selection processing error:', error); setModalConfig({ title: 'Gallery Error', message: error instanceof Error ? error.message : 'Failed to select photo', type: 'error', onConfirm: () => {}, showCancel: false, icon: 'alert-circle', }); setModalVisible(true); } }); } catch (error) { console.error('Gallery launch error:', error); setModalConfig({ title: 'Error', message: 'Failed to open gallery. Please try again.', type: 'error', onConfirm: () => {}, showCancel: false, icon: 'alert-circle', }); setModalVisible(true); } }; /** * uploadProfilePhoto Function * * Purpose: Upload selected profile photo to server * * @param imageUri - URI of the selected image */ const uploadProfilePhoto = async (imageUri: string) => { try { setUploadingPhoto(true); // Create form data const formData = new FormData(); formData.append('profile_photo', { uri: imageUri, type: 'image/jpeg', name: 'profile_photo.jpg', } as any); // Get user token from Redux const token = user?.access_token; if (!token) { throw new Error('Authentication token not found'); } // Upload using authAPI const response = await authAPI.uploadProfilePhoto(formData, token); // Type the response properly const responseData = response.data as UploadPhotoResponse; if (responseData.success) { // Update local state with new photo setTempProfilePhoto(null); // Update Redux state with new profile photo URL if (responseData.data?.profile_photo_url) { console.log('Updating user profile with new photo URL:', responseData.data.profile_photo_url); dispatch(updateUserProfile({ self_url: responseData.data.profile_photo_url })); console.log('Redux state updated successfully'); } // Show success message setModalConfig({ title: 'Success', message: responseData.message || 'Profile photo updated successfully!', type: 'success', icon: 'check-circle', onConfirm: () => { // Optional: Refresh if needed, but Redux update should be enough // handleRefresh(); }, showCancel: false, }); setModalVisible(true); } else { throw new Error(responseData.message || 'Upload failed'); } } catch (error) { console.error('Error uploading photo:', error); setModalConfig({ title: 'Upload Failed', message: error instanceof Error ? error.message : 'Failed to upload profile photo', type: 'error', icon: 'alert-circle', onConfirm: () => {}, showCancel: false, }); setModalVisible(true); } finally { setUploadingPhoto(false); } }; /** * handleRefresh Function * * Purpose: Handle pull-to-refresh functionality to update settings data * * Flow: * 1. Set refreshing state to true (show loading indicator) * 2. Simulate API call with delay * 3. Regenerate settings sections with current user data * 4. Set refreshing state to false (hide loading indicator) */ const handleRefresh = async () => { setRefreshing(true); // Simulate API call with 1-second delay await new Promise(resolve => setTimeout(() => resolve(), 1000)); // Regenerate settings sections with current user data setSettingsSections(generateSettingsSections()); setRefreshing(false); }; /** * handleNavigation Function * * Purpose: Handle navigation to different settings screens * * @param screen - Screen to navigate to */ const handleNavigation = (screen: string) => { switch (screen) { case 'APP_INFO': navigation.navigate('AppInfoScreen'); break; case 'PROFILE': navigation.navigate('EditProfileScreen'); break; case 'CHANGE_PASSWORD': navigation.navigate('ChangePasswordScreen'); break; case 'HELP': // TODO: Implement help and support setModalConfig({ title: 'Help & Support', message: 'Help and support functionality coming soon!', type: 'info', onConfirm: () => {}, showCancel: false, icon: 'info', }); setModalVisible(true); break; default: console.log('Navigate to:', screen); setModalConfig({ title: 'Navigation', message: `Navigate to ${screen} screen`, type: 'info', onConfirm: () => {}, showCancel: false, icon: 'info', }); setModalVisible(true); } }; /** * handleToggleSetting Function * * Purpose: Handle toggle settings changes * * @param setting - Setting to toggle */ const handleToggleSetting = (setting: string) => { // TODO: Implement setting toggle logic console.log('Toggle setting:', setting); setModalConfig({ title: 'Setting Toggle', message: `Toggle ${setting} setting`, type: 'info', icon: 'info', onConfirm: () => {}, showCancel: false, }); setModalVisible(true); }; /** * handleLogout Function * * Purpose: Handle user logout with Redux integration * * Flow: * 1. Show confirmation dialog * 2. Dispatch logout action to Redux * 3. Clear authentication state * 4. Show success message * 5. Automatically navigate to login screen via Redux state change */ const handleLogout = () => { setModalConfig({ title: 'Sign Out', message: 'Are you sure you want to sign out?', type: 'confirm', icon: 'log-out', onConfirm: async () => { try { // Dispatch logout thunk to Redux await dispatch(logoutUser()); // Log the logout action console.log('User logged out successfully'); } catch (error) { console.error('Logout error:', error); setModalConfig({ title: 'Error', message: 'Failed to sign out. Please try again.', type: 'error', icon: 'info', onConfirm: () => {}, showCancel: false, }); setModalVisible(true); } }, showCancel: true, }); setModalVisible(true); }; // ============================================================================ // MAIN RENDER // ============================================================================ return ( {/* Settings header with title */} {/* Scrollable settings content */} } showsVerticalScrollIndicator={false} > {/* Profile card section */} {user && ( {tempProfilePhoto ? ( ) : user.self_url ? ( ) : ( {user.first_name.charAt(0)}{user.last_name.charAt(0)} )} {/* Edit icon overlay */} {/* Loading indicator */} {uploadingPhoto && ( )} {user.display_name || `${user.first_name} ${user.last_name}`} {user.email} Radiologist )} {/* Settings sections */} {settingsSections.map((section, index) => React.createElement(SettingsSectionComponent, { key: `${section.id}-${index}`, section: section }) )} {/* Bottom spacing for tab bar */} {/* Custom Modal */} setModalVisible(false)} /> ); }; // ============================================================================ // STYLES SECTION // ============================================================================ const styles = StyleSheet.create({ // Main container for the settings screen container: { flex: 1, backgroundColor: theme.colors.background, }, // Loading container for initial data loading loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: theme.colors.background, }, // Loading text styling loadingText: { fontSize: theme.typography.fontSize.bodyLarge, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.medium, }, // Scroll view styling scrollView: { flex: 1, }, // Scroll content styling scrollContent: { paddingHorizontal: theme.spacing.md, }, // Bottom spacing for tab bar bottomSpacing: { height: theme.spacing.xl, }, // Profile card styles profileCard: { backgroundColor: theme.colors.background, borderRadius: theme.borderRadius.medium, padding: theme.spacing.md, marginBottom: theme.spacing.md, ...theme.shadows.primary, }, profileHeader: { flexDirection: 'row', alignItems: 'center', }, profileImageContainer: { marginRight: theme.spacing.md, }, profileImage: { width: 60, height: 60, borderRadius: 30, backgroundColor:theme.colors.primary, }, fallbackAvatar: { width: 60, height: 60, borderRadius: 30, backgroundColor: theme.colors.primary, justifyContent: 'center', alignItems: 'center', }, fallbackText: { color: theme.colors.background, fontSize: theme.typography.fontSize.bodyLarge, fontFamily: theme.typography.fontFamily.bold, }, profileInfo: { flex: 1, }, profileName: { fontSize: theme.typography.fontSize.bodyLarge, fontFamily: theme.typography.fontFamily.bold, color: theme.colors.textPrimary, marginBottom: theme.spacing.xs, }, profileEmail: { fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.regular, color: theme.colors.textSecondary, marginBottom: theme.spacing.xs, }, profileRole: { fontSize: theme.typography.fontSize.bodySmall, fontFamily: theme.typography.fontFamily.medium, color: theme.colors.primary, }, // Edit icon overlay for profile photo update editIconOverlay: { position: 'absolute', bottom: 0, right: 0, backgroundColor: theme.colors.primary, borderRadius: 12, width: 24, height: 24, justifyContent: 'center', alignItems: 'center', borderWidth: 2, borderColor: theme.colors.background, }, // Uploading overlay with loading indicator uploadingOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.5)', borderRadius: 30, }, }); /* * End of File: SettingsScreen.tsx * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */