mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 00:32:04 +00:00
some fixes
This commit is contained in:
parent
838f74caa2
commit
a7d3a8acc7
7 changed files with 158 additions and 38 deletions
24
App.tsx
24
App.tsx
|
|
@ -35,6 +35,7 @@ import * as Sentry from '@sentry/react-native';
|
||||||
import UpdateService from './src/services/updateService';
|
import UpdateService from './src/services/updateService';
|
||||||
import { memoryMonitorService } from './src/services/memoryMonitorService';
|
import { memoryMonitorService } from './src/services/memoryMonitorService';
|
||||||
import { aiService } from './src/services/aiService';
|
import { aiService } from './src/services/aiService';
|
||||||
|
import { AccountProvider, useAccount } from './src/contexts/AccountContext';
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992',
|
dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992',
|
||||||
|
|
@ -143,21 +144,18 @@ const ThemedApp = () => {
|
||||||
const shouldShowApp = isAppReady && hasCompletedOnboarding !== null;
|
const shouldShowApp = isAppReady && hasCompletedOnboarding !== null;
|
||||||
const initialRouteName = hasCompletedOnboarding ? 'MainTabs' : 'Onboarding';
|
const initialRouteName = hasCompletedOnboarding ? 'MainTabs' : 'Onboarding';
|
||||||
|
|
||||||
return (
|
const NavigationWithRef = () => {
|
||||||
<PaperProvider theme={customDarkTheme}>
|
const { navigationRef } = useAccount() as any;
|
||||||
|
return (
|
||||||
<NavigationContainer
|
<NavigationContainer
|
||||||
|
ref={navigationRef as any}
|
||||||
theme={customNavigationTheme}
|
theme={customNavigationTheme}
|
||||||
// Disable automatic linking which can cause layout issues
|
|
||||||
linking={undefined}
|
linking={undefined}
|
||||||
>
|
>
|
||||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||||
<StatusBar
|
<StatusBar style="light" />
|
||||||
style="light"
|
|
||||||
/>
|
|
||||||
{!isAppReady && <SplashScreen onFinish={handleSplashComplete} />}
|
{!isAppReady && <SplashScreen onFinish={handleSplashComplete} />}
|
||||||
{shouldShowApp && <AppNavigator initialRouteName={initialRouteName} />}
|
{shouldShowApp && <AppNavigator initialRouteName={initialRouteName} />}
|
||||||
|
|
||||||
{/* Update Popup */}
|
|
||||||
{Platform.OS === 'ios' && (
|
{Platform.OS === 'ios' && (
|
||||||
<UpdatePopup
|
<UpdatePopup
|
||||||
visible={showUpdatePopup}
|
visible={showUpdatePopup}
|
||||||
|
|
@ -170,7 +168,15 @@ const ThemedApp = () => {
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
</PaperProvider>
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccountProvider>
|
||||||
|
<PaperProvider theme={customDarkTheme}>
|
||||||
|
<NavigationWithRef />
|
||||||
|
</PaperProvider>
|
||||||
|
</AccountProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
65
src/components/icons/PluginIcon.tsx
Normal file
65
src/components/icons/PluginIcon.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import Svg, { Path, Line } from 'react-native-svg';
|
||||||
|
|
||||||
|
interface PluginIconProps {
|
||||||
|
size?: number;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PluginIcon: React.FC<PluginIconProps> = ({ size = 24, color = '#FFFFFF' }) => {
|
||||||
|
return (
|
||||||
|
<View style={{ width: size, height: size }}>
|
||||||
|
<Svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
>
|
||||||
|
<Path
|
||||||
|
d="M16,22L16,22c-2.2,0-4-1.8-4-4v-4h8v4C20,20.2,18.2,22,16,22z"
|
||||||
|
fill="none"
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
x1="14"
|
||||||
|
y1="10"
|
||||||
|
x2="14"
|
||||||
|
y2="14"
|
||||||
|
fill="none"
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
x1="18"
|
||||||
|
y1="10"
|
||||||
|
x2="18"
|
||||||
|
y2="14"
|
||||||
|
fill="none"
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
<Path
|
||||||
|
d="M16,22v3.1c0,2.3-2.3,4-4.4,3.2C6.4,26.4,2.8,21.3,3,15.5C3.3,8.8,8.8,3.3,15.5,3C22.9,2.7,29,8.7,29,16c0,5.6-3.5,10.3-8.4,12.2"
|
||||||
|
fill="none"
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
</Svg>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PluginIcon;
|
||||||
35
src/components/icons/ProfileIcon.tsx
Normal file
35
src/components/icons/ProfileIcon.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import Svg, { Path } from 'react-native-svg';
|
||||||
|
|
||||||
|
interface ProfileIconProps {
|
||||||
|
size?: number;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProfileIcon: React.FC<ProfileIconProps> = ({ size = 24, color = '#FFFFFF' }) => {
|
||||||
|
return (
|
||||||
|
<View style={{ width: size, height: size }}>
|
||||||
|
<Svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
>
|
||||||
|
<Path
|
||||||
|
d="M12 14c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7zm0-12C9.243 2 7 4.243 7 7s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5zM19.942 32H4.058A4.062 4.062 0 0 1 0 27.942c0-6.616 5.383-12 12-12s12 5.384 12 12A4.062 4.062 0 0 1 19.942 32zM12 17.942c-5.514 0-10 4.486-10 10A2.06 2.06 0 0 0 4.058 30h15.884A2.06 2.06 0 0 0 22 27.942c0-5.514-4.486-10-10-10z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
<Path
|
||||||
|
d="M20 16.942c-1.413 0-2.759.276-4 .762 4.095 1.601 7 5.576 7 10.238A3.058 3.058 0 0 1 19.942 31h8A3.058 3.058 0 0 0 31 27.942c0-6.075-4.925-11-11-11zM20 1a5.97 5.97 0 0 0-4 1.537 5.978 5.978 0 0 1 0 8.926A5.97 5.97 0 0 0 20 13a6 6 0 1 0 0-12z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
<Path
|
||||||
|
d="M27.942 32h-8a1 1 0 1 1 0-2A2.06 2.06 0 0 0 22 27.942c0-4.142-2.498-7.795-6.364-9.307a1 1 0 0 1 0-1.863c1.408-.55 2.877-.83 4.364-.83 6.617 0 12 5.384 12 12A4.062 4.062 0 0 1 27.942 32zm-4.504-2h4.504A2.06 2.06 0 0 0 30 27.942c0-5.514-4.486-10-10-10-.419 0-.836.027-1.251.08C22.004 20.22 24 23.886 24 27.942c0 .75-.205 1.454-.562 2.058zM20 14a6.984 6.984 0 0 1-4.667-1.792.999.999 0 0 1-.001-1.489C16.392 9.77 17 8.413 17 7s-.607-2.768-1.668-3.72a1.002 1.002 0 0 1 .001-1.488A6.984 6.984 0 0 1 20 0c3.86 0 7 3.14 7 7s-3.14 7-7 7zm-2.503-2.677c.758.44 1.62.677 2.503.677 2.757 0 5-2.243 5-5s-2.243-5-5-5c-.884 0-1.745.236-2.503.677C18.463 3.903 19 5.426 19 7s-.537 3.097-1.503 4.323z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
</Svg>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProfileIcon;
|
||||||
|
|
@ -49,20 +49,32 @@ export const AccountProvider: React.FC<{ children: React.ReactNode }> = ({ child
|
||||||
|
|
||||||
// Auth state listener
|
// Auth state listener
|
||||||
const { data: subscription } = supabase.auth.onAuthStateChange(async (_event, session) => {
|
const { data: subscription } = supabase.auth.onAuthStateChange(async (_event, session) => {
|
||||||
|
// Do not block UI on auth transitions
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const fullUser = session?.user ? await accountService.getCurrentUser() : null;
|
const fullUser = session?.user ? await accountService.getCurrentUser() : null;
|
||||||
setUser(fullUser);
|
setUser(fullUser);
|
||||||
|
// Immediately clear loading so UI can transition to MainTabs/Auth
|
||||||
|
setLoading(false);
|
||||||
if (fullUser) {
|
if (fullUser) {
|
||||||
await syncService.migrateLocalScopeToUser();
|
// Run sync in background without blocking UI
|
||||||
await syncService.subscribeRealtime();
|
setTimeout(async () => {
|
||||||
// Pull first to hydrate local state, then push to avoid wiping server with empty local
|
try {
|
||||||
await syncService.fullPull();
|
await syncService.migrateLocalScopeToUser();
|
||||||
await syncService.fullPush();
|
await new Promise(r => setTimeout(r, 0));
|
||||||
|
await syncService.subscribeRealtime();
|
||||||
|
await new Promise(r => setTimeout(r, 0));
|
||||||
|
await syncService.fullPull();
|
||||||
|
await new Promise(r => setTimeout(r, 0));
|
||||||
|
await syncService.fullPush();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[AccountContext] Background sync failed:', error);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
} else {
|
} else {
|
||||||
syncService.unsubscribeRealtime();
|
syncService.unsubscribeRealtime();
|
||||||
}
|
}
|
||||||
} finally {
|
} catch (e) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ import ThemeScreen from '../screens/ThemeScreen';
|
||||||
import OnboardingScreen from '../screens/OnboardingScreen';
|
import OnboardingScreen from '../screens/OnboardingScreen';
|
||||||
import AuthScreen from '../screens/AuthScreen';
|
import AuthScreen from '../screens/AuthScreen';
|
||||||
import AccountManageScreen from '../screens/AccountManageScreen';
|
import AccountManageScreen from '../screens/AccountManageScreen';
|
||||||
import { AccountProvider, useAccount } from '../contexts/AccountContext';
|
import { useAccount } from '../contexts/AccountContext';
|
||||||
import { LoadingProvider, useLoading } from '../contexts/LoadingContext';
|
import { LoadingProvider, useLoading } from '../contexts/LoadingContext';
|
||||||
import PluginsScreen from '../screens/PluginsScreen';
|
import PluginsScreen from '../screens/PluginsScreen';
|
||||||
import CastMoviesScreen from '../screens/CastMoviesScreen';
|
import CastMoviesScreen from '../screens/CastMoviesScreen';
|
||||||
|
|
@ -1336,11 +1336,9 @@ const AppNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootStack
|
||||||
host: "https://us.i.posthog.com",
|
host: "https://us.i.posthog.com",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AccountProvider>
|
<LoadingProvider>
|
||||||
<LoadingProvider>
|
<InnerNavigator initialRouteName={initialRouteName} />
|
||||||
<InnerNavigator initialRouteName={initialRouteName} />
|
</LoadingProvider>
|
||||||
</LoadingProvider>
|
|
||||||
</AccountProvider>
|
|
||||||
</PostHogProvider>
|
</PostHogProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,14 +74,6 @@ const onboardingData: OnboardingSlide[] = [
|
||||||
icon: 'library-books',
|
icon: 'library-books',
|
||||||
gradient: ['#43e97b', '#38f9d7'],
|
gradient: ['#43e97b', '#38f9d7'],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: '5',
|
|
||||||
title: 'Plugins',
|
|
||||||
subtitle: 'Stream Sources Only',
|
|
||||||
description: 'Plugins add streaming sources to Nuvio.',
|
|
||||||
icon: 'widgets',
|
|
||||||
gradient: ['#ff9a9e', '#fad0c4'],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const OnboardingScreen = () => {
|
const OnboardingScreen = () => {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ import { catalogService } from '../services/catalogService';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import * as Sentry from '@sentry/react-native';
|
import * as Sentry from '@sentry/react-native';
|
||||||
import CustomAlert from '../components/CustomAlert';
|
import CustomAlert from '../components/CustomAlert';
|
||||||
|
import ProfileIcon from '../components/icons/ProfileIcon';
|
||||||
|
import PluginIcon from '../components/icons/PluginIcon';
|
||||||
|
|
||||||
const { width, height } = Dimensions.get('window');
|
const { width, height } = Dimensions.get('window');
|
||||||
const isTablet = width >= 768;
|
const isTablet = width >= 768;
|
||||||
|
|
@ -91,7 +93,8 @@ const SettingsCard: React.FC<SettingsCardProps> = ({ children, title, isTablet =
|
||||||
interface SettingItemProps {
|
interface SettingItemProps {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
icon: keyof typeof MaterialIcons.glyphMap;
|
icon?: keyof typeof MaterialIcons.glyphMap;
|
||||||
|
customIcon?: React.ReactNode;
|
||||||
renderControl?: () => React.ReactNode;
|
renderControl?: () => React.ReactNode;
|
||||||
isLast?: boolean;
|
isLast?: boolean;
|
||||||
onPress?: () => void;
|
onPress?: () => void;
|
||||||
|
|
@ -103,6 +106,7 @@ const SettingItem: React.FC<SettingItemProps> = ({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
icon,
|
icon,
|
||||||
|
customIcon,
|
||||||
renderControl,
|
renderControl,
|
||||||
isLast = false,
|
isLast = false,
|
||||||
onPress,
|
onPress,
|
||||||
|
|
@ -124,14 +128,22 @@ const SettingItem: React.FC<SettingItemProps> = ({
|
||||||
>
|
>
|
||||||
<View style={[
|
<View style={[
|
||||||
styles.settingIconContainer,
|
styles.settingIconContainer,
|
||||||
{ backgroundColor: currentTheme.colors.elevation2 },
|
{
|
||||||
|
backgroundColor: currentTheme.colors.darkGray,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: currentTheme.colors.primary + '20'
|
||||||
|
},
|
||||||
isTablet && styles.tabletSettingIconContainer
|
isTablet && styles.tabletSettingIconContainer
|
||||||
]}>
|
]}>
|
||||||
<MaterialIcons
|
{customIcon ? (
|
||||||
name={icon}
|
customIcon
|
||||||
size={isTablet ? 24 : 20}
|
) : (
|
||||||
color={currentTheme.colors.primary}
|
<MaterialIcons
|
||||||
/>
|
name={icon!}
|
||||||
|
size={isTablet ? 24 : 20}
|
||||||
|
color={currentTheme.colors.primary}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.settingContent}>
|
<View style={styles.settingContent}>
|
||||||
<View style={styles.settingTextContainer}>
|
<View style={styles.settingTextContainer}>
|
||||||
|
|
@ -426,7 +438,7 @@ const SettingsScreen: React.FC = () => {
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title={user.displayName || user.email || user.id}
|
title={user.displayName || user.email || user.id}
|
||||||
description="Manage account"
|
description="Manage account"
|
||||||
icon="account-circle"
|
customIcon={<ProfileIcon size={isTablet ? 24 : 20} color={currentTheme.colors.primary} />}
|
||||||
onPress={() => navigation.navigate('AccountManage')}
|
onPress={() => navigation.navigate('AccountManage')}
|
||||||
isTablet={isTablet}
|
isTablet={isTablet}
|
||||||
/>
|
/>
|
||||||
|
|
@ -473,7 +485,7 @@ const SettingsScreen: React.FC = () => {
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title="Plugins"
|
title="Plugins"
|
||||||
description="Manage plugins and repositories"
|
description="Manage plugins and repositories"
|
||||||
icon="code"
|
customIcon={<PluginIcon size={isTablet ? 24 : 20} color={currentTheme.colors.primary} />}
|
||||||
renderControl={ChevronRight}
|
renderControl={ChevronRight}
|
||||||
onPress={() => navigation.navigate('ScraperSettings')}
|
onPress={() => navigation.navigate('ScraperSettings')}
|
||||||
isTablet={isTablet}
|
isTablet={isTablet}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue