945 lines
26 KiB
TypeScript
945 lines
26 KiB
TypeScript
/*
|
|
* 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<SettingsScreenProps> = ({
|
|
navigation,
|
|
}) => {
|
|
// ============================================================================
|
|
// STATE MANAGEMENT
|
|
// ============================================================================
|
|
|
|
// Settings sections state
|
|
const [settingsSections, setSettingsSections] = useState<SettingsSectionData[]>([]);
|
|
|
|
// UI state
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
|
|
// Profile photo state
|
|
const [uploadingPhoto, setUploadingPhoto] = useState(false);
|
|
const [tempProfilePhoto, setTempProfilePhoto] = useState<string | null>(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: 'phone',
|
|
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<boolean> - Whether permission was granted
|
|
*/
|
|
const requestCameraPermission = async (): Promise<boolean> => {
|
|
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) {
|
|
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<void>(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 (
|
|
<View style={styles.container}>
|
|
{/* Settings header with title */}
|
|
<SettingsHeader title="Settings" />
|
|
|
|
{/* Scrollable settings content */}
|
|
<ScrollView
|
|
style={styles.scrollView}
|
|
contentContainerStyle={styles.scrollContent}
|
|
refreshControl={
|
|
<RefreshControl
|
|
refreshing={refreshing}
|
|
onRefresh={handleRefresh}
|
|
colors={[theme.colors.primary]}
|
|
tintColor={theme.colors.primary}
|
|
/>
|
|
}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
{/* Profile card section */}
|
|
{user && (
|
|
<View style={styles.profileCard}>
|
|
<View style={styles.profileHeader}>
|
|
<TouchableOpacity
|
|
style={styles.profileImageContainer}
|
|
onPress={handleProfilePhotoUpdate}
|
|
disabled={uploadingPhoto}
|
|
>
|
|
{tempProfilePhoto ? (
|
|
<Image
|
|
source={{ uri: tempProfilePhoto }}
|
|
style={styles.profileImage}
|
|
resizeMode="cover"
|
|
/>
|
|
) : user.self_url ? (
|
|
<Image
|
|
source={{ uri: API_CONFIG.BASE_URL + '/api/auth' + user.self_url }}
|
|
style={styles.profileImage}
|
|
resizeMode="cover"
|
|
/>
|
|
) : (
|
|
<View style={styles.fallbackAvatar}>
|
|
<Text style={styles.fallbackText}>
|
|
{user.first_name.charAt(0)}{user.last_name.charAt(0)}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
|
|
{/* Edit icon overlay */}
|
|
<View style={styles.editIconOverlay}>
|
|
<Icon name="edit-3" size={16} color={theme.colors.background} />
|
|
</View>
|
|
|
|
{/* Loading indicator */}
|
|
{uploadingPhoto && (
|
|
<View style={styles.uploadingOverlay}>
|
|
<ActivityIndicator
|
|
size="small"
|
|
color={theme.colors.primary}
|
|
/>
|
|
</View>
|
|
)}
|
|
</TouchableOpacity>
|
|
|
|
<View style={styles.profileInfo}>
|
|
<Text style={styles.profileName}>
|
|
{user.display_name || `${user.first_name} ${user.last_name}`}
|
|
</Text>
|
|
<Text style={styles.profileEmail}>{user.email}</Text>
|
|
<Text style={styles.profileRole}>Radiologist</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{/* Settings sections */}
|
|
{settingsSections.map((section, index) =>
|
|
React.createElement(SettingsSectionComponent, {
|
|
key: `${section.id}-${index}`,
|
|
section: section
|
|
})
|
|
)}
|
|
|
|
{/* Bottom spacing for tab bar */}
|
|
<View style={styles.bottomSpacing} />
|
|
</ScrollView>
|
|
|
|
{/* Custom Modal */}
|
|
<CustomModal
|
|
visible={modalVisible}
|
|
title={modalConfig.title}
|
|
message={modalConfig.message}
|
|
type={modalConfig.type}
|
|
onConfirm={modalConfig.onConfirm}
|
|
showCancel={modalConfig.showCancel}
|
|
icon={modalConfig.icon}
|
|
confirmText={modalConfig.type === 'confirm' ? 'Sign Out' : 'OK'}
|
|
cancelText="Cancel"
|
|
onClose={() => setModalVisible(false)}
|
|
/>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
// ============================================================================
|
|
// 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.
|
|
*/
|