mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
test
This commit is contained in:
parent
c21f279aa3
commit
d605383720
4 changed files with 172 additions and 63 deletions
|
|
@ -586,21 +586,7 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
// Cinemeta addon details
|
||||
const cinemetaAddon: CommunityAddon = {
|
||||
transportUrl: 'https://v3-cinemeta.strem.io/manifest.json',
|
||||
manifest: {
|
||||
id: 'com.linvo.cinemeta',
|
||||
version: '3.0.13',
|
||||
name: 'Cinemeta',
|
||||
description: 'Provides metadata for movies and series from TheTVDB, TheMovieDB, etc.',
|
||||
logo: 'https://static.strem.io/addons/cinemeta.png',
|
||||
types: ['movie', 'series'],
|
||||
behaviorHints: {
|
||||
configurable: false
|
||||
}
|
||||
} as ExtendedManifest,
|
||||
};
|
||||
|
||||
|
||||
const AddonsScreen = () => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
|
|
@ -671,16 +657,15 @@ const AddonsScreen = () => {
|
|||
// Filter out addons without a manifest or transportUrl (basic validation)
|
||||
let validAddons = response.data.filter(addon => addon.manifest && addon.transportUrl);
|
||||
|
||||
// Filter out Cinemeta if it's already in the community list to avoid duplication
|
||||
// Filter out Cinemeta since it's now pre-installed
|
||||
validAddons = validAddons.filter(addon => addon.manifest.id !== 'com.linvo.cinemeta');
|
||||
|
||||
// Add Cinemeta to the beginning of the list
|
||||
setCommunityAddons([cinemetaAddon, ...validAddons]);
|
||||
setCommunityAddons(validAddons);
|
||||
} catch (error) {
|
||||
logger.error('Failed to load community addons:', error);
|
||||
setCommunityError('Failed to load community addons. Please try again later.');
|
||||
// Still show Cinemeta if the community list fails to load
|
||||
setCommunityAddons([cinemetaAddon]);
|
||||
// Set empty array on error since Cinemeta is pre-installed
|
||||
setCommunityAddons([]);
|
||||
} finally {
|
||||
setCommunityLoading(false);
|
||||
}
|
||||
|
|
@ -746,6 +731,16 @@ const AddonsScreen = () => {
|
|||
};
|
||||
|
||||
const handleRemoveAddon = (addon: ExtendedManifest) => {
|
||||
// Check if this is a pre-installed addon
|
||||
if (stremioService.isPreInstalledAddon(addon.id)) {
|
||||
Alert.alert(
|
||||
'Cannot Remove Addon',
|
||||
`${addon.name} is a pre-installed addon and cannot be removed.`,
|
||||
[{ text: 'OK', style: 'default' }]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
'Uninstall Addon',
|
||||
`Are you sure you want to uninstall ${addon.name}?`,
|
||||
|
|
@ -900,6 +895,8 @@ const AddonsScreen = () => {
|
|||
const logo = item.logo || null;
|
||||
// Check if addon is configurable
|
||||
const isConfigurable = item.behaviorHints?.configurable === true;
|
||||
// Check if addon is pre-installed
|
||||
const isPreInstalled = stremioService.isPreInstalledAddon(item.id);
|
||||
|
||||
// Format the types into a simple category text
|
||||
const categoryText = types.length > 0
|
||||
|
|
@ -951,7 +948,14 @@ const AddonsScreen = () => {
|
|||
</View>
|
||||
)}
|
||||
<View style={styles.addonTitleContainer}>
|
||||
<Text style={styles.addonName}>{item.name}</Text>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 2 }}>
|
||||
<Text style={styles.addonName}>{item.name}</Text>
|
||||
{isPreInstalled && (
|
||||
<View style={[styles.priorityBadge, { marginLeft: 8, backgroundColor: colors.success }]}>
|
||||
<Text style={[styles.priorityText, { fontSize: 10 }]}>PRE-INSTALLED</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.addonMetaContainer}>
|
||||
<Text style={styles.addonVersion}>v{item.version || '1.0.0'}</Text>
|
||||
<Text style={styles.addonDot}>•</Text>
|
||||
|
|
@ -969,12 +973,14 @@ const AddonsScreen = () => {
|
|||
<MaterialIcons name="settings" size={20} color={colors.primary} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={styles.deleteButton}
|
||||
onPress={() => handleRemoveAddon(item)}
|
||||
>
|
||||
<MaterialIcons name="delete" size={20} color={colors.error} />
|
||||
</TouchableOpacity>
|
||||
{!stremioService.isPreInstalledAddon(item.id) && (
|
||||
<TouchableOpacity
|
||||
style={styles.deleteButton}
|
||||
onPress={() => handleRemoveAddon(item)}
|
||||
>
|
||||
<MaterialIcons name="delete" size={20} color={colors.error} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<View style={styles.priorityBadge}>
|
||||
|
|
@ -1410,4 +1416,4 @@ const AddonsScreen = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default AddonsScreen;
|
||||
export default AddonsScreen;
|
||||
|
|
@ -187,12 +187,12 @@ const PulsingChip = memo(({ text, delay }: { text: string; delay: number }) => {
|
|||
const { currentTheme } = useTheme();
|
||||
const styles = React.useMemo(() => createStyles(currentTheme.colors), [currentTheme.colors]);
|
||||
|
||||
const pulseValue = useSharedValue(0.7);
|
||||
const pulseValue = useSharedValue(0.6);
|
||||
|
||||
useEffect(() => {
|
||||
const startPulse = () => {
|
||||
pulseValue.value = withTiming(1, { duration: 800 }, () => {
|
||||
pulseValue.value = withTiming(0.7, { duration: 800 }, () => {
|
||||
pulseValue.value = withTiming(1, { duration: 1200 }, () => {
|
||||
pulseValue.value = withTiming(0.6, { duration: 1200 }, () => {
|
||||
runOnJS(startPulse)();
|
||||
});
|
||||
});
|
||||
|
|
@ -207,8 +207,7 @@ const PulsingChip = memo(({ text, delay }: { text: string; delay: number }) => {
|
|||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
opacity: pulseValue.value,
|
||||
transform: [{ scale: interpolate(pulseValue.value, [0.7, 1], [0.95, 1], Extrapolate.CLAMP) }]
|
||||
opacity: pulseValue.value
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -1722,36 +1721,34 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
},
|
||||
activeScrapersContainer: {
|
||||
paddingHorizontal: 16,
|
||||
paddingBottom: 12,
|
||||
backgroundColor: colors.elevation1,
|
||||
paddingVertical: 8,
|
||||
backgroundColor: 'transparent',
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 8,
|
||||
borderRadius: 8,
|
||||
paddingVertical: 12,
|
||||
marginBottom: 4,
|
||||
},
|
||||
activeScrapersTitle: {
|
||||
color: colors.primary,
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
marginBottom: 8,
|
||||
color: colors.mediumEmphasis,
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
marginBottom: 6,
|
||||
opacity: 0.8,
|
||||
},
|
||||
activeScrapersRow: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 6,
|
||||
gap: 4,
|
||||
},
|
||||
activeScraperChip: {
|
||||
backgroundColor: colors.surfaceVariant,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.primary + '40',
|
||||
backgroundColor: colors.elevation2,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 3,
|
||||
borderRadius: 6,
|
||||
borderWidth: 0,
|
||||
},
|
||||
activeScraperText: {
|
||||
color: colors.highEmphasis,
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
color: colors.mediumEmphasis,
|
||||
fontSize: 11,
|
||||
fontWeight: '400',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ class NotificationService {
|
|||
private backgroundSyncInterval: NodeJS.Timeout | null = null;
|
||||
private librarySubscription: (() => void) | null = null;
|
||||
private appStateSubscription: any = null;
|
||||
private lastSyncTime: number = 0;
|
||||
private readonly MIN_SYNC_INTERVAL = 5 * 60 * 1000; // 5 minutes minimum between syncs
|
||||
|
||||
private constructor() {
|
||||
// Initialize notifications
|
||||
|
|
@ -149,6 +151,16 @@ class NotificationService {
|
|||
return null;
|
||||
}
|
||||
|
||||
// Check if notification already exists for this episode
|
||||
const existingNotification = this.scheduledNotifications.find(
|
||||
notification => notification.seriesId === item.seriesId &&
|
||||
notification.season === item.season &&
|
||||
notification.episode === item.episode
|
||||
);
|
||||
if (existingNotification) {
|
||||
return null; // Don't schedule duplicate notifications
|
||||
}
|
||||
|
||||
const releaseDate = parseISO(item.releaseDate);
|
||||
const now = new Date();
|
||||
|
||||
|
|
@ -162,9 +174,9 @@ class NotificationService {
|
|||
const notificationTime = new Date(releaseDate);
|
||||
notificationTime.setHours(notificationTime.getHours() - this.settings.timeBeforeAiring);
|
||||
|
||||
// If notification time has already passed, set to now + 1 minute
|
||||
// If notification time has already passed, don't schedule the notification
|
||||
if (notificationTime < now) {
|
||||
notificationTime.setTime(now.getTime() + 60000);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Schedule the notification
|
||||
|
|
@ -254,9 +266,17 @@ class NotificationService {
|
|||
this.librarySubscription = catalogService.subscribeToLibraryUpdates(async (libraryItems) => {
|
||||
if (!this.settings.enabled) return;
|
||||
|
||||
// Reduced logging verbosity
|
||||
// logger.log('[NotificationService] Library updated, syncing notifications for', libraryItems.length, 'items');
|
||||
await this.syncNotificationsForLibrary(libraryItems);
|
||||
const now = Date.now();
|
||||
const timeSinceLastSync = now - this.lastSyncTime;
|
||||
|
||||
// Only sync if enough time has passed since last sync
|
||||
if (timeSinceLastSync >= this.MIN_SYNC_INTERVAL) {
|
||||
// Reduced logging verbosity
|
||||
// logger.log('[NotificationService] Library updated, syncing notifications for', libraryItems.length, 'items');
|
||||
await this.syncNotificationsForLibrary(libraryItems);
|
||||
} else {
|
||||
// logger.log(`[NotificationService] Library updated, but skipping sync (last sync ${Math.round(timeSinceLastSync / 1000)}s ago)`);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[NotificationService] Error setting up library integration:', error);
|
||||
|
|
@ -284,10 +304,18 @@ class NotificationService {
|
|||
|
||||
private handleAppStateChange = async (nextAppState: AppStateStatus) => {
|
||||
if (nextAppState === 'active' && this.settings.enabled) {
|
||||
// App came to foreground, sync notifications
|
||||
// Reduced logging verbosity
|
||||
// logger.log('[NotificationService] App became active, syncing notifications');
|
||||
await this.performBackgroundSync();
|
||||
const now = Date.now();
|
||||
const timeSinceLastSync = now - this.lastSyncTime;
|
||||
|
||||
// Only sync if enough time has passed since last sync
|
||||
if (timeSinceLastSync >= this.MIN_SYNC_INTERVAL) {
|
||||
// App came to foreground, sync notifications
|
||||
// Reduced logging verbosity
|
||||
// logger.log('[NotificationService] App became active, syncing notifications');
|
||||
await this.performBackgroundSync();
|
||||
} else {
|
||||
// logger.log(`[NotificationService] App became active, but skipping sync (last sync ${Math.round(timeSinceLastSync / 1000)}s ago)`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -312,6 +340,9 @@ class NotificationService {
|
|||
// Perform comprehensive background sync including Trakt integration
|
||||
private async performBackgroundSync(): Promise<void> {
|
||||
try {
|
||||
// Update last sync time at the start
|
||||
this.lastSyncTime = Date.now();
|
||||
|
||||
// Reduced logging verbosity
|
||||
// logger.log('[NotificationService] Starting comprehensive background sync');
|
||||
|
||||
|
|
@ -467,7 +498,13 @@ class NotificationService {
|
|||
if (!video.released) return false;
|
||||
const releaseDate = parseISO(video.released);
|
||||
return releaseDate > now && releaseDate < fourWeeksLater;
|
||||
});
|
||||
}).map(video => ({
|
||||
id: video.id,
|
||||
title: (video as any).title || (video as any).name || `Episode ${video.episode}`,
|
||||
season: video.season || 0,
|
||||
episode: video.episode || 0,
|
||||
released: video.released,
|
||||
}));
|
||||
}
|
||||
|
||||
// If no upcoming episodes from Stremio, try TMDB
|
||||
|
|
|
|||
|
|
@ -213,6 +213,51 @@ class StremioService {
|
|||
}
|
||||
}
|
||||
|
||||
// Ensure Cinemeta is always installed as a pre-installed addon
|
||||
const cinemetaId = 'com.linvo.cinemeta';
|
||||
if (!this.installedAddons.has(cinemetaId)) {
|
||||
const cinemetaManifest: Manifest = {
|
||||
id: cinemetaId,
|
||||
name: 'Cinemeta',
|
||||
version: '3.0.13',
|
||||
description: 'Provides metadata for movies and series from TheTVDB, TheMovieDB, etc.',
|
||||
url: 'https://v3-cinemeta.strem.io',
|
||||
originalUrl: 'https://v3-cinemeta.strem.io/manifest.json',
|
||||
types: ['movie', 'series'],
|
||||
catalogs: [
|
||||
{
|
||||
type: 'movie',
|
||||
id: 'top',
|
||||
name: 'Top Movies',
|
||||
extraSupported: ['search', 'genre', 'skip']
|
||||
},
|
||||
{
|
||||
type: 'series',
|
||||
id: 'top',
|
||||
name: 'Top Series',
|
||||
extraSupported: ['search', 'genre', 'skip']
|
||||
}
|
||||
],
|
||||
resources: [
|
||||
{
|
||||
name: 'catalog',
|
||||
types: ['movie', 'series'],
|
||||
idPrefixes: ['tt']
|
||||
},
|
||||
{
|
||||
name: 'meta',
|
||||
types: ['movie', 'series'],
|
||||
idPrefixes: ['tt']
|
||||
}
|
||||
],
|
||||
behaviorHints: {
|
||||
configurable: false
|
||||
}
|
||||
};
|
||||
this.installedAddons.set(cinemetaId, cinemetaManifest);
|
||||
logger.log('✅ Cinemeta pre-installed as default addon');
|
||||
}
|
||||
|
||||
// Load addon order if exists
|
||||
const storedOrder = await AsyncStorage.getItem(this.ADDON_ORDER_KEY);
|
||||
if (storedOrder) {
|
||||
|
|
@ -221,13 +266,26 @@ class StremioService {
|
|||
this.addonOrder = this.addonOrder.filter(id => this.installedAddons.has(id));
|
||||
}
|
||||
|
||||
// Ensure Cinemeta is first in the order
|
||||
if (!this.addonOrder.includes(cinemetaId)) {
|
||||
this.addonOrder.unshift(cinemetaId);
|
||||
} else {
|
||||
// Move Cinemeta to the front if it's not already there
|
||||
const cinemetaIndex = this.addonOrder.indexOf(cinemetaId);
|
||||
if (cinemetaIndex > 0) {
|
||||
this.addonOrder.splice(cinemetaIndex, 1);
|
||||
this.addonOrder.unshift(cinemetaId);
|
||||
}
|
||||
}
|
||||
|
||||
// Add any missing addons to the order
|
||||
const installedIds = Array.from(this.installedAddons.keys());
|
||||
const missingIds = installedIds.filter(id => !this.addonOrder.includes(id));
|
||||
this.addonOrder = [...this.addonOrder, ...missingIds];
|
||||
|
||||
// Ensure order is saved
|
||||
// Ensure order and addons are saved
|
||||
await this.saveAddonOrder();
|
||||
await this.saveInstalledAddons();
|
||||
|
||||
this.initialized = true;
|
||||
} catch (error) {
|
||||
|
|
@ -336,6 +394,12 @@ class StremioService {
|
|||
}
|
||||
|
||||
removeAddon(id: string): void {
|
||||
// Prevent removal of Cinemeta as it's a pre-installed addon
|
||||
if (id === 'com.linvo.cinemeta') {
|
||||
logger.warn('❌ Cannot remove Cinemeta - it is a pre-installed addon');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.installedAddons.has(id)) {
|
||||
this.installedAddons.delete(id);
|
||||
// Remove from order
|
||||
|
|
@ -359,6 +423,11 @@ class StremioService {
|
|||
return this.getInstalledAddons();
|
||||
}
|
||||
|
||||
// Check if an addon is pre-installed and cannot be removed
|
||||
isPreInstalledAddon(id: string): boolean {
|
||||
return id === 'com.linvo.cinemeta';
|
||||
}
|
||||
|
||||
private formatId(id: string): string {
|
||||
return id.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue