Merge pull request #10 from MathieuVce/feat/ios-runtime-fixes

fix: correctifs de comportement iOS au lancement et à l'exécution
This commit is contained in:
MysticSaba 2026-05-19 22:33:18 +02:00 committed by GitHub
commit 4d60772001
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 152 additions and 72 deletions

BIN
app/assets/movix512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View file

@ -57,5 +57,9 @@
<array>
<string>audio</string>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>tg</string>
</array>
</dict>
</plist>

View file

@ -0,0 +1,4 @@
#import <React/RCTBridgeModule.h>
@interface UpdateModule : NSObject <RCTBridgeModule>
@end

View file

@ -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

View file

@ -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 = "<group>"; };
00E356F21AD99517003FC87E /* MovixAppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MovixAppTests.m; sourceTree = "<group>"; };
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 = "<group>"; };
AA000002AA000002AA000002 /* UpdateModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = UpdateModule.m; path = Movix/UpdateModule.m; sourceTree = "<group>"; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Movix/AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Movix/AppDelegate.mm; sourceTree = "<group>"; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Movix/Images.xcassets; sourceTree = "<group>"; };
@ -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 = "<group>";
};
41107E703FD4EF2098255694 /* MovixApp */ = {
isa = PBXGroup;
children = (
);
name = MovixApp;
sourceTree = "<group>";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
@ -141,13 +139,6 @@
name = Products;
sourceTree = "<group>";
};
A027C3DBE9B2BA83B1F37CBE /* MovixAppTests */ = {
isa = PBXGroup;
children = (
);
name = MovixAppTests;
sourceTree = "<group>";
};
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;

View file

@ -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<WebViewBrowserRef, WebViewBrowserProps>(
({ url, onNavigationStateChange, onError }, ref) => {
({ url, onNavigationStateChange, onError, onLoadEnd }, ref) => {
const webViewRef = useRef<WebView>(null);
useImperativeHandle(ref, () => ({
@ -61,6 +63,27 @@ const WebViewBrowser = forwardRef<WebViewBrowserRef, WebViewBrowserProps>(
[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<WebViewBrowserRef, WebViewBrowserProps>(
// 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<WebViewBrowserRef, WebViewBrowserProps>(
allowsFullscreenVideo={true}
allowsBackForwardNavigationGestures={true}
// Sécurité
originWhitelist={['https://*', 'http://*']}
originWhitelist={['https://*', 'http://*', 'about:*', 'blob:*']}
mixedContentMode="compatibility"
// Cache
cacheEnabled={true}

View file

@ -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 (
<View style={[styles.container, styles.centered, { paddingTop: insets.top }]}>
<ActivityIndicator size="large" color="#8b5cf6" />
</View>
);
}
if (allMirrorsFailed) {
return (
<MirrorErrorScreen telegramUrl={config.telegramUrl} onRetry={onRetry} />
);
}
const showWebView = !isLoading && !!config && !allMirrorsFailed;
const showSplash = (!webViewReady || isLoading || !config) && !allMirrorsFailed;
return (
<View style={[styles.container, { paddingTop: insets.top }]}>
<View style={styles.webViewContainer}>
<WebViewBrowser
key={activeUrl}
ref={webViewRef}
url={activeUrl}
onNavigationStateChange={onNavigationStateChange}
onError={onWebViewError}
/>
</View>
{showWebView && (
<View style={styles.webViewContainer}>
<WebViewBrowser
key={activeUrl}
ref={webViewRef}
url={activeUrl}
onNavigationStateChange={onNavigationStateChange}
onError={onWebViewError}
onLoadEnd={onWebViewLoadEnd}
/>
</View>
)}
{!toolbarHidden && (
{allMirrorsFailed && config && (
<MirrorErrorScreen telegramUrl={config.telegramUrl} onRetry={onRetry} />
)}
{!toolbarHidden && showWebView && (
<View style={{ paddingBottom: insets.bottom }}>
<BrowserToolbar
canGoBack={canGoBack}
@ -153,23 +163,39 @@ export default function BrowserScreen() {
</View>
)}
<Modal
visible={settingsVisible}
animationType="slide"
onRequestClose={closeSettings}>
<View style={[styles.modalContainer, { paddingTop: insets.top }]}>
<View style={styles.modalHeader}>
<TouchableOpacity onPress={closeSettings} style={styles.closeButton}>
<Text style={styles.closeText}>Fermer</Text>
</TouchableOpacity>
<Text style={styles.modalTitle}>Paramètres</Text>
<View style={styles.closeButton} />
{showWebView && (
<Modal
visible={settingsVisible}
animationType="slide"
onRequestClose={closeSettings}>
<View style={[styles.modalContainer, { paddingTop: insets.top }]}>
<View style={styles.modalHeader}>
<TouchableOpacity onPress={closeSettings} style={styles.closeButton}>
<Text style={styles.closeText}>Fermer</Text>
</TouchableOpacity>
<Text style={styles.modalTitle}>Paramètres</Text>
<View style={styles.closeButton} />
</View>
<SettingsScreen />
</View>
<SettingsScreen />
</View>
</Modal>
</Modal>
)}
{navBarHidden && <MiniPill onPress={() => setSettingsVisible(true)} />}
{navBarHidden && showWebView && (
<MiniPill onPress={() => setSettingsVisible(true)} />
)}
{showSplash && (
<Animated.View
style={[StyleSheet.absoluteFillObject, styles.splash, { opacity: splashFade }]}
pointerEvents="none">
<Image
source={require('../../assets/movix512.png')}
style={styles.splashLogo}
resizeMode="contain"
/>
</Animated.View>
)}
</View>
);
}
@ -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,
},
});

View file

@ -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<boolean> {
if (Platform.OS !== 'android') return false;
try {
return await ensureModule().isSupported();
} catch (err) {