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:
bobbert committed 2026-02-21 13:52:28 +00:00
1 parent 37511780b7
commit c1d7139055
17 files changed
+1431 -10040

No files matched your search

+185
View File
@@ -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;