mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
Sync behaviour improvments
This commit is contained in:
parent
62defd4773
commit
abfccd0e36
3 changed files with 57 additions and 14 deletions
|
|
@ -10,6 +10,7 @@ type AccountContextValue = {
|
|||
signIn: (email: string, password: string) => Promise<string | null>;
|
||||
signUp: (email: string, password: string) => Promise<string | null>;
|
||||
signOut: () => Promise<void>;
|
||||
refreshCurrentUser: () => Promise<void>;
|
||||
updateProfile: (partial: { avatarUrl?: string; displayName?: string }) => Promise<string | null>;
|
||||
};
|
||||
|
||||
|
|
@ -47,16 +48,21 @@ export const AccountProvider: React.FC<{ children: React.ReactNode }> = ({ child
|
|||
|
||||
// Auth state listener
|
||||
const { data: subscription } = supabase.auth.onAuthStateChange(async (_event, session) => {
|
||||
const fullUser = session?.user ? await accountService.getCurrentUser() : null;
|
||||
setUser(fullUser);
|
||||
if (fullUser) {
|
||||
await syncService.migrateLocalScopeToUser();
|
||||
await syncService.subscribeRealtime();
|
||||
// Pull first to hydrate local state, then push to avoid wiping server with empty local
|
||||
await syncService.fullPull();
|
||||
await syncService.fullPush();
|
||||
} else {
|
||||
syncService.unsubscribeRealtime();
|
||||
setLoading(true);
|
||||
try {
|
||||
const fullUser = session?.user ? await accountService.getCurrentUser() : null;
|
||||
setUser(fullUser);
|
||||
if (fullUser) {
|
||||
await syncService.migrateLocalScopeToUser();
|
||||
await syncService.subscribeRealtime();
|
||||
// Pull first to hydrate local state, then push to avoid wiping server with empty local
|
||||
await syncService.fullPull();
|
||||
await syncService.fullPush();
|
||||
} else {
|
||||
syncService.unsubscribeRealtime();
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -81,6 +87,15 @@ export const AccountProvider: React.FC<{ children: React.ReactNode }> = ({ child
|
|||
await accountService.signOut();
|
||||
setUser(null);
|
||||
},
|
||||
refreshCurrentUser: async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const u = await accountService.getCurrentUser();
|
||||
setUser(u);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
updateProfile: async (partial) => {
|
||||
const err = await accountService.updateProfile(partial);
|
||||
if (!err) {
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ const SettingsScreen: React.FC = () => {
|
|||
const { isAuthenticated, userProfile, refreshAuthStatus } = useTraktContext();
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { user, signOut, loading: accountLoading } = useAccount();
|
||||
const { user, signOut, loading: accountLoading, refreshCurrentUser } = useAccount();
|
||||
|
||||
// Tablet-specific state
|
||||
const [selectedCategory, setSelectedCategory] = useState('account');
|
||||
|
|
@ -248,10 +248,12 @@ const SettingsScreen: React.FC = () => {
|
|||
if (__DEV__) console.log('SettingsScreen focused, refreshing auth status. Current state:', { isAuthenticated, userProfile: userProfile?.username });
|
||||
}
|
||||
refreshAuthStatus();
|
||||
// Also refresh account user in case we returned from auth flow
|
||||
refreshCurrentUser();
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [navigation, isAuthenticated, userProfile, refreshAuthStatus]);
|
||||
}, [navigation, isAuthenticated, userProfile, refreshAuthStatus, refreshCurrentUser]);
|
||||
|
||||
// States for dynamic content
|
||||
const [addonCount, setAddonCount] = useState<number>(0);
|
||||
|
|
|
|||
|
|
@ -693,6 +693,8 @@ class SyncService {
|
|||
(stremioService as any).addonOrder = order;
|
||||
await (stremioService as any).saveInstalledAddons();
|
||||
await (stremioService as any).saveAddonOrder();
|
||||
// Mark addons initialized for this user to prevent destructive merges on first push
|
||||
try { await AsyncStorage.setItem(`@user:${userId}:addons_initialized`, 'true'); } catch {}
|
||||
// Push merged order to server to preserve across devices
|
||||
try {
|
||||
const rows = order.map((addonId: string, idx: number) => ({
|
||||
|
|
@ -848,9 +850,27 @@ class SyncService {
|
|||
const user = await accountService.getCurrentUser();
|
||||
if (!user) return;
|
||||
const userId = user.id;
|
||||
const addons = await stremioService.getInstalledAddonsAsync();
|
||||
let addons = await stremioService.getInstalledAddonsAsync();
|
||||
logger.log(`[Sync] push installed_addons count=${addons.length}`);
|
||||
const order = (stremioService as any).addonOrder as string[];
|
||||
let order = (stremioService as any).addonOrder as string[];
|
||||
|
||||
// Safety: if this is a first-time push and local addons are fewer than remote, pull before pushing
|
||||
try {
|
||||
const initialized = (await AsyncStorage.getItem(`@user:${userId}:addons_initialized`)) === 'true';
|
||||
const { data: remoteBefore } = await supabase
|
||||
.from('installed_addons')
|
||||
.select('addon_id')
|
||||
.eq('user_id', userId);
|
||||
const remoteCount = (remoteBefore || []).length;
|
||||
if (!initialized && remoteCount > addons.length) {
|
||||
logger.log('[Sync] addons not initialized and local smaller than remote → pulling before push');
|
||||
await this.pullAddonsSnapshot(userId);
|
||||
// refresh local state after pull
|
||||
addons = await stremioService.getInstalledAddonsAsync();
|
||||
order = (stremioService as any).addonOrder as string[];
|
||||
}
|
||||
} catch {}
|
||||
|
||||
const rows = addons.map((a: any) => ({
|
||||
user_id: userId,
|
||||
addon_id: a.id,
|
||||
|
|
@ -863,12 +883,17 @@ class SyncService {
|
|||
manifest_data: a,
|
||||
}));
|
||||
// Delete remote addons that no longer exist locally (excluding pre-installed to be safe)
|
||||
// Guard: do not perform deletions on first-time merge when remote has more addons
|
||||
try {
|
||||
const { data: remote, error: rErr } = await supabase
|
||||
.from('installed_addons')
|
||||
.select('addon_id')
|
||||
.eq('user_id', userId);
|
||||
if (!rErr && remote) {
|
||||
const initialized = (await AsyncStorage.getItem(`@user:${userId}:addons_initialized`)) === 'true';
|
||||
if (!initialized && (remote as any[]).length > addons.length) {
|
||||
logger.log('[Sync] skipping deletions during first-time addon merge');
|
||||
} else {
|
||||
const localIds = new Set(addons.map((a: any) => a.id));
|
||||
const toDeletePromises = (remote as any[])
|
||||
.map(r => r.addon_id as string)
|
||||
|
|
@ -894,6 +919,7 @@ class SyncService {
|
|||
.in('addon_id', toDelete);
|
||||
if (del.error && __DEV__) console.warn('[SyncService] delete addons error', del.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (__DEV__) console.warn('[SyncService] deletion sync for addons failed', e);
|
||||
|
|
|
|||
Loading…
Reference in a new issue