mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-20 08:12:05 +00:00
added supprot for catalog renaming. fixed epside layout revertion during app restart
This commit is contained in:
parent
cc6c3d9d29
commit
69e5141c58
4 changed files with 129 additions and 32 deletions
|
|
@ -23,6 +23,7 @@ import { stremioService } from '../services/stremioService';
|
|||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import { useCatalogContext } from '../contexts/CatalogContext';
|
||||
import { logger } from '../utils/logger';
|
||||
import { clearCustomNameCache } from '../utils/catalogNameUtils';
|
||||
import { BlurView } from 'expo-blur';
|
||||
|
||||
interface CatalogSetting {
|
||||
|
|
@ -133,6 +134,17 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
hintRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
hintText: {
|
||||
fontSize: 12,
|
||||
color: colors.mediumGray,
|
||||
},
|
||||
enabledCount: {
|
||||
fontSize: 15,
|
||||
color: colors.mediumGray,
|
||||
|
|
@ -250,6 +262,26 @@ const CatalogSettingsScreen = () => {
|
|||
const settingKey = `${addon.id}:${catalog.type}:${catalog.id}`;
|
||||
let displayName = catalog.name || catalog.id;
|
||||
const catalogType = catalog.type === 'movie' ? 'Movies' : catalog.type === 'series' ? 'TV Shows' : catalog.type.charAt(0).toUpperCase() + catalog.type.slice(1);
|
||||
|
||||
// Clean duplicate words within the catalog name (e.g., "Popular Popular")
|
||||
if (displayName) {
|
||||
const words = displayName.split(' ').filter(Boolean);
|
||||
const uniqueWords: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const w of words) {
|
||||
const lw = w.toLowerCase();
|
||||
if (!seen.has(lw)) {
|
||||
uniqueWords.push(w);
|
||||
seen.add(lw);
|
||||
}
|
||||
}
|
||||
displayName = uniqueWords.join(' ');
|
||||
}
|
||||
|
||||
// Append content type if not already present (case-insensitive)
|
||||
if (!displayName.toLowerCase().includes(catalogType.toLowerCase())) {
|
||||
displayName = `${displayName} ${catalogType}`.trim();
|
||||
}
|
||||
|
||||
uniqueCatalogs.set(settingKey, {
|
||||
addonId: addon.id,
|
||||
|
|
@ -379,9 +411,13 @@ const CatalogSettingsScreen = () => {
|
|||
}
|
||||
|
||||
await AsyncStorage.setItem(CATALOG_CUSTOM_NAMES_KEY, JSON.stringify(customNames));
|
||||
// Clear in-memory cache so new name is used immediately
|
||||
try { clearCustomNameCache(); } catch {}
|
||||
|
||||
// --- Reload settings to reflect the change ---
|
||||
await loadSettings();
|
||||
// Also trigger home/catalog consumers to refresh
|
||||
try { refreshCatalogs(); } catch {}
|
||||
// --- No need to manually update local state anymore ---
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -459,7 +495,13 @@ const CatalogSettingsScreen = () => {
|
|||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
{group.expanded && group.catalogs.map((setting, index) => (
|
||||
{group.expanded && (
|
||||
<>
|
||||
<View style={styles.hintRow}>
|
||||
<MaterialIcons name="edit" size={14} color={colors.mediumGray} />
|
||||
<Text style={styles.hintText}>Long-press a catalog to rename</Text>
|
||||
</View>
|
||||
{group.catalogs.map((setting, index) => (
|
||||
<Pressable
|
||||
key={`${setting.addonId}:${setting.type}:${setting.catalogId}`}
|
||||
onLongPress={() => handleLongPress(setting)} // Added long press handler
|
||||
|
|
@ -484,7 +526,9 @@ const CatalogSettingsScreen = () => {
|
|||
ios_backgroundColor="#505050"
|
||||
/>
|
||||
</Pressable>
|
||||
))}
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import * as Haptics from 'expo-haptics';
|
|||
import { tmdbService } from '../services/tmdbService';
|
||||
import { logger } from '../utils/logger';
|
||||
import { storageService } from '../services/storageService';
|
||||
import { getCatalogDisplayName, clearCustomNameCache } from '../utils/catalogNameUtils';
|
||||
import { useHomeCatalogs } from '../hooks/useHomeCatalogs';
|
||||
import { useFeaturedContent } from '../hooks/useFeaturedContent';
|
||||
import { useSettings, settingsEmitter } from '../hooks/useSettings';
|
||||
|
|
@ -188,7 +189,7 @@ const HomeScreen = () => {
|
|||
const metas = await stremioService.getCatalog(manifest, catalog.type, catalog.id, 1);
|
||||
if (metas && metas.length > 0) {
|
||||
// Limit items per catalog to reduce memory usage
|
||||
const limitedMetas = metas.slice(0, 8); // Further reduced for memory
|
||||
const limitedMetas = metas.slice(0, 30);
|
||||
|
||||
const items = limitedMetas.map((meta: any) => ({
|
||||
id: meta.id,
|
||||
|
|
@ -209,11 +210,27 @@ const HomeScreen = () => {
|
|||
}));
|
||||
|
||||
// Skip prefetching to reduce memory pressure
|
||||
|
||||
let displayName = catalog.name;
|
||||
const contentType = catalog.type === 'movie' ? 'Movies' : 'TV Shows';
|
||||
if (!displayName.toLowerCase().includes(contentType.toLowerCase())) {
|
||||
displayName = `${displayName} ${contentType}`;
|
||||
// Resolve custom display name; if custom exists, use as-is
|
||||
const originalName = catalog.name || catalog.id;
|
||||
let displayName = await getCatalogDisplayName(addon.id, catalog.type, catalog.id, originalName);
|
||||
const isCustom = displayName !== originalName;
|
||||
|
||||
if (!isCustom) {
|
||||
// De-duplicate repeated words (case-insensitive)
|
||||
const words = displayName.split(' ').filter(Boolean);
|
||||
const uniqueWords: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const w of words) {
|
||||
const lw = w.toLowerCase();
|
||||
if (!seen.has(lw)) { uniqueWords.push(w); seen.add(lw); }
|
||||
}
|
||||
displayName = uniqueWords.join(' ');
|
||||
|
||||
// Append content type if not present
|
||||
const contentType = catalog.type === 'movie' ? 'Movies' : 'TV Shows';
|
||||
if (!displayName.toLowerCase().includes(contentType.toLowerCase())) {
|
||||
displayName = `${displayName} ${contentType}`;
|
||||
}
|
||||
}
|
||||
|
||||
const catalogContent = {
|
||||
|
|
|
|||
|
|
@ -406,9 +406,39 @@ class SyncService {
|
|||
.eq('user_id', userId)
|
||||
.single();
|
||||
if (us) {
|
||||
await AsyncStorage.setItem(`@user:${userId}:app_settings`, JSON.stringify(us.app_settings || {}));
|
||||
await AsyncStorage.setItem('app_settings', JSON.stringify(us.app_settings || {}));
|
||||
await storageService.saveSubtitleSettings(us.subtitle_settings || {});
|
||||
// Merge remote settings with existing local settings, preferring remote values
|
||||
// but preserving any local-only keys (e.g., newly added client-side settings
|
||||
// not yet present on the server). This avoids losing local preferences on restart.
|
||||
try {
|
||||
const localScopedJson = (await AsyncStorage.getItem(`@user:${userId}:app_settings`)) || '{}';
|
||||
const localLegacyJson = (await AsyncStorage.getItem('app_settings')) || '{}';
|
||||
// Prefer scoped local if available; fall back to legacy
|
||||
let localSettings: Record<string, any> = {};
|
||||
try { localSettings = JSON.parse(localScopedJson); } catch {}
|
||||
if (!localSettings || Object.keys(localSettings).length === 0) {
|
||||
try { localSettings = JSON.parse(localLegacyJson); } catch { localSettings = {}; }
|
||||
}
|
||||
|
||||
const remoteRaw: Record<string, any> = (us.app_settings || {}) as Record<string, any>;
|
||||
// Exclude episodeLayoutStyle from remote to keep it local-only
|
||||
const { episodeLayoutStyle: _remoteEpisodeLayoutStyle, ...remoteSettingsSansLocalOnly } = remoteRaw || {};
|
||||
// Merge: start from local, override with remote (sans excluded keys)
|
||||
const mergedSettings = { ...(localSettings || {}), ...(remoteSettingsSansLocalOnly || {}) };
|
||||
|
||||
await AsyncStorage.setItem(`@user:${userId}:app_settings`, JSON.stringify(mergedSettings));
|
||||
await AsyncStorage.setItem('app_settings', JSON.stringify(mergedSettings));
|
||||
await storageService.saveSubtitleSettings(us.subtitle_settings || {});
|
||||
// Notify listeners that settings changed due to sync
|
||||
try { settingsEmitter.emit(); } catch {}
|
||||
} catch (e) {
|
||||
// Fallback to writing remote settings as-is if merge fails
|
||||
const remoteRaw: Record<string, any> = (us.app_settings || {}) as Record<string, any>;
|
||||
const { episodeLayoutStyle: _remoteEpisodeLayoutStyle, ...remoteSettingsSansLocalOnly } = remoteRaw || {};
|
||||
await AsyncStorage.setItem(`@user:${userId}:app_settings`, JSON.stringify(remoteSettingsSansLocalOnly));
|
||||
await AsyncStorage.setItem('app_settings', JSON.stringify(remoteSettingsSansLocalOnly));
|
||||
await storageService.saveSubtitleSettings(us.subtitle_settings || {});
|
||||
try { settingsEmitter.emit(); } catch {}
|
||||
}
|
||||
}
|
||||
})(),
|
||||
this.pullAddonsSnapshot(userId),
|
||||
|
|
@ -697,7 +727,9 @@ class SyncService {
|
|||
(await AsyncStorage.getItem(`@user:${scope}:app_settings`)) ||
|
||||
(await AsyncStorage.getItem('app_settings')) ||
|
||||
'{}';
|
||||
const appSettings = JSON.parse(appSettingsJson);
|
||||
const parsed = JSON.parse(appSettingsJson) as Record<string, any>;
|
||||
// Exclude local-only settings from push
|
||||
const { episodeLayoutStyle: _localEpisodeLayoutStyle, ...appSettings } = parsed || {};
|
||||
const subtitleSettings = (await storageService.getSubtitleSettings()) || {};
|
||||
const { error } = await supabase.from('user_settings').upsert({
|
||||
user_id: userId,
|
||||
|
|
|
|||
|
|
@ -205,29 +205,33 @@ class CatalogService {
|
|||
const metas = await stremioService.getCatalog(manifest, catalog.type, catalog.id, 1);
|
||||
if (metas && metas.length > 0) {
|
||||
// Cap items per catalog to reduce memory and rendering load
|
||||
const limited = metas.slice(0, 8); // Further reduced for memory
|
||||
const limited = metas.slice(0, 12);
|
||||
const items = limited.map(meta => this.convertMetaToStreamingContent(meta));
|
||||
|
||||
// Get potentially custom display name
|
||||
let displayName = await getCatalogDisplayName(addon.id, catalog.type, catalog.id, catalog.name);
|
||||
|
||||
// Remove duplicate words and clean up the name (case-insensitive)
|
||||
const words = displayName.split(' ');
|
||||
const uniqueWords = [];
|
||||
const seenWords = new Set();
|
||||
for (const word of words) {
|
||||
const lowerWord = word.toLowerCase();
|
||||
if (!seenWords.has(lowerWord)) {
|
||||
uniqueWords.push(word);
|
||||
seenWords.add(lowerWord);
|
||||
// Get potentially custom display name; if customized, respect it as-is
|
||||
const originalName = catalog.name || catalog.id;
|
||||
let displayName = await getCatalogDisplayName(addon.id, catalog.type, catalog.id, originalName);
|
||||
const isCustom = displayName !== originalName;
|
||||
|
||||
if (!isCustom) {
|
||||
// Remove duplicate words and clean up the name (case-insensitive)
|
||||
const words = displayName.split(' ');
|
||||
const uniqueWords: string[] = [];
|
||||
const seenWords = new Set<string>();
|
||||
for (const word of words) {
|
||||
const lowerWord = word.toLowerCase();
|
||||
if (!seenWords.has(lowerWord)) {
|
||||
uniqueWords.push(word);
|
||||
seenWords.add(lowerWord);
|
||||
}
|
||||
}
|
||||
displayName = uniqueWords.join(' ');
|
||||
|
||||
// Add content type if not present
|
||||
const contentType = catalog.type === 'movie' ? 'Movies' : 'TV Shows';
|
||||
if (!displayName.toLowerCase().includes(contentType.toLowerCase())) {
|
||||
displayName = `${displayName} ${contentType}`;
|
||||
}
|
||||
}
|
||||
displayName = uniqueWords.join(' ');
|
||||
|
||||
// Add content type if not present
|
||||
const contentType = catalog.type === 'movie' ? 'Movies' : 'TV Shows';
|
||||
if (!displayName.toLowerCase().includes(contentType.toLowerCase())) {
|
||||
displayName = `${displayName} ${contentType}`;
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
Loading…
Reference in a new issue