diff --git a/README.md b/README.md
index 370cdf35..d4d11391 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+
+
+
+
# Nuvio - Streaming App
Nuvio is an Open-Source cross-platform streaming application built with React Native and Expo, allowing users to browse, discover, and watch video content.
@@ -68,24 +72,24 @@ Nuvio is an Open-Source cross-platform streaming application built with React Na
* **For Expo Go (Development):**
```bash
- npm start
+ npx expo start
# or
- yarn start
+ yarn dlx expo start
```
Scan the QR code with the Expo Go app on your iOS or Android device.
* **For Native Android Build/Emulator:**
```bash
- npm run android
+ npx expo run:android
# or
- yarn android
+ yarn dlx expo run:android
```
* **For Native iOS Build/Simulator:**
```bash
- npm run ios
+ npx expo run:ios
# or
- yarn ios
+ yarn dlx expo run:ios
```
## 🤝 Contributing
diff --git a/assets/adaptive-icon.png b/assets/adaptive-icon.png
index 03d6f6b6..d5a9df52 100644
Binary files a/assets/adaptive-icon.png and b/assets/adaptive-icon.png differ
diff --git a/assets/app-icon.png b/assets/app-icon.png
deleted file mode 100644
index dac44269..00000000
Binary files a/assets/app-icon.png and /dev/null differ
diff --git a/assets/icon.png b/assets/icon.png
index dac44269..d5a9df52 100644
Binary files a/assets/icon.png and b/assets/icon.png differ
diff --git a/assets/titlelogo.png b/assets/titlelogo.png
new file mode 100644
index 00000000..f1aebd5d
Binary files /dev/null and b/assets/titlelogo.png differ
diff --git a/package-lock.json b/package-lock.json
index 5ef1cbbe..5acc3d71 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -29,6 +29,7 @@
"expo": "~52.0.43",
"expo-auth-session": "^6.0.3",
"expo-blur": "^14.0.3",
+ "expo-dev-client": "~5.0.20",
"expo-file-system": "^18.0.12",
"expo-haptics": "~14.0.1",
"expo-image": "~2.0.7",
@@ -4671,6 +4672,22 @@
"node": ">=8"
}
},
+ "node_modules/ajv": {
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
+ "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
"node_modules/anser": {
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz",
@@ -6720,6 +6737,58 @@
"expo": "*"
}
},
+ "node_modules/expo-dev-client": {
+ "version": "5.0.20",
+ "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-5.0.20.tgz",
+ "integrity": "sha512-bLNkHdU7V3I4UefgJbJnIDUBUL0LxIal/xYEx9BbgDd3B7wgQKY//+BpPIxBOKCQ22lkyiHY8y9tLhO903sAgg==",
+ "license": "MIT",
+ "dependencies": {
+ "expo-dev-launcher": "5.0.35",
+ "expo-dev-menu": "6.0.25",
+ "expo-dev-menu-interface": "1.9.3",
+ "expo-manifests": "~0.15.8",
+ "expo-updates-interface": "~1.0.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-dev-launcher": {
+ "version": "5.0.35",
+ "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-5.0.35.tgz",
+ "integrity": "sha512-hEQr0ZREnUMxZ6wtQgfK1lzYnbb0zar3HqYZhmANzXmE6UEPbQ4GByLzhpfz/d+xxdBVQZsrHdtiV28KPG2sog==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "8.11.0",
+ "expo-dev-menu": "6.0.25",
+ "expo-manifests": "~0.15.8",
+ "resolve-from": "^5.0.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-dev-menu": {
+ "version": "6.0.25",
+ "resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-6.0.25.tgz",
+ "integrity": "sha512-K2m4z/I+CPWbMtHlDzU68lHaQs52De0v5gbsjAmA5ig8FrYh4MKZvPxSVANaiKENzgmtglu8qaFh7ua9Gt2TfA==",
+ "license": "MIT",
+ "dependencies": {
+ "expo-dev-menu-interface": "1.9.3"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-dev-menu-interface": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/expo-dev-menu-interface/-/expo-dev-menu-interface-1.9.3.tgz",
+ "integrity": "sha512-KY/dWTBE1l47i9V366JN5rC6YIdOc9hz8yAmZzkl5DrPia5l3M2WIjtnpHC9zUkNjiSiG2urYoOAq4H/uLdmyg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
"node_modules/expo-file-system": {
"version": "18.0.12",
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-18.0.12.tgz",
@@ -6781,6 +6850,12 @@
"expo": "*"
}
},
+ "node_modules/expo-json-utils": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-0.14.0.tgz",
+ "integrity": "sha512-xjGfK9dL0B1wLnOqNkX0jM9p48Y0I5xEPzHude28LY67UmamUyAACkqhZGaPClyPNfdzczk7Ej6WaRMT3HfXvw==",
+ "license": "MIT"
+ },
"node_modules/expo-keep-awake": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-14.0.3.tgz",
@@ -6816,6 +6891,19 @@
"react-native": "*"
}
},
+ "node_modules/expo-manifests": {
+ "version": "0.15.8",
+ "resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-0.15.8.tgz",
+ "integrity": "sha512-VuIyaMfRfLZeETNsRohqhy1l7iZ7I+HKMPfZXVL2Yn17TT0WkOhZoq1DzYwPbOHPgp1Uk6phNa86EyaHrD2DLw==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/config": "~10.0.11",
+ "expo-json-utils": "~0.14.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
"node_modules/expo-modules-autolinking": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.0.8.tgz",
@@ -6959,6 +7047,15 @@
"integrity": "sha512-FRjRvs7RgsXjkbGSOjYSxhX5V70c0IzA/jy3HXeYpATMwD9fOR1DbveLW497QGsVdCa0vThbJUtR8rIzAfpHQA==",
"license": "MIT"
},
+ "node_modules/expo-updates-interface": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-1.0.0.tgz",
+ "integrity": "sha512-93oWtvULJOj+Pp+N/lpTcFfuREX1wNeHtp7Lwn8EbzYYmdn37MvZU3TPW2tYYCZuhzmKEXnUblYcruYoDu7IrQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
"node_modules/expo-web-browser": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-14.0.2.tgz",
@@ -8383,6 +8480,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -12811,6 +12914,15 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
"node_modules/use-latest-callback": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.3.tgz",
diff --git a/package.json b/package.json
index 99902dda..9011b3bf 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,8 @@
"react-native-tab-view": "^4.0.10",
"react-native-video": "^6.12.0",
"react-native-web": "~0.19.13",
- "subsrt": "^1.1.1"
+ "subsrt": "^1.1.1",
+ "expo-dev-client": "~5.0.20"
},
"devDependencies": {
"@babel/core": "^7.25.2",
diff --git a/src/assets/IMG_0761.PNG b/src/assets/IMG_0761.PNG
new file mode 100644
index 00000000..d5a9df52
Binary files /dev/null and b/src/assets/IMG_0761.PNG differ
diff --git a/src/assets/IMG_0762.png b/src/assets/IMG_0762.png
new file mode 100644
index 00000000..6e044a35
Binary files /dev/null and b/src/assets/IMG_0762.png differ
diff --git a/src/components/NuvioHeader.tsx b/src/components/NuvioHeader.tsx
index ba052223..bbaf0797 100644
--- a/src/components/NuvioHeader.tsx
+++ b/src/components/NuvioHeader.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { View, Text, TouchableOpacity, Platform, StyleSheet } from 'react-native';
+import { View, TouchableOpacity, Platform, StyleSheet, Image } from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { colors } from '../styles/colors';
import { useNavigation } from '@react-navigation/native';
@@ -38,9 +38,12 @@ export const NuvioHeader = () => {
)
)}
-
- NUVIO
-
+
+
item.id === id);
@@ -496,40 +484,10 @@ export const useMetadata = ({ id, type }: UseMetadataProps): UseMetadataReturn =
setInLibrary(isInLib);
cacheService.setMetadata(id, type, content.value);
- // Fetch and add logo from TMDB
- let finalMetadata = { ...content.value };
- try {
- // Get TMDB ID if not already set
- const contentTmdbId = await tmdbService.extractTMDBIdFromStremioId(id);
- if (contentTmdbId) {
- // Determine content type for TMDB API (movie or tv)
- const tmdbType = type === 'series' ? 'tv' : 'movie';
- // Fetch logo from TMDB
- const logoUrl = await tmdbService.getContentLogo(tmdbType, contentTmdbId);
- if (logoUrl) {
- // Update metadata with logo
- finalMetadata.logo = logoUrl;
- logger.log(`[useMetadata] Successfully fetched and set logo from TMDB for ${id}`);
- } else {
- // If TMDB has no logo, ensure logo property is null/undefined
- finalMetadata.logo = undefined;
- logger.log(`[useMetadata] No logo found on TMDB for ${id}. Setting logo to undefined.`);
- }
- } else {
- // If we couldn't get a TMDB ID, ensure logo is null/undefined
- finalMetadata.logo = undefined;
- logger.log(`[useMetadata] Could not determine TMDB ID for ${id}. Setting logo to undefined.`);
- }
- } catch (error) {
- logger.error(`[useMetadata] Error fetching logo from TMDB for ${id}:`, error);
- // Ensure logo is null/undefined on error
- finalMetadata.logo = undefined;
- }
-
- // Set the final metadata state
- setMetadata(finalMetadata);
- // Update cache with final metadata (including potentially nulled logo)
- cacheService.setMetadata(id, type, finalMetadata);
+ // Set the final metadata state without fetching logo (this will be handled by MetadataScreen)
+ setMetadata(content.value);
+ // Update cache
+ cacheService.setMetadata(id, type, content.value);
if (type === 'series') {
// Load series data in parallel with other data
diff --git a/src/screens/AddonsScreen.tsx b/src/screens/AddonsScreen.tsx
index 4af15cdc..0d1f0a83 100644
--- a/src/screens/AddonsScreen.tsx
+++ b/src/screens/AddonsScreen.tsx
@@ -63,9 +63,7 @@ const AddonsScreen = () => {
const [showConfirmModal, setShowConfirmModal] = useState(false);
const [installing, setInstalling] = useState(false);
const [catalogCount, setCatalogCount] = useState(0);
- // Add state for reorder mode
const [reorderMode, setReorderMode] = useState(false);
- // Force dark mode
const isDarkMode = true;
// State for community addons
@@ -502,333 +500,124 @@ const AddonsScreen = () => {
);
return (
-
-
+
+
- {/* Header */}
- navigation.goBack()}
- >
-
- Settings
+ Addons
+
+
-
-
- {/* Reorder Mode Toggle Button */}
-
-
-
-
- {/* Refresh Button */}
-
-
-
-
-
-
- Addons
- {reorderMode && (Reorder Mode)}
-
-
- {reorderMode && (
-
-
-
- Addons at the top have higher priority when loading content
-
-
- )}
-
+
+
+
+
+
+
+
+
+ handleAddAddon()}
+ disabled={!addonUrl || installing}
+ >
+ {installing ? (
+
+ ) : (
+
+ )}
+
+
+
{loading ? (
+ Loading addons...
) : (
-
- {/* Overview Section */}
-
- OVERVIEW
-
-
-
-
-
-
-
-
-
- {/* Hide Add Addon Section in reorder mode */}
- {!reorderMode && (
-
- ADD NEW ADDON
-
-
- handleAddAddon()}
- disabled={installing || !addonUrl}
- >
-
- {installing ? 'Loading...' : 'Add Addon'}
-
-
-
+ item.id}
+ style={styles.list}
+ contentContainerStyle={styles.listContent}
+ ListEmptyComponent={() => (
+
+
+ No addons installed
+ Add an addon using the URL field above
)}
-
- {/* Installed Addons Section */}
-
-
- {reorderMode ? "DRAG ADDONS TO REORDER" : "INSTALLED ADDONS"}
-
-
- {addons.length === 0 ? (
-
-
- No addons installed
-
- ) : (
- addons.map((addon, index) => (
-
- {renderAddonItem({ item: addon, index })}
-
- ))
- )}
-
-
-
- {/* Separator */}
-
-
- {/* Community Addons Section */}
-
- COMMUNITY ADDONS
-
- {communityLoading ? (
-
-
-
- ) : communityError ? (
-
-
- {communityError}
-
- ) : communityAddons.length === 0 ? (
-
-
- No community addons available
-
- ) : (
- communityAddons.map((item, index) => (
-
-
-
- {item.manifest.logo ? (
-
- ) : (
-
-
-
- )}
-
- {item.manifest.name}
-
- v{item.manifest.version || 'N/A'}
- •
-
- {item.manifest.types && item.manifest.types.length > 0
- ? item.manifest.types.map(t => t.charAt(0).toUpperCase() + t.slice(1)).join(' • ')
- : 'General'}
-
-
-
-
- {item.manifest.behaviorHints?.configurable && (
- handleConfigureAddon(item.manifest, item.transportUrl)}
- >
-
-
- )}
- handleAddAddon(item.transportUrl)}
- disabled={installing}
- >
- {installing ? (
-
- ) : (
-
- )}
-
-
-
-
-
- {item.manifest.description
- ? (item.manifest.description.length > 100
- ? item.manifest.description.substring(0, 100) + '...'
- : item.manifest.description)
- : 'No description provided.'}
-
-
-
- ))
- )}
-
-
-
+ />
)}
- {/* Addon Details Confirmation Modal */}
+ {/* Community Addons Section */}
+
+ Community Addons
+ {communityLoading ? (
+
+
+ Loading community addons...
+
+ ) : communityError ? (
+
+
+ {communityError}
+
+ ) : (
+ item.manifest.id}
+ horizontal
+ showsHorizontalScrollIndicator={false}
+ style={styles.communityList}
+ contentContainerStyle={styles.communityListContent}
+ />
+ )}
+
+
+ {/* Confirmation Modal */}
{
- setShowConfirmModal(false);
- setAddonDetails(null);
- }}
+ onRequestClose={() => setShowConfirmModal(false)}
>
-
+
+ Install Addon
{addonDetails && (
<>
-
- Install Addon
- {
- setShowConfirmModal(false);
- setAddonDetails(null);
- }}
- >
-
-
-
-
-
-
- {/* @ts-ignore */}
- {addonDetails.logo ? (
-
- ) : (
-
-
-
- )}
- {addonDetails.name}
- v{addonDetails.version || '1.0.0'}
-
-
-
- Description
-
- {addonDetails.description || 'No description available'}
-
-
-
- {addonDetails.types && addonDetails.types.length > 0 && (
-
- Supported Types
-
- {addonDetails.types.map((type, index) => (
-
- {type}
-
- ))}
-
-
- )}
-
- {addonDetails.catalogs && addonDetails.catalogs.length > 0 && (
-
- Catalogs
-
- {addonDetails.catalogs.map((catalog, index) => (
-
-
- {catalog.type} - {catalog.id}
-
-
- ))}
-
-
- )}
-
-
-
- {
- setShowConfirmModal(false);
- setAddonDetails(null);
- }}
- >
- Cancel
-
-
- {installing ? (
-
- ) : (
- Install
- )}
-
-
+ {addonDetails.name}
+ {addonDetails.description}
>
)}
+
+ setShowConfirmModal(false)}
+ >
+ Cancel
+
+
+ Install
+
+
@@ -839,197 +628,156 @@ const AddonsScreen = () => {
const styles = StyleSheet.create({
container: {
flex: 1,
- backgroundColor: colors.darkBackground,
+ paddingTop: Platform.OS === 'android' ? ANDROID_STATUSBAR_HEIGHT : 0,
},
header: {
flexDirection: 'row',
- alignItems: 'center',
justifyContent: 'space-between',
+ alignItems: 'center',
paddingHorizontal: 16,
- paddingTop: Platform.OS === 'android' ? ANDROID_STATUSBAR_HEIGHT + 8 : 8,
+ paddingVertical: 12,
},
- headerActions: {
- flexDirection: 'row',
- alignItems: 'center',
- },
- headerButton: {
- padding: 8,
- marginLeft: 8,
- },
- activeHeaderButton: {
- backgroundColor: 'rgba(45, 156, 219, 0.2)',
- borderRadius: 6,
- },
- reorderModeText: {
- color: colors.primary,
- fontSize: 18,
- fontWeight: '400',
- },
- reorderInfoBanner: {
- backgroundColor: 'rgba(45, 156, 219, 0.15)',
- paddingHorizontal: 16,
- paddingVertical: 10,
- marginHorizontal: 16,
- borderRadius: 8,
- flexDirection: 'row',
- alignItems: 'center',
- marginBottom: 16,
- },
- reorderInfoText: {
- color: colors.white,
- fontSize: 14,
- marginLeft: 8,
- },
- reorderButtons: {
- position: 'absolute',
- left: -12,
- top: '50%',
- marginTop: -40,
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'center',
- zIndex: 10,
- },
- reorderButton: {
- backgroundColor: colors.elevation3,
- width: 30,
- height: 30,
- borderRadius: 15,
- justifyContent: 'center',
- alignItems: 'center',
- marginVertical: 4,
- },
- disabledButton: {
- opacity: 0.5,
- backgroundColor: colors.elevation2,
- },
- priorityBadge: {
- backgroundColor: colors.primary,
- borderRadius: 12,
- paddingHorizontal: 8,
- paddingVertical: 3,
- },
- priorityText: {
- color: colors.white,
- fontSize: 12,
- fontWeight: 'bold',
- },
- backButton: {
- flexDirection: 'row',
- alignItems: 'center',
- padding: 8,
- },
- backText: {
- fontSize: 17,
- fontWeight: '400',
- color: colors.primary,
- },
- headerTitle: {
- fontSize: 34,
- fontWeight: '700',
- color: colors.white,
- paddingHorizontal: 16,
- paddingBottom: 16,
- paddingTop: 8,
- },
- scrollView: {
- flex: 1,
- },
- section: {
- marginBottom: 24,
- },
- sectionTitle: {
- fontSize: 13,
- fontWeight: '600',
- color: colors.mediumGray,
- marginHorizontal: 16,
- marginBottom: 8,
- letterSpacing: 0.5,
- textTransform: 'uppercase',
- },
- statsContainer: {
- flexDirection: 'row',
- justifyContent: 'space-between',
- marginHorizontal: 16,
- backgroundColor: colors.elevation2,
- borderRadius: 12,
- padding: 16,
- shadowColor: '#000',
- shadowOffset: { width: 0, height: 2 },
- shadowOpacity: 0.1,
- shadowRadius: 4,
- elevation: 2,
- },
- statsCard: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- },
- statsDivider: {
- width: 1,
- height: '80%',
- backgroundColor: 'rgba(150, 150, 150, 0.2)',
- alignSelf: 'center',
- },
- statsValue: {
+ title: {
fontSize: 24,
fontWeight: 'bold',
color: colors.white,
- marginBottom: 4,
},
- statsLabel: {
- fontSize: 13,
- color: colors.mediumGray,
- },
- addAddonContainer: {
- marginHorizontal: 16,
- backgroundColor: colors.elevation2,
- borderRadius: 12,
+ statsContainer: {
+ flexDirection: 'row',
padding: 16,
- shadowColor: '#000',
- shadowOffset: { width: 0, height: 2 },
- shadowOpacity: 0.1,
- shadowRadius: 4,
- elevation: 2,
+ justifyContent: 'space-around',
},
- addonInput: {
- backgroundColor: colors.elevation1,
+ searchContainer: {
+ flexDirection: 'row',
+ paddingHorizontal: 16,
+ paddingBottom: 16,
+ alignItems: 'center',
+ },
+ searchInput: {
+ flex: 1,
+ height: 40,
+ backgroundColor: colors.darkGray,
borderRadius: 8,
- padding: 12,
+ paddingHorizontal: 12,
+ marginRight: 8,
color: colors.white,
- marginBottom: 16,
- fontSize: 15,
},
addButton: {
+ width: 40,
+ height: 40,
backgroundColor: colors.primary,
borderRadius: 8,
- padding: 12,
+ justifyContent: 'center',
alignItems: 'center',
},
- addButtonText: {
- color: colors.white,
- fontWeight: '600',
- fontSize: 16,
+ disabledButton: {
+ opacity: 0.5,
},
- addonList: {
+ list: {
+ flex: 1,
+ },
+ listContent: {
paddingHorizontal: 16,
},
- emptyContainer: {
- backgroundColor: colors.elevation2,
- borderRadius: 12,
- padding: 32,
- alignItems: 'center',
+ loadingContainer: {
+ flex: 1,
justifyContent: 'center',
- shadowColor: '#000',
- shadowOffset: { width: 0, height: 2 },
- shadowOpacity: 0.1,
- shadowRadius: 4,
- elevation: 2,
+ alignItems: 'center',
+ },
+ loadingText: {
+ marginTop: 12,
+ color: colors.mediumGray,
+ },
+ emptyContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingVertical: 32,
},
emptyText: {
- marginTop: 8,
+ fontSize: 16,
color: colors.mediumGray,
- fontSize: 15,
+ marginTop: 16,
+ },
+ emptySubtext: {
+ fontSize: 14,
+ color: colors.mediumGray,
+ marginTop: 8,
+ },
+ modalOverlay: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+ },
+ modalContent: {
+ backgroundColor: colors.darkGray,
+ borderRadius: 12,
+ padding: 24,
+ width: '80%',
+ maxWidth: 400,
+ },
+ modalTitle: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ color: colors.white,
+ marginBottom: 16,
+ },
+ modalAddonName: {
+ fontSize: 16,
+ color: colors.white,
+ marginBottom: 8,
+ },
+ modalAddonDesc: {
+ fontSize: 14,
+ color: colors.mediumGray,
+ marginBottom: 24,
+ },
+ modalButtons: {
+ flexDirection: 'row',
+ justifyContent: 'flex-end',
+ },
+ modalButton: {
+ paddingHorizontal: 16,
+ paddingVertical: 8,
+ borderRadius: 6,
+ marginLeft: 12,
+ },
+ modalButtonText: {
+ color: colors.white,
+ fontSize: 14,
+ fontWeight: '500',
+ },
+ cancelButton: {
+ backgroundColor: colors.mediumGray,
+ },
+ confirmButton: {
+ backgroundColor: colors.primary,
+ },
+ communitySection: {
+ paddingTop: 16,
+ },
+ sectionTitle: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ color: colors.white,
+ paddingHorizontal: 16,
+ marginBottom: 12,
+ },
+ communityList: {
+ height: 160,
+ },
+ communityListContent: {
+ paddingHorizontal: 16,
+ },
+ errorContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: 16,
+ },
+ errorText: {
+ color: colors.error,
+ marginLeft: 8,
},
addonItem: {
backgroundColor: colors.elevation2,
@@ -1099,136 +847,29 @@ const styles = StyleSheet.create({
lineHeight: 20,
marginLeft: 48, // Align with title, accounting for icon width
},
- loadingContainer: {
- flex: 1,
+ reorderButton: {
+ padding: 8,
+ },
+ reorderButtons: {
+ position: 'absolute',
+ left: -12,
+ top: '50%',
+ marginTop: -40,
+ flexDirection: 'column',
+ alignItems: 'center',
justifyContent: 'center',
- alignItems: 'center',
+ zIndex: 10,
},
- modalContainer: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- },
- modalContent: {
- backgroundColor: colors.elevation2,
- borderRadius: 14,
- width: '85%',
- maxHeight: '85%',
- overflow: 'hidden',
- shadowColor: '#000',
- shadowOffset: { width: 0, height: 6 },
- shadowOpacity: 0.25,
- shadowRadius: 8,
- elevation: 5,
- },
- modalHeader: {
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: 16,
- borderBottomWidth: 1,
- borderBottomColor: colors.elevation3,
- },
- modalTitle: {
- fontSize: 17,
- fontWeight: 'bold',
- color: colors.white,
- },
- modalScrollContent: {
- maxHeight: 400,
- },
- addonDetailHeader: {
- alignItems: 'center',
- padding: 24,
- borderBottomWidth: 1,
- borderBottomColor: colors.elevation3,
- },
- addonLogo: {
- width: 64,
- height: 64,
- borderRadius: 12,
- marginBottom: 16,
- backgroundColor: colors.elevation3,
- },
- addonLogoPlaceholder: {
- width: 64,
- height: 64,
- borderRadius: 12,
- backgroundColor: colors.elevation3,
- justifyContent: 'center',
- alignItems: 'center',
- marginBottom: 16,
- },
- addonDetailName: {
- fontSize: 20,
- fontWeight: 'bold',
- color: colors.white,
- marginBottom: 4,
- textAlign: 'center',
- },
- addonDetailVersion: {
- fontSize: 14,
- color: colors.mediumGray,
- },
- addonDetailSection: {
- padding: 16,
- borderBottomWidth: 1,
- borderBottomColor: colors.elevation3,
- },
- addonDetailSectionTitle: {
- fontSize: 16,
- fontWeight: '600',
- color: colors.white,
- marginBottom: 8,
- },
- addonDetailDescription: {
- fontSize: 15,
- color: colors.mediumEmphasis,
- lineHeight: 20,
- },
- addonDetailChips: {
- flexDirection: 'row',
- flexWrap: 'wrap',
- gap: 8,
- },
- addonDetailChip: {
- backgroundColor: colors.elevation3,
+ priorityBadge: {
+ backgroundColor: colors.primary,
borderRadius: 12,
paddingHorizontal: 8,
- paddingVertical: 4,
+ paddingVertical: 3,
},
- addonDetailChipText: {
- fontSize: 13,
+ priorityText: {
color: colors.white,
- },
- modalActions: {
- flexDirection: 'row',
- justifyContent: 'flex-end',
- padding: 16,
- borderTopWidth: 1,
- borderTopColor: colors.elevation3,
- },
- modalButton: {
- paddingVertical: 8,
- paddingHorizontal: 16,
- borderRadius: 8,
- minWidth: 80,
- alignItems: 'center',
- },
- cancelButton: {
- backgroundColor: colors.elevation3,
- marginRight: 8,
- },
- installButton: {
- backgroundColor: colors.success,
- borderRadius: 6,
- padding: 8,
- justifyContent: 'center',
- alignItems: 'center',
- },
- modalButtonText: {
- color: colors.white,
- fontWeight: '600',
+ fontSize: 12,
+ fontWeight: 'bold',
},
addonActions: {
flexDirection: 'row',
@@ -1241,9 +882,6 @@ const styles = StyleSheet.create({
padding: 6,
marginRight: 8,
},
- communityAddonsList: {
- paddingHorizontal: 20,
- },
communityAddonItem: {
flexDirection: 'row',
alignItems: 'center',
@@ -1333,6 +971,30 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
},
+ installButton: {
+ backgroundColor: colors.success,
+ borderRadius: 6,
+ padding: 8,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ statsCard: {
+ backgroundColor: colors.darkGray,
+ borderRadius: 8,
+ padding: 12,
+ alignItems: 'center',
+ minWidth: 100,
+ },
+ statsValue: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ color: colors.white,
+ marginBottom: 4,
+ },
+ statsLabel: {
+ fontSize: 13,
+ color: colors.mediumGray,
+ },
});
export default AddonsScreen;
\ No newline at end of file
diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx
index 66326644..9fc5ec6f 100644
--- a/src/screens/MetadataScreen.tsx
+++ b/src/screens/MetadataScreen.tsx
@@ -259,29 +259,78 @@ const MetadataScreen = () => {
// Fetch logo immediately for TMDB content
useEffect(() => {
- if (metadata && id.startsWith('tmdb:') && !metadata.logo) {
+ if (metadata && !metadata.logo) {
const fetchLogo = async () => {
try {
- const tmdbId = id.split(':')[1];
- const tmdbType = type === 'series' ? 'tv' : 'movie';
- const logoUrl = await TMDBService.getInstance().getContentLogo(tmdbType, tmdbId);
+ // First try to get logo from Metahub
+ const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
- if (logoUrl) {
- // Update metadata with logo
- setMetadata(prevMetadata => ({
- ...prevMetadata!,
- logo: logoUrl
- }));
- logger.log(`Successfully fetched logo for ${type} ${tmdbId} from TMDB on MetadataScreen`);
+ logger.log(`[MetadataScreen] Attempting to fetch logo from Metahub for ${imdbId}`);
+
+ // Test if Metahub logo exists with a HEAD request
+ try {
+ const response = await fetch(metahubUrl, { method: 'HEAD' });
+ if (response.ok) {
+ logger.log(`[MetadataScreen] Successfully fetched logo from Metahub:
+ - Content ID: ${id}
+ - Content Type: ${type}
+ - Logo URL: ${metahubUrl}
+ `);
+
+ // Update metadata with Metahub logo
+ setMetadata(prevMetadata => ({
+ ...prevMetadata!,
+ logo: metahubUrl
+ }));
+ return; // Exit if Metahub logo was found
+ }
+ } catch (metahubError) {
+ logger.warn(`[MetadataScreen] Failed to fetch logo from Metahub:`, metahubError);
+ }
+
+ // If Metahub fails, try TMDB as fallback
+ if (id.startsWith('tmdb:')) {
+ const tmdbId = id.split(':')[1];
+ const tmdbType = type === 'series' ? 'tv' : 'movie';
+
+ logger.log(`[MetadataScreen] Attempting to fetch logo from TMDB as fallback for ${tmdbType} (ID: ${tmdbId})`);
+
+ const logoUrl = await TMDBService.getInstance().getContentLogo(tmdbType, tmdbId);
+
+ if (logoUrl) {
+ logger.log(`[MetadataScreen] Successfully fetched fallback logo from TMDB:
+ - Content Type: ${tmdbType}
+ - TMDB ID: ${tmdbId}
+ - Logo URL: ${logoUrl}
+ `);
+
+ // Update metadata with TMDB logo
+ setMetadata(prevMetadata => ({
+ ...prevMetadata!,
+ logo: logoUrl
+ }));
+ } else {
+ logger.warn(`[MetadataScreen] No logo found from either Metahub or TMDB for ${type} (ID: ${id})`);
+ }
}
} catch (error) {
- logger.error('Failed to fetch logo in MetadataScreen:', error);
+ logger.error('[MetadataScreen] Failed to fetch logo from all sources:', {
+ error,
+ contentId: id,
+ contentType: type
+ });
}
};
fetchLogo();
+ } else if (metadata?.logo) {
+ logger.log(`[MetadataScreen] Using existing logo from metadata:
+ - Content ID: ${id}
+ - Content Type: ${type}
+ - Logo URL: ${metadata.logo}
+ `);
}
- }, [id, type, metadata, setMetadata]);
+ }, [id, type, metadata, setMetadata, imdbId]);
// Function to get episode details from episodeId
const getEpisodeDetails = useCallback((episodeId: string): { seasonNumber: string; episodeNumber: string; episodeName: string } | null => {
@@ -1188,8 +1237,8 @@ const styles = StyleSheet.create({
width: '100%',
},
titleLogo: {
- width: width * 0.65,
- height: 70,
+ width: width * 0.8,
+ height: 100,
marginBottom: 0,
alignSelf: 'center',
},