Various Fixes (#8212)
This commit is contained in:
parent
20d248fb99
commit
394da4487c
21 changed files with 259 additions and 77 deletions
|
|
@ -6,6 +6,7 @@ import {Button} from '@rneui/base';
|
|||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {useIntl, type IntlShape} from 'react-intl';
|
||||
import {Alert, StyleSheet} from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {ITEM_HEIGHT} from '@components/option_item';
|
||||
|
|
@ -149,20 +150,19 @@ const ChannelBookmark = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
containerStyle={styles.container}
|
||||
buttonStyle={styles.button}
|
||||
onPress={onGestureEvent}
|
||||
onLongPress={handleLongPress}
|
||||
|
||||
// @ts-expect-error ref not present in TS def
|
||||
ref={ref}
|
||||
>
|
||||
<BookmarkDetails
|
||||
bookmark={bookmark}
|
||||
file={file}
|
||||
/>
|
||||
</Button>
|
||||
<Animated.View ref={ref}>
|
||||
<Button
|
||||
containerStyle={styles.container}
|
||||
buttonStyle={styles.button}
|
||||
onPress={onGestureEvent}
|
||||
onLongPress={handleLongPress}
|
||||
>
|
||||
<BookmarkDetails
|
||||
bookmark={bookmark}
|
||||
file={file}
|
||||
/>
|
||||
</Button>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import RNUtils, {type WindowDimensions} from '@mattermost/rnutils';
|
||||
import React, {type RefObject, useEffect, useRef, useState, useContext} from 'react';
|
||||
import {AppState, DeviceEventEmitter, Keyboard, Platform, useWindowDimensions, View} from 'react-native';
|
||||
import {AppState, DeviceEventEmitter, Keyboard, NativeEventEmitter, Platform, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {DeviceContext} from '@context/device';
|
||||
|
||||
import type {KeyboardTrackingViewRef, KeyboardWillShowEventData} from '@mattermost/keyboard-tracker';
|
||||
|
||||
const utilsEmitter = new NativeEventEmitter(RNUtils);
|
||||
|
||||
export function useSplitView() {
|
||||
const {isSplit} = React.useContext(DeviceContext);
|
||||
return isSplit;
|
||||
|
|
@ -28,6 +31,20 @@ export function useAppState() {
|
|||
return appState;
|
||||
}
|
||||
|
||||
export function useWindowDimensions() {
|
||||
const [dimensions, setDimensions] = useState(RNUtils.getWindowDimensions());
|
||||
|
||||
useEffect(() => {
|
||||
const listener = utilsEmitter.addListener('DimensionsChanged', (window: WindowDimensions) => {
|
||||
setDimensions(window);
|
||||
});
|
||||
|
||||
return () => listener.remove();
|
||||
}, []);
|
||||
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
export function useIsTablet() {
|
||||
const {isSplit, isTablet} = useContext(DeviceContext);
|
||||
return isTablet && !isSplit;
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
|
||||
import RNUtils from '@mattermost/rnutils';
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {useWindowDimensions, Platform} from 'react-native';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import {CaptionsEnabledContext} from '@calls/context';
|
||||
import {hasCaptions} from '@calls/utils';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {useIsTablet, useWindowDimensions} from '@hooks/device';
|
||||
import {useGalleryControls} from '@hooks/gallery';
|
||||
import {dismissOverlay, setScreensOrientation} from '@screens/navigation';
|
||||
import {freezeOtherScreens} from '@utils/gallery';
|
||||
|
|
@ -35,7 +35,7 @@ const GalleryScreen = ({componentId, galleryIdentifier, hideActions, initialInde
|
|||
const [captionsEnabled, setCaptionsEnabled] = useState<boolean[]>(new Array(items.length).fill(true));
|
||||
const [captionsAvailable, setCaptionsAvailable] = useState<boolean[]>([]);
|
||||
const {setControlsHidden, headerStyles, footerStyles} = useGalleryControls();
|
||||
const dimensions = useMemo(() => ({width: dim.width, height: dim.height}), [dim.width]);
|
||||
const dimensions = useMemo(() => ({width: dim.width, height: dim.height}), [dim]);
|
||||
const galleryRef = useRef<GalleryRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {DeviceEventEmitter, StyleSheet, useWindowDimensions} from 'react-native';
|
||||
import {DeviceEventEmitter, StyleSheet} from 'react-native';
|
||||
import Animated, {
|
||||
Easing,
|
||||
useAnimatedStyle,
|
||||
|
|
@ -47,7 +47,6 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
const VideoRenderer = ({height, index, initialIndex, item, isPageActive, onShouldHideControls, width}: VideoRendererProps) => {
|
||||
const dimensions = useWindowDimensions();
|
||||
const fullscreen = useSharedValue(false);
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const serverUrl = useServerUrl();
|
||||
|
|
@ -126,23 +125,15 @@ const VideoRenderer = ({height, index, initialIndex, item, isPageActive, onShoul
|
|||
}, []);
|
||||
|
||||
const dimensionsStyle = useMemo(() => {
|
||||
let w = width;
|
||||
let h = height - (VIDEO_INSET + GALLERY_FOOTER_HEIGHT + bottom);
|
||||
const w = width;
|
||||
const h = height - (VIDEO_INSET + GALLERY_FOOTER_HEIGHT + bottom);
|
||||
|
||||
if (hasError) {
|
||||
return {height: 0, width: 0};
|
||||
}
|
||||
|
||||
if (fullscreen.value) {
|
||||
w = dimensions.width;
|
||||
h = dimensions.height;
|
||||
} else if (dimensions.width > dimensions.height) {
|
||||
w = h;
|
||||
h = width;
|
||||
}
|
||||
|
||||
return {width: w, height: h};
|
||||
}, [hasError, fullscreen.value, dimensions.height]);
|
||||
}, [hasError, fullscreen.value, height, width]);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ const GalleryViewer = ({
|
|||
}
|
||||
|
||||
return (<ImageRenderer {...props}/>);
|
||||
}, [items]);
|
||||
}, [items, width, height]);
|
||||
|
||||
return (
|
||||
<Pager
|
||||
|
|
|
|||
|
|
@ -25,14 +25,13 @@ import {getIntlShape} from '@utils/general';
|
|||
import {logError} from '@utils/log';
|
||||
import {escapeRegex} from '@utils/markdown';
|
||||
import {addNewServer} from '@utils/server';
|
||||
import {removeProtocol, stripTrailingSlashes} from '@utils/url';
|
||||
import {
|
||||
TEAM_NAME_PATH_PATTERN,
|
||||
IDENTIFIER_PATH_PATTERN,
|
||||
ID_PATH_PATTERN,
|
||||
} from '@utils/url/path';
|
||||
|
||||
import {removeProtocol} from '../url';
|
||||
|
||||
import type {DeepLinkChannel, DeepLinkDM, DeepLinkGM, DeepLinkPermalink, DeepLinkWithData, LaunchProps} from '@typings/launch';
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
|
|
@ -121,29 +120,30 @@ export async function handleDeepLink(deepLinkUrl: string, intlShape?: IntlShape,
|
|||
|
||||
type ChannelPathParams = {
|
||||
hostname: string;
|
||||
serverUrl: string;
|
||||
serverUrl: string[];
|
||||
teamName: string;
|
||||
path: 'channels' | 'messages';
|
||||
identifier: string;
|
||||
};
|
||||
|
||||
const CHANNEL_PATH = `:serverUrl(.*)/:teamName(${TEAM_NAME_PATH_PATTERN})/:path(channels|messages)/:identifier(${IDENTIFIER_PATH_PATTERN})`;
|
||||
const CHANNEL_PATH = '*serverUrl/:teamName/:path/:identifier';
|
||||
export const matchChannelDeeplink = match<ChannelPathParams>(CHANNEL_PATH);
|
||||
|
||||
type PermalinkPathParams = {
|
||||
serverUrl: string;
|
||||
serverUrl: string[];
|
||||
teamName: string;
|
||||
postId: string;
|
||||
};
|
||||
const PERMALINK_PATH = `:serverUrl(.*)/:teamName(${TEAM_NAME_PATH_PATTERN})/pl/:postId(${ID_PATH_PATTERN})`;
|
||||
const PERMALINK_PATH = '*serverUrl/:teamName/pl/:postId';
|
||||
export const matchPermalinkDeeplink = match<PermalinkPathParams>(PERMALINK_PATH);
|
||||
|
||||
type ServerPathParams = {
|
||||
serverUrl: string;
|
||||
path: string;
|
||||
subpath?: string[];
|
||||
}
|
||||
|
||||
export const matchServerDeepLink = match<ServerPathParams>(':serverUrl(.*)/:path(.*)', {decode: decodeURIComponent});
|
||||
export const matchServerDeepLink = match<ServerPathParams>(':serverUrl/{:path}{/*subpath}', {decode: decodeURIComponent});
|
||||
const reservedWords = ['login', 'signup', 'admin_console'];
|
||||
|
||||
export function extractServerUrl(url: string) {
|
||||
|
|
@ -168,43 +168,72 @@ export function extractServerUrl(url: string) {
|
|||
const matched = matchServerDeepLink(deepLinkUrl);
|
||||
|
||||
if (matched) {
|
||||
const {path} = matched.params;
|
||||
const segments = path.split('/');
|
||||
const {path, subpath} = matched.params;
|
||||
|
||||
if (segments.length > 0 && reservedWords.includes(segments[segments.length - 1])) {
|
||||
return matched.params.serverUrl;
|
||||
let extra = '';
|
||||
|
||||
if (!path || reservedWords.includes(path)) {
|
||||
return stripTrailingSlashes(matched.params.serverUrl);
|
||||
}
|
||||
return path ? `${matched.params.serverUrl}/${path}` : matched.params.serverUrl;
|
||||
|
||||
if (subpath && subpath.length > 0) {
|
||||
if (reservedWords.includes(subpath[subpath.length - 1])) {
|
||||
subpath.pop();
|
||||
}
|
||||
|
||||
extra = subpath.join('/');
|
||||
}
|
||||
|
||||
if (extra) {
|
||||
return stripTrailingSlashes(`${matched.params.serverUrl}/${path}/${extra}`);
|
||||
}
|
||||
|
||||
return stripTrailingSlashes(`${matched.params.serverUrl}/${path}`);
|
||||
}
|
||||
|
||||
return deepLinkUrl;
|
||||
}
|
||||
|
||||
function isValidTeamName(teamName: string): boolean {
|
||||
const regex = new RegExp(`^${TEAM_NAME_PATH_PATTERN}$`);
|
||||
return regex.test(teamName);
|
||||
}
|
||||
|
||||
function isValidIdentifierPathPattern(id: string): boolean {
|
||||
const regex = new RegExp(`^${IDENTIFIER_PATH_PATTERN}$`);
|
||||
return regex.test(id);
|
||||
}
|
||||
|
||||
function isValidPostId(id: string): boolean {
|
||||
const regex = new RegExp(`^${ID_PATH_PATTERN}$`);
|
||||
return regex.test(id);
|
||||
}
|
||||
|
||||
export function parseDeepLink(deepLinkUrl: string, asServer = false): DeepLinkWithData {
|
||||
try {
|
||||
const url = removeProtocol(deepLinkUrl);
|
||||
|
||||
const channelMatch = matchChannelDeeplink(url);
|
||||
if (channelMatch) {
|
||||
if (channelMatch && isValidTeamName(channelMatch.params.teamName) && isValidIdentifierPathPattern(channelMatch.params.identifier)) {
|
||||
const {params: {serverUrl, teamName, path, identifier}} = channelMatch;
|
||||
|
||||
if (path === 'channels') {
|
||||
return {type: DeepLink.Channel, url: deepLinkUrl, data: {serverUrl, teamName, channelName: identifier}};
|
||||
return {type: DeepLink.Channel, url: deepLinkUrl, data: {serverUrl: serverUrl.join('/'), teamName, channelName: identifier}};
|
||||
}
|
||||
|
||||
if (path === 'messages') {
|
||||
if (identifier.startsWith('@')) {
|
||||
return {type: DeepLink.DirectMessage, url: deepLinkUrl, data: {serverUrl, teamName, userName: identifier.substring(1)}};
|
||||
return {type: DeepLink.DirectMessage, url: deepLinkUrl, data: {serverUrl: serverUrl.join('/'), teamName, userName: identifier.substring(1)}};
|
||||
}
|
||||
|
||||
return {type: DeepLink.GroupMessage, url: deepLinkUrl, data: {serverUrl, teamName, channelName: identifier}};
|
||||
return {type: DeepLink.GroupMessage, url: deepLinkUrl, data: {serverUrl: serverUrl.join('/'), teamName, channelName: identifier}};
|
||||
}
|
||||
}
|
||||
|
||||
const permalinkMatch = matchPermalinkDeeplink(url);
|
||||
if (permalinkMatch) {
|
||||
if (permalinkMatch && isValidTeamName(permalinkMatch.params.teamName) && isValidPostId(permalinkMatch.params.postId)) {
|
||||
const {params: {serverUrl, teamName, postId}} = permalinkMatch;
|
||||
return {type: DeepLink.Permalink, url: deepLinkUrl, data: {serverUrl, teamName, postId}};
|
||||
return {type: DeepLink.Permalink, url: deepLinkUrl, data: {serverUrl: serverUrl.join('/'), teamName, postId}};
|
||||
}
|
||||
|
||||
if (asServer) {
|
||||
|
|
|
|||
|
|
@ -16,17 +16,17 @@ GEM
|
|||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.966.0)
|
||||
aws-sdk-core (3.201.5)
|
||||
aws-partitions (1.973.0)
|
||||
aws-sdk-core (3.204.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.88.0)
|
||||
aws-sdk-core (~> 3, >= 3.201.0)
|
||||
aws-sdk-kms (1.90.0)
|
||||
aws-sdk-core (~> 3, >= 3.203.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.159.0)
|
||||
aws-sdk-core (~> 3, >= 3.201.0)
|
||||
aws-sdk-s3 (1.161.0)
|
||||
aws-sdk-core (~> 3, >= 3.203.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.9.1)
|
||||
|
|
@ -188,8 +188,7 @@ GEM
|
|||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.3.5)
|
||||
strscan
|
||||
rexml (3.3.7)
|
||||
rouge (2.0.7)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
|
|
@ -202,7 +201,6 @@ GEM
|
|||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
strscan (3.1.0)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
|
|
|
|||
|
|
@ -69,6 +69,10 @@ class RNUtilsModuleImpl(private val reactContext: ReactApplicationContext) {
|
|||
return SplitView.isRunningInSplitView()
|
||||
}
|
||||
|
||||
fun getWindowDimensions(): WritableMap? {
|
||||
return SplitView.getWindowDimensions()
|
||||
}
|
||||
|
||||
fun unlockOrientation() {}
|
||||
|
||||
fun lockPortrait() {}
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@ package com.mattermost.rnutils.enums
|
|||
|
||||
enum class Events(val event: String) {
|
||||
SAVE_ERROR_EVENT("SaveError"),
|
||||
SPLIT_VIEW_CHANGED("SplitViewChanged")
|
||||
SPLIT_VIEW_CHANGED("SplitViewChanged"),
|
||||
DIMENSIONS_CHANGED("DimensionsChanged")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.mattermost.rnutils.helpers
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Rect
|
||||
import androidx.window.core.layout.WindowHeightSizeClass
|
||||
import androidx.window.core.layout.WindowSizeClass
|
||||
import androidx.window.core.layout.WindowWidthSizeClass
|
||||
|
|
@ -19,6 +20,7 @@ class FoldableObserver(activity: Activity) {
|
|||
private lateinit var observable: Observable<WindowLayoutInfo>
|
||||
var isDeviceFolded: Boolean = false
|
||||
private val activityRef = WeakReference(activity)
|
||||
private var windowBounds: Rect? = null
|
||||
|
||||
companion object {
|
||||
private var instance: FoldableObserver? = null
|
||||
|
|
@ -43,6 +45,7 @@ class FoldableObserver(activity: Activity) {
|
|||
val activity = activityRef.get() ?: return
|
||||
observable = WindowInfoTracker.getOrCreate(activity)
|
||||
.windowLayoutInfoObservable(activity)
|
||||
this.windowBounds = getWindowSize()
|
||||
}
|
||||
|
||||
fun onStart() {
|
||||
|
|
@ -52,6 +55,7 @@ class FoldableObserver(activity: Activity) {
|
|||
disposable = observable.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { layoutInfo ->
|
||||
setIsDeviceFolded(layoutInfo)
|
||||
handleWindowLayoutInfo(layoutInfo)
|
||||
SplitView.setDeviceFolded()
|
||||
}
|
||||
}
|
||||
|
|
@ -78,11 +82,43 @@ class FoldableObserver(activity: Activity) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleWindowLayoutInfo(windowLayoutInfo: WindowLayoutInfo) {
|
||||
val bounds = getWindowSize()
|
||||
|
||||
if (bounds?.width() != windowBounds?.width()) {
|
||||
// emit the dimensions changed event
|
||||
windowBounds = bounds
|
||||
SplitView.emitDimensionsChanged()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getWindowSize(): Rect? {
|
||||
val activity = activityRef.get() ?: return null
|
||||
val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity)
|
||||
val bounds = metrics.bounds
|
||||
val widthPx = bounds.width()
|
||||
val heightPx = bounds.height()
|
||||
|
||||
// Get the screen density (scale factor)
|
||||
val displayMetrics = activity.resources.displayMetrics
|
||||
val density = displayMetrics.density
|
||||
|
||||
// Adjust dimensions based on the scale factor (density)
|
||||
val widthDp = (widthPx / density).toInt()
|
||||
val heightDp = (heightPx / density).toInt()
|
||||
return Rect(0, 0, widthDp, heightDp)
|
||||
}
|
||||
|
||||
fun getWindowDimensions(): Rect? {
|
||||
return this.windowBounds;
|
||||
}
|
||||
|
||||
fun isCompactView(): Boolean {
|
||||
val activity = activityRef.get() ?: return false
|
||||
val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity)
|
||||
val width = metrics.bounds.width()
|
||||
val height = metrics.bounds.height()
|
||||
val bounds = getWindowSize()
|
||||
val width = bounds?.width() ?: 0
|
||||
val height = bounds?.height() ?: 0
|
||||
val density = activity.resources.displayMetrics.density
|
||||
val windowSizeClass = WindowSizeClass.compute(width / density, height / density)
|
||||
val widthWindowSizeClass = windowSizeClass.windowWidthSizeClass
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ class SplitView {
|
|||
return isLargeScreen || isSmallestWidthLarge || isNotPhone
|
||||
}
|
||||
|
||||
|
||||
fun setDeviceFolded() {
|
||||
val map = getSplitViewResults(FoldableObserver.getInstance()?.isDeviceFolded == true)
|
||||
RNUtilsModuleImpl.sendJSEvent(Events.SPLIT_VIEW_CHANGED.event, map)
|
||||
|
|
@ -57,5 +56,28 @@ class SplitView {
|
|||
fun isRunningInSplitView(): WritableMap? {
|
||||
return getSplitViewResults(FoldableObserver.getInstance()?.isDeviceFolded == true)
|
||||
}
|
||||
|
||||
fun getWindowDimensions(): WritableMap? {
|
||||
if (context?.currentActivity != null) {
|
||||
val map = Arguments.createMap()
|
||||
val bounds = FoldableObserver.getInstance()?.getWindowDimensions()
|
||||
if (bounds != null) {
|
||||
map.putInt("width", bounds.width())
|
||||
map.putInt("height", bounds.height())
|
||||
} else {
|
||||
map.putInt("width", 0)
|
||||
map.putInt("height", 0)
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun emitDimensionsChanged() {
|
||||
val map = getWindowDimensions()
|
||||
RNUtilsModuleImpl.sendJSEvent(Events.DIMENSIONS_CHANGED.event, map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ class RNUtilsModule(val reactContext: ReactApplicationContext) : NativeRNUtilsSp
|
|||
|
||||
override fun isRunningInSplitView(): WritableMap? = implementation.isRunningInSplitView()
|
||||
|
||||
override fun getWindowDimensions(): WritableMap? = implementation.getWindowDimensions()
|
||||
|
||||
override fun unlockOrientation() {
|
||||
implementation.unlockOrientation()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ class RNUtilsModule(context: ReactApplicationContext) :
|
|||
return implementation.isRunningInSplitView()
|
||||
}
|
||||
|
||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||
fun getWindowDimensions(): WritableMap? {
|
||||
return implementation.getWindowDimensions()
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun unlockOrientation() {
|
||||
implementation.unlockOrientation()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import Foundation
|
|||
extension RNUtilsWrapper {
|
||||
enum Event: String, CaseIterable {
|
||||
case SplitViewChanged
|
||||
case DimensionsChanged
|
||||
}
|
||||
|
||||
@objc
|
||||
|
|
|
|||
|
|
@ -72,6 +72,10 @@ RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(isRunningInSplitView, NSDictionary*, isSpl
|
|||
return [wrapper isRunningInSplitView];
|
||||
}
|
||||
|
||||
RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(getWindowDimensions, NSDictionary*, windowDimensions) {
|
||||
return [wrapper getWindowDimensions];
|
||||
}
|
||||
|
||||
RCT_REMAP_METHOD(unlockOrientation, unlock) {
|
||||
[wrapper unlockOrientation];
|
||||
}
|
||||
|
|
@ -158,6 +162,9 @@ RCT_EXPORT_METHOD(saveFile:(NSString *)filePath
|
|||
return [wrapper isRunningInSplitView];
|
||||
}
|
||||
|
||||
- (NSDictionary *)getWindowDimensions {
|
||||
return [wrapper getWindowDimensions];
|
||||
}
|
||||
|
||||
- (void)lockPortrait {
|
||||
[wrapper lockOrientation];
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ import React
|
|||
return sharedDirectory.appendingPathComponent("databases").path
|
||||
}
|
||||
|
||||
func getWindowSize() -> (CGSize?, CGSize?) {
|
||||
guard let w = UIApplication.shared.delegate?.window, let window = w else { return (nil, nil) }
|
||||
return (window.screen.bounds.size, window.frame.size)
|
||||
}
|
||||
|
||||
func isRunningInFullScreen() -> Bool {
|
||||
guard let w = UIApplication.shared.delegate?.window, let window = w else { return false }
|
||||
let screenSize = window.screen.bounds.size.width
|
||||
|
|
@ -30,16 +35,37 @@ import React
|
|||
return shouldBeConsideredFullScreen
|
||||
}
|
||||
|
||||
@objc public func captureEvents() {
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(isSplitView), name: NSNotification.Name.RCTUserInterfaceStyleDidChange,
|
||||
object: nil)
|
||||
func isRunningInFullScreen(screen: CGSize?, frame: CGSize?) -> Bool {
|
||||
guard let screenSize = screen?.width,
|
||||
let frameSize = frame?.width else {return false}
|
||||
let shouldBeConsideredFullScreen = frameSize >= (screenSize * 0.6)
|
||||
return shouldBeConsideredFullScreen
|
||||
}
|
||||
|
||||
@objc func isSplitView() {
|
||||
@objc public func captureEvents() {
|
||||
DispatchQueue.main.async {
|
||||
guard let w = UIApplication.shared.delegate?.window, let window = w else { return }
|
||||
window.addObserver(self, forKeyPath: "frame", options: .new, context: nil)
|
||||
}
|
||||
}
|
||||
|
||||
override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
if keyPath == "frame" {
|
||||
let (screen, frame) = getWindowSize()
|
||||
guard let screen = screen, let frame = frame else {return}
|
||||
isSplitView(screen: screen, frame: frame)
|
||||
|
||||
delegate?.sendEvent(name: "DimensionsChanged", result: [
|
||||
"width": frame.width,
|
||||
"height": frame.height
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@objc func isSplitView(screen: CGSize, frame: CGSize) {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
delegate?.sendEvent(name: "SplitViewChanged", result: [
|
||||
"isSplitView": !isRunningInFullScreen(),
|
||||
"isSplit": !isRunningInFullScreen(screen: screen, frame: frame),
|
||||
"isTablet": UIDevice.current.userInterfaceIdiom == .pad,
|
||||
])
|
||||
}
|
||||
|
|
@ -203,6 +229,28 @@ import React
|
|||
]
|
||||
}
|
||||
|
||||
@objc public func getWindowDimensions() -> Dictionary<String, Any> {
|
||||
let queue = DispatchQueue.main
|
||||
let group = DispatchGroup()
|
||||
var dimensions = [
|
||||
"width": 0.0,
|
||||
"height": 0.0
|
||||
]
|
||||
group.enter()
|
||||
queue.async(group: group) { [weak self] in
|
||||
if let (_, frame) = self?.getWindowSize(),
|
||||
let frame = frame {
|
||||
dimensions = [
|
||||
"width": frame.width,
|
||||
"height": frame.height
|
||||
]
|
||||
}
|
||||
group.leave()
|
||||
}
|
||||
group.wait()
|
||||
return dimensions
|
||||
}
|
||||
|
||||
@objc public func unlockOrientation() {
|
||||
DispatchQueue.main.async {
|
||||
OrientationManager.shared.unlockOrientation()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import {type TurboModule, TurboModuleRegistry} from 'react-native';
|
||||
|
||||
import type {Int32, UnsafeObject} from 'react-native/Libraries/Types/CodegenTypes';
|
||||
import type {Double, Int32, UnsafeObject} from 'react-native/Libraries/Types/CodegenTypes';
|
||||
|
||||
export type SplitView = Readonly<{
|
||||
isSplit: boolean;
|
||||
|
|
@ -36,6 +36,11 @@ type Constants = Readonly<{
|
|||
}>;
|
||||
}>
|
||||
|
||||
export type WindowDimensionsChanged = Readonly<{
|
||||
width: Double;
|
||||
height: Double;
|
||||
}>
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
readonly getConstants: () => Constants;
|
||||
|
||||
|
|
@ -45,6 +50,7 @@ export interface Spec extends TurboModule {
|
|||
getRealFilePath: (filePath: string) => Promise<string>;
|
||||
saveFile: (filePath: string) => Promise<string>;
|
||||
|
||||
getWindowDimensions: () => WindowDimensionsChanged;
|
||||
isRunningInSplitView: () => SplitView;
|
||||
unlockOrientation: () => void;
|
||||
lockPortrait: () => void;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
import {NativeModules, Platform} from 'react-native';
|
||||
|
||||
import type {Spec, SplitView} from './NativeRNUtils';
|
||||
import type {Spec, SplitView, WindowDimensionsChanged} from './NativeRNUtils';
|
||||
|
||||
const LINKING_ERROR =
|
||||
'The package \'@mattermost/rnutils\' doesn\'t seem to be linked. Make sure: \n\n' +
|
||||
|
|
@ -26,5 +26,7 @@ const RNUtils = RNUtilsModule || new Proxy(
|
|||
|
||||
export type SplitViewResult = SplitView;
|
||||
|
||||
export type WindowDimensions = WindowDimensionsChanged;
|
||||
|
||||
export default RNUtils;
|
||||
|
||||
|
|
|
|||
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -62,7 +62,7 @@
|
|||
"moment-timezone": "0.5.45",
|
||||
"node-html-parser": "6.1.13",
|
||||
"pako": "2.1.0",
|
||||
"path-to-regexp": "7.1.0",
|
||||
"path-to-regexp": "8.1.0",
|
||||
"react": "18.2.0",
|
||||
"react-freeze": "1.0.4",
|
||||
"react-intl": "6.6.8",
|
||||
|
|
@ -22634,9 +22634,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-7.1.0.tgz",
|
||||
"integrity": "sha512-ZToe+MbUF4lBqk6dV8GKot4DKfzrxXsplOddH8zN3YK+qw9/McvP7+4ICjZvOne0jQhN4eJwHsX6tT0Ns19fvw==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.1.0.tgz",
|
||||
"integrity": "sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
"moment-timezone": "0.5.45",
|
||||
"node-html-parser": "6.1.13",
|
||||
"pako": "2.1.0",
|
||||
"path-to-regexp": "7.1.0",
|
||||
"path-to-regexp": "8.1.0",
|
||||
"react": "18.2.0",
|
||||
"react-freeze": "1.0.4",
|
||||
"react-intl": "6.6.8",
|
||||
|
|
|
|||
13
patches/expo-video-thumbnails+8.0.0.patch
Normal file
13
patches/expo-video-thumbnails+8.0.0.patch
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
diff --git a/node_modules/expo-video-thumbnails/ios/VideoThumbnailsModule.swift b/node_modules/expo-video-thumbnails/ios/VideoThumbnailsModule.swift
|
||||
index 2b01a1d..78c7ffa 100644
|
||||
--- a/node_modules/expo-video-thumbnails/ios/VideoThumbnailsModule.swift
|
||||
+++ b/node_modules/expo-video-thumbnails/ios/VideoThumbnailsModule.swift
|
||||
@@ -7,7 +7,7 @@ public class VideoThumbnailsModule: Module {
|
||||
public func definition() -> ModuleDefinition {
|
||||
Name("ExpoVideoThumbnails")
|
||||
|
||||
- AsyncFunction("getThumbnail", getVideoThumbnail).runOnQueue(.main)
|
||||
+ AsyncFunction("getThumbnail", getVideoThumbnail).runOnQueue(.global())
|
||||
}
|
||||
|
||||
internal func getVideoThumbnail(sourceFilename: URL, options: VideoThumbnailsOptions) throws -> [String: Any] {
|
||||
Loading…
Reference in a new issue