mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +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 { Provider as PaperProvider } from 'react-native-paper';
|
||||
import { enableScreens, enableFreeze } from 'react-native-screens';
|
||||
import AppNavigator, {
|
||||
import AppNavigator, {
|
||||
CustomNavigationDarkTheme,
|
||||
CustomDarkTheme
|
||||
} from './src/navigation/AppNavigator';
|
||||
|
|
@ -41,6 +41,7 @@ import { aiService } from './src/services/aiService';
|
|||
import { AccountProvider, useAccount } from './src/contexts/AccountContext';
|
||||
import { ToastProvider } from './src/contexts/ToastContext';
|
||||
import { mmkvStorage } from './src/services/mmkvStorage';
|
||||
import AnnouncementOverlay from './src/components/AnnouncementOverlay';
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992',
|
||||
|
|
@ -82,12 +83,13 @@ const ThemedApp = () => {
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const engine = (global as any).HermesInternal ? 'Hermes' : 'JSC';
|
||||
console.log('JS Engine:', engine);
|
||||
} catch {}
|
||||
} catch { }
|
||||
}, []);
|
||||
const { currentTheme } = useTheme();
|
||||
const [isAppReady, setIsAppReady] = useState(false);
|
||||
const [hasCompletedOnboarding, setHasCompletedOnboarding] = useState<boolean | null>(null);
|
||||
|
||||
const [showAnnouncement, setShowAnnouncement] = useState(false);
|
||||
|
||||
// Update popup functionality
|
||||
const {
|
||||
showUpdatePopup,
|
||||
|
|
@ -100,7 +102,17 @@ const ThemedApp = () => {
|
|||
|
||||
// GitHub major/minor release overlay
|
||||
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
|
||||
useEffect(() => {
|
||||
const initializeApp = async () => {
|
||||
|
|
@ -108,28 +120,37 @@ const ThemedApp = () => {
|
|||
// Check onboarding status
|
||||
const onboardingCompleted = await mmkvStorage.getItem('hasCompletedOnboarding');
|
||||
setHasCompletedOnboarding(onboardingCompleted === 'true');
|
||||
|
||||
|
||||
// Initialize update service
|
||||
await UpdateService.initialize();
|
||||
|
||||
|
||||
// Initialize memory monitoring service to prevent OutOfMemoryError
|
||||
memoryMonitorService; // Just accessing it starts the monitoring
|
||||
console.log('Memory monitoring service initialized');
|
||||
|
||||
|
||||
// Initialize AI service
|
||||
await aiService.initialize();
|
||||
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) {
|
||||
console.error('Error initializing app:', error);
|
||||
// Default to showing onboarding if we can't check
|
||||
setHasCompletedOnboarding(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
initializeApp();
|
||||
}, []);
|
||||
|
||||
|
||||
// Create custom themes based on current theme
|
||||
const customDarkTheme = {
|
||||
...CustomDarkTheme,
|
||||
|
|
@ -138,7 +159,7 @@ const ThemedApp = () => {
|
|||
primary: currentTheme.colors.primary,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const customNavigationTheme = {
|
||||
...CustomNavigationDarkTheme,
|
||||
colors: {
|
||||
|
|
@ -153,15 +174,33 @@ const ThemedApp = () => {
|
|||
const handleSplashComplete = () => {
|
||||
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
|
||||
const shouldShowApp = isAppReady && hasCompletedOnboarding !== null;
|
||||
const initialRouteName = hasCompletedOnboarding ? 'MainTabs' : 'Onboarding';
|
||||
|
||||
|
||||
return (
|
||||
<AccountProvider>
|
||||
<PaperProvider theme={customDarkTheme}>
|
||||
<NavigationContainer
|
||||
<NavigationContainer
|
||||
ref={navigationRef}
|
||||
theme={customNavigationTheme}
|
||||
linking={undefined}
|
||||
>
|
||||
|
|
@ -186,6 +225,13 @@ const ThemedApp = () => {
|
|||
onDismiss={githubUpdate.onDismiss}
|
||||
onLater={githubUpdate.onLater}
|
||||
/>
|
||||
<AnnouncementOverlay
|
||||
visible={showAnnouncement}
|
||||
announcements={announcements}
|
||||
onClose={handleAnnouncementClose}
|
||||
onActionPress={handleNavigateToDebrid}
|
||||
actionButtonText="Connect Now"
|
||||
/>
|
||||
</View>
|
||||
</DownloadsProvider>
|
||||
</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}
|
||||
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
|
||||
title="Clear All Data"
|
||||
icon="trash-2"
|
||||
|
|
|
|||
Loading…
Reference in a new issue