iPad: enable rotation in all directions (#7007)

* iPad: enable rotation in all directions

* feedback review
This commit is contained in:
Elias Nahum
2023-01-24 21:48:37 +02:00
committed by GitHub
parent 24bb5f9b25
commit 15e75ac24b
7 changed files with 146 additions and 39 deletions

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect, useState} from 'react';
import React, {useCallback} from 'react';
import {Modal, StyleSheet, useWindowDimensions, View} from 'react-native';
import HighlightItem from './item';
@@ -11,45 +11,43 @@ type Props = {
itemBounds: TutorialItemBounds;
itemBorderRadius?: number;
onDismiss: () => void;
onLayout: () => void;
onShow?: () => void;
}
const TutorialHighlight = ({children, itemBounds, itemBorderRadius, onDismiss, onShow}: Props) => {
const TutorialHighlight = ({children, itemBounds, itemBorderRadius, onDismiss, onLayout, onShow}: Props) => {
const {width, height} = useWindowDimensions();
const [visible, setIsVisible] = useState(false);
const isLandscape = width > height;
const supportedOrientations = isLandscape ? 'landscape' : 'portrait';
useEffect(() => {
const t = setTimeout(() => {
setIsVisible(true);
}, 500);
return () => clearTimeout(t);
}, []);
const handleShowTutorial = useCallback(() => {
if (onShow) {
setTimeout(onShow, 1000);
}
}, [itemBounds]);
return (
<Modal
visible={visible}
visible={true}
transparent={true}
animationType='fade'
onShow={onShow}
onDismiss={onDismiss}
onRequestClose={onDismiss}
supportedOrientations={[supportedOrientations]}
testID='tutorial_highlight'
>
<View
style={StyleSheet.absoluteFill}
pointerEvents='box-none'
onLayout={onLayout}
/>
{itemBounds.endX > 0 &&
<HighlightItem
borderRadius={itemBorderRadius}
itemBounds={itemBounds}
height={height}
onDismiss={onDismiss}
width={width}
onLayout={handleShowTutorial}
/>
}
{children}
</Modal>
);

View File

@@ -14,22 +14,24 @@ type Props = {
height: number;
itemBounds: TutorialItemBounds;
onDismiss: () => void;
onLayout: () => void;
width: number;
}
const HighlightItem = ({height, itemBounds, onDismiss, borderRadius = 0, width}: Props) => {
const HighlightItem = ({height, itemBounds, onDismiss, onLayout, borderRadius = 0, width}: Props) => {
const theme = useTheme();
const isDark = tinyColor(theme.centerChannelBg).isDark();
const pathD = useMemo(() => {
const parent = {startX: 0, startY: 0, endX: width, endY: height};
return constructRectangularPathWithBorderRadius(parent, itemBounds, borderRadius);
}, [borderRadius, itemBounds, width]);
}, [borderRadius, itemBounds, width, height]);
return (
<Svg
style={StyleSheet.absoluteFill}
onPress={onDismiss}
onLayout={onLayout}
>
<G>
<Defs>

View File

@@ -538,6 +538,86 @@ exports[`components/channel_list_row should show results and tutorial 1`] = `
</View>
</View>
</View>
<Modal
animationType="fade"
hardwareAccelerated={false}
onDismiss={[Function]}
onRequestClose={[Function]}
testID="tutorial_highlight"
transparent={true}
visible={true}
>
<View
onLayout={[Function]}
pointerEvents="box-none"
style={
{
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
<View
pointerEvents="none"
style={
[
{
"alignItems": "center",
"bottom": 0,
"justifyContent": "center",
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
},
undefined,
]
}
testID="tutorial_swipe_left"
>
<View
style={
[
{
"alignItems": "center",
"backgroundColor": "#ffffff",
"borderRadius": 8,
"height": 161,
"padding": 16,
"width": 247,
},
{
"top": -74,
},
]
}
>
<long_press_illustration.svg />
<Text
style={
[
{
"color": "#3f4350",
"fontFamily": "Metropolis-SemiBold",
"fontSize": 16,
"fontWeight": "600",
"lineHeight": 24,
"marginTop": 8,
"paddingHorizontal": 12,
"textAlign": "center",
},
undefined,
]
}
>
Long-press on an item to view a user's profile
</Text>
</View>
</View>
</Modal>
</View>
<View
onFocusCapture={[Function]}

View File

@@ -4,6 +4,7 @@
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import {
InteractionManager,
Platform,
Text,
View,
@@ -122,13 +123,12 @@ function UserListRow({
const startTutorial = () => {
viewRef.current?.measureInWindow((x, y, w, h) => {
const bounds: TutorialItemBounds = {
startX: x - 20,
startX: x,
startY: y,
endX: x + w,
endY: y + h,
};
if (viewRef.current) {
setShowTutorial(true);
setItemBounds(bounds);
}
});
@@ -140,12 +140,16 @@ function UserListRow({
}, []);
useEffect(() => {
let time: NodeJS.Timeout;
if (highlight && !tutorialWatched) {
time = setTimeout(startTutorial, 650);
if (isTablet) {
setShowTutorial(true);
return;
}
InteractionManager.runAfterInteractions(() => {
setShowTutorial(true);
});
}
return () => clearTimeout(time);
}, [highlight, tutorialWatched]);
}, [highlight, tutorialWatched, isTablet]);
const handlePress = useCallback(() => {
onPress?.(user);
@@ -155,6 +159,10 @@ function UserListRow({
onLongPress?.(user);
}, [onLongPress, user]);
const onLayout = useCallback(() => {
startTutorial();
}, []);
const icon = useMemo(() => {
if (!selectable) {
return null;
@@ -256,6 +264,7 @@ function UserListRow({
<TutorialHighlight
itemBounds={itemBounds}
onDismiss={handleDismissTutorial}
onLayout={onLayout}
>
<TutorialLongPress
message={intl.formatMessage({id: 'user.tutorial.long_press', defaultMessage: "Long-press on an item to view a user's profile"})}

View File

@@ -96,6 +96,10 @@ const BottomSheet = ({
const styles = getStyleSheet(theme);
const interaction = useRef<Handle>();
useEffect(() => {
interaction.current = InteractionManager.createInteractionHandle();
}, []);
const bottomSheetBackgroundStyle = useMemo(() => [
styles.bottomSheetBackground,
{borderWidth: isTablet ? 0 : 1},
@@ -118,7 +122,9 @@ const BottomSheet = ({
}, [close]);
const handleAnimationStart = useCallback(() => {
interaction.current = InteractionManager.createInteractionHandle();
if (!interaction.current) {
interaction.current = InteractionManager.createInteractionHandle();
}
}, []);
const handleClose = useCallback(() => {

View File

@@ -3,7 +3,7 @@
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import {Animated, DeviceEventEmitter, Platform, StyleProp, Text, View, ViewStyle} from 'react-native';
import {Animated, DeviceEventEmitter, InteractionManager, Platform, StyleProp, Text, View, ViewStyle} from 'react-native';
import {RectButton} from 'react-native-gesture-handler';
import Swipeable from 'react-native-gesture-handler/Swipeable';
import {Navigation} from 'react-native-navigation';
@@ -200,19 +200,23 @@ const ServerItem = ({
const startTutorial = () => {
viewRef.current?.measureInWindow((x, y, w, h) => {
const bounds: TutorialItemBounds = {
startX: x - 20,
startX: x,
startY: y,
endX: x + w + 20,
endX: x + w,
endY: y + h,
};
if (viewRef.current) {
setShowTutorial(true);
setItemBounds(bounds);
}
});
};
const onLayout = useCallback(() => {
swipeable.current?.close();
startTutorial();
}, []);
const containerStyle = useMemo(() => {
const style: StyleProp<ViewStyle> = [styles.container];
if (isActive) {
@@ -344,12 +348,16 @@ const ServerItem = ({
}, [server.lastActiveAt, isActive]);
useEffect(() => {
let time: NodeJS.Timeout;
if (highlight && !tutorialWatched) {
time = setTimeout(startTutorial, 650);
if (isTablet) {
setShowTutorial(true);
return;
}
InteractionManager.runAfterInteractions(() => {
setShowTutorial(true);
});
}
return () => clearTimeout(time);
}, [highlight, tutorialWatched]);
}, [highlight, tutorialWatched, isTablet]);
const serverItem = `server_list.server_item.${server.displayName.replace(/ /g, '_').toLocaleLowerCase()}`;
const serverItemTestId = isActive ? `${serverItem}.active` : `${serverItem}.inactive`;
@@ -466,6 +474,8 @@ const ServerItem = ({
itemBounds={itemBounds}
onDismiss={handleDismissTutorial}
onShow={handleShowTutorial}
onLayout={onLayout}
itemBorderRadius={8}
>
<TutorialSwipeLeft
message={intl.formatMessage({id: 'server.tutorial.swipe', defaultMessage: 'Swipe left on a server to see more actions'})}

View File

@@ -83,6 +83,10 @@
<string>Upload photos and videos from your device to $(PRODUCT_NAME)</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Send voice messages to $(PRODUCT_NAME)</string>
<key>NSUserActivityTypes</key>
<array>
<string>INSendMessageIntent</string>
</array>
<key>UIAppFonts</key>
<array>
<string>OpenSans-Bold.ttf</string>
@@ -113,23 +117,21 @@
<string>armv7</string>
</array>
<key>UIRequiresFullScreen</key>
<true/>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>NSUserActivityTypes</key>
<array>
<string>INSendMessageIntent</string>
</array>
</dict>
</plist>