mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 00:32:04 +00:00
ui changes
This commit is contained in:
parent
6d1ba14ab4
commit
6c08b459bf
3 changed files with 383 additions and 14 deletions
74
App.tsx
74
App.tsx
|
|
@ -18,7 +18,7 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { Provider as PaperProvider } from 'react-native-paper';
|
import { Provider as PaperProvider } from 'react-native-paper';
|
||||||
import { enableScreens, enableFreeze } from 'react-native-screens';
|
import { enableScreens, enableFreeze } from 'react-native-screens';
|
||||||
import AppNavigator, {
|
import AppNavigator, {
|
||||||
CustomNavigationDarkTheme,
|
CustomNavigationDarkTheme,
|
||||||
CustomDarkTheme
|
CustomDarkTheme
|
||||||
} from './src/navigation/AppNavigator';
|
} from './src/navigation/AppNavigator';
|
||||||
|
|
@ -41,6 +41,7 @@ import { aiService } from './src/services/aiService';
|
||||||
import { AccountProvider, useAccount } from './src/contexts/AccountContext';
|
import { AccountProvider, useAccount } from './src/contexts/AccountContext';
|
||||||
import { ToastProvider } from './src/contexts/ToastContext';
|
import { ToastProvider } from './src/contexts/ToastContext';
|
||||||
import { mmkvStorage } from './src/services/mmkvStorage';
|
import { mmkvStorage } from './src/services/mmkvStorage';
|
||||||
|
import AnnouncementOverlay from './src/components/AnnouncementOverlay';
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992',
|
dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992',
|
||||||
|
|
@ -82,12 +83,13 @@ const ThemedApp = () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const engine = (global as any).HermesInternal ? 'Hermes' : 'JSC';
|
const engine = (global as any).HermesInternal ? 'Hermes' : 'JSC';
|
||||||
console.log('JS Engine:', engine);
|
console.log('JS Engine:', engine);
|
||||||
} catch {}
|
} catch { }
|
||||||
}, []);
|
}, []);
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const [isAppReady, setIsAppReady] = useState(false);
|
const [isAppReady, setIsAppReady] = useState(false);
|
||||||
const [hasCompletedOnboarding, setHasCompletedOnboarding] = useState<boolean | null>(null);
|
const [hasCompletedOnboarding, setHasCompletedOnboarding] = useState<boolean | null>(null);
|
||||||
|
const [showAnnouncement, setShowAnnouncement] = useState(false);
|
||||||
|
|
||||||
// Update popup functionality
|
// Update popup functionality
|
||||||
const {
|
const {
|
||||||
showUpdatePopup,
|
showUpdatePopup,
|
||||||
|
|
@ -100,7 +102,17 @@ const ThemedApp = () => {
|
||||||
|
|
||||||
// GitHub major/minor release overlay
|
// GitHub major/minor release overlay
|
||||||
const githubUpdate = useGithubMajorUpdate();
|
const githubUpdate = useGithubMajorUpdate();
|
||||||
|
|
||||||
|
// Announcement data
|
||||||
|
const announcements = [
|
||||||
|
{
|
||||||
|
icon: 'zap',
|
||||||
|
title: 'Debrid Integration',
|
||||||
|
description: 'Unlock 4K high-quality streams with lightning-fast speeds. Connect your TorBox account to access cached premium content with zero buffering.',
|
||||||
|
tag: 'NEW',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Check onboarding status and initialize services
|
// Check onboarding status and initialize services
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeApp = async () => {
|
const initializeApp = async () => {
|
||||||
|
|
@ -108,28 +120,37 @@ const ThemedApp = () => {
|
||||||
// Check onboarding status
|
// Check onboarding status
|
||||||
const onboardingCompleted = await mmkvStorage.getItem('hasCompletedOnboarding');
|
const onboardingCompleted = await mmkvStorage.getItem('hasCompletedOnboarding');
|
||||||
setHasCompletedOnboarding(onboardingCompleted === 'true');
|
setHasCompletedOnboarding(onboardingCompleted === 'true');
|
||||||
|
|
||||||
// Initialize update service
|
// Initialize update service
|
||||||
await UpdateService.initialize();
|
await UpdateService.initialize();
|
||||||
|
|
||||||
// Initialize memory monitoring service to prevent OutOfMemoryError
|
// Initialize memory monitoring service to prevent OutOfMemoryError
|
||||||
memoryMonitorService; // Just accessing it starts the monitoring
|
memoryMonitorService; // Just accessing it starts the monitoring
|
||||||
console.log('Memory monitoring service initialized');
|
console.log('Memory monitoring service initialized');
|
||||||
|
|
||||||
// Initialize AI service
|
// Initialize AI service
|
||||||
await aiService.initialize();
|
await aiService.initialize();
|
||||||
console.log('AI service initialized');
|
console.log('AI service initialized');
|
||||||
|
|
||||||
|
// Check if announcement should be shown (version 1.0.0)
|
||||||
|
const announcementShown = await mmkvStorage.getItem('announcement_v1.0.0_shown');
|
||||||
|
if (!announcementShown && onboardingCompleted === 'true') {
|
||||||
|
// Show announcement only after app is ready
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowAnnouncement(true);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error initializing app:', error);
|
console.error('Error initializing app:', error);
|
||||||
// Default to showing onboarding if we can't check
|
// Default to showing onboarding if we can't check
|
||||||
setHasCompletedOnboarding(false);
|
setHasCompletedOnboarding(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
initializeApp();
|
initializeApp();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Create custom themes based on current theme
|
// Create custom themes based on current theme
|
||||||
const customDarkTheme = {
|
const customDarkTheme = {
|
||||||
...CustomDarkTheme,
|
...CustomDarkTheme,
|
||||||
|
|
@ -138,7 +159,7 @@ const ThemedApp = () => {
|
||||||
primary: currentTheme.colors.primary,
|
primary: currentTheme.colors.primary,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const customNavigationTheme = {
|
const customNavigationTheme = {
|
||||||
...CustomNavigationDarkTheme,
|
...CustomNavigationDarkTheme,
|
||||||
colors: {
|
colors: {
|
||||||
|
|
@ -153,15 +174,33 @@ const ThemedApp = () => {
|
||||||
const handleSplashComplete = () => {
|
const handleSplashComplete = () => {
|
||||||
setIsAppReady(true);
|
setIsAppReady(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Navigation reference
|
||||||
|
const navigationRef = React.useRef<any>(null);
|
||||||
|
|
||||||
|
// Handler for navigating to debrid integration
|
||||||
|
const handleNavigateToDebrid = () => {
|
||||||
|
if (navigationRef.current) {
|
||||||
|
navigationRef.current.navigate('DebridIntegration');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handler for announcement close
|
||||||
|
const handleAnnouncementClose = async () => {
|
||||||
|
setShowAnnouncement(false);
|
||||||
|
// Mark announcement as shown
|
||||||
|
await mmkvStorage.setItem('announcement_v1.0.0_shown', 'true');
|
||||||
|
};
|
||||||
|
|
||||||
// Don't render anything until we know the onboarding status
|
// Don't render anything until we know the onboarding status
|
||||||
const shouldShowApp = isAppReady && hasCompletedOnboarding !== null;
|
const shouldShowApp = isAppReady && hasCompletedOnboarding !== null;
|
||||||
const initialRouteName = hasCompletedOnboarding ? 'MainTabs' : 'Onboarding';
|
const initialRouteName = hasCompletedOnboarding ? 'MainTabs' : 'Onboarding';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountProvider>
|
<AccountProvider>
|
||||||
<PaperProvider theme={customDarkTheme}>
|
<PaperProvider theme={customDarkTheme}>
|
||||||
<NavigationContainer
|
<NavigationContainer
|
||||||
|
ref={navigationRef}
|
||||||
theme={customNavigationTheme}
|
theme={customNavigationTheme}
|
||||||
linking={undefined}
|
linking={undefined}
|
||||||
>
|
>
|
||||||
|
|
@ -186,6 +225,13 @@ const ThemedApp = () => {
|
||||||
onDismiss={githubUpdate.onDismiss}
|
onDismiss={githubUpdate.onDismiss}
|
||||||
onLater={githubUpdate.onLater}
|
onLater={githubUpdate.onLater}
|
||||||
/>
|
/>
|
||||||
|
<AnnouncementOverlay
|
||||||
|
visible={showAnnouncement}
|
||||||
|
announcements={announcements}
|
||||||
|
onClose={handleAnnouncementClose}
|
||||||
|
onActionPress={handleNavigateToDebrid}
|
||||||
|
actionButtonText="Connect Now"
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</DownloadsProvider>
|
</DownloadsProvider>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
|
|
|
||||||
308
src/components/AnnouncementOverlay.tsx
Normal file
308
src/components/AnnouncementOverlay.tsx
Normal file
|
|
@ -0,0 +1,308 @@
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
Modal,
|
||||||
|
TouchableOpacity,
|
||||||
|
Animated,
|
||||||
|
Dimensions,
|
||||||
|
ScrollView,
|
||||||
|
} from 'react-native';
|
||||||
|
import { useTheme } from '../contexts/ThemeContext';
|
||||||
|
import { Feather } from '@expo/vector-icons';
|
||||||
|
|
||||||
|
const { width, height } = Dimensions.get('window');
|
||||||
|
|
||||||
|
interface Announcement {
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tag?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnnouncementOverlayProps {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onActionPress?: () => void;
|
||||||
|
title?: string;
|
||||||
|
announcements: Announcement[];
|
||||||
|
actionButtonText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AnnouncementOverlay: React.FC<AnnouncementOverlayProps> = ({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
onActionPress,
|
||||||
|
title = "What's New",
|
||||||
|
announcements,
|
||||||
|
actionButtonText = "Got it!",
|
||||||
|
}) => {
|
||||||
|
const { currentTheme } = useTheme();
|
||||||
|
const colors = currentTheme.colors;
|
||||||
|
|
||||||
|
const scaleAnim = useRef(new Animated.Value(0.8)).current;
|
||||||
|
const opacityAnim = useRef(new Animated.Value(0)).current;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
Animated.parallel([
|
||||||
|
Animated.spring(scaleAnim, {
|
||||||
|
toValue: 1,
|
||||||
|
tension: 50,
|
||||||
|
friction: 7,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}),
|
||||||
|
Animated.timing(opacityAnim, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: 300,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}),
|
||||||
|
]).start();
|
||||||
|
} else {
|
||||||
|
scaleAnim.setValue(0.8);
|
||||||
|
opacityAnim.setValue(0);
|
||||||
|
}
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
Animated.parallel([
|
||||||
|
Animated.spring(scaleAnim, {
|
||||||
|
toValue: 0.8,
|
||||||
|
tension: 50,
|
||||||
|
friction: 7,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}),
|
||||||
|
Animated.timing(opacityAnim, {
|
||||||
|
toValue: 0,
|
||||||
|
duration: 200,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}),
|
||||||
|
]).start(() => {
|
||||||
|
onClose();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAction = () => {
|
||||||
|
if (onActionPress) {
|
||||||
|
handleClose();
|
||||||
|
// Delay navigation slightly to allow animation to complete
|
||||||
|
setTimeout(() => {
|
||||||
|
onActionPress();
|
||||||
|
}, 300);
|
||||||
|
} else {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
transparent
|
||||||
|
animationType="none"
|
||||||
|
statusBarTranslucent
|
||||||
|
onRequestClose={handleClose}
|
||||||
|
>
|
||||||
|
<View style={styles.overlay}>
|
||||||
|
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.container,
|
||||||
|
{
|
||||||
|
opacity: opacityAnim,
|
||||||
|
transform: [{ scale: scaleAnim }],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View style={styles.card}>
|
||||||
|
{/* Close Button */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.closeButton}
|
||||||
|
onPress={handleClose}
|
||||||
|
>
|
||||||
|
<Feather name="x" size={20} color={colors.white} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<View style={[styles.iconContainer, { backgroundColor: colors.primary + '20' }]}>
|
||||||
|
<Feather name="zap" size={32} color={colors.primary} />
|
||||||
|
</View>
|
||||||
|
<Text style={[styles.title, { color: colors.white }]}>{title}</Text>
|
||||||
|
<Text style={[styles.subtitle, { color: colors.mediumEmphasis }]}>
|
||||||
|
Exciting updates in this release
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Announcements */}
|
||||||
|
<ScrollView
|
||||||
|
style={styles.scrollView}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
|
{announcements.map((announcement, index) => (
|
||||||
|
<View
|
||||||
|
key={index}
|
||||||
|
style={styles.announcementItem}
|
||||||
|
>
|
||||||
|
<View style={[styles.announcementIcon, { backgroundColor: colors.primary + '15' }]}>
|
||||||
|
<Feather name={announcement.icon as any} size={24} color={colors.primary} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.announcementContent}>
|
||||||
|
<View style={styles.announcementHeader}>
|
||||||
|
<Text style={[styles.announcementTitle, { color: colors.white }]}>
|
||||||
|
{announcement.title}
|
||||||
|
</Text>
|
||||||
|
{announcement.tag && (
|
||||||
|
<View style={[styles.tag, { backgroundColor: colors.primary }]}>
|
||||||
|
<Text style={styles.tagText}>{announcement.tag}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<Text style={[styles.announcementDescription, { color: colors.mediumEmphasis }]}>
|
||||||
|
{announcement.description}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
{/* Action Button */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, { backgroundColor: colors.primary }]}
|
||||||
|
onPress={handleAction}
|
||||||
|
>
|
||||||
|
<Text style={styles.buttonText}>{actionButtonText}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
overlay: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.85)',
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
width: width * 0.9,
|
||||||
|
maxWidth: 500,
|
||||||
|
maxHeight: height * 0.8,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
backgroundColor: '#1a1a1a',
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 24,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 8 },
|
||||||
|
shadowOpacity: 0.3,
|
||||||
|
shadowRadius: 16,
|
||||||
|
},
|
||||||
|
closeButton: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 16,
|
||||||
|
right: 16,
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: 16,
|
||||||
|
backgroundColor: '#2a2a2a',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
zIndex: 10,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
iconContainer: {
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
borderRadius: 32,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: '700',
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
textAlign: 'center',
|
||||||
|
opacity: 0.9,
|
||||||
|
},
|
||||||
|
scrollView: {
|
||||||
|
maxHeight: height * 0.45,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
announcementItem: {
|
||||||
|
backgroundColor: '#252525',
|
||||||
|
flexDirection: 'row',
|
||||||
|
padding: 16,
|
||||||
|
borderRadius: 16,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
announcementIcon: {
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 24,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: 16,
|
||||||
|
},
|
||||||
|
announcementContent: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
announcementHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 6,
|
||||||
|
},
|
||||||
|
announcementTitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '700',
|
||||||
|
letterSpacing: 0.3,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
tag: {
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 6,
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
tagText: {
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#FFFFFF',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
|
announcementDescription: {
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: 20,
|
||||||
|
opacity: 0.9,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
borderRadius: 12,
|
||||||
|
paddingVertical: 16,
|
||||||
|
alignItems: 'center',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 4,
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
color: '#FFFFFF',
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '700',
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AnnouncementOverlay;
|
||||||
|
|
@ -764,6 +764,21 @@ const SettingsScreen: React.FC = () => {
|
||||||
renderControl={ChevronRight}
|
renderControl={ChevronRight}
|
||||||
isTablet={isTablet}
|
isTablet={isTablet}
|
||||||
/>
|
/>
|
||||||
|
<SettingItem
|
||||||
|
title="Test Announcement"
|
||||||
|
icon="bell"
|
||||||
|
description="Show what's new overlay"
|
||||||
|
onPress={async () => {
|
||||||
|
try {
|
||||||
|
await mmkvStorage.removeItem('announcement_v1.0.0_shown');
|
||||||
|
openAlert('Success', 'Announcement reset. Restart the app to see the announcement overlay.');
|
||||||
|
} catch (error) {
|
||||||
|
openAlert('Error', 'Failed to reset announcement.');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title="Clear All Data"
|
title="Clear All Data"
|
||||||
icon="trash-2"
|
icon="trash-2"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue