forked from Ivasoft/mattermost-mobile
[Keyboard] Keyboard tracking (#6050)
* Pause/Resume tracking keyboard * fix keyboard tracking view on tablets * add EDIT_POST screen to pause keyboard tracking
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import {DeviceEventEmitter, Platform, View} from 'react-native';
|
||||
import React, {RefObject, useState} from 'react';
|
||||
import {Platform, useWindowDimensions, View} from 'react-native';
|
||||
import {KeyboardTrackingView, KeyboardTrackingViewRef} from 'react-native-keyboard-tracking-view';
|
||||
|
||||
import Autocomplete from '@components/autocomplete';
|
||||
import {PostDraft as PostDraftConstants, View as ViewConstants} from '@constants';
|
||||
import {View as ViewConstants} from '@constants';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
|
||||
import Archived from './archived';
|
||||
@@ -26,9 +26,16 @@ type Props = {
|
||||
message?: string;
|
||||
rootId?: string;
|
||||
scrollViewNativeID?: string;
|
||||
keyboardTracker: RefObject<KeyboardTrackingViewRef>;
|
||||
}
|
||||
|
||||
export default function PostDraft({
|
||||
const {
|
||||
KEYBOARD_TRACKING_OFFSET,
|
||||
KEYBOARD_TRACKING_OFFSET_MODAL_LANDSCAPE,
|
||||
KEYBOARD_TRACKING_OFFSET_MODAL_PORTRAIT,
|
||||
} = ViewConstants;
|
||||
|
||||
function PostDraft({
|
||||
testID,
|
||||
accessoriesContainerID,
|
||||
canPost,
|
||||
@@ -41,35 +48,14 @@ export default function PostDraft({
|
||||
message = '',
|
||||
rootId = '',
|
||||
scrollViewNativeID,
|
||||
keyboardTracker,
|
||||
}: Props) {
|
||||
const keyboardTracker = useRef<KeyboardTrackingViewRef>(null);
|
||||
const resetScrollViewAnimationFrame = useRef<number>();
|
||||
const [value, setValue] = useState(message);
|
||||
const [cursorPosition, setCursorPosition] = useState(message.length);
|
||||
const [postInputTop, setPostInputTop] = useState(0);
|
||||
const isTablet = useIsTablet();
|
||||
|
||||
const updateNativeScrollView = useCallback((scrollViewNativeIDToUpdate: string) => {
|
||||
if (keyboardTracker?.current && scrollViewNativeID === scrollViewNativeIDToUpdate) {
|
||||
resetScrollViewAnimationFrame.current = requestAnimationFrame(() => {
|
||||
keyboardTracker.current?.resetScrollView(scrollViewNativeIDToUpdate);
|
||||
if (resetScrollViewAnimationFrame.current != null) {
|
||||
cancelAnimationFrame(resetScrollViewAnimationFrame.current);
|
||||
}
|
||||
resetScrollViewAnimationFrame.current = undefined;
|
||||
});
|
||||
}
|
||||
}, [scrollViewNativeID]);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = DeviceEventEmitter.addListener(PostDraftConstants.UPDATE_NATIVE_SCROLLVIEW, updateNativeScrollView);
|
||||
return () => {
|
||||
listener.remove();
|
||||
if (resetScrollViewAnimationFrame.current) {
|
||||
cancelAnimationFrame(resetScrollViewAnimationFrame.current);
|
||||
}
|
||||
};
|
||||
}, [updateNativeScrollView]);
|
||||
const dimensions = useWindowDimensions();
|
||||
const isLandscape = dimensions.width > dimensions.height;
|
||||
|
||||
if (channelIsArchived || deactivatedChannel) {
|
||||
const archivedTestID = `${testID}.archived`;
|
||||
@@ -128,13 +114,18 @@ export default function PostDraft({
|
||||
);
|
||||
}
|
||||
|
||||
let viewInitialOffsetY = isTablet ? KEYBOARD_TRACKING_OFFSET : 0;
|
||||
if (isTablet && rootId) {
|
||||
viewInitialOffsetY = isLandscape ? KEYBOARD_TRACKING_OFFSET_MODAL_LANDSCAPE : KEYBOARD_TRACKING_OFFSET_MODAL_PORTRAIT;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<KeyboardTrackingView
|
||||
accessoriesContainerID={accessoriesContainerID}
|
||||
ref={keyboardTracker}
|
||||
scrollViewNativeID={scrollViewNativeID}
|
||||
viewInitialOffsetY={isTablet ? ViewConstants.BOTTOM_TAB_HEIGHT : 0}
|
||||
viewInitialOffsetY={viewInitialOffsetY}
|
||||
>
|
||||
{draftHandler}
|
||||
</KeyboardTrackingView>
|
||||
@@ -144,3 +135,5 @@ export default function PostDraft({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PostDraft;
|
||||
|
||||
@@ -14,8 +14,10 @@ export default keyMirror({
|
||||
LEAVE_TEAM: null,
|
||||
LOADING_CHANNEL_POSTS: null,
|
||||
NOTIFICATION_ERROR: null,
|
||||
PAUSE_KEYBOARD_TRACKING_VIEW: null,
|
||||
SERVER_LOGOUT: null,
|
||||
SERVER_VERSION_CHANGED: null,
|
||||
TAB_BAR_VISIBLE: null,
|
||||
TEAM_LOAD_ERROR: null,
|
||||
USER_TYPING: null,
|
||||
USER_STOP_TYPING: null,
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
export const MAX_MESSAGE_LENGTH_FALLBACK = 4000;
|
||||
export const DEFAULT_SERVER_MAX_FILE_SIZE = 50 * 1024 * 1024;// 50 Mb
|
||||
export const ICON_SIZE = 24;
|
||||
export const UPDATE_NATIVE_SCROLLVIEW = 'onUpdateNativeScrollView';
|
||||
export const TYPING_HEIGHT = 26;
|
||||
export const ACCESSORIES_CONTAINER_NATIVE_ID = 'channelAccessoriesContainer';
|
||||
export const THREAD_ACCESSORIES_CONTAINER_NATIVE_ID = 'threadAccessoriesContainer';
|
||||
@@ -18,5 +17,4 @@ export default {
|
||||
MAX_MESSAGE_LENGTH_FALLBACK,
|
||||
NOTIFY_ALL_MEMBERS,
|
||||
TYPING_HEIGHT,
|
||||
UPDATE_NATIVE_SCROLLVIEW,
|
||||
};
|
||||
|
||||
@@ -21,6 +21,9 @@ export const HEADER_WITH_SUBTITLE = 24;
|
||||
export const IOS_HEADER_SEARCH_INSET = 20;
|
||||
export const TABLET_HEADER_SEARCH_INSET = 28;
|
||||
export const ANDROID_HEADER_SEARCH_INSET = 11;
|
||||
export const KEYBOARD_TRACKING_OFFSET = 72;
|
||||
export const KEYBOARD_TRACKING_OFFSET_MODAL_LANDSCAPE = 44;
|
||||
export const KEYBOARD_TRACKING_OFFSET_MODAL_PORTRAIT = 154;
|
||||
|
||||
export const INDICATOR_BAR_HEIGHT = 38;
|
||||
|
||||
@@ -44,5 +47,8 @@ export default {
|
||||
TABLET_HEADER_SEARCH_INSET,
|
||||
ANDROID_HEADER_SEARCH_INSET,
|
||||
INDICATOR_BAR_HEIGHT,
|
||||
KEYBOARD_TRACKING_OFFSET,
|
||||
KEYBOARD_TRACKING_OFFSET_MODAL_LANDSCAPE,
|
||||
KEYBOARD_TRACKING_OFFSET_MODAL_PORTRAIT,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import React, {useCallback, useEffect, useMemo, useRef} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {DeviceEventEmitter, Keyboard, Platform, StyleSheet, View} from 'react-native';
|
||||
import {KeyboardTrackingViewRef} from 'react-native-keyboard-tracking-view';
|
||||
import {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FreezeScreen from '@components/freeze_screen';
|
||||
import NavigationHeader from '@components/navigation_header';
|
||||
import PostDraft from '@components/post_draft';
|
||||
import {Navigation} from '@constants';
|
||||
import {Events, Navigation} from '@constants';
|
||||
import {ACCESSORIES_CONTAINER_NATIVE_ID} from '@constants/post_draft';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useAppState, useIsTablet} from '@hooks/device';
|
||||
@@ -48,6 +49,7 @@ const Channel = ({channelId, componentId, displayName, isOwnDirectMessage, membe
|
||||
const insets = useSafeAreaInsets();
|
||||
const theme = useTheme();
|
||||
const defaultHeight = useDefaultHeaderHeight();
|
||||
const postDraftRef = useRef<KeyboardTrackingViewRef>(null);
|
||||
const rightButtons: HeaderRightButton[] = useMemo(() => ([{
|
||||
iconName: 'magnify',
|
||||
onPress: () => {
|
||||
@@ -88,6 +90,19 @@ const Channel = ({channelId, componentId, displayName, isOwnDirectMessage, membe
|
||||
console.log('Title Press go to Channel Info', displayName);
|
||||
}, [channelId]);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = DeviceEventEmitter.addListener(Events.PAUSE_KEYBOARD_TRACKING_VIEW, (pause: boolean) => {
|
||||
if (pause) {
|
||||
postDraftRef.current?.pauseTracking(channelId);
|
||||
return;
|
||||
}
|
||||
|
||||
postDraftRef.current?.resumeTracking(channelId);
|
||||
});
|
||||
|
||||
return () => listener.remove();
|
||||
}, []);
|
||||
|
||||
let title = displayName;
|
||||
if (isOwnDirectMessage) {
|
||||
title = formatMessage({id: 'channel_header.directchannel.you', defaultMessage: '{displayName} (you)'}, {displayName});
|
||||
@@ -125,6 +140,7 @@ const Channel = ({channelId, componentId, displayName, isOwnDirectMessage, membe
|
||||
</View>
|
||||
<PostDraft
|
||||
channelId={channelId}
|
||||
keyboardTracker={postDraftRef}
|
||||
scrollViewNativeID={channelId}
|
||||
accessoriesContainerID={ACCESSORIES_CONTAINER_NATIVE_ID}
|
||||
/>
|
||||
|
||||
@@ -7,7 +7,7 @@ import {Shadow} from 'react-native-neomorph-shadows';
|
||||
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {Navigation as NavigationConstants, Screens, View as ViewConstants} from '@constants';
|
||||
import {Events, Navigation as NavigationConstants, Screens, View as ViewConstants} from '@constants';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
@@ -66,7 +66,7 @@ function TabBar({state, descriptors, navigation, theme}: BottomTabBarProps & {th
|
||||
const safeareaInsets = useSafeAreaInsets();
|
||||
|
||||
useEffect(() => {
|
||||
const event = DeviceEventEmitter.addListener('tabBarVisible', (show) => {
|
||||
const event = DeviceEventEmitter.addListener(Events.TAB_BAR_VISIBLE, (show) => {
|
||||
setVisible(show);
|
||||
});
|
||||
|
||||
|
||||
@@ -307,7 +307,7 @@ export function goToScreen(name: string, title: string, passProps = {}, options
|
||||
const theme = getThemeFromState();
|
||||
const isDark = tinyColor(theme.sidebarBg).isDark();
|
||||
const componentId = EphemeralStore.getNavigationTopComponentId();
|
||||
DeviceEventEmitter.emit('tabBarVisible', false);
|
||||
DeviceEventEmitter.emit(Events.TAB_BAR_VISIBLE, false);
|
||||
const defaultOptions: Options = {
|
||||
layout: {
|
||||
componentBackgroundColor: theme.centerChannelBg,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect} from 'react';
|
||||
import React, {useCallback, useEffect, useRef} from 'react';
|
||||
import {BackHandler, StyleSheet, View} from 'react-native';
|
||||
import {KeyboardTrackingViewRef} from 'react-native-keyboard-tracking-view';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
@@ -32,6 +33,7 @@ const getStyleSheet = StyleSheet.create(() => ({
|
||||
const Thread = ({closeButtonId, componentId, rootPost}: ThreadProps) => {
|
||||
const appState = useAppState();
|
||||
const styles = getStyleSheet();
|
||||
const postDraftRef = useRef<KeyboardTrackingViewRef>(null);
|
||||
|
||||
const close = useCallback(() => {
|
||||
dismissModal({componentId});
|
||||
@@ -83,6 +85,7 @@ const Thread = ({closeButtonId, componentId, rootPost}: ThreadProps) => {
|
||||
scrollViewNativeID={rootPost!.id}
|
||||
accessoriesContainerID={THREAD_ACCESSORIES_CONTAINER_NATIVE_ID}
|
||||
rootId={rootPost!.id}
|
||||
keyboardTracker={postDraftRef}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user