mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
bug fixes
This commit is contained in:
parent
2c2f28ddd2
commit
591bdfeb77
3 changed files with 122 additions and 18 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue