mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-18 07:12:18 +00:00
trakt sync now bug fox
This commit is contained in:
parent
374bc8e2d3
commit
407514301b
4 changed files with 125 additions and 65 deletions
|
|
@ -611,7 +611,7 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
|||
// Stop any playing trailer
|
||||
try {
|
||||
setTrailerPlaying(false);
|
||||
} catch {}
|
||||
} catch { }
|
||||
|
||||
// Check if we should resume based on watch progress
|
||||
const shouldResume = watchProgress &&
|
||||
|
|
@ -744,13 +744,32 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
|||
const timeSinceInteraction = Date.now() - lastInteractionRef.current;
|
||||
// Only auto-advance if user hasn't interacted recently (5 seconds) and no trailer playing
|
||||
if (timeSinceInteraction >= 5000 && (!globalTrailerPlaying || !trailerReady)) {
|
||||
setCurrentIndex((prev) => (prev + 1) % items.length);
|
||||
// Set next index preview for crossfade
|
||||
const nextIdx = (currentIndex + 1) % items.length;
|
||||
setNextIndex(nextIdx);
|
||||
|
||||
// Set drag direction for slide animation (left/next)
|
||||
dragDirection.value = -1;
|
||||
|
||||
// Animate crossfade before changing index
|
||||
dragProgress.value = withTiming(
|
||||
1,
|
||||
{
|
||||
duration: 500,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
},
|
||||
(finished) => {
|
||||
if (finished) {
|
||||
runOnJS(setCurrentIndex)(nextIdx);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Retry after remaining time
|
||||
startAutoPlay();
|
||||
}
|
||||
}, 25000); // Auto-advance every 25 seconds
|
||||
}, [items.length, globalTrailerPlaying, trailerReady]);
|
||||
}, [items.length, globalTrailerPlaying, trailerReady, currentIndex, dragDirection, dragProgress]);
|
||||
|
||||
useEffect(() => {
|
||||
startAutoPlay();
|
||||
|
|
@ -852,7 +871,7 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
|||
.onEnd((event) => {
|
||||
const velocity = event.velocityX;
|
||||
const translationX = event.translationX;
|
||||
const swipeThreshold = width * 0.05; // Very small threshold - minimal swipe needed
|
||||
const swipeThreshold = width * 0.16; // 16% threshold for swipe detection
|
||||
|
||||
if (Math.abs(translationX) > swipeThreshold || Math.abs(velocity) > 300) {
|
||||
// Complete the swipe - animate to full opacity before navigation
|
||||
|
|
@ -1159,61 +1178,61 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
|||
style={logoAnimatedStyle}
|
||||
>
|
||||
{currentItem.logo && !logoError[currentIndex] ? (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={() => {
|
||||
if (currentItem) {
|
||||
navigation.navigate('Metadata', {
|
||||
id: currentItem.id,
|
||||
type: currentItem.type,
|
||||
});
|
||||
}
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={() => {
|
||||
if (currentItem) {
|
||||
navigation.navigate('Metadata', {
|
||||
id: currentItem.id,
|
||||
type: currentItem.type,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.logoContainer,
|
||||
logoHeights[currentIndex] && logoHeights[currentIndex] < 80
|
||||
? { marginBottom: 4 } // Minimal spacing for small logos
|
||||
: { marginBottom: 8 } // Small spacing for normal logos
|
||||
]}
|
||||
onLayout={(event) => {
|
||||
const { height } = event.nativeEvent.layout;
|
||||
setLogoHeights((prev) => ({ ...prev, [currentIndex]: height }));
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.logoContainer,
|
||||
logoHeights[currentIndex] && logoHeights[currentIndex] < 80
|
||||
? { marginBottom: 4 } // Minimal spacing for small logos
|
||||
: { marginBottom: 8 } // Small spacing for normal logos
|
||||
]}
|
||||
onLayout={(event) => {
|
||||
const { height } = event.nativeEvent.layout;
|
||||
setLogoHeights((prev) => ({ ...prev, [currentIndex]: height }));
|
||||
<Image
|
||||
source={{ uri: currentItem.logo }}
|
||||
style={styles.logo}
|
||||
resizeMode="contain"
|
||||
onLoad={() => setLogoLoaded((prev) => ({ ...prev, [currentIndex]: true }))}
|
||||
onError={() => {
|
||||
setLogoError((prev) => ({ ...prev, [currentIndex]: true }));
|
||||
logger.warn('[AppleTVHero] Logo load failed:', currentItem.logo);
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: currentItem.logo }}
|
||||
style={styles.logo}
|
||||
resizeMode="contain"
|
||||
onLoad={() => setLogoLoaded((prev) => ({ ...prev, [currentIndex]: true }))}
|
||||
onError={() => {
|
||||
setLogoError((prev) => ({ ...prev, [currentIndex]: true }));
|
||||
logger.warn('[AppleTVHero] Logo load failed:', currentItem.logo);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.8}
|
||||
onPress={() => {
|
||||
if (currentItem) {
|
||||
navigation.navigate('Metadata', {
|
||||
id: currentItem.id,
|
||||
type: currentItem.type,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title} numberOfLines={2}>
|
||||
{currentItem.name}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</Animated.View>
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.8}
|
||||
onPress={() => {
|
||||
if (currentItem) {
|
||||
navigation.navigate('Metadata', {
|
||||
id: currentItem.id,
|
||||
type: currentItem.type,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title} numberOfLines={2}>
|
||||
{currentItem.name}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</Animated.View>
|
||||
|
||||
{/* Metadata Badge - Always Visible */}
|
||||
<View style={styles.metadataContainer}>
|
||||
|
|
@ -1231,7 +1250,7 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
|||
</View>
|
||||
</View>
|
||||
|
||||
{/* Action Buttons - Play and Save buttons */}
|
||||
{/* Action Buttons - Play and Save buttons */}
|
||||
<View style={styles.buttonsContainer}>
|
||||
{/* Play Button */}
|
||||
<TouchableOpacity
|
||||
|
|
|
|||
|
|
@ -443,21 +443,49 @@ export function useTraktIntegration() {
|
|||
// Process batch items with individual error handling
|
||||
const batchPromises = batch.map(async (item) => {
|
||||
try {
|
||||
const season = item.episodeId ? parseInt(item.episodeId.split('S')[1]?.split('E')[0] || '0') : undefined;
|
||||
const episode = item.episodeId ? parseInt(item.episodeId.split('E')[1] || '0') : undefined;
|
||||
|
||||
// Build content data from stored progress
|
||||
const contentData: TraktContentData = {
|
||||
type: item.type as 'movie' | 'episode',
|
||||
imdbId: item.id,
|
||||
title: 'Unknown', // We don't store title in progress, this would need metadata lookup
|
||||
year: 0,
|
||||
season: item.episodeId ? parseInt(item.episodeId.split('S')[1]?.split('E')[0] || '0') : undefined,
|
||||
episode: item.episodeId ? parseInt(item.episodeId.split('E')[1] || '0') : undefined
|
||||
season: season,
|
||||
episode: episode
|
||||
};
|
||||
|
||||
const progressPercent = (item.progress.currentTime / item.progress.duration) * 100;
|
||||
const isCompleted = progressPercent >= traktService.completionThreshold;
|
||||
|
||||
let success = false;
|
||||
|
||||
if (isCompleted) {
|
||||
// Item is completed - add to history with original watched date
|
||||
const watchedAt = new Date(item.progress.lastUpdated);
|
||||
logger.log(`[useTraktIntegration] Syncing completed item to history with date ${watchedAt.toISOString()}: ${item.type}:${item.id}`);
|
||||
|
||||
if (item.type === 'movie') {
|
||||
success = await traktService.addToWatchedMovies(item.id, watchedAt);
|
||||
} else if (item.type === 'series' || item.type === 'episode') { // Handle both type strings for safety
|
||||
if (season !== undefined && episode !== undefined) {
|
||||
success = await traktService.addToWatchedEpisodes(item.id, season, episode, watchedAt);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Item is in progress - sync as paused (scrobble)
|
||||
success = await traktService.syncProgressToTrakt(contentData, progressPercent, true);
|
||||
}
|
||||
|
||||
const success = await traktService.syncProgressToTrakt(contentData, progressPercent, true);
|
||||
if (success) {
|
||||
await storageService.updateTraktSyncStatus(item.id, item.type, true, progressPercent, item.episodeId);
|
||||
await storageService.updateTraktSyncStatus(
|
||||
item.id,
|
||||
item.type,
|
||||
true,
|
||||
isCompleted ? 100 : progressPercent,
|
||||
item.episodeId
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -121,13 +121,23 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
// Handle startup update check results
|
||||
useEffect(() => {
|
||||
|
||||
const handleStartupUpdateCheck = (updateInfo: UpdateInfo) => {
|
||||
const handleStartupUpdateCheck = async (updateInfo: UpdateInfo) => {
|
||||
console.log('UpdatePopup: Received startup update check result', updateInfo);
|
||||
setUpdateInfo(updateInfo);
|
||||
setHasCheckedOnStartup(true);
|
||||
|
||||
if (updateInfo.isAvailable) {
|
||||
setShowUpdatePopup(true);
|
||||
// Check setting before showing
|
||||
try {
|
||||
const otaAlertsEnabled = await mmkvStorage.getItem('@ota_updates_alerts_enabled');
|
||||
if (otaAlertsEnabled === 'false') {
|
||||
console.log('OTA alerts disabled, suppressing popup');
|
||||
return;
|
||||
}
|
||||
setShowUpdatePopup(true);
|
||||
} catch {
|
||||
setShowUpdatePopup(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -155,6 +165,9 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
// Check if user hasn't dismissed this version
|
||||
(async () => {
|
||||
try {
|
||||
const otaAlertsEnabled = await mmkvStorage.getItem('@ota_updates_alerts_enabled');
|
||||
if (otaAlertsEnabled === 'false') return;
|
||||
|
||||
const dismissedVersion = await mmkvStorage.getItem(UPDATE_POPUP_STORAGE_KEY);
|
||||
const currentVersion = updateInfo.manifest?.id;
|
||||
|
||||
|
|
|
|||
|
|
@ -639,14 +639,14 @@ export class TraktService {
|
|||
/**
|
||||
* Get the current completion threshold (user-configured or default)
|
||||
*/
|
||||
private get completionThreshold(): number {
|
||||
public get completionThreshold(): number {
|
||||
return this._completionThreshold || this.DEFAULT_COMPLETION_THRESHOLD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the completion threshold
|
||||
*/
|
||||
private set completionThreshold(value: number) {
|
||||
public set completionThreshold(value: number) {
|
||||
this._completionThreshold = value;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue