import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, StyleSheet, FlatList, TextInput, TouchableOpacity, ActivityIndicator, Alert, SafeAreaView, StatusBar, Modal, KeyboardAvoidingView, Platform, Image, Dimensions, ScrollView, useColorScheme } from 'react-native'; import { stremioService, Manifest } from '../services/stremioService'; import { MaterialIcons } from '@expo/vector-icons'; import { colors } from '../styles'; import { Image as ExpoImage } from 'expo-image'; import { LinearGradient } from 'expo-linear-gradient'; import { useNavigation } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; import { RootStackParamList } from '../navigation/AppNavigator'; import { logger } from '../utils/logger'; // Extend Manifest type to include logo interface ExtendedManifest extends Manifest { logo?: string; } const { width } = Dimensions.get('window'); const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0; const AddonsScreen = () => { const navigation = useNavigation>(); const [addons, setAddons] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [installing, setInstalling] = useState(false); const [showAddModal, setShowAddModal] = useState(false); const [addonUrl, setAddonUrl] = useState(''); const [addonDetails, setAddonDetails] = useState(null); const [showConfirmModal, setShowConfirmModal] = useState(false); const isDarkMode = useColorScheme() === 'dark'; useEffect(() => { loadAddons(); }, []); const loadAddons = async () => { try { setLoading(true); const installedAddons = await stremioService.getInstalledAddonsAsync(); setAddons(installedAddons); } catch (error) { logger.error('Failed to load addons:', error); Alert.alert('Error', 'Failed to load addons'); } finally { setLoading(false); } }; const handleInstallAddon = async () => { if (!addonUrl) { Alert.alert('Error', 'Please enter an addon URL'); return; } try { setInstalling(true); // First fetch the addon manifest const manifest = await stremioService.getManifest(addonUrl); setAddonDetails(manifest); setShowAddModal(false); setShowConfirmModal(true); } catch (error) { logger.error('Failed to fetch addon details:', error); Alert.alert('Error', 'Failed to fetch addon details'); } finally { setInstalling(false); } }; const confirmInstallAddon = async () => { if (!addonDetails) return; try { setInstalling(true); await stremioService.installAddon(addonUrl); setAddonUrl(''); setShowConfirmModal(false); setAddonDetails(null); loadAddons(); Alert.alert('Success', 'Addon installed successfully'); } catch (error) { logger.error('Failed to install addon:', error); Alert.alert('Error', 'Failed to install addon'); } finally { setInstalling(false); } }; const handleConfigureAddon = (addon: ExtendedManifest) => { // TODO: Implement addon configuration Alert.alert('Configure', `Configure ${addon.name}`); }; const handleRemoveAddon = (addon: ExtendedManifest) => { Alert.alert( 'Uninstall', `Are you sure you want to uninstall ${addon.name}?`, [ { text: 'Cancel', style: 'cancel' }, { text: 'Uninstall', style: 'destructive', onPress: () => { stremioService.removeAddon(addon.id); loadAddons(); }, }, ] ); }; const renderAddonItem = ({ item }: { item: ExtendedManifest }) => { const types = item.types || []; const description = item.description || ''; // @ts-ignore - some addons might have logo property even though it's not in the type const logo = item.logo || null; return ( {logo ? ( ) : ( )} {item.name} {types.join(', ')} {description} handleConfigureAddon(item)} > handleRemoveAddon(item)} > Uninstall ); }; return ( Addons {loading ? ( ) : ( item.id} contentContainerStyle={styles.addonsList} ListEmptyComponent={() => ( No addons installed )} /> )} {/* Add Addon FAB */} setShowAddModal(true)} > {/* Add Addon URL Modal */} setShowAddModal(false)} > Add New Addon setShowAddModal(false)} > Cancel {installing ? ( ) : ( Next )} {/* Addon Details Confirmation Modal */} { setShowConfirmModal(false); setAddonDetails(null); }} > {addonDetails && ( <> {/* @ts-ignore - some addons might have logo property even though it's not in the type */} {addonDetails.logo ? ( ) : ( )} {addonDetails.name} Version {addonDetails.version} Description {addonDetails.description || 'No description available'} Supported Types {(addonDetails.types || []).map((type, index) => ( {type} ))} {addonDetails.catalogs && addonDetails.catalogs.length > 0 && ( <> Catalogs {addonDetails.catalogs.map((catalog, index) => ( {catalog.type} ))} )} { setShowConfirmModal(false); setAddonDetails(null); }} > Cancel {installing ? ( ) : ( Install )} )} ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: colors.darkBackground, }, header: { paddingHorizontal: 16, paddingVertical: 12, paddingTop: Platform.OS === 'android' ? ANDROID_STATUSBAR_HEIGHT + 12 : 4, borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.1)', backgroundColor: colors.darkBackground, }, headerContent: { flexDirection: 'row', alignItems: 'center', }, headerTitle: { fontSize: 32, fontWeight: '800', letterSpacing: 0.5, color: colors.white, }, searchContainer: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.elevation1, margin: 16, padding: 12, borderRadius: 8, }, searchInput: { flex: 1, marginLeft: 8, color: colors.text, fontSize: 16, }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, addonsList: { padding: 16, }, addonItem: { backgroundColor: colors.elevation1, borderRadius: 12, marginBottom: 16, padding: 16, }, addonContent: { flexDirection: 'row', marginBottom: 16, }, addonIconContainer: { width: 48, height: 48, marginRight: 16, }, addonIcon: { width: '100%', height: '100%', borderRadius: 8, }, placeholderIcon: { width: '100%', height: '100%', backgroundColor: colors.elevation2, borderRadius: 8, justifyContent: 'center', alignItems: 'center', }, addonInfo: { flex: 1, }, addonName: { color: colors.text, fontSize: 18, fontWeight: 'bold', marginBottom: 4, }, addonType: { color: colors.mediumGray, fontSize: 14, marginBottom: 4, }, addonDescription: { color: colors.mediumEmphasis, fontSize: 14, lineHeight: 20, marginBottom: 12, }, addonActions: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', borderTopWidth: 1, borderTopColor: colors.elevation2, paddingTop: 16, }, configButton: { padding: 8, }, uninstallButton: { backgroundColor: 'transparent', paddingVertical: 8, paddingHorizontal: 16, borderRadius: 20, borderWidth: 1, borderColor: colors.elevation2, }, uninstallText: { color: colors.text, fontSize: 14, }, emptyContainer: { alignItems: 'center', justifyContent: 'center', padding: 32, }, emptyText: { marginTop: 16, fontSize: 16, color: colors.mediumGray, textAlign: 'center', }, fab: { position: 'absolute', right: 16, bottom: 90, width: 56, height: 56, borderRadius: 28, backgroundColor: colors.primary, justifyContent: 'center', alignItems: 'center', elevation: 8, shadowColor: colors.black, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.30, shadowRadius: 4.65, }, modalContainer: { flex: 1, backgroundColor: colors.darkBackground, justifyContent: 'center', alignItems: 'center', }, modalContent: { backgroundColor: colors.elevation1, borderRadius: 12, padding: 20, width: '85%', maxWidth: 360, }, modalTitle: { color: colors.text, fontSize: 20, fontWeight: 'bold', marginBottom: 16, }, modalInput: { backgroundColor: colors.elevation2, borderRadius: 8, padding: 12, color: colors.text, marginBottom: 24, }, modalActions: { flexDirection: 'row', justifyContent: 'flex-end', }, modalButton: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20, marginLeft: 8, }, modalButtonPrimary: { backgroundColor: colors.primary, }, modalButtonText: { color: colors.mediumGray, fontSize: 14, fontWeight: 'bold', }, modalButtonTextPrimary: { color: colors.text, }, confirmModalContent: { width: '85%', maxWidth: 360, maxHeight: '80%', padding: 0, borderRadius: 16, overflow: 'hidden', backgroundColor: colors.darkBackground, }, addonHeader: { alignItems: 'center', padding: 20, borderBottomWidth: 1, borderBottomColor: colors.elevation1, backgroundColor: colors.elevation2, width: '100%', }, addonLogo: { width: 64, height: 64, marginBottom: 12, borderRadius: 12, backgroundColor: colors.elevation1, }, placeholderLogo: { width: 64, height: 64, borderRadius: 12, backgroundColor: colors.elevation1, justifyContent: 'center', alignItems: 'center', marginBottom: 12, }, addonTitle: { fontSize: 20, fontWeight: '700', color: colors.text, marginBottom: 4, textAlign: 'center', }, addonVersion: { fontSize: 13, color: colors.textMuted, marginBottom: 0, }, addonDetailsSection: { padding: 20, }, sectionTitle: { fontSize: 15, fontWeight: '600', color: colors.text, marginBottom: 8, marginTop: 12, }, typeContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 6, marginBottom: 12, width: '100%', }, typeChip: { backgroundColor: colors.elevation2, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, borderWidth: 1, borderColor: colors.elevation3, }, typeText: { color: colors.text, fontSize: 13, }, confirmActions: { flexDirection: 'row', justifyContent: 'flex-end', padding: 12, gap: 8, borderTopWidth: 1, borderTopColor: colors.elevation1, backgroundColor: colors.elevation2, width: '100%', }, confirmButton: { paddingHorizontal: 16, paddingVertical: 10, borderRadius: 8, minWidth: 90, alignItems: 'center', }, cancelButton: { backgroundColor: colors.elevation3, }, installButton: { backgroundColor: colors.primary, }, confirmButtonText: { color: colors.text, fontSize: 16, fontWeight: '600', }, scrollContent: { flexGrow: 1, }, }); export default AddonsScreen;