fixed addon reorder issue

This commit is contained in:
tapframe 2025-08-14 11:58:03 +05:30
parent a32fb39743
commit 2f404d7c99
4 changed files with 101 additions and 38 deletions

View file

@ -1346,7 +1346,7 @@ const VideoPlayer: React.FC = () => {
if (type !== 'series' || !nextEpisode || duration <= 0) { if (type !== 'series' || !nextEpisode || duration <= 0) {
if (showNextEpisodeButton) { if (showNextEpisodeButton) {
// Hide button with animation // Hide button with animation
Animated.parallel([ fi Animated.parallel([
Animated.timing(nextEpisodeButtonOpacity, { Animated.timing(nextEpisodeButtonOpacity, {
toValue: 0, toValue: 0,
duration: 200, duration: 200,

View file

@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { syncService } from '../services/SyncService';
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
// Simple event emitter for settings changes // Simple event emitter for settings changes
@ -122,12 +123,40 @@ export const useSettings = () => {
const loadSettings = async () => { const loadSettings = async () => {
try { try {
const scope = (await AsyncStorage.getItem('@user:current')) || 'local'; const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
const storedSettings = await AsyncStorage.getItem(`@user:${scope}:${SETTINGS_STORAGE_KEY}`); const scopedKey = `@user:${scope}:${SETTINGS_STORAGE_KEY}`;
if (storedSettings) { const [scopedJson, legacyJson] = await Promise.all([
const parsedSettings = JSON.parse(storedSettings); AsyncStorage.getItem(scopedKey),
// Merge with defaults to ensure all properties exist AsyncStorage.getItem(SETTINGS_STORAGE_KEY),
setSettings({ ...DEFAULT_SETTINGS, ...parsedSettings }); ]);
const parsedScoped = scopedJson ? JSON.parse(scopedJson) : null;
const parsedLegacy = legacyJson ? JSON.parse(legacyJson) : null;
let merged = parsedScoped || parsedLegacy;
// Fallback: scan any existing user-scoped settings if current scope not set yet
if (!merged) {
try {
const allKeys = await AsyncStorage.getAllKeys();
const candidateKeys = (allKeys || []).filter(k => k.endsWith(`:${SETTINGS_STORAGE_KEY}`));
if (candidateKeys.length > 0) {
const pairs = await AsyncStorage.multiGet(candidateKeys);
for (const [, value] of pairs) {
if (value) {
try {
const candidate = JSON.parse(value);
if (candidate && typeof candidate === 'object') {
merged = candidate;
break;
}
} catch {}
}
}
}
} catch {}
} }
if (merged) setSettings({ ...DEFAULT_SETTINGS, ...merged });
else setSettings(DEFAULT_SETTINGS);
} catch (error) { } catch (error) {
console.error('Failed to load settings:', error); console.error('Failed to load settings:', error);
// Fallback to default settings on error // Fallback to default settings on error
@ -143,7 +172,14 @@ export const useSettings = () => {
const newSettings = { ...settings, [key]: value }; const newSettings = { ...settings, [key]: value };
try { try {
const scope = (await AsyncStorage.getItem('@user:current')) || 'local'; const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
await AsyncStorage.setItem(`@user:${scope}:${SETTINGS_STORAGE_KEY}`, JSON.stringify(newSettings)); const scopedKey = `@user:${scope}:${SETTINGS_STORAGE_KEY}`;
// Write to both scoped key (multi-user aware) and legacy key for backward compatibility
await Promise.all([
AsyncStorage.setItem(scopedKey, JSON.stringify(newSettings)),
AsyncStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(newSettings)),
]);
// Ensure a current scope exists to avoid future loads missing the chosen scope
await AsyncStorage.setItem('@user:current', scope);
setSettings(newSettings); setSettings(newSettings);
console.log(`Setting updated: ${key}`, value); console.log(`Setting updated: ${key}`, value);
@ -152,6 +188,9 @@ export const useSettings = () => {
console.log('Emitting settings change event'); console.log('Emitting settings change event');
settingsEmitter.emit(); settingsEmitter.emit();
} }
// If authenticated, push settings to server to prevent overwrite on next pull
try { syncService.pushSettings(); } catch {}
} catch (error) { } catch (error) {
console.error('Failed to save settings:', error); console.error('Failed to save settings:', error);
} }

View file

@ -612,7 +612,7 @@ class SyncService {
try { map.set('org.stremio.opensubtitlesv3', await stremioService.getManifest('https://opensubtitles-v3.strem.io/manifest.json')); } catch {} try { map.set('org.stremio.opensubtitlesv3', await stremioService.getManifest('https://opensubtitles-v3.strem.io/manifest.json')); } catch {}
(stremioService as any).installedAddons = map; (stremioService as any).installedAddons = map;
const order = (addons as any[]).map(a => a.addon_id); let order = (addons as any[]).map(a => a.addon_id);
const ensureFront = (arr: string[], id: string) => { const ensureFront = (arr: string[], id: string) => {
const idx = arr.indexOf(id); const idx = arr.indexOf(id);
if (idx === -1) arr.unshift(id); if (idx === -1) arr.unshift(id);
@ -620,11 +620,42 @@ class SyncService {
}; };
ensureFront(order, 'com.linvo.cinemeta'); ensureFront(order, 'com.linvo.cinemeta');
ensureFront(order, 'org.stremio.opensubtitlesv3'); ensureFront(order, 'org.stremio.opensubtitlesv3');
// Keep order strictly from server after preinstalled // Prefer local order if it exists; otherwise use remote
// Do not append missing local-only ids to avoid resurrecting removed addons try {
const userScope = `@user:${userId}:stremio-addon-order`;
const [localScopedOrder, localLegacyOrder, localGuestOrder] = await Promise.all([
AsyncStorage.getItem(userScope),
AsyncStorage.getItem('stremio-addon-order'),
AsyncStorage.getItem('@user:local:stremio-addon-order'),
]);
const localOrderRaw = localScopedOrder || localLegacyOrder || localGuestOrder;
if (localOrderRaw) {
const localOrder = JSON.parse(localOrderRaw) as string[];
// Filter to only installed ids
const localFiltered = localOrder.filter(id => map.has(id));
if (localFiltered.length > 0) {
order = localFiltered;
}
}
} catch {}
(stremioService as any).addonOrder = order; (stremioService as any).addonOrder = order;
await (stremioService as any).saveInstalledAddons(); await (stremioService as any).saveInstalledAddons();
await (stremioService as any).saveAddonOrder(); await (stremioService as any).saveAddonOrder();
// Push merged order to server to preserve across devices
try {
const rows = order.map((addonId: string, idx: number) => ({
user_id: userId,
addon_id: addonId,
position: idx,
}));
const { error } = await supabase
.from('installed_addons')
.upsert(rows, { onConflict: 'user_id,addon_id' });
if (error) logger.warn('[SyncService] push merged addon order error', error);
} catch (e) {
logger.warn('[SyncService] push merged addon order exception', e);
}
} }
async pushWatchProgress(): Promise<void> { async pushWatchProgress(): Promise<void> {

View file

@ -301,40 +301,23 @@ class StremioService {
} }
} }
// Load addon order if exists // Load addon order if exists (scoped first, then legacy, then @user:local for migration safety)
const storedOrder = await AsyncStorage.getItem(`@user:${scope}:${this.ADDON_ORDER_KEY}`); let storedOrder = await AsyncStorage.getItem(`@user:${scope}:${this.ADDON_ORDER_KEY}`);
if (!storedOrder) storedOrder = await AsyncStorage.getItem(this.ADDON_ORDER_KEY);
if (!storedOrder) storedOrder = await AsyncStorage.getItem(`@user:local:${this.ADDON_ORDER_KEY}`);
if (storedOrder) { if (storedOrder) {
this.addonOrder = JSON.parse(storedOrder); this.addonOrder = JSON.parse(storedOrder);
// Filter out any ids that aren't in installedAddons // Filter out any ids that aren't in installedAddons
this.addonOrder = this.addonOrder.filter(id => this.installedAddons.has(id)); this.addonOrder = this.addonOrder.filter(id => this.installedAddons.has(id));
} }
// Ensure Cinemeta is first in the order // Ensure required pre-installed addons are present without forcing their position
if (!this.addonOrder.includes(cinemetaId)) { if (!this.addonOrder.includes(cinemetaId) && this.installedAddons.has(cinemetaId)) {
this.addonOrder.unshift(cinemetaId); this.addonOrder.push(cinemetaId);
} else { }
// Move Cinemeta to the front if it's not already there if (!this.addonOrder.includes(opensubsId) && this.installedAddons.has(opensubsId)) {
const cinemetaIndex = this.addonOrder.indexOf(cinemetaId); this.addonOrder.push(opensubsId);
if (cinemetaIndex > 0) {
this.addonOrder.splice(cinemetaIndex, 1);
this.addonOrder.unshift(cinemetaId);
}
} }
// Ensure OpenSubtitles v3 is present right after Cinemeta (if not already ordered)
const ensureOpensubsPosition = () => {
const idx = this.addonOrder.indexOf(opensubsId);
const cinIdx = this.addonOrder.indexOf(cinemetaId);
if (idx === -1) {
// Insert after Cinemeta
this.addonOrder.splice(cinIdx + 1, 0, opensubsId);
} else if (idx <= cinIdx) {
// Move it to right after Cinemeta
this.addonOrder.splice(idx, 1);
this.addonOrder.splice(cinIdx + 1, 0, opensubsId);
}
};
ensureOpensubsPosition();
// Add any missing addons to the order // Add any missing addons to the order
const installedIds = Array.from(this.installedAddons.keys()); const installedIds = Array.from(this.installedAddons.keys());
@ -399,7 +382,11 @@ class StremioService {
private async saveAddonOrder(): Promise<void> { private async saveAddonOrder(): Promise<void> {
try { try {
const scope = (await AsyncStorage.getItem('@user:current')) || 'local'; const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
await AsyncStorage.setItem(`@user:${scope}:${this.ADDON_ORDER_KEY}`, JSON.stringify(this.addonOrder)); // Write to both scoped and legacy keys for compatibility
await Promise.all([
AsyncStorage.setItem(`@user:${scope}:${this.ADDON_ORDER_KEY}`, JSON.stringify(this.addonOrder)),
AsyncStorage.setItem(this.ADDON_ORDER_KEY, JSON.stringify(this.addonOrder)),
]);
} catch (error) { } catch (error) {
logger.error('Failed to save addon order:', error); logger.error('Failed to save addon order:', error);
} }
@ -446,6 +433,7 @@ class StremioService {
await this.saveInstalledAddons(); await this.saveInstalledAddons();
await this.saveAddonOrder(); await this.saveAddonOrder();
try { (require('./SyncService').syncService as any).pushAddons?.(); } catch {}
// Emit an event that an addon was added // Emit an event that an addon was added
addonEmitter.emit(ADDON_EVENTS.ADDON_ADDED, manifest.id); addonEmitter.emit(ADDON_EVENTS.ADDON_ADDED, manifest.id);
} else { } else {
@ -466,6 +454,7 @@ class StremioService {
this.addonOrder = this.addonOrder.filter(addonId => addonId !== id); this.addonOrder = this.addonOrder.filter(addonId => addonId !== id);
this.saveInstalledAddons(); this.saveInstalledAddons();
this.saveAddonOrder(); this.saveAddonOrder();
try { (require('./SyncService').syncService as any).pushAddons?.(); } catch {}
// Emit an event that an addon was removed // Emit an event that an addon was removed
addonEmitter.emit(ADDON_EVENTS.ADDON_REMOVED, id); addonEmitter.emit(ADDON_EVENTS.ADDON_REMOVED, id);
} }
@ -1238,6 +1227,8 @@ class StremioService {
[this.addonOrder[index - 1], this.addonOrder[index]] = [this.addonOrder[index - 1], this.addonOrder[index]] =
[this.addonOrder[index], this.addonOrder[index - 1]]; [this.addonOrder[index], this.addonOrder[index - 1]];
this.saveAddonOrder(); this.saveAddonOrder();
// Immediately push to server to avoid resets on restart
try { (require('./SyncService').syncService as any).pushAddons?.(); } catch {}
// Emit an event that the order has changed // Emit an event that the order has changed
addonEmitter.emit(ADDON_EVENTS.ORDER_CHANGED); addonEmitter.emit(ADDON_EVENTS.ORDER_CHANGED);
return true; return true;
@ -1252,6 +1243,8 @@ class StremioService {
[this.addonOrder[index], this.addonOrder[index + 1]] = [this.addonOrder[index], this.addonOrder[index + 1]] =
[this.addonOrder[index + 1], this.addonOrder[index]]; [this.addonOrder[index + 1], this.addonOrder[index]];
this.saveAddonOrder(); this.saveAddonOrder();
// Immediately push to server to avoid resets on restart
try { (require('./SyncService').syncService as any).pushAddons?.(); } catch {}
// Emit an event that the order has changed // Emit an event that the order has changed
addonEmitter.emit(ADDON_EVENTS.ORDER_CHANGED); addonEmitter.emit(ADDON_EVENTS.ORDER_CHANGED);
return true; return true;