import React, { useState, useCallback } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, ScrollView, StatusBar, Switch, Platform, Alert, Modal, TextInput, ActivityIndicator, } from 'react-native'; import * as Clipboard from 'expo-clipboard'; import * as DocumentPicker from 'expo-document-picker'; import { cacheDirectory, writeAsStringAsync, readAsStringAsync, EncodingType } from 'expo-file-system/legacy'; import * as Sharing from 'expo-sharing'; import { useNavigation, useFocusEffect, NavigationProp } from '@react-navigation/native'; import { MaterialIcons } from '@expo/vector-icons'; import { useTheme } from '../contexts/ThemeContext'; import { useToast } from '../contexts/ToastContext'; import { Collection } from '../types/collections'; import { collectionsService } from '../services/collectionsService'; import { useCollections } from '../hooks/useCollections'; import ScreenHeader from '../components/common/ScreenHeader'; import CustomAlert from '../components/CustomAlert'; import { RootStackParamList } from '../navigation/AppNavigator'; import { useTranslation } from 'react-i18next'; const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0; const CollectionManagementScreen = () => { const { t } = useTranslation(); const navigation = useNavigation>(); const { currentTheme } = useTheme(); const { showInfo, showError } = useToast(); const { collections, refresh } = useCollections(); const colors = currentTheme.colors; const [alertVisible, setAlertVisible] = useState(false); const [alertTitle, setAlertTitle] = useState(''); const [alertMessage, setAlertMessage] = useState(''); const [alertActions, setAlertActions] = useState([]); const [enabledMap, setEnabledMap] = useState>({}); const [showImportModal, setShowImportModal] = useState(false); const [importTab, setImportTab] = useState<'paste' | 'file' | 'url'>('paste'); const [importText, setImportText] = useState(''); const [importUrl, setImportUrl] = useState(''); const [importLoading, setImportLoading] = useState(false); const [importPreview, setImportPreview] = useState<{ collectionCount: number; folderCount: number } | null>(null); const [importError, setImportError] = useState(null); const [pendingJson, setPendingJson] = useState(null); const loadEnabledState = useCallback(async () => { const settings = await collectionsService.getCollectionSettings(); setEnabledMap(settings); }, []); useFocusEffect( useCallback(() => { refresh(); loadEnabledState(); }, [refresh, loadEnabledState]) ); const handleToggleEnabled = useCallback(async (id: string, value: boolean) => { setEnabledMap(prev => ({ ...prev, [id]: value })); await collectionsService.setCollectionEnabled(id, value); }, []); const handleNewCollection = useCallback(() => { navigation.navigate('CollectionEditor' as any, {}); }, [navigation]); const handleEdit = useCallback((id: string) => { navigation.navigate('CollectionEditor' as any, { collectionId: id }); }, [navigation]); const handleDelete = useCallback((collection: Collection) => { setAlertTitle('Delete Collection'); setAlertMessage(`Are you sure you want to delete "${collection.title}"?`); setAlertActions([ { label: 'Cancel', onPress: () => setAlertVisible(false) }, { label: 'Delete', onPress: async () => { setAlertVisible(false); await collectionsService.deleteCollection(collection.id); }, style: 'destructive', }, ]); setAlertVisible(true); }, []); const handleMoveUp = useCallback(async (id: string) => { await collectionsService.moveCollection(id, 'up'); }, []); const handleMoveDown = useCallback(async (id: string) => { await collectionsService.moveCollection(id, 'down'); }, []); const handleExport = useCallback(async () => { try { const json = await collectionsService.exportToJson(); const fileUri = cacheDirectory + 'nuvio-collections.json'; await writeAsStringAsync(fileUri, json, { encoding: EncodingType.UTF8 }); await Sharing.shareAsync(fileUri, { mimeType: 'application/json', dialogTitle: 'Export Collections', UTI: 'public.json', }); } catch { showError('Export failed'); } }, [showError]); const resetImportState = useCallback(() => { setImportText(''); setImportUrl(''); setImportError(null); setImportPreview(null); setPendingJson(null); setImportLoading(false); setImportTab('paste'); }, []); const handleOpenImport = useCallback(() => { resetImportState(); setShowImportModal(true); }, [resetImportState]); const handleCloseImport = useCallback(() => { setShowImportModal(false); resetImportState(); }, [resetImportState]); const validateAndPreview = useCallback((json: string) => { const result = collectionsService.validateCollectionsJson(json); if (result.valid) { setImportError(null); setImportPreview({ collectionCount: result.collectionCount, folderCount: result.folderCount }); setPendingJson(json); } else { setImportError(result.error || 'Invalid data'); setImportPreview(null); setPendingJson(null); } }, []); const handlePasteValidate = useCallback(async () => { const text = await Clipboard.getStringAsync(); if (!text?.trim()) { setImportError('Clipboard is empty'); return; } setImportText(text); validateAndPreview(text); }, [validateAndPreview]); const handleTextValidate = useCallback(() => { if (!importText.trim()) { setImportError('Please paste or type JSON'); return; } validateAndPreview(importText); }, [importText, validateAndPreview]); const handleFilePick = useCallback(async () => { try { setImportLoading(true); setImportError(null); const result = await DocumentPicker.getDocumentAsync({ type: 'application/json', copyToCacheDirectory: true, }); if (result.canceled) { setImportLoading(false); return; } const fileUri = result.assets[0].uri; const content = await readAsStringAsync(fileUri, { encoding: EncodingType.UTF8 }); setImportText(content); validateAndPreview(content); } catch { setImportError('Failed to read file'); } finally { setImportLoading(false); } }, [validateAndPreview]); const handleUrlFetch = useCallback(async () => { if (!importUrl.trim()) { setImportError('Please enter a URL'); return; } try { setImportLoading(true); setImportError(null); const response = await fetch(importUrl.trim()); if (!response.ok) { setImportError(`Failed to fetch: HTTP ${response.status}`); return; } const text = await response.text(); setImportText(text); validateAndPreview(text); } catch { setImportError('Failed to fetch URL'); } finally { setImportLoading(false); } }, [importUrl, validateAndPreview]); const handleConfirmImport = useCallback(async () => { if (!pendingJson) return; try { const result = await collectionsService.importFromJson(pendingJson); showInfo(`Imported: ${result.added} added, ${result.updated} updated`); handleCloseImport(); } catch { showError('Import failed'); } }, [pendingJson, showInfo, showError, handleCloseImport]); return ( navigation.goBack()} /> New Collection {collections.length > 0 && ( Export Import )} {collections.length === 0 && ( No collections yet Create a collection to organize your catalogs into folders )} {collections.map((collection, index) => { const isEnabled = enabledMap[collection.id] !== false; return ( {collection.title} {collection.folders.length} folder{collection.folders.length !== 1 ? 's' : ''}{!isEnabled ? ' ยท Hidden' : ''} handleToggleEnabled(collection.id, value)} trackColor={{ false: colors.elevation3, true: colors.primary + '80' }} thumbColor={isEnabled ? colors.primary : colors.textMuted} style={styles.switch} /> handleMoveUp(collection.id)} disabled={index === 0} style={[styles.iconButton, index === 0 && styles.disabledButton]} > handleMoveDown(collection.id)} disabled={index === collections.length - 1} style={[styles.iconButton, index === collections.length - 1 && styles.disabledButton]} > handleEdit(collection.id)} style={styles.iconButton}> handleDelete(collection)} style={styles.iconButton}> ); })} {collections.length === 0 && ( Import )} {/* Import Modal */} {/* Header */} Import Collections {/* Tab Selector */} {([ { key: 'paste' as const, label: 'Paste JSON', icon: 'content-paste' as const }, { key: 'file' as const, label: 'Pick File', icon: 'folder-open' as const }, { key: 'url' as const, label: 'From URL', icon: 'link' as const }, ]).map(tab => ( { setImportTab(tab.key); setImportError(null); setImportPreview(null); setPendingJson(null); }} > {tab.label} ))} {/* Paste Tab */} {importTab === 'paste' && ( Paste from Clipboard or type/paste below: { setImportText(text); setImportError(null); setImportPreview(null); setPendingJson(null); }} placeholder="Paste collections JSON here..." placeholderTextColor={colors.disabled} multiline textAlignVertical="top" /> {!importPreview && !importError && importText.trim().length > 0 && ( Validate )} )} {/* File Tab */} {importTab === 'file' && ( {importLoading ? ( ) : ( )} {importLoading ? 'Reading file...' : 'Choose .json File'} )} {/* URL Tab */} {importTab === 'url' && ( { setImportUrl(text); setImportError(null); setImportPreview(null); setPendingJson(null); }} placeholder="https://example.com/collections.json" placeholderTextColor={colors.disabled} autoCapitalize="none" autoCorrect={false} keyboardType="url" /> {importLoading ? ( ) : ( )} {importLoading ? 'Fetching...' : 'Fetch & Validate'} )} {/* Error */} {importError && ( {importError} )} {/* Preview */} {importPreview && ( Valid: {importPreview.collectionCount} collection{importPreview.collectionCount !== 1 ? 's' : ''}, {importPreview.folderCount} folder{importPreview.folderCount !== 1 ? 's' : ''} )} {/* Import Button */} {importPreview && pendingJson && ( Import {importPreview.collectionCount} Collection{importPreview.collectionCount !== 1 ? 's' : ''} )} setAlertVisible(false)} /> ); }; const styles = StyleSheet.create({ container: { flex: 1, }, scrollContent: { padding: 16, paddingBottom: 40, }, newButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', padding: 14, borderRadius: 12, marginBottom: 16, }, newButtonText: { color: '#fff', fontSize: 16, fontWeight: '600', marginLeft: 8, }, exportImportRow: { flexDirection: 'row', gap: 10, marginBottom: 16, }, importOnlyRow: { marginTop: 16, }, actionButton: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', padding: 10, borderRadius: 10, gap: 6, }, actionButtonText: { fontSize: 14, fontWeight: '500', }, emptyContainer: { alignItems: 'center', paddingVertical: 48, }, emptyText: { fontSize: 18, fontWeight: '600', marginTop: 12, }, emptySubtext: { fontSize: 14, marginTop: 6, textAlign: 'center', paddingHorizontal: 32, }, collectionCard: { borderRadius: 12, padding: 14, marginBottom: 10, borderWidth: 1, }, cardHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, cardTitleRow: { flex: 1, marginRight: 8, }, collectionTitle: { fontSize: 16, fontWeight: '600', }, folderCount: { fontSize: 12, marginTop: 2, }, cardActions: { flexDirection: 'row', alignItems: 'center', gap: 4, }, switch: { transform: [{ scaleX: 0.8 }, { scaleY: 0.8 }], }, iconButton: { padding: 6, }, disabledButton: { opacity: 0.4, }, modalOverlay: { flex: 1, justifyContent: 'center', alignItems: 'center', }, importModal: { width: '92%', maxHeight: '85%', borderRadius: 16, padding: 20, }, importModalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, }, importModalTitle: { fontSize: 20, fontWeight: '700', }, importTabRow: { flexDirection: 'row', gap: 8, marginBottom: 16, }, importTab: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 6, paddingVertical: 10, borderRadius: 10, borderWidth: 1, }, importTabText: { fontSize: 12, fontWeight: '600', }, importModalBody: { flexGrow: 0, }, importActionBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, padding: 14, borderRadius: 10, borderWidth: 1, }, importActionBtnText: { fontSize: 14, fontWeight: '500', }, importOrText: { textAlign: 'center', fontSize: 12, marginVertical: 10, }, importTextInput: { borderWidth: 1, borderRadius: 10, padding: 12, fontSize: 13, minHeight: 150, maxHeight: 250, }, importUrlInput: { borderWidth: 1, borderRadius: 10, padding: 12, fontSize: 14, }, validateBtn: { alignSelf: 'flex-start', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 8, borderWidth: 1, marginTop: 10, }, validateBtnText: { fontSize: 14, fontWeight: '600', }, importResultBox: { flexDirection: 'row', alignItems: 'center', gap: 8, padding: 12, borderRadius: 10, borderWidth: 1, marginTop: 12, }, importResultText: { fontSize: 13, flex: 1, }, confirmImportBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, padding: 14, borderRadius: 12, marginTop: 12, }, confirmImportBtnText: { color: '#fff', fontSize: 16, fontWeight: '600', }, }); export default CollectionManagementScreen;