diff --git a/.gitignore b/.gitignore index f7c2b63..bf6e7ea 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ yarn-error.* # typescript *.tsbuildinfo +plan.md +release_announcement.md \ No newline at end of file diff --git a/App.tsx b/App.tsx index df02cd8..bf19654 100644 --- a/App.tsx +++ b/App.tsx @@ -5,7 +5,7 @@ * @format */ -import React from 'react'; +import React, { useState } from 'react'; import { View, StyleSheet @@ -24,6 +24,7 @@ import { CatalogProvider } from './src/contexts/CatalogContext'; import { GenreProvider } from './src/contexts/GenreContext'; import { TraktProvider } from './src/contexts/TraktContext'; import { ThemeProvider, useTheme } from './src/contexts/ThemeContext'; +import SplashScreen from './src/components/SplashScreen'; // This fixes many navigation layout issues by using native screen containers enableScreens(true); @@ -31,6 +32,7 @@ enableScreens(true); // Inner app component that uses the theme context const ThemedApp = () => { const { currentTheme } = useTheme(); + const [isAppReady, setIsAppReady] = useState(false); // Create custom themes based on current theme const customDarkTheme = { @@ -50,6 +52,11 @@ const ThemedApp = () => { background: currentTheme.colors.darkBackground, } }; + + // Handler for splash screen completion + const handleSplashComplete = () => { + setIsAppReady(true); + }; return ( @@ -62,7 +69,8 @@ const ThemedApp = () => { - + {!isAppReady && } + {isAppReady && } diff --git a/app.json b/app.json index 07c328d..5cb6c50 100644 --- a/app.json +++ b/app.json @@ -5,13 +5,13 @@ "version": "1.0.0", "orientation": "default", "icon": "./assets/icon.png", - "userInterfaceStyle": "light", + "userInterfaceStyle": "dark", "scheme": "stremioexpo", "newArchEnabled": true, "splash": { "image": "./assets/splash-icon.png", "resizeMode": "contain", - "backgroundColor": "#ffffff" + "backgroundColor": "#020404" }, "ios": { "supportsTablet": true, @@ -41,15 +41,21 @@ }, "android": { "adaptiveIcon": { - "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#ffffff" + "foregroundImage": "./assets/icon.png", + "backgroundColor": "#020404", + "monochromeImage": "./assets/icon.png" }, "permissions": [ "INTERNET", "WAKE_LOCK" ], "package": "com.nuvio.app", - "enableSplitAPKs": true + "enableSplitAPKs": true, + "versionCode": 1, + "enableProguardInReleaseBuilds": true, + "enableHermes": true, + "enableSeparateBuildPerCPUArchitecture": true, + "enableVectorDrawables": true }, "web": { "favicon": "./assets/favicon.png" diff --git a/components/AndroidVideoPlayer.tsx b/components/AndroidVideoPlayer.tsx new file mode 100644 index 0000000..8865071 --- /dev/null +++ b/components/AndroidVideoPlayer.tsx @@ -0,0 +1,119 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { Platform } from 'react-native'; +import Video, { VideoRef, SelectedTrack, BufferingStrategyType } from 'react-native-video'; + +interface VideoPlayerProps { + src: string; + paused: boolean; + volume: number; + currentTime: number; + selectedAudioTrack?: SelectedTrack; + selectedTextTrack?: SelectedTrack; + onProgress?: (data: { currentTime: number; playableDuration: number }) => void; + onLoad?: (data: { duration: number }) => void; + onError?: (error: any) => void; + onBuffer?: (data: { isBuffering: boolean }) => void; + onSeek?: (data: { currentTime: number; seekTime: number }) => void; + onEnd?: () => void; +} + +export const AndroidVideoPlayer: React.FC = ({ + src, + paused, + volume, + currentTime, + selectedAudioTrack, + selectedTextTrack, + onProgress, + onLoad, + onError, + onBuffer, + onSeek, + onEnd, +}) => { + const videoRef = useRef(null); + const [isLoaded, setIsLoaded] = useState(false); + const [isSeeking, setIsSeeking] = useState(false); + const [lastSeekTime, setLastSeekTime] = useState(0); + + // Only render on Android + if (Platform.OS !== 'android') { + return null; + } + + useEffect(() => { + if (isLoaded && !isSeeking && Math.abs(currentTime - lastSeekTime) > 1) { + setIsSeeking(true); + videoRef.current?.seek(currentTime); + setLastSeekTime(currentTime); + } + }, [currentTime, isLoaded, isSeeking, lastSeekTime]); + + const handleLoad = (data: any) => { + setIsLoaded(true); + onLoad?.(data); + }; + + const handleProgress = (data: any) => { + if (!isSeeking) { + onProgress?.(data); + } + }; + + const handleSeek = (data: any) => { + setIsSeeking(false); + onSeek?.(data); + }; + + const handleBuffer = (data: any) => { + onBuffer?.(data); + }; + + const handleError = (error: any) => { + console.error('Video playback error:', error); + onError?.(error); + }; + + const handleEnd = () => { + onEnd?.(); + }; + + return ( +