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) {