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 (showNextEpisodeButton) {
// Hide button with animation
Animated.parallel([
fi Animated.parallel([
Animated.timing(nextEpisodeButtonOpacity, {
toValue: 0,
duration: 200,

View file

@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from 'react';
import { syncService } from '../services/SyncService';
import AsyncStorage from '@react-native-async-storage/async-storage';
// Simple event emitter for settings changes
@ -122,12 +123,40 @@ export const useSettings = () => {
const loadSettings = async () => {
try {
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
const storedSettings = await AsyncStorage.getItem(`@user:${scope}:${SETTINGS_STORAGE_KEY}`);
if (storedSettings) {
const parsedSettings = JSON.parse(storedSettings);
// Merge with defaults to ensure all properties exist
setSettings({ ...DEFAULT_SETTINGS, ...parsedSettings });
const scopedKey = `@user:${scope}:${SETTINGS_STORAGE_KEY}`;
const [scopedJson, legacyJson] = await Promise.all([
AsyncStorage.getItem(scopedKey),
AsyncStorage.getItem(SETTINGS_STORAGE_KEY),
]);
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) {
console.error('Failed to load settings:', error);
// Fallback to default settings on error
@ -143,7 +172,14 @@ export const useSettings = () => {
const newSettings = { ...settings, [key]: value };
try {
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);
console.log(`Setting updated: ${key}`, value);
@ -152,6 +188,9 @@ export const useSettings = () => {
console.log('Emitting settings change event');
settingsEmitter.emit();
}
// If authenticated, push settings to server to prevent overwrite on next pull
try { syncService.pushSettings(); } catch {}
} catch (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 {}
(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 idx = arr.indexOf(id);
if (idx === -1) arr.unshift(id);
@ -620,11 +620,42 @@ class SyncService {
};
ensureFront(order, 'com.linvo.cinemeta');
ensureFront(order, 'org.stremio.opensubtitlesv3');
// Keep order strictly from server after preinstalled
// Do not append missing local-only ids to avoid resurrecting removed addons
// Prefer local order if it exists; otherwise use remote
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;
await (stremioService as any).saveInstalledAddons();
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> {

View file

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