mirror of
https://github.com/mudabbir-ahmad/Media-Aggregation-and-Sorting-System.git
synced 2026-10-07 19:40:20 +00:00
slight refactor of all the code, made it so that login and onboarding works, need to add system to validate connections to google photos + NAS before just blindly accepting
Added library home page and navigation bar at the bottom, using emojis as icon placeholders, album page also works. elipses for settings and icon also work.
This commit is contained in:
1 parent
37511780b7
commit
c1d7139055
17 files changed
+1431
-10040
No files matched your search
@@ -0,0 +1,185 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
StyleSheet, View, Text, TextInput, FlatList, TouchableOpacity,
|
||||
Dimensions, Image, Modal, ActivityIndicator,
|
||||
} from 'react-native';
|
||||
import DatabaseService from '../services/DatabaseService';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
const GRID_COLUMNS = 3;
|
||||
const GRID_GAP = 2;
|
||||
const imageSize = (width - GRID_GAP * (GRID_COLUMNS + 1)) / GRID_COLUMNS;
|
||||
|
||||
const SearchScreen = ({ route }) => {
|
||||
const userId = route?.params?.userId;
|
||||
const [query, setQuery] = useState('');
|
||||
const [results, setResults] = useState([]);
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [selectedMedia, setSelectedMedia] = useState(null);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (query.trim().length > 0) {
|
||||
performSearch();
|
||||
} else {
|
||||
setResults([]);
|
||||
}
|
||||
}, 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, [query]);
|
||||
|
||||
const performSearch = async () => {
|
||||
if (!userId) return;
|
||||
setSearching(true);
|
||||
try {
|
||||
const media = await DatabaseService.searchMedia(userId, query.trim());
|
||||
setResults(media);
|
||||
} catch (err) {
|
||||
console.error('Search error:', err);
|
||||
} finally {
|
||||
setSearching(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getServiceIcon = (type) => ({ GOOGLE_PHOTOS: '📷', ICLOUD_PHOTOS: '🍎', NAS: '💾' }[type] || '📱');
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<TouchableOpacity
|
||||
style={styles.gridItem}
|
||||
onPress={() => { setSelectedMedia(item); setModalVisible(true); }}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
{item.local_path ? (
|
||||
<Image source={{ uri: item.local_path }} style={styles.gridImage} resizeMode="cover" />
|
||||
) : (
|
||||
<View style={styles.placeholder}>
|
||||
<Text style={{ fontSize: 28 }}>📷</Text>
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.sourceBadge}>
|
||||
<Text style={{ fontSize: 10 }}>{getServiceIcon(item.source_service)}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* Search bar */}
|
||||
<View style={styles.searchBar}>
|
||||
<Text style={styles.searchIcon}>🔍</Text>
|
||||
<TextInput
|
||||
style={styles.searchInput}
|
||||
placeholder="Search photos and videos"
|
||||
placeholderTextColor="#9aa0a6"
|
||||
value={query}
|
||||
onChangeText={setQuery}
|
||||
autoFocus
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
/>
|
||||
{query.length > 0 && (
|
||||
<TouchableOpacity onPress={() => setQuery('')}>
|
||||
<Text style={styles.clearButton}>✕</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{searching && (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="small" color="#1a73e8" />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{!searching && query.length > 0 && results.length === 0 && (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Text style={styles.emptyIcon}>🔍</Text>
|
||||
<Text style={styles.emptyTitle}>No results</Text>
|
||||
<Text style={styles.emptyText}>Try searching with different keywords</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{query.length === 0 && (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Text style={styles.emptyIcon}>🔍</Text>
|
||||
<Text style={styles.emptyTitle}>Search your photos</Text>
|
||||
<Text style={styles.emptyText}>Search by title or description</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{results.length > 0 && (
|
||||
<>
|
||||
<Text style={styles.resultCount}>{results.length} result{results.length !== 1 ? 's' : ''}</Text>
|
||||
<FlatList
|
||||
data={results}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item, i) => item.id?.toString() || i.toString()}
|
||||
numColumns={GRID_COLUMNS}
|
||||
contentContainerStyle={styles.gridContent}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Photo viewer modal */}
|
||||
<Modal visible={modalVisible} transparent animationType="fade" onRequestClose={() => setModalVisible(false)}>
|
||||
<View style={styles.modalContainer}>
|
||||
<TouchableOpacity style={styles.modalClose} onPress={() => setModalVisible(false)}>
|
||||
<Text style={styles.modalCloseText}>✕</Text>
|
||||
</TouchableOpacity>
|
||||
{selectedMedia?.local_path ? (
|
||||
<Image source={{ uri: selectedMedia.local_path }} style={styles.fullImage} resizeMode="contain" />
|
||||
) : (
|
||||
<View style={styles.fullPlaceholder}>
|
||||
<Text style={{ fontSize: 64 }}>📷</Text>
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.mediaInfo}>
|
||||
<Text style={styles.mediaTitle}>{selectedMedia?.title || 'Photo'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#fff' },
|
||||
searchBar: {
|
||||
flexDirection: 'row', alignItems: 'center', marginHorizontal: 12,
|
||||
marginTop: 48, marginBottom: 8, backgroundColor: '#f1f3f4',
|
||||
borderRadius: 24, paddingHorizontal: 14, paddingVertical: 10, gap: 8,
|
||||
},
|
||||
searchIcon: { fontSize: 16 },
|
||||
searchInput: { flex: 1, fontSize: 15, color: '#202124', padding: 0 },
|
||||
clearButton: { fontSize: 16, color: '#5f6368', padding: 4 },
|
||||
loadingContainer: { padding: 20, alignItems: 'center' },
|
||||
emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 24 },
|
||||
emptyIcon: { fontSize: 48, marginBottom: 12 },
|
||||
emptyTitle: { fontSize: 18, fontWeight: '600', color: '#202124', marginBottom: 6 },
|
||||
emptyText: { fontSize: 13, color: '#5f6368', textAlign: 'center' },
|
||||
resultCount: { paddingHorizontal: 16, paddingVertical: 8, fontSize: 13, color: '#5f6368' },
|
||||
gridContent: { paddingBottom: 20 },
|
||||
gridItem: {
|
||||
width: imageSize, height: imageSize, margin: GRID_GAP / 2,
|
||||
backgroundColor: '#f1f3f4', position: 'relative',
|
||||
},
|
||||
gridImage: { width: '100%', height: '100%' },
|
||||
placeholder: { width: '100%', height: '100%', justifyContent: 'center', alignItems: 'center', backgroundColor: '#e8eaed' },
|
||||
sourceBadge: {
|
||||
position: 'absolute', bottom: 4, right: 4, backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
borderRadius: 8, paddingHorizontal: 4, paddingVertical: 2,
|
||||
},
|
||||
modalContainer: { flex: 1, backgroundColor: '#000' },
|
||||
modalClose: {
|
||||
position: 'absolute', top: 44, right: 16, zIndex: 20, width: 36, height: 36,
|
||||
borderRadius: 18, backgroundColor: 'rgba(255,255,255,0.2)', justifyContent: 'center', alignItems: 'center',
|
||||
},
|
||||
modalCloseText: { color: '#fff', fontSize: 20, fontWeight: '600' },
|
||||
fullImage: { flex: 1, width: '100%' },
|
||||
fullPlaceholder: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
mediaInfo: { backgroundColor: '#1a1a1a', padding: 16, paddingBottom: 32, borderTopLeftRadius: 16, borderTopRightRadius: 16 },
|
||||
mediaTitle: { fontSize: 16, fontWeight: '600', color: '#fff' },
|
||||
});
|
||||
|
||||
export default SearchScreen;
|
||||
|
||||
Reference in new issue
Block a user