trakt sync now bug fox

This commit is contained in:
tapframe 2025-12-17 21:53:21 +05:30
parent 374bc8e2d3
commit 407514301b
4 changed files with 125 additions and 65 deletions

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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;
}