forked from Ivasoft/mattermost-mobile
iPad: enable rotation in all directions (#7007)
* iPad: enable rotation in all directions * feedback review
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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]}
|
||||
|
||||
@@ -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"})}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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'})}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user