Skip to content

Commit

Permalink
Extra keyboard (#8348)
Browse files Browse the repository at this point in the history
* Add support to use the keyboard area with a component

* fix import

* add missing providers to involved screens

* Change the way we handle the keyboard to allow using custom components in that area

* review feedback
  • Loading branch information
enahum authored Nov 29, 2024
1 parent 732b17a commit d25c6fe
Show file tree
Hide file tree
Showing 41 changed files with 427 additions and 1,658 deletions.
47 changes: 7 additions & 40 deletions app/components/post_draft/post_draft.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import KeyboardTrackingView, {type KeyboardTrackingViewRef} from '@mattermost/keyboard-tracker';
import React, {type RefObject, useEffect, useState} from 'react';
import {Platform} from 'react-native';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import React, {useEffect, useState} from 'react';

import Autocomplete from '@components/autocomplete';
import {View as ViewConstants} from '@constants';
import {ExtraKeyboard} from '@context/extra_keyboard';
import {useServerUrl} from '@context/server';
import {useAutocompleteDefaultAnimatedValues} from '@hooks/autocomplete';
import {useIsTablet, useKeyboardHeight} from '@hooks/device';
import {useKeyboardHeight} from '@hooks/device';
import {useDefaultHeaderHeight} from '@hooks/header';

import Archived from './archived';
Expand All @@ -20,7 +17,6 @@ import ReadOnly from './read_only';
const AUTOCOMPLETE_ADJUST = -5;
type Props = {
testID?: string;
accessoriesContainerID?: string;
canPost: boolean;
channelId: string;
channelIsArchived?: boolean;
Expand All @@ -30,18 +26,13 @@ type Props = {
isSearch?: boolean;
message?: string;
rootId?: string;
scrollViewNativeID?: string;
keyboardTracker: RefObject<KeyboardTrackingViewRef>;
containerHeight: number;
isChannelScreen: boolean;
canShowPostPriority?: boolean;
}

const {KEYBOARD_TRACKING_OFFSET} = ViewConstants;

function PostDraft({
testID,
accessoriesContainerID,
canPost,
channelId,
channelIsArchived,
Expand All @@ -51,8 +42,6 @@ function PostDraft({
isSearch,
message = '',
rootId = '',
scrollViewNativeID,
keyboardTracker,
containerHeight,
isChannelScreen,
canShowPostPriority,
Expand All @@ -61,9 +50,7 @@ function PostDraft({
const [cursorPosition, setCursorPosition] = useState(message.length);
const [postInputTop, setPostInputTop] = useState(0);
const [isFocused, setIsFocused] = useState(false);
const isTablet = useIsTablet();
const keyboardHeight = useKeyboardHeight(keyboardTracker);
const insets = useSafeAreaInsets();
const keyboardHeight = useKeyboardHeight();
const headerHeight = useDefaultHeaderHeight();
const serverUrl = useServerUrl();

Expand All @@ -73,12 +60,7 @@ function PostDraft({
setCursorPosition(message.length);
}, [channelId, rootId]);

const keyboardAdjustment = (isTablet && isChannelScreen) ? KEYBOARD_TRACKING_OFFSET : 0;
const insetsAdjustment = (isTablet && isChannelScreen) ? 0 : insets.bottom;
const autocompletePosition = AUTOCOMPLETE_ADJUST + Platform.select({
ios: (keyboardHeight ? keyboardHeight - keyboardAdjustment : (postInputTop + insetsAdjustment)),
default: postInputTop + insetsAdjustment,
});
const autocompletePosition = AUTOCOMPLETE_ADJUST + keyboardHeight + postInputTop;
const autocompleteAvailableSpace = containerHeight - autocompletePosition - (isChannelScreen ? headerHeight : 0);

const [animatedAutocompletePosition, animatedAutocompleteAvailableSpace] = useAutocompleteDefaultAnimatedValues(autocompletePosition, autocompleteAvailableSpace);
Expand Down Expand Up @@ -136,26 +118,11 @@ function PostDraft({
/>
) : null;

if (Platform.OS === 'android') {
return (
<>
{draftHandler}
{autoComplete}
</>
);
}

return (
<>
<KeyboardTrackingView
accessoriesContainerID={accessoriesContainerID}
ref={keyboardTracker}
scrollViewNativeID={scrollViewNativeID}
viewInitialOffsetY={isTablet && !rootId ? KEYBOARD_TRACKING_OFFSET : 0}
>
{draftHandler}
</KeyboardTrackingView>
{draftHandler}
{autoComplete}
<ExtraKeyboard/>
</>
);
}
Expand Down
8 changes: 6 additions & 2 deletions app/components/post_draft/post_input/post_input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import {updateDraftMessage} from '@actions/local/draft';
import {userTyping} from '@actions/websocket/users';
import {Events, Screens} from '@constants';
import {useExtraKeyboardContext} from '@context/extra_keyboard';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
Expand Down Expand Up @@ -121,6 +122,7 @@ export default function PostInput({
const style = getStyleSheet(theme);
const serverUrl = useServerUrl();
const managedConfig = useManagedConfig<ManagedConfig>();
const keyboardContext = useExtraKeyboardContext();
const [propagateValue, shouldProcessEvent] = useInputPropagation();

const lastTypingEventSent = useRef(0);
Expand All @@ -145,13 +147,15 @@ export default function PostInput({
};

const onBlur = useCallback(() => {
keyboardContext?.registerTextInputBlur();
updateDraftMessage(serverUrl, channelId, rootId, value);
setIsFocused(false);
}, [channelId, rootId, value, setIsFocused]);
}, [keyboardContext, serverUrl, channelId, rootId, value, setIsFocused]);

const onFocus = useCallback(() => {
keyboardContext?.registerTextInputFocus();
setIsFocused(true);
}, [setIsFocused]);
}, [setIsFocused, keyboardContext]);

const checkMessageLength = useCallback((newValue: string) => {
const valueLength = newValue.trim().length;
Expand Down
25 changes: 15 additions & 10 deletions app/components/post_list/post/post.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React, {type ReactNode, useEffect, useMemo, useRef, useState} from 'react';
import React, {type ReactNode, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import {Keyboard, Platform, type StyleProp, View, type ViewStyle, TouchableHighlight} from 'react-native';

Expand All @@ -14,14 +14,14 @@ import SystemAvatar from '@components/system_avatar';
import SystemHeader from '@components/system_header';
import {POST_TIME_TO_FAIL} from '@constants/post';
import * as Screens from '@constants/screens';
import {useHideExtraKeyboardIfNeeded} from '@context/extra_keyboard';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
import PerformanceMetricsManager from '@managers/performance_metrics_manager';
import {openAsBottomSheet} from '@screens/navigation';
import {hasJumboEmojiOnly} from '@utils/emoji/helpers';
import {fromAutoResponder, isFromWebhook, isPostFailed, isPostPendingOrFailed, isSystemMessage} from '@utils/post';
import {preventDoubleTap} from '@utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';

import Avatar from './avatar';
Expand Down Expand Up @@ -176,7 +176,7 @@ const Post = ({
return false;
}, [customEmojiNames, post.message]);

const handlePostPress = () => {
const handlePostPress = useCallback(() => {
if ([Screens.SAVED_MESSAGES, Screens.MENTIONS, Screens.SEARCH, Screens.PINNED_MESSAGES].includes(location)) {
showPermalink(serverUrl, '', post.id);
return;
Expand All @@ -195,19 +195,20 @@ const Post = ({
setTimeout(() => {
pressDetected.current = false;
}, 300);
};
}, [
hasBeenDeleted, isAutoResponder, isEphemeral,
isPendingOrFailed, isSystemPost, location, serverUrl, post,
]);

const handlePress = preventDoubleTap(() => {
const handlePress = useHideExtraKeyboardIfNeeded(() => {
pressDetected.current = true;

if (post) {
Keyboard.dismiss();

setTimeout(handlePostPress, 300);
}
});
}, [handlePostPress, post]);

const showPostOptions = () => {
const showPostOptions = useHideExtraKeyboardIfNeeded(() => {
if (!post) {
return;
}
Expand All @@ -231,7 +232,11 @@ const Post = ({
title,
props: passProps,
});
};
}, [
canDelete, hasBeenDeleted, intl,
isEphemeral, isPendingOrFailed, isTablet, isSystemPost,
location, post, serverUrl, showAddReaction, theme,
]);

const [, rerender] = useState(false);
useEffect(() => {
Expand Down
3 changes: 0 additions & 3 deletions app/constants/post_draft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,10 @@ 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 TYPING_HEIGHT = 16;
export const ACCESSORIES_CONTAINER_NATIVE_ID = 'channelAccessoriesContainer';
export const THREAD_ACCESSORIES_CONTAINER_NATIVE_ID = 'threadAccessoriesContainer';

export const NOTIFY_ALL_MEMBERS = 5;

export default {
ACCESSORIES_CONTAINER_NATIVE_ID,
DEFAULT_SERVER_MAX_FILE_SIZE,
ICON_SIZE,
MAX_MESSAGE_LENGTH_FALLBACK,
Expand Down
Loading

0 comments on commit d25c6fe

Please sign in to comment.