added supprot for catalog renaming. fixed epside layout revertion during app restart

This commit is contained in:
tapframe 2025-08-11 13:49:13 +05:30
parent cc6c3d9d29
commit 69e5141c58
4 changed files with 129 additions and 32 deletions

View file

@ -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>
))}

View file

@ -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 = {

View file

@ -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,

View file

@ -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 {