bug fixes

This commit is contained in:
tapframe 2025-09-13 15:16:04 +05:30
parent 2c2f28ddd2
commit 591bdfeb77
3 changed files with 122 additions and 18 deletions

View file

@ -147,7 +147,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
isRefreshingRef.current = true;
// Helper to merge a batch of items into state (dedupe by type:id, keep newest)
const mergeBatchIntoState = (batch: ContinueWatchingItem[]) => {
const mergeBatchIntoState = async (batch: ContinueWatchingItem[]) => {
if (!batch || batch.length === 0) return;
setContinueWatchingItems((prev) => {
@ -156,25 +156,45 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
map.set(`${it.type}:${it.id}`, it);
}
for (const it of batch) {
const key = `${it.type}:${it.id}`;
// Skip recently removed items to prevent immediate re-addition
if (recentlyRemovedRef.current.has(key)) {
continue;
}
const existing = map.get(key);
if (!existing || (it.lastUpdated ?? 0) > (existing.lastUpdated ?? 0)) {
map.set(key, it);
}
}
const merged = Array.from(map.values());
merged.sort((a, b) => (b.lastUpdated ?? 0) - (a.lastUpdated ?? 0));
return merged;
});
// Process batch items asynchronously to check removal status
for (const it of batch) {
const key = `${it.type}:${it.id}`;
// Skip recently removed items to prevent immediate re-addition
if (recentlyRemovedRef.current.has(key)) {
continue;
}
// Skip items that have been persistently marked as removed
const isRemoved = await storageService.isContinueWatchingRemoved(it.id, it.type);
if (isRemoved) {
continue;
}
// Add the item to state
setContinueWatchingItems((prev) => {
const map = new Map<string, ContinueWatchingItem>();
for (const existing of prev) {
map.set(`${existing.type}:${existing.id}`, existing);
}
const existing = map.get(key);
if (!existing || (it.lastUpdated ?? 0) > (existing.lastUpdated ?? 0)) {
map.set(key, it);
const merged = Array.from(map.values());
merged.sort((a, b) => (b.lastUpdated ?? 0) - (a.lastUpdated ?? 0));
return merged;
}
return prev;
});
}
};
try {
@ -288,7 +308,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
} as ContinueWatchingItem);
}
if (batch.length > 0) mergeBatchIntoState(batch);
if (batch.length > 0) await mergeBatchIntoState(batch);
} catch (error) {
// Continue processing other groups even if one fails
}
@ -337,7 +357,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
}
if (nextEpisodeVideo && isEpisodeReleased(nextEpisodeVideo)) {
logger.log(` [TraktSync] Adding next episode for ${showId}: S${info.season}E${nextEpisode}`);
mergeBatchIntoState([
await mergeBatchIntoState([
{
...basicContent,
id: showId,
@ -536,6 +556,9 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
const itemKey = `${item.type}:${item.id}`;
recentlyRemovedRef.current.add(itemKey);
// Persist the removed state for long-term tracking
await storageService.addContinueWatchingRemoved(item.id, item.type);
// Clear from recently removed after the ignore duration
setTimeout(() => {
recentlyRemovedRef.current.delete(itemKey);

View file

@ -427,6 +427,12 @@ class SyncService {
await AsyncStorage.setItem(`@user:${userId}:app_settings`, JSON.stringify(mergedSettings));
await AsyncStorage.setItem('app_settings', JSON.stringify(mergedSettings));
// Sync continue watching removed items (stored in app_settings)
if (remoteSettingsSansLocalOnly?.continue_watching_removed) {
await AsyncStorage.setItem(`@user:${userId}:@continue_watching_removed`, JSON.stringify(remoteSettingsSansLocalOnly.continue_watching_removed));
}
await storageService.saveSubtitleSettings(us.subtitle_settings || {});
// Notify listeners that settings changed due to sync
try { settingsEmitter.emit(); } catch {}
@ -436,6 +442,12 @@ class SyncService {
const { episodeLayoutStyle: _remoteEpisodeLayoutStyle, ...remoteSettingsSansLocalOnly } = remoteRaw || {};
await AsyncStorage.setItem(`@user:${userId}:app_settings`, JSON.stringify(remoteSettingsSansLocalOnly));
await AsyncStorage.setItem('app_settings', JSON.stringify(remoteSettingsSansLocalOnly));
// Sync continue watching removed items in fallback (stored in app_settings)
if (remoteSettingsSansLocalOnly?.continue_watching_removed) {
await AsyncStorage.setItem(`@user:${userId}:@continue_watching_removed`, JSON.stringify(remoteSettingsSansLocalOnly.continue_watching_removed));
}
await storageService.saveSubtitleSettings(us.subtitle_settings || {});
try { settingsEmitter.emit(); } catch {}
}
@ -762,9 +774,17 @@ class SyncService {
// Exclude local-only settings from push
const { episodeLayoutStyle: _localEpisodeLayoutStyle, ...appSettings } = parsed || {};
const subtitleSettings = (await storageService.getSubtitleSettings()) || {};
const continueWatchingRemoved = await storageService.getContinueWatchingRemoved();
// Include continue watching removed items in app_settings
const appSettingsWithRemoved = {
...appSettings,
continue_watching_removed: continueWatchingRemoved
};
const { error } = await supabase.from('user_settings').upsert({
user_id: userId,
app_settings: appSettings,
app_settings: appSettingsWithRemoved,
subtitle_settings: subtitleSettings,
});
if (error && __DEV__) console.warn('[SyncService] push settings error', error);

View file

@ -16,6 +16,7 @@ class StorageService {
private readonly CONTENT_DURATION_KEY = '@content_duration:';
private readonly SUBTITLE_SETTINGS_KEY = '@subtitle_settings';
private readonly WP_TOMBSTONES_KEY = '@wp_tombstones';
private readonly CONTINUE_WATCHING_REMOVED_KEY = '@continue_watching_removed';
private watchProgressSubscribers: (() => void)[] = [];
private watchProgressRemoveListeners: ((id: string, type: string, episodeId?: string) => void)[] = [];
private notificationDebounceTimer: NodeJS.Timeout | null = null;
@ -61,6 +62,11 @@ class StorageService {
return `@user:${scope}:${this.WP_TOMBSTONES_KEY}`;
}
private async getContinueWatchingRemovedKeyScoped(): Promise<string> {
const scope = await this.getUserScope();
return `@user:${scope}:${this.CONTINUE_WATCHING_REMOVED_KEY}`;
}
private buildWpKeyString(id: string, type: string, episodeId?: string): string {
return `${type}:${id}${episodeId ? `:${episodeId}` : ''}`;
}
@ -107,6 +113,61 @@ class StorageService {
}
}
public async addContinueWatchingRemoved(
id: string,
type: string,
removedAtMs?: number
): Promise<void> {
try {
const key = await this.getContinueWatchingRemovedKeyScoped();
const json = (await AsyncStorage.getItem(key)) || '{}';
const map = JSON.parse(json) as Record<string, number>;
map[this.buildWpKeyString(id, type)] = removedAtMs || Date.now();
await AsyncStorage.setItem(key, JSON.stringify(map));
} catch (error) {
logger.error('Error adding continue watching removed item:', error);
}
}
public async removeContinueWatchingRemoved(
id: string,
type: string
): Promise<void> {
try {
const key = await this.getContinueWatchingRemovedKeyScoped();
const json = (await AsyncStorage.getItem(key)) || '{}';
const map = JSON.parse(json) as Record<string, number>;
const k = this.buildWpKeyString(id, type);
if (map[k] != null) {
delete map[k];
await AsyncStorage.setItem(key, JSON.stringify(map));
}
} catch (error) {
logger.error('Error removing continue watching removed item:', error);
}
}
public async getContinueWatchingRemoved(): Promise<Record<string, number>> {
try {
const key = await this.getContinueWatchingRemovedKeyScoped();
const json = (await AsyncStorage.getItem(key)) || '{}';
return JSON.parse(json) as Record<string, number>;
} catch (error) {
logger.error('Error getting continue watching removed items:', error);
return {};
}
}
public async isContinueWatchingRemoved(id: string, type: string): Promise<boolean> {
try {
const removedItems = await this.getContinueWatchingRemoved();
const key = this.buildWpKeyString(id, type);
return removedItems[key] != null;
} catch {
return false;
}
}
public async setContentDuration(
id: string,
type: string,