diff --git a/app/assets/movix512.png b/app/assets/movix512.png new file mode 100644 index 0000000..1e5feb1 Binary files /dev/null and b/app/assets/movix512.png differ diff --git a/app/ios/Movix/Info.plist b/app/ios/Movix/Info.plist index f89cacc..b808500 100644 --- a/app/ios/Movix/Info.plist +++ b/app/ios/Movix/Info.plist @@ -57,5 +57,9 @@ audio + LSApplicationQueriesSchemes + + tg + diff --git a/app/ios/Movix/UpdateModule.h b/app/ios/Movix/UpdateModule.h new file mode 100644 index 0000000..0cf6ae4 --- /dev/null +++ b/app/ios/Movix/UpdateModule.h @@ -0,0 +1,4 @@ +#import + +@interface UpdateModule : NSObject +@end diff --git a/app/ios/Movix/UpdateModule.m b/app/ios/Movix/UpdateModule.m new file mode 100644 index 0000000..6bc20c9 --- /dev/null +++ b/app/ios/Movix/UpdateModule.m @@ -0,0 +1,21 @@ +#import "UpdateModule.h" + +@implementation UpdateModule + +RCT_EXPORT_MODULE() + +RCT_EXPORT_METHOD(getVersionName:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +{ + NSString *version = [NSBundle mainBundle].infoDictionary[@"CFBundleShortVersionString"]; + resolve(version ?: @"unknown"); +} + +RCT_EXPORT_METHOD(getVersionCode:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +{ + NSString *build = [NSBundle mainBundle].infoDictionary[@"CFBundleVersion"]; + resolve(@([build integerValue])); +} + +@end diff --git a/app/ios/MovixApp.xcodeproj/project.pbxproj b/app/ios/MovixApp.xcodeproj/project.pbxproj index 152ccef..b479278 100644 --- a/app/ios/MovixApp.xcodeproj/project.pbxproj +++ b/app/ios/MovixApp.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 00E356F31AD99517003FC87E /* MovixAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* MovixAppTests.m */; }; 0C80B921A6F3F58F76C31292 /* libPods-MovixApp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-MovixApp.a */; }; + AA000003AA000003AA000003 /* UpdateModule.m in Sources */ = {isa = PBXBuildFile; fileRef = AA000002AA000002AA000002 /* UpdateModule.m */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; @@ -31,6 +32,8 @@ 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* MovixAppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MovixAppTests.m; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* MovixApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MovixApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + AA000001AA000001AA000001 /* UpdateModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UpdateModule.h; path = Movix/UpdateModule.h; sourceTree = ""; }; + AA000002AA000002AA000002 /* UpdateModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = UpdateModule.m; path = Movix/UpdateModule.m; sourceTree = ""; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Movix/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Movix/AppDelegate.mm; sourceTree = ""; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Movix/Images.xcassets; sourceTree = ""; }; @@ -83,6 +86,8 @@ 13B07FAE1A68108700A75B9A /* MovixApp */ = { isa = PBXGroup; children = ( + AA000001AA000001AA000001 /* UpdateModule.h */, + AA000002AA000002AA000002 /* UpdateModule.m */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.mm */, 13B07FB51A68108700A75B9A /* Images.xcassets */, @@ -103,13 +108,6 @@ name = Frameworks; sourceTree = ""; }; - 41107E703FD4EF2098255694 /* MovixApp */ = { - isa = PBXGroup; - children = ( - ); - name = MovixApp; - sourceTree = ""; - }; 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( @@ -141,13 +139,6 @@ name = Products; sourceTree = ""; }; - A027C3DBE9B2BA83B1F37CBE /* MovixAppTests */ = { - isa = PBXGroup; - children = ( - ); - name = MovixAppTests; - sourceTree = ""; - }; BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( @@ -281,10 +272,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-MovixApp/Pods-MovixApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-MovixApp/Pods-MovixApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MovixApp/Pods-MovixApp-frameworks.sh\"\n"; @@ -320,10 +315,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-MovixApp/Pods-MovixApp-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-MovixApp/Pods-MovixApp-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MovixApp/Pods-MovixApp-resources.sh\"\n"; @@ -344,6 +343,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + AA000003AA000003AA000003 /* UpdateModule.m in Sources */, 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */, ); @@ -419,7 +419,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 8F3K3NVGNG; + DEVELOPMENT_TEAM = 52RQH3U49D; ENABLE_BITCODE = NO; INFOPLIST_FILE = Movix/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -453,7 +453,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 8F3K3NVGNG; + DEVELOPMENT_TEAM = 52RQH3U49D; INFOPLIST_FILE = Movix/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -552,10 +552,7 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -630,10 +627,7 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/app/src/components/WebViewBrowser.tsx b/app/src/components/WebViewBrowser.tsx index 7ac4d44..2355641 100644 --- a/app/src/components/WebViewBrowser.tsx +++ b/app/src/components/WebViewBrowser.tsx @@ -4,11 +4,12 @@ import React, { useImperativeHandle, useRef, } from 'react'; -import { Platform } from 'react-native'; +import { Linking, Platform } from 'react-native'; import { WebView, type WebViewNavigation } from 'react-native-webview'; import type { WebViewErrorEvent, WebViewMessageEvent, + ShouldStartLoadRequest, } from 'react-native-webview/lib/WebViewTypes'; import { handleBridgeMessage } from '../services/bridge'; import { buildInjectedJavaScript } from '../injection/inject'; @@ -26,12 +27,13 @@ interface WebViewBrowserProps { url: string; onNavigationStateChange?: (state: WebViewNavigation) => void; onError?: (error: string) => void; + onLoadEnd?: () => void; } const injectedJS = buildInjectedJavaScript(); const WebViewBrowser = forwardRef( - ({ url, onNavigationStateChange, onError }, ref) => { + ({ url, onNavigationStateChange, onError, onLoadEnd }, ref) => { const webViewRef = useRef(null); useImperativeHandle(ref, () => ({ @@ -61,6 +63,27 @@ const WebViewBrowser = forwardRef( [onError], ); + const onShouldStartLoadWithRequest = useCallback( + (request: ShouldStartLoadRequest) => { + const { url, navigationType } = request; + if ( + url.startsWith('https://') || + url.startsWith('http://') || + url.startsWith('about:') || + url.startsWith('blob:') + ) { + return true; + } + // Ouvre uniquement les deep links déclenchés par un vrai clic utilisateur. + // Les redirections automatiques (pubs, iframes) sont silencieusement bloquées. + if (navigationType === 'click') { + Linking.openURL(url).catch(() => {}); + } + return false; + }, + [], + ); + const onWebViewError = useCallback( (event: WebViewErrorEvent) => { onError?.(event.nativeEvent.description); @@ -83,10 +106,12 @@ const WebViewBrowser = forwardRef( // Bridge messages onMessage={onMessage} // Navigation + onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} onNavigationStateChange={onNavigationStateChange} // Errors onError={onWebViewError} onHttpError={onHttpError} + onLoadEnd={onLoadEnd} // Config userAgent={userAgent} javaScriptEnabled={true} @@ -96,7 +121,7 @@ const WebViewBrowser = forwardRef( allowsFullscreenVideo={true} allowsBackForwardNavigationGestures={true} // Sécurité - originWhitelist={['https://*', 'http://*']} + originWhitelist={['https://*', 'http://*', 'about:*', 'blob:*']} mixedContentMode="compatibility" // Cache cacheEnabled={true} diff --git a/app/src/screens/BrowserScreen.tsx b/app/src/screens/BrowserScreen.tsx index 998279c..1290ff2 100644 --- a/app/src/screens/BrowserScreen.tsx +++ b/app/src/screens/BrowserScreen.tsx @@ -7,7 +7,8 @@ import { Platform, Modal, TouchableOpacity, - ActivityIndicator, + Image, + Animated, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import type { WebViewNavigation } from 'react-native-webview'; @@ -44,6 +45,8 @@ export default function BrowserScreen() { const [currentUrl, setCurrentUrl] = useState(''); const [dnsEnabled, setDnsEnabled] = useState(false); const [settingsVisible, setSettingsVisible] = useState(false); + const [webViewReady, setWebViewReady] = useState(false); + const splashFade = useRef(new Animated.Value(1)).current; const activeUrl = urlChain[mirrorIndex] ?? ''; @@ -95,6 +98,15 @@ export default function BrowserScreen() { [activeUrl, mirrorIndex, urlChain.length], ); + const onWebViewLoadEnd = useCallback(() => { + if (webViewReady) return; + Animated.timing(splashFade, { + toValue: 0, + duration: 400, + useNativeDriver: true, + }).start(() => setWebViewReady(true)); + }, [webViewReady, splashFade]); + const closeSettings = useCallback(() => { setSettingsVisible(false); AsyncStorage.getItem('dns_enabled').then(val => { @@ -105,36 +117,34 @@ export default function BrowserScreen() { const onRetry = useCallback(async () => { setAllMirrorsFailed(false); setMirrorIndex(0); + setWebViewReady(false); + splashFade.setValue(1); await refresh(); - }, [refresh]); + }, [refresh, splashFade]); - if (isLoading || !config) { - return ( - - - - ); - } - - if (allMirrorsFailed) { - return ( - - ); - } + const showWebView = !isLoading && !!config && !allMirrorsFailed; + const showSplash = (!webViewReady || isLoading || !config) && !allMirrorsFailed; return ( - - - + {showWebView && ( + + + + )} - {!toolbarHidden && ( + {allMirrorsFailed && config && ( + + )} + + {!toolbarHidden && showWebView && ( )} - - - - - Fermer - - Paramètres - + {showWebView && ( + + + + + Fermer + + Paramètres + + + - - - + + )} - {navBarHidden && setSettingsVisible(true)} />} + {navBarHidden && showWebView && ( + setSettingsVisible(true)} /> + )} + + {showSplash && ( + + + + )} ); } @@ -179,10 +205,6 @@ const styles = StyleSheet.create({ flex: 1, backgroundColor: '#0a0a0a', }, - centered: { - justifyContent: 'center', - alignItems: 'center', - }, webViewContainer: { flex: 1, }, @@ -213,4 +235,13 @@ const styles = StyleSheet.create({ fontSize: 15, fontWeight: '500', }, + splash: { + backgroundColor: '#B5302C', + justifyContent: 'center', + alignItems: 'center', + }, + splashLogo: { + width: 150, + height: 150, + }, }); diff --git a/app/src/services/cast.ts b/app/src/services/cast.ts index d135afd..cc0f04f 100644 --- a/app/src/services/cast.ts +++ b/app/src/services/cast.ts @@ -1,4 +1,4 @@ -import { DeviceEventEmitter, NativeModules } from 'react-native'; +import { DeviceEventEmitter, NativeModules, Platform } from 'react-native'; export type CastSessionState = 'idle' | 'starting' | 'connected' | 'ending'; @@ -29,6 +29,7 @@ function ensureModule(): CastModuleType { } export async function isCastSupported(): Promise { + if (Platform.OS !== 'android') return false; try { return await ensureModule().isSupported(); } catch (err) {