mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-29 05:48:45 +00:00
Merge branch 'main' into saimuelbr/main
This commit is contained in:
commit
e6faa7c59f
30 changed files with 21611 additions and 20166 deletions
|
|
@ -768,7 +768,17 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
onProgress={handleProgress}
|
||||
onSeek={(data) => {
|
||||
playerState.isSeeking.current = false;
|
||||
if (data.currentTime) traktAutosync.handleProgressUpdate(data.currentTime, playerState.duration, true);
|
||||
if (data.currentTime) {
|
||||
if (id && type && playerState.duration > 0) {
|
||||
void storageService.setWatchProgress(id, type, {
|
||||
currentTime: data.currentTime,
|
||||
duration: playerState.duration,
|
||||
lastUpdated: Date.now(),
|
||||
addonId: currentStreamProvider
|
||||
}, episodeId);
|
||||
}
|
||||
traktAutosync.handleProgressUpdate(data.currentTime, playerState.duration, true);
|
||||
}
|
||||
}}
|
||||
onEnd={() => {
|
||||
if (modals.showEpisodeStreamsModal) return;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import { useTraktAutosync } from '../../hooks/useTraktAutosync';
|
|||
import { useMetadata } from '../../hooks/useMetadata';
|
||||
import { usePlayerGestureControls } from '../../hooks/usePlayerGestureControls';
|
||||
import stremioService from '../../services/stremioService';
|
||||
import { storageService } from '../../services/storageService';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
// Utils
|
||||
|
|
@ -227,7 +228,15 @@ const KSPlayerCore: React.FC = () => {
|
|||
currentTime,
|
||||
duration,
|
||||
isSeeking,
|
||||
isMounted
|
||||
isMounted,
|
||||
onSeekComplete: (timeInSeconds) => {
|
||||
if (!id || !type || duration <= 0) return;
|
||||
void storageService.setWatchProgress(id, type, {
|
||||
currentTime: timeInSeconds,
|
||||
duration,
|
||||
lastUpdated: Date.now()
|
||||
}, episodeId);
|
||||
}
|
||||
});
|
||||
|
||||
const watchProgress = useWatchProgress(
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ interface PlayerControlsConfig {
|
|||
duration: number;
|
||||
isSeeking: MutableRefObject<boolean>;
|
||||
isMounted: MutableRefObject<boolean>;
|
||||
onSeekComplete?: (timeInSeconds: number) => void;
|
||||
}
|
||||
|
||||
export const usePlayerControls = (config: PlayerControlsConfig) => {
|
||||
|
|
@ -27,7 +28,8 @@ export const usePlayerControls = (config: PlayerControlsConfig) => {
|
|||
currentTime,
|
||||
duration,
|
||||
isSeeking,
|
||||
isMounted
|
||||
isMounted,
|
||||
onSeekComplete
|
||||
} = config;
|
||||
|
||||
// iOS seeking helpers
|
||||
|
|
@ -54,6 +56,7 @@ export const usePlayerControls = (config: PlayerControlsConfig) => {
|
|||
|
||||
// Actually perform the seek
|
||||
playerRef.current.seek(timeInSeconds);
|
||||
onSeekComplete?.(timeInSeconds);
|
||||
|
||||
// Debounce the seeking state reset
|
||||
seekTimeoutRef.current = setTimeout(() => {
|
||||
|
|
@ -62,7 +65,7 @@ export const usePlayerControls = (config: PlayerControlsConfig) => {
|
|||
}
|
||||
}, 500);
|
||||
}
|
||||
}, [duration, paused, playerRef, isSeeking, isMounted]);
|
||||
}, [duration, paused, playerRef, isSeeking, isMounted, onSeekComplete]);
|
||||
|
||||
const skip = useCallback((seconds: number) => {
|
||||
seekToTime(currentTime + seconds);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect, useRef } from 'react';
|
||||
import { AppState, AppStateStatus } from 'react-native';
|
||||
import { AppState } from 'react-native';
|
||||
import { storageService } from '../../../services/storageService';
|
||||
import { logger } from '../../../utils/logger';
|
||||
import { useSettings } from '../../../hooks/useSettings';
|
||||
|
|
@ -19,10 +19,9 @@ export const useWatchProgress = (
|
|||
const [savedDuration, setSavedDuration] = useState<number | null>(null);
|
||||
const [initialPosition, setInitialPosition] = useState<number | null>(null);
|
||||
const [showResumeOverlay, setShowResumeOverlay] = useState(false);
|
||||
const [progressSaveInterval, setProgressSaveInterval] = useState<NodeJS.Timeout | null>(null);
|
||||
|
||||
const { settings: appSettings } = useSettings();
|
||||
const initialSeekTargetRef = useRef<number | null>(null);
|
||||
const wasPausedRef = useRef<boolean>(paused);
|
||||
|
||||
// Values refs for unmount cleanup
|
||||
const currentTimeRef = useRef(currentTime);
|
||||
|
|
@ -126,22 +125,16 @@ export const useWatchProgress = (
|
|||
}
|
||||
};
|
||||
|
||||
// Save Interval
|
||||
|
||||
useEffect(() => {
|
||||
if (id && type && !paused && duration > 0) {
|
||||
if (progressSaveInterval) clearInterval(progressSaveInterval);
|
||||
|
||||
const interval = setInterval(() => {
|
||||
saveWatchProgress();
|
||||
}, 10000);
|
||||
|
||||
setProgressSaveInterval(interval);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
setProgressSaveInterval(null);
|
||||
};
|
||||
if (wasPausedRef.current !== paused) {
|
||||
const becamePaused = paused;
|
||||
wasPausedRef.current = paused;
|
||||
if (becamePaused) {
|
||||
void saveWatchProgress();
|
||||
}
|
||||
}
|
||||
}, [id, type, paused, currentTime, duration]);
|
||||
}, [paused]);
|
||||
|
||||
// Unmount Save - deferred to allow navigation to complete first
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -759,7 +759,65 @@
|
|||
},
|
||||
"clear_data_desc": "سيؤدي هذا إلى إعادة تعيين كل الإعدادات ومسح كل البيانات المخزنة مؤقتاً. هل أنت متأكد؟",
|
||||
"app_updates": "تحديثات التطبيق",
|
||||
"about_nuvio": "حول Nuvio"
|
||||
"about_nuvio": "حول Nuvio",
|
||||
"cloud_sync": {
|
||||
"title": "Nuvio Sync",
|
||||
"description": "Sync data across your Nuvio devices",
|
||||
"hero_title": "Cloud Sync",
|
||||
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
|
||||
"auth": {
|
||||
"account": "Account",
|
||||
"not_configured": "Supabase not configured",
|
||||
"not_authenticated": "Not authenticated",
|
||||
"email_session": "Email session",
|
||||
"signed_in_as": "Signed in as {{email}}",
|
||||
"not_signed_in": "Not signed in",
|
||||
"effective_owner": "Effective owner: {{id}}"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Database Statistics",
|
||||
"plugins": "Plugins",
|
||||
"addons": "Addons",
|
||||
"watch_progress": "Watch Progress",
|
||||
"library_items": "Library Items",
|
||||
"watched_items": "Watched Items",
|
||||
"signin_required": "Sign in to load remote data counts."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
|
||||
"pull_btn": "Pull from Cloud",
|
||||
"push_btn": "Push from Device",
|
||||
"manage_account": "Manage Account",
|
||||
"sign_out": "Sign Out",
|
||||
"sign_in_up": "Sign In / Up"
|
||||
},
|
||||
"alerts": {
|
||||
"pull_success_title": "Cloud Data Pulled",
|
||||
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
|
||||
"pull_failed_title": "Pull Failed",
|
||||
"pull_failed_msg": "Failed to download data from the cloud",
|
||||
"push_success_title": "Push Completed",
|
||||
"push_success_msg": "Device data has been uploaded to the cloud.",
|
||||
"push_failed_title": "Push Failed",
|
||||
"push_failed_msg": "Failed to upload local data",
|
||||
"sign_out_failed": "Sign Out Failed",
|
||||
"sign_out_failed_title": "Logout Error"
|
||||
},
|
||||
"external_sync": {
|
||||
"title": "External Sync Priority",
|
||||
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
|
||||
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
|
||||
},
|
||||
"pre_auth": {
|
||||
"title": "Before Syncing",
|
||||
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
|
||||
"point_1": "• Addons and plugins settings",
|
||||
"point_2": "• Watch progress and library",
|
||||
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
|
||||
},
|
||||
"connection": "Connection"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"title": "الخصوصية والبيانات",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -765,7 +765,65 @@
|
|||
},
|
||||
"clear_data_desc": "Això restablirà tota la configuració i esborrarà totes les dades en memòria cau. Estàs segur?",
|
||||
"app_updates": "Actualitzacions de l'aplicació",
|
||||
"about_nuvio": "Quant a Nuvio"
|
||||
"about_nuvio": "Quant a Nuvio",
|
||||
"cloud_sync": {
|
||||
"title": "Nuvio Sync",
|
||||
"description": "Sync data across your Nuvio devices",
|
||||
"hero_title": "Cloud Sync",
|
||||
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
|
||||
"auth": {
|
||||
"account": "Account",
|
||||
"not_configured": "Supabase not configured",
|
||||
"not_authenticated": "Not authenticated",
|
||||
"email_session": "Email session",
|
||||
"signed_in_as": "Signed in as {{email}}",
|
||||
"not_signed_in": "Not signed in",
|
||||
"effective_owner": "Effective owner: {{id}}"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Database Statistics",
|
||||
"plugins": "Plugins",
|
||||
"addons": "Addons",
|
||||
"watch_progress": "Watch Progress",
|
||||
"library_items": "Library Items",
|
||||
"watched_items": "Watched Items",
|
||||
"signin_required": "Sign in to load remote data counts."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
|
||||
"pull_btn": "Pull from Cloud",
|
||||
"push_btn": "Push from Device",
|
||||
"manage_account": "Manage Account",
|
||||
"sign_out": "Sign Out",
|
||||
"sign_in_up": "Sign In / Up"
|
||||
},
|
||||
"alerts": {
|
||||
"pull_success_title": "Cloud Data Pulled",
|
||||
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
|
||||
"pull_failed_title": "Pull Failed",
|
||||
"pull_failed_msg": "Failed to download data from the cloud",
|
||||
"push_success_title": "Push Completed",
|
||||
"push_success_msg": "Device data has been uploaded to the cloud.",
|
||||
"push_failed_title": "Push Failed",
|
||||
"push_failed_msg": "Failed to upload local data",
|
||||
"sign_out_failed": "Sign Out Failed",
|
||||
"sign_out_failed_title": "Logout Error"
|
||||
},
|
||||
"external_sync": {
|
||||
"title": "External Sync Priority",
|
||||
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
|
||||
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
|
||||
},
|
||||
"pre_auth": {
|
||||
"title": "Before Syncing",
|
||||
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
|
||||
"point_1": "• Addons and plugins settings",
|
||||
"point_2": "• Watch progress and library",
|
||||
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
|
||||
},
|
||||
"connection": "Connection"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Privadesa i dades",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -759,7 +759,65 @@
|
|||
},
|
||||
"clear_data_desc": "Dies setzt alle Einstellungen zurück und löscht alle zwischengespeicherten Daten. Sind Sie sicher?",
|
||||
"app_updates": "App-Updates",
|
||||
"about_nuvio": "Über Nuvio"
|
||||
"about_nuvio": "Über Nuvio",
|
||||
"cloud_sync": {
|
||||
"title": "Nuvio Sync",
|
||||
"description": "Sync data across your Nuvio devices",
|
||||
"hero_title": "Cloud Sync",
|
||||
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
|
||||
"auth": {
|
||||
"account": "Account",
|
||||
"not_configured": "Supabase not configured",
|
||||
"not_authenticated": "Not authenticated",
|
||||
"email_session": "Email session",
|
||||
"signed_in_as": "Signed in as {{email}}",
|
||||
"not_signed_in": "Not signed in",
|
||||
"effective_owner": "Effective owner: {{id}}"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Database Statistics",
|
||||
"plugins": "Plugins",
|
||||
"addons": "Addons",
|
||||
"watch_progress": "Watch Progress",
|
||||
"library_items": "Library Items",
|
||||
"watched_items": "Watched Items",
|
||||
"signin_required": "Sign in to load remote data counts."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
|
||||
"pull_btn": "Pull from Cloud",
|
||||
"push_btn": "Push from Device",
|
||||
"manage_account": "Manage Account",
|
||||
"sign_out": "Sign Out",
|
||||
"sign_in_up": "Sign In / Up"
|
||||
},
|
||||
"alerts": {
|
||||
"pull_success_title": "Cloud Data Pulled",
|
||||
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
|
||||
"pull_failed_title": "Pull Failed",
|
||||
"pull_failed_msg": "Failed to download data from the cloud",
|
||||
"push_success_title": "Push Completed",
|
||||
"push_success_msg": "Device data has been uploaded to the cloud.",
|
||||
"push_failed_title": "Push Failed",
|
||||
"push_failed_msg": "Failed to upload local data",
|
||||
"sign_out_failed": "Sign Out Failed",
|
||||
"sign_out_failed_title": "Logout Error"
|
||||
},
|
||||
"external_sync": {
|
||||
"title": "External Sync Priority",
|
||||
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
|
||||
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
|
||||
},
|
||||
"pre_auth": {
|
||||
"title": "Before Syncing",
|
||||
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
|
||||
"point_1": "• Addons and plugins settings",
|
||||
"point_2": "• Watch progress and library",
|
||||
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
|
||||
},
|
||||
"connection": "Connection"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Datenschutz & Daten",
|
||||
|
|
|
|||
|
|
@ -765,65 +765,65 @@
|
|||
},
|
||||
"clear_data_desc": "This will reset all settings and clear all cached data. Are you sure?",
|
||||
"app_updates": "App Updates",
|
||||
"about_nuvio": "About Nuvio"
|
||||
},
|
||||
"cloud_sync": {
|
||||
"title": "Nuvio Sync",
|
||||
"description": "Sync data across your Nuvio devices",
|
||||
"hero_title": "Cloud Sync",
|
||||
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
|
||||
"auth": {
|
||||
"account": "Account",
|
||||
"not_configured": "Supabase not configured",
|
||||
"not_authenticated": "Not authenticated",
|
||||
"email_session": "Email session",
|
||||
"signed_in_as": "Signed in as {{email}}",
|
||||
"not_signed_in": "Not signed in",
|
||||
"effective_owner": "Effective owner: {{id}}"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Database Statistics",
|
||||
"plugins": "Plugins",
|
||||
"addons": "Addons",
|
||||
"watch_progress": "Watch Progress",
|
||||
"library_items": "Library Items",
|
||||
"watched_items": "Watched Items",
|
||||
"signin_required": "Sign in to load remote data counts."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
|
||||
"pull_btn": "Pull from Cloud",
|
||||
"push_btn": "Push from Device",
|
||||
"manage_account": "Manage Account",
|
||||
"sign_out": "Sign Out",
|
||||
"sign_in_up": "Sign In / Up"
|
||||
},
|
||||
"alerts": {
|
||||
"pull_success_title": "Cloud Data Pulled",
|
||||
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
|
||||
"pull_failed_title": "Pull Failed",
|
||||
"pull_failed_msg": "Failed to download data from the cloud",
|
||||
"push_success_title": "Push Completed",
|
||||
"push_success_msg": "Device data has been uploaded to the cloud.",
|
||||
"push_failed_title": "Push Failed",
|
||||
"push_failed_msg": "Failed to upload local data",
|
||||
"sign_out_failed": "Sign Out Failed",
|
||||
"sign_out_failed_title": "Logout Error"
|
||||
},
|
||||
"external_sync": {
|
||||
"title": "External Sync Priority",
|
||||
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
|
||||
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
|
||||
},
|
||||
"pre_auth": {
|
||||
"title": "Before Syncing",
|
||||
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
|
||||
"point_1": "• Addons and plugins settings",
|
||||
"point_2": "• Watch progress and library",
|
||||
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
|
||||
},
|
||||
"connection": "Connection"
|
||||
"about_nuvio": "About Nuvio",
|
||||
"cloud_sync": {
|
||||
"title": "Nuvio Sync",
|
||||
"description": "Sync data across your Nuvio devices",
|
||||
"hero_title": "Cloud Sync",
|
||||
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
|
||||
"auth": {
|
||||
"account": "Account",
|
||||
"not_configured": "Supabase not configured",
|
||||
"not_authenticated": "Not authenticated",
|
||||
"email_session": "Email session",
|
||||
"signed_in_as": "Signed in as {{email}}",
|
||||
"not_signed_in": "Not signed in",
|
||||
"effective_owner": "Effective owner: {{id}}"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Database Statistics",
|
||||
"plugins": "Plugins",
|
||||
"addons": "Addons",
|
||||
"watch_progress": "Watch Progress",
|
||||
"library_items": "Library Items",
|
||||
"watched_items": "Watched Items",
|
||||
"signin_required": "Sign in to load remote data counts."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
|
||||
"pull_btn": "Pull from Cloud",
|
||||
"push_btn": "Push from Device",
|
||||
"manage_account": "Manage Account",
|
||||
"sign_out": "Sign Out",
|
||||
"sign_in_up": "Sign In / Up"
|
||||
},
|
||||
"alerts": {
|
||||
"pull_success_title": "Cloud Data Pulled",
|
||||
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
|
||||
"pull_failed_title": "Pull Failed",
|
||||
"pull_failed_msg": "Failed to download data from the cloud",
|
||||
"push_success_title": "Push Completed",
|
||||
"push_success_msg": "Device data has been uploaded to the cloud.",
|
||||
"push_failed_title": "Push Failed",
|
||||
"push_failed_msg": "Failed to upload local data",
|
||||
"sign_out_failed": "Sign Out Failed",
|
||||
"sign_out_failed_title": "Logout Error"
|
||||
},
|
||||
"external_sync": {
|
||||
"title": "External Sync Priority",
|
||||
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
|
||||
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
|
||||
},
|
||||
"pre_auth": {
|
||||
"title": "Before Syncing",
|
||||
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
|
||||
"point_1": "• Addons and plugins settings",
|
||||
"point_2": "• Watch progress and library",
|
||||
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
|
||||
},
|
||||
"connection": "Connection"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Privacy & Data",
|
||||
|
|
|
|||
|
|
@ -759,7 +759,65 @@
|
|||
},
|
||||
"clear_data_desc": "Esto restablecerá todos los ajustes y borrará todos los datos almacenados en caché. ¿Estás seguro?",
|
||||
"app_updates": "Actualizaciones de la app",
|
||||
"about_nuvio": "Acerca de Nuvio"
|
||||
"about_nuvio": "Acerca de Nuvio",
|
||||
"cloud_sync": {
|
||||
"title": "Nuvio Sync",
|
||||
"description": "Sync data across your Nuvio devices",
|
||||
"hero_title": "Cloud Sync",
|
||||
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
|
||||
"auth": {
|
||||
"account": "Account",
|
||||
"not_configured": "Supabase not configured",
|
||||
"not_authenticated": "Not authenticated",
|
||||
"email_session": "Email session",
|
||||
"signed_in_as": "Signed in as {{email}}",
|
||||
"not_signed_in": "Not signed in",
|
||||
"effective_owner": "Effective owner: {{id}}"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Database Statistics",
|
||||
"plugins": "Plugins",
|
||||
"addons": "Addons",
|
||||
"watch_progress": "Watch Progress",
|
||||
"library_items": "Library Items",
|
||||
"watched_items": "Watched Items",
|
||||
"signin_required": "Sign in to load remote data counts."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
|
||||
"pull_btn": "Pull from Cloud",
|
||||
"push_btn": "Push from Device",
|
||||
"manage_account": "Manage Account",
|
||||
"sign_out": "Sign Out",
|
||||
"sign_in_up": "Sign In / Up"
|
||||
},
|
||||
"alerts": {
|
||||
"pull_success_title": "Cloud Data Pulled",
|
||||
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
|
||||
"pull_failed_title": "Pull Failed",
|
||||
"pull_failed_msg": "Failed to download data from the cloud",
|
||||
"push_success_title": "Push Completed",
|
||||
"push_success_msg": "Device data has been uploaded to the cloud.",
|
||||
"push_failed_title": "Push Failed",
|
||||
"push_failed_msg": "Failed to upload local data",
|
||||
"sign_out_failed": "Sign Out Failed",
|
||||
"sign_out_failed_title": "Logout Error"
|
||||
},
|
||||
"external_sync": {
|
||||
"title": "External Sync Priority",
|
||||
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
|
||||
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
|
||||
},
|
||||
"pre_auth": {
|
||||
"title": "Before Syncing",
|
||||
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
|
||||
"point_1": "• Addons and plugins settings",
|
||||
"point_2": "• Watch progress and library",
|
||||
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
|
||||
},
|
||||
"connection": "Connection"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Privacidad y Datos",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -759,7 +759,65 @@
|
|||
},
|
||||
"clear_data_desc": "Cela réinitialisera tous les paramètres et effacera toutes les données en cache. Êtes-vous sûr ?",
|
||||
"app_updates": "Mises à jour de l'application",
|
||||
"about_nuvio": "À propos de Nuvio"
|
||||
"about_nuvio": "À propos de Nuvio",
|
||||
"cloud_sync": {
|
||||
"title": "Nuvio Sync",
|
||||
"description": "Sync data across your Nuvio devices",
|
||||
"hero_title": "Cloud Sync",
|
||||
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
|
||||
"auth": {
|
||||
"account": "Account",
|
||||
"not_configured": "Supabase not configured",
|
||||
"not_authenticated": "Not authenticated",
|
||||
"email_session": "Email session",
|
||||
"signed_in_as": "Signed in as {{email}}",
|
||||
"not_signed_in": "Not signed in",
|
||||
"effective_owner": "Effective owner: {{id}}"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Database Statistics",
|
||||
"plugins": "Plugins",
|
||||
"addons": "Addons",
|
||||
"watch_progress": "Watch Progress",
|
||||
"library_items": "Library Items",
|
||||
"watched_items": "Watched Items",
|
||||
"signin_required": "Sign in to load remote data counts."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
|
||||
"pull_btn": "Pull from Cloud",
|
||||
"push_btn": "Push from Device",
|
||||
"manage_account": "Manage Account",
|
||||
"sign_out": "Sign Out",
|
||||
"sign_in_up": "Sign In / Up"
|
||||
},
|
||||
"alerts": {
|
||||
"pull_success_title": "Cloud Data Pulled",
|
||||
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
|
||||
"pull_failed_title": "Pull Failed",
|
||||
"pull_failed_msg": "Failed to download data from the cloud",
|
||||
"push_success_title": "Push Completed",
|
||||
"push_success_msg": "Device data has been uploaded to the cloud.",
|
||||
"push_failed_title": "Push Failed",
|
||||
"push_failed_msg": "Failed to upload local data",
|
||||
"sign_out_failed": "Sign Out Failed",
|
||||
"sign_out_failed_title": "Logout Error"
|
||||
},
|
||||
"external_sync": {
|
||||
"title": "External Sync Priority",
|
||||
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
|
||||
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
|
||||
},
|
||||
"pre_auth": {
|
||||
"title": "Before Syncing",
|
||||
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
|
||||
"point_1": "• Addons and plugins settings",
|
||||
"point_2": "• Watch progress and library",
|
||||
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
|
||||
},
|
||||
"connection": "Connection"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Confidentialité et Données",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -759,7 +759,65 @@
|
|||
},
|
||||
"clear_data_desc": "यह सभी सेटिंग्स को रीसेट कर देगा और सभी कैश किए गए डेटा को साफ़ कर देगा। क्या आप सुनिश्चित हैं?",
|
||||
"app_updates": "ऐप अपडेट",
|
||||
"about_nuvio": "Nuvio के बारे में"
|
||||
"about_nuvio": "Nuvio के बारे में",
|
||||
"cloud_sync": {
|
||||
"title": "Nuvio Sync",
|
||||
"description": "Sync data across your Nuvio devices",
|
||||
"hero_title": "Cloud Sync",
|
||||
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
|
||||
"auth": {
|
||||
"account": "Account",
|
||||
"not_configured": "Supabase not configured",
|
||||
"not_authenticated": "Not authenticated",
|
||||
"email_session": "Email session",
|
||||
"signed_in_as": "Signed in as {{email}}",
|
||||
"not_signed_in": "Not signed in",
|
||||
"effective_owner": "Effective owner: {{id}}"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Database Statistics",
|
||||
"plugins": "Plugins",
|
||||
"addons": "Addons",
|
||||
"watch_progress": "Watch Progress",
|
||||
"library_items": "Library Items",
|
||||
"watched_items": "Watched Items",
|
||||
"signin_required": "Sign in to load remote data counts."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
|
||||
"pull_btn": "Pull from Cloud",
|
||||
"push_btn": "Push from Device",
|
||||
"manage_account": "Manage Account",
|
||||
"sign_out": "Sign Out",
|
||||
"sign_in_up": "Sign In / Up"
|
||||
},
|
||||
"alerts": {
|
||||
"pull_success_title": "Cloud Data Pulled",
|
||||
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
|
||||
"pull_failed_title": "Pull Failed",
|
||||
"pull_failed_msg": "Failed to download data from the cloud",
|
||||
"push_success_title": "Push Completed",
|
||||
"push_success_msg": "Device data has been uploaded to the cloud.",
|
||||
"push_failed_title": "Push Failed",
|
||||
"push_failed_msg": "Failed to upload local data",
|
||||
"sign_out_failed": "Sign Out Failed",
|
||||
"sign_out_failed_title": "Logout Error"
|
||||
},
|
||||
"external_sync": {
|
||||
"title": "External Sync Priority",
|
||||
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
|
||||
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
|
||||
},
|
||||
"pre_auth": {
|
||||
"title": "Before Syncing",
|
||||
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
|
||||
"point_1": "• Addons and plugins settings",
|
||||
"point_2": "• Watch progress and library",
|
||||
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
|
||||
},
|
||||
"connection": "Connection"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"title": "गोपनीयता और डेटा",
|
||||
|
|
|
|||
|
|
@ -747,7 +747,65 @@
|
|||
},
|
||||
"clear_data_desc": "Ovo će resetirati sve postavke i obrisati sve privremene podatke. Jeste li sigurni?",
|
||||
"app_updates": "Ažuriranja aplikacije",
|
||||
"about_nuvio": "O Nuviju"
|
||||
"about_nuvio": "O Nuviju",
|
||||
"cloud_sync": {
|
||||
"title": "Nuvio Sync",
|
||||
"description": "Sync data across your Nuvio devices",
|
||||
"hero_title": "Cloud Sync",
|
||||
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
|
||||
"auth": {
|
||||
"account": "Account",
|
||||
"not_configured": "Supabase not configured",
|
||||
"not_authenticated": "Not authenticated",
|
||||
"email_session": "Email session",
|
||||
"signed_in_as": "Signed in as {{email}}",
|
||||
"not_signed_in": "Not signed in",
|
||||
"effective_owner": "Effective owner: {{id}}"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Database Statistics",
|
||||
"plugins": "Plugins",
|
||||
"addons": "Addons",
|
||||
"watch_progress": "Watch Progress",
|
||||
"library_items": "Library Items",
|
||||
"watched_items": "Watched Items",
|
||||
"signin_required": "Sign in to load remote data counts."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
|
||||
"pull_btn": "Pull from Cloud",
|
||||
"push_btn": "Push from Device",
|
||||
"manage_account": "Manage Account",
|
||||
"sign_out": "Sign Out",
|
||||
"sign_in_up": "Sign In / Up"
|
||||
},
|
||||
"alerts": {
|
||||
"pull_success_title": "Cloud Data Pulled",
|
||||
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
|
||||
"pull_failed_title": "Pull Failed",
|
||||
"pull_failed_msg": "Failed to download data from the cloud",
|
||||
"push_success_title": "Push Completed",
|
||||
"push_success_msg": "Device data has been uploaded to the cloud.",
|
||||
"push_failed_title": "Push Failed",
|
||||
"push_failed_msg": "Failed to upload local data",
|
||||
"sign_out_failed": "Sign Out Failed",
|
||||
"sign_out_failed_title": "Logout Error"
|
||||
},
|
||||
"external_sync": {
|
||||
"title": "External Sync Priority",
|
||||
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
|
||||
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
|
||||
},
|
||||
"pre_auth": {
|
||||
"title": "Before Syncing",
|
||||
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
|
||||
"point_1": "• Addons and plugins settings",
|
||||
"point_2": "• Watch progress and library",
|
||||
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
|
||||
},
|
||||
"connection": "Connection"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Privatnost i Podaci",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -773,65 +773,65 @@
|
|||
},
|
||||
"clear_data_desc": "Isso redefinirá todas as configurações e limpará todos os dados em cache. Você tem certeza?",
|
||||
"app_updates": "Atualizações do App",
|
||||
"about_nuvio": "Sobre o Nuvio"
|
||||
},
|
||||
"cloud_sync": {
|
||||
"title": "Nuvio Sync",
|
||||
"description": "Sincronize dados entre seus dispositivos Nuvio",
|
||||
"hero_title": "Sincronização na Nuvem",
|
||||
"hero_subtitle": "Mantenha seus addons, progresso e biblioteca alinhados em todos os dispositivos.",
|
||||
"auth": {
|
||||
"account": "Conta",
|
||||
"not_configured": "Supabase não configurado",
|
||||
"not_authenticated": "Não autenticado",
|
||||
"email_session": "Sessão de email",
|
||||
"signed_in_as": "Logado como {{email}}",
|
||||
"not_signed_in": "Não logado",
|
||||
"effective_owner": "Proprietário efetivo: {{id}}"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Estatísticas do Banco de Dados",
|
||||
"plugins": "Plugins",
|
||||
"addons": "Addons",
|
||||
"watch_progress": "Progresso",
|
||||
"library_items": "Itens na Biblioteca",
|
||||
"watched_items": "Itens Assistidos",
|
||||
"signin_required": "Faça login para carregar contagens de dados remotos."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Ações",
|
||||
"description": "Baixe para atualizar este dispositivo a partir da nuvem, ou envie deste dispositivo como a fonte mais recente.",
|
||||
"pull_btn": "Baixar da Nuvem",
|
||||
"push_btn": "Enviar deste Dispositivo",
|
||||
"manage_account": "Gerenciar Conta",
|
||||
"sign_out": "Sair",
|
||||
"sign_in_up": "Entrar / Cadastrar"
|
||||
},
|
||||
"alerts": {
|
||||
"pull_success_title": "Dados da Nuvem Baixados",
|
||||
"pull_success_msg": "Os dados mais recentes da nuvem foram baixados para este dispositivo.",
|
||||
"pull_failed_title": "Falha ao Baixar",
|
||||
"pull_failed_msg": "Falha ao baixar dados da nuvem",
|
||||
"push_success_title": "Envio Concluído",
|
||||
"push_success_msg": "Os dados deste dispositivo foram enviados para a nuvem.",
|
||||
"push_failed_title": "Falha ao Enviar",
|
||||
"push_failed_msg": "Falha ao enviar dados locais",
|
||||
"sign_out_failed": "Falha ao Sair",
|
||||
"sign_out_failed_title": "Erro de Saida"
|
||||
},
|
||||
"external_sync": {
|
||||
"title": "Prioridade de Sincronização Externa",
|
||||
"active_msg": "{{services}} está ativo. O progresso e atualizações da biblioteca são gerenciados por esses serviços em vez do banco de dados Nuvio.",
|
||||
"inactive_msg": "Se a sincronização do Trakt ou Simkl estiver ativada, o progresso e a biblioteca usarão esses serviços em vez do banco de dados Nuvio."
|
||||
},
|
||||
"pre_auth": {
|
||||
"title": "Antes de Sincronizar",
|
||||
"description": "Faça login para iniciar a sincronização na nuvem e manter seus dados consistentes entre dispositivos.",
|
||||
"point_1": "• Configurações de addons e plugins",
|
||||
"point_2": "• Progresso de exibição e biblioteca",
|
||||
"env_warning": "Defina EXPO_PUBLIC_SUPABASE_URL e EXPO_PUBLIC_SUPABASE_ANON_KEY para habilitar a sincronização."
|
||||
},
|
||||
"connection": "Conexão"
|
||||
"about_nuvio": "Sobre o Nuvio",
|
||||
"cloud_sync": {
|
||||
"title": "Nuvio Sync",
|
||||
"description": "Sincronize dados entre seus dispositivos Nuvio",
|
||||
"hero_title": "Sincronização na Nuvem",
|
||||
"hero_subtitle": "Mantenha seus addons, progresso e biblioteca alinhados em todos os dispositivos.",
|
||||
"auth": {
|
||||
"account": "Conta",
|
||||
"not_configured": "Supabase não configurado",
|
||||
"not_authenticated": "Não autenticado",
|
||||
"email_session": "Sessão de email",
|
||||
"signed_in_as": "Logado como {{email}}",
|
||||
"not_signed_in": "Não logado",
|
||||
"effective_owner": "Proprietário efetivo: {{id}}"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Estatísticas do Banco de Dados",
|
||||
"plugins": "Plugins",
|
||||
"addons": "Addons",
|
||||
"watch_progress": "Progresso",
|
||||
"library_items": "Itens na Biblioteca",
|
||||
"watched_items": "Itens Assistidos",
|
||||
"signin_required": "Faça login para carregar contagens de dados remotos."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Ações",
|
||||
"description": "Baixe para atualizar este dispositivo a partir da nuvem, ou envie deste dispositivo como a fonte mais recente.",
|
||||
"pull_btn": "Baixar da Nuvem",
|
||||
"push_btn": "Enviar deste Dispositivo",
|
||||
"manage_account": "Gerenciar Conta",
|
||||
"sign_out": "Sair",
|
||||
"sign_in_up": "Entrar / Cadastrar"
|
||||
},
|
||||
"alerts": {
|
||||
"pull_success_title": "Dados da Nuvem Baixados",
|
||||
"pull_success_msg": "Os dados mais recentes da nuvem foram baixados para este dispositivo.",
|
||||
"pull_failed_title": "Falha ao Baixar",
|
||||
"pull_failed_msg": "Falha ao baixar dados da nuvem",
|
||||
"push_success_title": "Envio Concluído",
|
||||
"push_success_msg": "Os dados deste dispositivo foram enviados para a nuvem.",
|
||||
"push_failed_title": "Falha ao Enviar",
|
||||
"push_failed_msg": "Falha ao enviar dados locais",
|
||||
"sign_out_failed": "Falha ao Sair",
|
||||
"sign_out_failed_title": "Erro de Saida"
|
||||
},
|
||||
"external_sync": {
|
||||
"title": "Prioridade de Sincronização Externa",
|
||||
"active_msg": "{{services}} está ativo. O progresso e atualizações da biblioteca são gerenciados por esses serviços em vez do banco de dados Nuvio.",
|
||||
"inactive_msg": "Se a sincronização do Trakt ou Simkl estiver ativada, o progresso e a biblioteca usarão esses serviços em vez do banco de dados Nuvio."
|
||||
},
|
||||
"pre_auth": {
|
||||
"title": "Antes de Sincronizar",
|
||||
"description": "Faça login para iniciar a sincronização na nuvem e manter seus dados consistentes entre dispositivos.",
|
||||
"point_1": "• Configurações de addons e plugins",
|
||||
"point_2": "• Progresso de exibição e biblioteca",
|
||||
"env_warning": "Defina EXPO_PUBLIC_SUPABASE_URL e EXPO_PUBLIC_SUPABASE_ANON_KEY para habilitar a sincronização."
|
||||
},
|
||||
"connection": "Conexão"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Privacidade e Dados",
|
||||
|
|
|
|||
|
|
@ -771,7 +771,65 @@
|
|||
},
|
||||
"clear_data_desc": "Isso redefinirá todas as configurações e limpará todos os dados em cache. Tens a certeza?",
|
||||
"app_updates": "Atualizações da App",
|
||||
"about_nuvio": "Sobre o Nuvio"
|
||||
"about_nuvio": "Sobre o Nuvio",
|
||||
"cloud_sync": {
|
||||
"title": "Nuvio Sync",
|
||||
"description": "Sync data across your Nuvio devices",
|
||||
"hero_title": "Cloud Sync",
|
||||
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
|
||||
"auth": {
|
||||
"account": "Account",
|
||||
"not_configured": "Supabase not configured",
|
||||
"not_authenticated": "Not authenticated",
|
||||
"email_session": "Email session",
|
||||
"signed_in_as": "Signed in as {{email}}",
|
||||
"not_signed_in": "Not signed in",
|
||||
"effective_owner": "Effective owner: {{id}}"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Database Statistics",
|
||||
"plugins": "Plugins",
|
||||
"addons": "Addons",
|
||||
"watch_progress": "Watch Progress",
|
||||
"library_items": "Library Items",
|
||||
"watched_items": "Watched Items",
|
||||
"signin_required": "Sign in to load remote data counts."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
|
||||
"pull_btn": "Pull from Cloud",
|
||||
"push_btn": "Push from Device",
|
||||
"manage_account": "Manage Account",
|
||||
"sign_out": "Sign Out",
|
||||
"sign_in_up": "Sign In / Up"
|
||||
},
|
||||
"alerts": {
|
||||
"pull_success_title": "Cloud Data Pulled",
|
||||
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
|
||||
"pull_failed_title": "Pull Failed",
|
||||
"pull_failed_msg": "Failed to download data from the cloud",
|
||||
"push_success_title": "Push Completed",
|
||||
"push_success_msg": "Device data has been uploaded to the cloud.",
|
||||
"push_failed_title": "Push Failed",
|
||||
"push_failed_msg": "Failed to upload local data",
|
||||
"sign_out_failed": "Sign Out Failed",
|
||||
"sign_out_failed_title": "Logout Error"
|
||||
},
|
||||
"external_sync": {
|
||||
"title": "External Sync Priority",
|
||||
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
|
||||
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
|
||||
},
|
||||
"pre_auth": {
|
||||
"title": "Before Syncing",
|
||||
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
|
||||
"point_1": "• Addons and plugins settings",
|
||||
"point_2": "• Watch progress and library",
|
||||
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
|
||||
},
|
||||
"connection": "Connection"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Privacidade e Dados",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -647,7 +647,7 @@
|
|||
"filipino": "菲律宾语",
|
||||
"dutch_nl": "荷兰语 (荷兰)",
|
||||
"romanian": "罗马尼亚语",
|
||||
"albanian": "阿尔巴尼亚语",
|
||||
"albanian": "阿尔巴尼亚语",
|
||||
"account": "账户",
|
||||
"content_discovery": "内容与发现",
|
||||
"appearance": "外观",
|
||||
|
|
@ -759,7 +759,65 @@
|
|||
},
|
||||
"clear_data_desc": "这将重置所有设置并清除所有缓存数据。您确定吗?",
|
||||
"app_updates": "应用更新",
|
||||
"about_nuvio": "关于 Nuvio"
|
||||
"about_nuvio": "关于 Nuvio",
|
||||
"cloud_sync": {
|
||||
"title": "Nuvio Sync",
|
||||
"description": "Sync data across your Nuvio devices",
|
||||
"hero_title": "Cloud Sync",
|
||||
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
|
||||
"auth": {
|
||||
"account": "Account",
|
||||
"not_configured": "Supabase not configured",
|
||||
"not_authenticated": "Not authenticated",
|
||||
"email_session": "Email session",
|
||||
"signed_in_as": "Signed in as {{email}}",
|
||||
"not_signed_in": "Not signed in",
|
||||
"effective_owner": "Effective owner: {{id}}"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Database Statistics",
|
||||
"plugins": "Plugins",
|
||||
"addons": "Addons",
|
||||
"watch_progress": "Watch Progress",
|
||||
"library_items": "Library Items",
|
||||
"watched_items": "Watched Items",
|
||||
"signin_required": "Sign in to load remote data counts."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
|
||||
"pull_btn": "Pull from Cloud",
|
||||
"push_btn": "Push from Device",
|
||||
"manage_account": "Manage Account",
|
||||
"sign_out": "Sign Out",
|
||||
"sign_in_up": "Sign In / Up"
|
||||
},
|
||||
"alerts": {
|
||||
"pull_success_title": "Cloud Data Pulled",
|
||||
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
|
||||
"pull_failed_title": "Pull Failed",
|
||||
"pull_failed_msg": "Failed to download data from the cloud",
|
||||
"push_success_title": "Push Completed",
|
||||
"push_success_msg": "Device data has been uploaded to the cloud.",
|
||||
"push_failed_title": "Push Failed",
|
||||
"push_failed_msg": "Failed to upload local data",
|
||||
"sign_out_failed": "Sign Out Failed",
|
||||
"sign_out_failed_title": "Logout Error"
|
||||
},
|
||||
"external_sync": {
|
||||
"title": "External Sync Priority",
|
||||
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
|
||||
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
|
||||
},
|
||||
"pre_auth": {
|
||||
"title": "Before Syncing",
|
||||
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
|
||||
"point_1": "• Addons and plugins settings",
|
||||
"point_2": "• Watch progress and library",
|
||||
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
|
||||
},
|
||||
"connection": "Connection"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"title": "隐私和数据",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { catalogService, StreamingContent } from './catalogService';
|
|||
import { storageService } from './storageService';
|
||||
import { watchedService, LocalWatchedItem } from './watchedService';
|
||||
import { TraktService } from './traktService';
|
||||
import { SimklService } from './simklService';
|
||||
|
||||
const SUPABASE_SESSION_KEY = '@supabase:session';
|
||||
const DEFAULT_SYNC_DEBOUNCE_MS = 2000;
|
||||
|
|
@ -123,6 +124,7 @@ class SupabaseSyncService {
|
|||
private readonly foregroundPullCooldownMs = 30000;
|
||||
private pendingWatchProgressDeleteKeys = new Set<string>();
|
||||
private watchProgressDeleteTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
private watchProgressPushedSignatures = new Map<string, string>();
|
||||
|
||||
private pendingPushTimers: Record<PushTarget, ReturnType<typeof setTimeout> | null> = {
|
||||
plugins: null,
|
||||
|
|
@ -236,6 +238,7 @@ class SupabaseSyncService {
|
|||
}
|
||||
|
||||
this.session = null;
|
||||
this.watchProgressPushedSignatures.clear();
|
||||
await mmkvStorage.removeItem(SUPABASE_SESSION_KEY);
|
||||
}
|
||||
|
||||
|
|
@ -277,13 +280,15 @@ class SupabaseSyncService {
|
|||
await this.pushPluginsFromLocal();
|
||||
await this.pushAddonsFromLocal();
|
||||
|
||||
const traktConnected = await this.isTraktConnected();
|
||||
if (traktConnected) {
|
||||
await this.pushLibraryFromLocal();
|
||||
const externalProgressSyncConnected = await this.isExternalProgressSyncConnected();
|
||||
if (externalProgressSyncConnected) {
|
||||
logger.log('[SupabaseSyncService] External sync (Trakt/Simkl) is connected; skipping watch progress/watched-items push, keeping library sync.');
|
||||
logger.log('[SupabaseSyncService] pushAllLocalData: complete');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.pushWatchProgressFromLocal();
|
||||
await this.pushLibraryFromLocal();
|
||||
await this.pushWatchedItemsFromLocal();
|
||||
logger.log('[SupabaseSyncService] pushAllLocalData: complete');
|
||||
}
|
||||
|
|
@ -296,13 +301,14 @@ class SupabaseSyncService {
|
|||
await this.pullPluginsToLocal();
|
||||
await this.pullAddonsToLocal();
|
||||
|
||||
const traktConnected = await this.isTraktConnected();
|
||||
if (traktConnected) {
|
||||
await this.pullLibraryToLocal();
|
||||
const externalProgressSyncConnected = await this.isExternalProgressSyncConnected();
|
||||
if (externalProgressSyncConnected) {
|
||||
logger.log('[SupabaseSyncService] External sync (Trakt/Simkl) is connected; skipping watch progress/watched-items pull, keeping library sync.');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.pullWatchProgressToLocal();
|
||||
await this.pullLibraryToLocal();
|
||||
await this.pullWatchedItemsToLocal();
|
||||
});
|
||||
logger.log('[SupabaseSyncService] pullAllToLocal: complete');
|
||||
|
|
@ -493,9 +499,18 @@ class SupabaseSyncService {
|
|||
logger.warn('[SupabaseSyncService] runStartupSync: one or more pull steps failed; skipped startup push-by-design');
|
||||
}
|
||||
|
||||
const traktConnected = await this.isTraktConnected();
|
||||
if (traktConnected) {
|
||||
logger.log('[SupabaseSyncService] Trakt is connected; skipping progress/library/watched Supabase sync.');
|
||||
const libraryPullOk = await this.safeRun('pull_library', async () => {
|
||||
await this.withSuppressedPushes(async () => {
|
||||
await this.pullLibraryToLocal();
|
||||
});
|
||||
});
|
||||
|
||||
const externalProgressSyncConnected = await this.isExternalProgressSyncConnected();
|
||||
if (externalProgressSyncConnected) {
|
||||
logger.log('[SupabaseSyncService] External sync (Trakt/Simkl) is connected; skipping watch progress/watched-items Supabase sync (library still synced).');
|
||||
if (!libraryPullOk) {
|
||||
logger.warn('[SupabaseSyncService] runStartupSync: library pull failed while external sync priority is active');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -505,12 +520,6 @@ class SupabaseSyncService {
|
|||
});
|
||||
});
|
||||
|
||||
const libraryPullOk = await this.safeRun('pull_library', async () => {
|
||||
await this.withSuppressedPushes(async () => {
|
||||
await this.pullLibraryToLocal();
|
||||
});
|
||||
});
|
||||
|
||||
const watchedPullOk = await this.safeRun('pull_watched_items', async () => {
|
||||
await this.withSuppressedPushes(async () => {
|
||||
await this.pullWatchedItemsToLocal();
|
||||
|
|
@ -629,8 +638,8 @@ class SupabaseSyncService {
|
|||
return;
|
||||
}
|
||||
|
||||
const traktConnected = await this.isTraktConnected();
|
||||
if (traktConnected) return;
|
||||
const externalProgressSyncConnected = await this.isExternalProgressSyncConnected();
|
||||
if (externalProgressSyncConnected) return;
|
||||
|
||||
try {
|
||||
logger.log(`[SupabaseSyncService] flushWatchProgressDeletes: deleting ${keys.length} keys`);
|
||||
|
|
@ -700,12 +709,12 @@ class SupabaseSyncService {
|
|||
return;
|
||||
}
|
||||
|
||||
const traktConnected = await this.isTraktConnected();
|
||||
if (traktConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target === 'watch_progress') {
|
||||
const externalProgressSyncConnected = await this.isExternalProgressSyncConnected();
|
||||
if (externalProgressSyncConnected) {
|
||||
logger.log('[SupabaseSyncService] executeScheduledPush: skipping watch_progress due to external sync priority (Trakt/Simkl)');
|
||||
return;
|
||||
}
|
||||
await this.pushWatchProgressFromLocal();
|
||||
logger.log(`[SupabaseSyncService] executeScheduledPush: target=${target}:done`);
|
||||
return;
|
||||
|
|
@ -716,6 +725,12 @@ class SupabaseSyncService {
|
|||
return;
|
||||
}
|
||||
|
||||
const externalProgressSyncConnected = await this.isExternalProgressSyncConnected();
|
||||
if (externalProgressSyncConnected) {
|
||||
logger.log('[SupabaseSyncService] executeScheduledPush: skipping watched_items due to external sync priority (Trakt/Simkl)');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.pushWatchedItemsFromLocal();
|
||||
logger.log(`[SupabaseSyncService] executeScheduledPush: target=${target}:done`);
|
||||
}
|
||||
|
|
@ -745,6 +760,8 @@ class SupabaseSyncService {
|
|||
}
|
||||
|
||||
private async setSession(session: SupabaseSession): Promise<void> {
|
||||
// Reset per-entry push cache on session changes to avoid cross-account state bleed.
|
||||
this.watchProgressPushedSignatures.clear();
|
||||
this.session = session;
|
||||
await mmkvStorage.setItem(SUPABASE_SESSION_KEY, JSON.stringify(session));
|
||||
}
|
||||
|
|
@ -775,6 +792,7 @@ class SupabaseSyncService {
|
|||
} catch (error) {
|
||||
logger.error('[SupabaseSyncService] Failed to refresh session:', error);
|
||||
this.session = null;
|
||||
this.watchProgressPushedSignatures.clear();
|
||||
await mmkvStorage.removeItem(SUPABASE_SESSION_KEY);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -791,6 +809,7 @@ class SupabaseSyncService {
|
|||
} catch (error) {
|
||||
logger.error('[SupabaseSyncService] Token refresh failed:', error);
|
||||
this.session = null;
|
||||
this.watchProgressPushedSignatures.clear();
|
||||
await mmkvStorage.removeItem(SUPABASE_SESSION_KEY);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -997,6 +1016,22 @@ class SupabaseSyncService {
|
|||
};
|
||||
}
|
||||
|
||||
private getWatchProgressEntrySignature(value: { currentTime?: number; duration?: number; lastUpdated?: number }): string {
|
||||
return [
|
||||
Number(value.currentTime || 0),
|
||||
Number(value.duration || 0),
|
||||
Number(value.lastUpdated || 0),
|
||||
].join('|');
|
||||
}
|
||||
|
||||
private buildLocalWatchProgressKey(
|
||||
contentType: 'movie' | 'series',
|
||||
contentId: string,
|
||||
episodeId?: string
|
||||
): string {
|
||||
return `${contentType}:${contentId}${episodeId ? `:${episodeId}` : ''}`;
|
||||
}
|
||||
|
||||
private toStreamingContent(item: LibraryRow): StreamingContent {
|
||||
const type = item.content_type === 'movie' ? 'movie' : 'series';
|
||||
const posterShape = (item.poster_shape || 'POSTER').toLowerCase() as 'poster' | 'square' | 'landscape';
|
||||
|
|
@ -1037,6 +1072,19 @@ class SupabaseSyncService {
|
|||
}
|
||||
}
|
||||
|
||||
private async isSimklConnected(): Promise<boolean> {
|
||||
try {
|
||||
return await SimklService.getInstance().isAuthenticated();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async isExternalProgressSyncConnected(): Promise<boolean> {
|
||||
if (await this.isTraktConnected()) return true;
|
||||
return await this.isSimklConnected();
|
||||
}
|
||||
|
||||
private async pullPluginsToLocal(): Promise<void> {
|
||||
const token = await this.getValidAccessToken();
|
||||
if (!token) return;
|
||||
|
|
@ -1254,11 +1302,10 @@ class SupabaseSyncService {
|
|||
const type = row.content_type === 'movie' ? 'movie' : 'series';
|
||||
const season = row.season == null ? null : Number(row.season);
|
||||
const episode = row.episode == null ? null : Number(row.episode);
|
||||
remoteSet.add(`${type}:${row.content_id}:${season ?? ''}:${episode ?? ''}`);
|
||||
|
||||
const episodeId = type === 'series' && season != null && episode != null
|
||||
? `${row.content_id}:${season}:${episode}`
|
||||
: undefined;
|
||||
remoteSet.add(this.buildLocalWatchProgressKey(type, row.content_id, episodeId));
|
||||
|
||||
const local = await storageService.getWatchProgress(row.content_id, type, episodeId);
|
||||
const remoteLastWatched = this.normalizeEpochMs(row.last_watched);
|
||||
|
|
@ -1284,15 +1331,46 @@ class SupabaseSyncService {
|
|||
);
|
||||
}
|
||||
|
||||
logger.log(`[SupabaseSyncService] pullWatchProgressToLocal: merged ${(rows || []).length} remote entries (no local prune)`);
|
||||
// Remote-first continue watching: remove local entries that no longer exist remotely.
|
||||
// This intentionally treats the successful remote pull as authoritative.
|
||||
const allLocal = await storageService.getAllWatchProgress();
|
||||
let pruned = 0;
|
||||
for (const [localKey] of Object.entries(allLocal)) {
|
||||
if (remoteSet.has(localKey)) continue;
|
||||
|
||||
const parsed = this.parseWatchProgressKey(localKey);
|
||||
if (!parsed) continue;
|
||||
|
||||
const episodeId = parsed.videoId && parsed.videoId !== parsed.contentId ? parsed.videoId : undefined;
|
||||
await storageService.removeWatchProgress(parsed.contentId, parsed.contentType, episodeId);
|
||||
this.watchProgressPushedSignatures.delete(localKey);
|
||||
pruned += 1;
|
||||
}
|
||||
|
||||
logger.log(`[SupabaseSyncService] pullWatchProgressToLocal: merged=${(rows || []).length} prunedLocalMissing=${pruned}`);
|
||||
}
|
||||
|
||||
private async pushWatchProgressFromLocal(): Promise<void> {
|
||||
const all = await storageService.getAllWatchProgress();
|
||||
const entries: WatchProgressRow[] = Object.entries(all).reduce<WatchProgressRow[]>((acc, [key, value]) => {
|
||||
const nextSeenKeys = new Set<string>();
|
||||
const changedEntries: Array<{ key: string; row: WatchProgressRow; signature: string }> = [];
|
||||
|
||||
for (const [key, value] of Object.entries(all)) {
|
||||
nextSeenKeys.add(key);
|
||||
const signature = this.getWatchProgressEntrySignature(value);
|
||||
if (this.watchProgressPushedSignatures.get(key) === signature) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const parsed = this.parseWatchProgressKey(key);
|
||||
if (!parsed) return acc;
|
||||
acc.push({
|
||||
if (!parsed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
changedEntries.push({
|
||||
key,
|
||||
signature,
|
||||
row: {
|
||||
content_id: parsed.contentId,
|
||||
content_type: parsed.contentType,
|
||||
video_id: parsed.videoId,
|
||||
|
|
@ -1302,11 +1380,30 @@ class SupabaseSyncService {
|
|||
duration: this.secondsToMsLong(value.duration),
|
||||
last_watched: this.normalizeEpochMs(value.lastUpdated || Date.now()),
|
||||
progress_key: parsed.progressKey,
|
||||
},
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
await this.callRpc<void>('sync_push_watch_progress', { p_entries: entries });
|
||||
// Prune signatures for entries no longer present locally (deletes are handled separately).
|
||||
for (const existingKey of Array.from(this.watchProgressPushedSignatures.keys())) {
|
||||
if (!nextSeenKeys.has(existingKey)) {
|
||||
this.watchProgressPushedSignatures.delete(existingKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (changedEntries.length === 0) {
|
||||
logger.log('[SupabaseSyncService] pushWatchProgressFromLocal: no changed entries; skipping push');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.callRpc<void>('sync_push_watch_progress', {
|
||||
p_entries: changedEntries.map((entry) => entry.row),
|
||||
});
|
||||
|
||||
for (const entry of changedEntries) {
|
||||
this.watchProgressPushedSignatures.set(entry.key, entry.signature);
|
||||
}
|
||||
logger.log(`[SupabaseSyncService] pushWatchProgressFromLocal: pushedChanged=${changedEntries.length} totalLocal=${Object.keys(all).length}`);
|
||||
}
|
||||
|
||||
private async pullLibraryToLocal(): Promise<void> {
|
||||
|
|
|
|||
Loading…
Reference in a new issue