mirror of
https://github.com/mudabbir-ahmad/Media-Aggregation-and-Sorting-System.git
synced 2026-10-08 03:40:20 +00:00
created a localised login system
This commit is contained in:
1 parent
59ac1fc926
commit
37511780b7
16 files changed
+3721
-20
No files matched your search
@@ -0,0 +1,821 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
Modal,
|
||||
TouchableOpacity,
|
||||
Dimensions,
|
||||
Image,
|
||||
Alert,
|
||||
ScrollView,
|
||||
RefreshControl,
|
||||
} from 'react-native';
|
||||
import DatabaseService from '../services/DatabaseService';
|
||||
import GoogleAUTH from '../AUTH/GoogleAUTH';
|
||||
import IOSAUTH from '../AUTH/IOSAUTH';
|
||||
import NASAUTH from '../AUTH/NASAUTH';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
const GRID_COLUMNS = 3;
|
||||
const imageSize = (width - 50) / GRID_COLUMNS;
|
||||
|
||||
/**
|
||||
* HomeScreen - Main photo gallery
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Load user data from database
|
||||
* - Fetch photos from all connected services
|
||||
* - Display photos in grid layout
|
||||
* - Handle photo viewing in modal
|
||||
* - Manage logout
|
||||
*/
|
||||
|
||||
const HomeScreen = ({ navigation, route }) => {
|
||||
const userId = route?.params?.userId;
|
||||
|
||||
// User data
|
||||
const [currentUser, setCurrentUser] = useState(null);
|
||||
const [linkedAccounts, setLinkedAccounts] = useState([]);
|
||||
|
||||
// Media data
|
||||
const [allMedia, setAllMedia] = useState([]);
|
||||
const [filteredMedia, setFilteredMedia] = useState([]);
|
||||
const [selectedFilter, setSelectedFilter] = useState('all');
|
||||
|
||||
// UI state
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [selectedMedia, setSelectedMedia] = useState(null);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// Effect: Load user data on mount
|
||||
useEffect(() => {
|
||||
if (!userId) {
|
||||
console.error('No userId provided to HomeScreen');
|
||||
setError('User ID is missing');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
loadUserData();
|
||||
}, [userId]);
|
||||
|
||||
// Effect: Load media when user data is ready
|
||||
useEffect(() => {
|
||||
if (currentUser) {
|
||||
loadAllMedia();
|
||||
}
|
||||
}, [currentUser]);
|
||||
|
||||
// Effect: Filter media when filter changes
|
||||
useEffect(() => {
|
||||
filterMedia();
|
||||
}, [selectedFilter, allMedia]);
|
||||
|
||||
/**
|
||||
* Load user and linked accounts from database
|
||||
*/
|
||||
const loadUserData = async () => {
|
||||
try {
|
||||
const user = await DatabaseService.getUserById(userId);
|
||||
setCurrentUser(user);
|
||||
|
||||
const accounts = await DatabaseService.getLinkedAccounts(userId);
|
||||
setLinkedAccounts(accounts);
|
||||
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
console.error('Error loading user data:', err);
|
||||
setError('Failed to load user information');
|
||||
Alert.alert('Error', 'Failed to load user information');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Load media from all connected services
|
||||
*/
|
||||
const loadAllMedia = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// First, try to get media from database
|
||||
const mediaFromDb = await DatabaseService.getMediaByUser(userId);
|
||||
|
||||
// Then fetch new media from each connected service
|
||||
const newMedia = [];
|
||||
|
||||
// Fetch from Google Photos if enabled
|
||||
const googleAccount = linkedAccounts.find(
|
||||
(acc) => acc.service_type === 'GOOGLE_PHOTOS'
|
||||
);
|
||||
if (googleAccount) {
|
||||
try {
|
||||
const googleAuth = new GoogleAUTH();
|
||||
console.log('Google Photos would be fetched here');
|
||||
} catch (err) {
|
||||
console.error('Google Photos fetch failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from iCloud if enabled
|
||||
const iosAccount = linkedAccounts.find(
|
||||
(acc) => acc.service_type === 'ICLOUD_PHOTOS'
|
||||
);
|
||||
if (iosAccount) {
|
||||
try {
|
||||
const iosAuth = new IOSAUTH();
|
||||
console.log('iCloud Photos would be fetched here');
|
||||
} catch (err) {
|
||||
console.error('iCloud Photos fetch failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from NAS if enabled
|
||||
const nasAccount = linkedAccounts.find(
|
||||
(acc) => acc.service_type === 'NAS'
|
||||
);
|
||||
if (nasAccount) {
|
||||
try {
|
||||
const nasAuth = new NASAUTH();
|
||||
const nasData = nasAccount.account_data;
|
||||
|
||||
// Connect to NAS via backend API
|
||||
await nasAuth.authenticate(
|
||||
nasData.host,
|
||||
nasData.port,
|
||||
nasData.username,
|
||||
nasData.password,
|
||||
nasData.photoFolder
|
||||
);
|
||||
|
||||
// Get photos
|
||||
const nasPhotos = await nasAuth.getPhotos(nasData.photoFolder);
|
||||
if (nasPhotos.mediaItems) {
|
||||
newMedia.push(...nasPhotos.mediaItems);
|
||||
}
|
||||
|
||||
// Disconnect
|
||||
await nasAuth.revokeAccess();
|
||||
} catch (err) {
|
||||
console.error('NAS Photos fetch failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Combine database media with newly fetched media
|
||||
const combinedMedia = [...mediaFromDb, ...newMedia];
|
||||
|
||||
// Remove duplicates
|
||||
const uniqueMedia = Array.from(
|
||||
new Map(combinedMedia.map((m) => [m.source_id || m.id || m.path, m])).values()
|
||||
);
|
||||
|
||||
setAllMedia(uniqueMedia);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
console.error('Error loading media:', err);
|
||||
setError('Failed to load photos');
|
||||
Alert.alert('Error', 'Failed to load photos from services');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter media based on selected service
|
||||
*/
|
||||
const filterMedia = () => {
|
||||
let filtered = allMedia;
|
||||
|
||||
if (selectedFilter !== 'all') {
|
||||
filtered = allMedia.filter(
|
||||
(media) => media.source_service === selectedFilter
|
||||
);
|
||||
}
|
||||
|
||||
// Sort by date (newest first)
|
||||
filtered.sort((a, b) => {
|
||||
const dateA = new Date(a.created_date || a.modifyTime || 0);
|
||||
const dateB = new Date(b.created_date || b.modifyTime || 0);
|
||||
return dateB - dateA;
|
||||
});
|
||||
|
||||
setFilteredMedia(filtered);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle refresh gesture
|
||||
*/
|
||||
const onRefresh = () => {
|
||||
setRefreshing(true);
|
||||
loadAllMedia();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle logout
|
||||
*/
|
||||
const handleLogout = () => {
|
||||
Alert.alert(
|
||||
'Logout',
|
||||
'Are you sure you want to logout?',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{
|
||||
text: 'Logout',
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
navigation.replace('Login');
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get service display name
|
||||
*/
|
||||
const getServiceName = (serviceType) => {
|
||||
const serviceMap = {
|
||||
GOOGLE_PHOTOS: 'Google Photos',
|
||||
ICLOUD_PHOTOS: 'iCloud',
|
||||
NAS: 'NAS',
|
||||
};
|
||||
return serviceMap[serviceType] || serviceType;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get service icon
|
||||
*/
|
||||
const getServiceIcon = (serviceType) => {
|
||||
const iconMap = {
|
||||
GOOGLE_PHOTOS: '🔵',
|
||||
ICLOUD_PHOTOS: '🍎',
|
||||
NAS: '💾',
|
||||
};
|
||||
return iconMap[serviceType] || '📱';
|
||||
};
|
||||
|
||||
/**
|
||||
* Render grid item (photo)
|
||||
*/
|
||||
const renderMediaItem = ({ item }) => (
|
||||
<TouchableOpacity
|
||||
style={styles.gridItem}
|
||||
onPress={() => {
|
||||
setSelectedMedia(item);
|
||||
setModalVisible(true);
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{item.local_path ? (
|
||||
<Image
|
||||
source={{ uri: item.local_path }}
|
||||
style={styles.gridImage}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.placeholderImage}>
|
||||
<Text style={styles.placeholderIcon}>📷</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{item.media_type === 'video' && (
|
||||
<View style={styles.videoOverlay}>
|
||||
<Text style={styles.playIcon}>▶</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.serviceBadge}>
|
||||
<Text style={styles.badgeIcon}>{getServiceIcon(item.source_service)}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
// Loading state
|
||||
if (loading) {
|
||||
return (
|
||||
<View style={styles.centerContainer}>
|
||||
<ActivityIndicator size="large" color="#007AFF" />
|
||||
<Text style={styles.loadingText}>Loading your photos...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Error state
|
||||
if (error && filteredMedia.length === 0) {
|
||||
return (
|
||||
<View style={styles.centerContainer}>
|
||||
<Text style={styles.errorIcon}>⚠️</Text>
|
||||
<Text style={styles.errorTitle}>Unable to Load Photos</Text>
|
||||
<Text style={styles.errorText}>{error}</Text>
|
||||
<TouchableOpacity style={styles.retryButton} onPress={loadAllMedia}>
|
||||
<Text style={styles.retryButtonText}>Try Again</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Empty state
|
||||
if (filteredMedia.length === 0) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<View>
|
||||
<Text style={styles.headerTitle}>Photo Library</Text>
|
||||
<Text style={styles.headerSubtitle}>
|
||||
Welcome, {currentUser?.username}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity style={styles.logoutButton} onPress={handleLogout}>
|
||||
<Text style={styles.logoutText}>Logout</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<View style={styles.centerContainer}>
|
||||
<Text style={styles.emptyIcon}>📸</Text>
|
||||
<Text style={styles.emptyTitle}>No Photos Yet</Text>
|
||||
<Text style={styles.emptyText}>
|
||||
Photos from your connected services will appear here
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Main content
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<View>
|
||||
<Text style={styles.headerTitle}>Photo Library</Text>
|
||||
<Text style={styles.headerSubtitle}>
|
||||
Welcome, {currentUser?.username}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity style={styles.logoutButton} onPress={handleLogout}>
|
||||
<Text style={styles.logoutText}>Logout</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{linkedAccounts.length > 0 && (
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.filterScroll}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.filterButton,
|
||||
selectedFilter === 'all' && styles.filterButtonActive,
|
||||
]}
|
||||
onPress={() => setSelectedFilter('all')}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.filterButtonText,
|
||||
selectedFilter === 'all' && styles.filterButtonTextActive,
|
||||
]}
|
||||
>
|
||||
All
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{linkedAccounts.map((account) => (
|
||||
<TouchableOpacity
|
||||
key={account.id}
|
||||
style={[
|
||||
styles.filterButton,
|
||||
selectedFilter === account.service_type &&
|
||||
styles.filterButtonActive,
|
||||
]}
|
||||
onPress={() => setSelectedFilter(account.service_type)}
|
||||
>
|
||||
<Text style={styles.filterIcon}>
|
||||
{getServiceIcon(account.service_type)}
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.filterButtonText,
|
||||
selectedFilter === account.service_type &&
|
||||
styles.filterButtonTextActive,
|
||||
]}
|
||||
>
|
||||
{getServiceName(account.service_type)}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
<FlatList
|
||||
data={filteredMedia}
|
||||
renderItem={renderMediaItem}
|
||||
keyExtractor={(item) => item.id?.toString() || item.path || Math.random().toString()}
|
||||
numColumns={GRID_COLUMNS}
|
||||
columnWrapperStyle={styles.columnWrapper}
|
||||
contentContainerStyle={styles.gridContent}
|
||||
scrollEnabled={true}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
||||
}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
visible={modalVisible}
|
||||
transparent={true}
|
||||
onRequestClose={() => setModalVisible(false)}
|
||||
animationType="fade"
|
||||
>
|
||||
<MediaDetailModal
|
||||
media={selectedMedia}
|
||||
onClose={() => setModalVisible(false)}
|
||||
serviceIcon={getServiceIcon(selectedMedia?.source_service)}
|
||||
serviceName={getServiceName(selectedMedia?.source_service)}
|
||||
/>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* MediaDetailModal - Full screen photo viewer
|
||||
*/
|
||||
const MediaDetailModal = ({ media, onClose, serviceIcon, serviceName }) => {
|
||||
if (!media) return null;
|
||||
|
||||
return (
|
||||
<View style={styles.modalContainer}>
|
||||
<TouchableOpacity
|
||||
style={styles.modalBackdrop}
|
||||
onPress={onClose}
|
||||
activeOpacity={1}
|
||||
/>
|
||||
|
||||
<View style={styles.modalContent}>
|
||||
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
|
||||
<Text style={styles.closeButtonText}>✕</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<ScrollView
|
||||
style={styles.imageContainer}
|
||||
contentContainerStyle={styles.imageContent}
|
||||
scrollEnabled={true}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{media.local_path ? (
|
||||
<Image
|
||||
source={{ uri: media.local_path }}
|
||||
style={styles.fullImage}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.placeholderLarge}>
|
||||
<Text style={styles.placeholderLargeIcon}>📷</Text>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
<View style={styles.infoPanel}>
|
||||
<Text style={styles.infoTitle}>{media.title || 'Untitled Photo'}</Text>
|
||||
|
||||
{media.description && (
|
||||
<Text style={styles.infoDescription}>{media.description}</Text>
|
||||
)}
|
||||
|
||||
<View style={styles.infoDivider} />
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={styles.infoLabel}>From</Text>
|
||||
<View style={styles.infoServiceBadge}>
|
||||
<Text style={styles.infoServiceIcon}>{serviceIcon}</Text>
|
||||
<Text style={styles.infoServiceName}>{serviceName}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{media.media_type && (
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={styles.infoLabel}>Type</Text>
|
||||
<Text style={styles.infoValue}>
|
||||
{media.media_type === 'video' ? '🎥 Video' : '📷 Photo'}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{(media.created_date || media.modifyTime) && (
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={styles.infoLabel}>Date</Text>
|
||||
<Text style={styles.infoValue}>
|
||||
{new Date(
|
||||
media.created_date || media.modifyTime
|
||||
).toLocaleDateString()}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{media.size && (
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={styles.infoLabel}>Size</Text>
|
||||
<Text style={styles.infoValue}>
|
||||
{(media.size / 1024 / 1024).toFixed(2)} MB
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f5f5f5',
|
||||
},
|
||||
centerContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
header: {
|
||||
backgroundColor: '#fff',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 16,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f0f0f0',
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 28,
|
||||
fontWeight: 'bold',
|
||||
color: '#1a1a1a',
|
||||
},
|
||||
headerSubtitle: {
|
||||
fontSize: 13,
|
||||
color: '#999',
|
||||
marginTop: 2,
|
||||
},
|
||||
logoutButton: {
|
||||
backgroundColor: '#ff3b30',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 6,
|
||||
},
|
||||
logoutText: {
|
||||
color: '#fff',
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
},
|
||||
filterScroll: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 12,
|
||||
gap: 8,
|
||||
},
|
||||
filterButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 16,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e0e0e0',
|
||||
gap: 6,
|
||||
},
|
||||
filterButtonActive: {
|
||||
backgroundColor: '#007AFF',
|
||||
borderColor: '#007AFF',
|
||||
},
|
||||
filterButtonText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
color: '#333',
|
||||
},
|
||||
filterButtonTextActive: {
|
||||
color: '#fff',
|
||||
},
|
||||
filterIcon: {
|
||||
fontSize: 14,
|
||||
},
|
||||
columnWrapper: {
|
||||
gap: 8,
|
||||
paddingHorizontal: 8,
|
||||
marginBottom: 8,
|
||||
},
|
||||
gridContent: {
|
||||
paddingVertical: 8,
|
||||
paddingBottom: 20,
|
||||
},
|
||||
gridItem: {
|
||||
width: imageSize,
|
||||
height: imageSize,
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#e8e8e8',
|
||||
position: 'relative',
|
||||
},
|
||||
gridImage: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
placeholderImage: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#e8e8e8',
|
||||
},
|
||||
placeholderIcon: {
|
||||
fontSize: 32,
|
||||
},
|
||||
videoOverlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
playIcon: {
|
||||
color: '#fff',
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
serviceBadge: {
|
||||
position: 'absolute',
|
||||
bottom: 6,
|
||||
right: 6,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 3,
|
||||
},
|
||||
badgeIcon: {
|
||||
fontSize: 12,
|
||||
},
|
||||
loadingText: {
|
||||
marginTop: 12,
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
},
|
||||
errorIcon: {
|
||||
fontSize: 64,
|
||||
marginBottom: 16,
|
||||
},
|
||||
errorTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: '#333',
|
||||
marginBottom: 8,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
marginBottom: 24,
|
||||
textAlign: 'center',
|
||||
},
|
||||
retryButton: {
|
||||
backgroundColor: '#007AFF',
|
||||
paddingHorizontal: 24,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 8,
|
||||
},
|
||||
retryButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
},
|
||||
emptyIcon: {
|
||||
fontSize: 64,
|
||||
marginBottom: 16,
|
||||
},
|
||||
emptyTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: '#333',
|
||||
marginBottom: 8,
|
||||
},
|
||||
emptyText: {
|
||||
fontSize: 14,
|
||||
color: '#999',
|
||||
textAlign: 'center',
|
||||
},
|
||||
modalContainer: {
|
||||
flex: 1,
|
||||
backgroundColor: '#000',
|
||||
},
|
||||
modalBackdrop: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
modalContent: {
|
||||
flex: 1,
|
||||
zIndex: 10,
|
||||
},
|
||||
closeButton: {
|
||||
position: 'absolute',
|
||||
top: 12,
|
||||
right: 12,
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 20,
|
||||
},
|
||||
closeButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 24,
|
||||
fontWeight: '600',
|
||||
},
|
||||
imageContainer: {
|
||||
flex: 0.6,
|
||||
backgroundColor: '#000',
|
||||
},
|
||||
imageContent: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: '100%',
|
||||
},
|
||||
fullImage: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
placeholderLarge: {
|
||||
width: 200,
|
||||
height: 200,
|
||||
backgroundColor: '#333',
|
||||
borderRadius: 12,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
placeholderLargeIcon: {
|
||||
fontSize: 64,
|
||||
},
|
||||
infoPanel: {
|
||||
flex: 0.4,
|
||||
backgroundColor: '#fff',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
padding: 16,
|
||||
paddingBottom: 24,
|
||||
},
|
||||
infoTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#1a1a1a',
|
||||
marginBottom: 6,
|
||||
},
|
||||
infoDescription: {
|
||||
fontSize: 13,
|
||||
color: '#666',
|
||||
marginBottom: 12,
|
||||
},
|
||||
infoDivider: {
|
||||
height: 1,
|
||||
backgroundColor: '#f0f0f0',
|
||||
marginVertical: 12,
|
||||
},
|
||||
infoRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 10,
|
||||
},
|
||||
infoLabel: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
color: '#999',
|
||||
},
|
||||
infoValue: {
|
||||
fontSize: 13,
|
||||
color: '#333',
|
||||
fontWeight: '600',
|
||||
},
|
||||
infoServiceBadge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
},
|
||||
infoServiceIcon: {
|
||||
fontSize: 14,
|
||||
},
|
||||
infoServiceName: {
|
||||
fontSize: 13,
|
||||
color: '#333',
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
|
||||
export default HomeScreen;
|
||||
|
||||
Reference in new issue
Block a user