diff --git a/app/components/post_draft/post_draft.tsx b/app/components/post_draft/post_draft.tsx index f86e060a1..704c14d05 100644 --- a/app/components/post_draft/post_draft.tsx +++ b/app/components/post_draft/post_draft.tsx @@ -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'; @@ -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; @@ -30,18 +26,13 @@ type Props = { isSearch?: boolean; message?: string; rootId?: string; - scrollViewNativeID?: string; - keyboardTracker: RefObject; containerHeight: number; isChannelScreen: boolean; canShowPostPriority?: boolean; } -const {KEYBOARD_TRACKING_OFFSET} = ViewConstants; - function PostDraft({ testID, - accessoriesContainerID, canPost, channelId, channelIsArchived, @@ -51,8 +42,6 @@ function PostDraft({ isSearch, message = '', rootId = '', - scrollViewNativeID, - keyboardTracker, containerHeight, isChannelScreen, canShowPostPriority, @@ -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(); @@ -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); @@ -136,26 +118,11 @@ function PostDraft({ /> ) : null; - if (Platform.OS === 'android') { - return ( - <> - {draftHandler} - {autoComplete} - - ); - } - return ( <> - - {draftHandler} - + {draftHandler} {autoComplete} + ); } diff --git a/app/components/post_draft/post_input/post_input.tsx b/app/components/post_draft/post_input/post_input.tsx index 756a05e77..ea7208c95 100644 --- a/app/components/post_draft/post_input/post_input.tsx +++ b/app/components/post_draft/post_input/post_input.tsx @@ -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'; @@ -121,6 +122,7 @@ export default function PostInput({ const style = getStyleSheet(theme); const serverUrl = useServerUrl(); const managedConfig = useManagedConfig(); + const keyboardContext = useExtraKeyboardContext(); const [propagateValue, shouldProcessEvent] = useInputPropagation(); const lastTypingEventSent = useRef(0); @@ -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; diff --git a/app/components/post_list/post/post.tsx b/app/components/post_list/post/post.tsx index cd86a79fd..cf42e8794 100644 --- a/app/components/post_list/post/post.tsx +++ b/app/components/post_list/post/post.tsx @@ -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'; @@ -14,6 +14,7 @@ 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'; @@ -21,7 +22,6 @@ 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'; @@ -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; @@ -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; } @@ -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(() => { diff --git a/app/constants/post_draft.ts b/app/constants/post_draft.ts index 2b8b858a1..9a601a8d2 100644 --- a/app/constants/post_draft.ts +++ b/app/constants/post_draft.ts @@ -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, diff --git a/app/context/extra_keyboard/index.tsx b/app/context/extra_keyboard/index.tsx new file mode 100644 index 000000000..cb14db6cc --- /dev/null +++ b/app/context/extra_keyboard/index.tsx @@ -0,0 +1,183 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {createContext, useCallback, useContext, useEffect, useState} from 'react'; +import {Keyboard, Platform} from 'react-native'; +import Animated, {KeyboardState, useAnimatedKeyboard, useAnimatedStyle, useDerivedValue, withTiming} from 'react-native-reanimated'; +import {useSafeAreaInsets} from 'react-native-safe-area-context'; + +import {Screens} from '@constants'; +import {useIsTablet} from '@hooks/device'; +import NavigationStore from '@store/navigation_store'; +import {preventDoubleTap} from '@utils/tap'; + +import type {AvailableScreens} from '@typings/screens/navigation'; + +export type ExtraKeyboardContextProps = { + isExtraKeyboardVisible: boolean; + component: React.ReactElement|null; + showExtraKeyboard: (component: React.ReactElement|null) => void; + hideExtraKeyboard: () => void; + registerTextInputFocus: () => void; + registerTextInputBlur: () => void; +}; + +// This is based on the size of the tab bar +const KEYBOARD_OFFSET = -77; + +export const ExtraKeyboardContext = createContext(undefined); + +const useOffetForCurrentScreen = (): number => { + const [screen, setScreen] = useState(); + const [offset, setOffset] = useState(0); + const isTablet = useIsTablet(); + + useEffect(() => { + const sub = NavigationStore.getSubject(); + const s = sub.subscribe(setScreen); + + return () => s.unsubscribe(); + }, []); + + useEffect(() => { + if (isTablet && screen === Screens.HOME) { + setOffset(KEYBOARD_OFFSET); + } + }, [isTablet, screen]); + + return offset; +}; + +export const ExtraKeyboardProvider = (({children}: {children: React.ReactElement|React.ReactElement[]}) => { + const [isExtraKeyboardVisible, setExtraKeyboardVisible] = useState(false); + const [component, setComponent] = useState(null); + const [isTextInputFocused, setIsTextInputFocused] = useState(false); + + const showExtraKeyboard = useCallback((newComponent: React.ReactElement|null) => { + setExtraKeyboardVisible(true); + setComponent(newComponent); + if (Keyboard.isVisible()) { + Keyboard.dismiss(); + } + }, []); + + const hideExtraKeyboard = useCallback(() => { + setExtraKeyboardVisible(false); + setComponent(null); + if (Keyboard.isVisible()) { + Keyboard.dismiss(); + } + }, []); + + const registerTextInputFocus = useCallback(() => { + // If the extra keyboard is opened if we don't do this + // we get a glitch in the UI that will animate the extra keyboard down + // and immediately bring the keyboard, by doing this + // we delay hidding the extra keyboard, so that there is no animation glitch + setIsTextInputFocused(true); + setTimeout(() => { + setExtraKeyboardVisible(false); + }, 400); + }, []); + + const registerTextInputBlur = useCallback(() => { + setIsTextInputFocused(false); + }, []); + + useEffect(() => { + const keyboardHideListener = Keyboard.addListener('keyboardDidHide', () => { + if (isTextInputFocused) { + setExtraKeyboardVisible(false); + } + }); + + return () => keyboardHideListener.remove(); + }, [isTextInputFocused]); + + return ( + + {children} + + ); +}); + +export const useExtraKeyboardContext = (): ExtraKeyboardContextProps|undefined => { + const context = useContext(ExtraKeyboardContext); + if (!context) { + throw new Error('useExtraKeyboardContext must be used within a ExtraKeyboardProvider'); + } + return context; +}; + +export const useHideExtraKeyboardIfNeeded = (callback: (...args: any) => void, dependencies: React.DependencyList = []) => { + const keyboardContext = useExtraKeyboardContext(); + + return useCallback(preventDoubleTap((...args: any) => { + if (keyboardContext?.isExtraKeyboardVisible) { + keyboardContext.hideExtraKeyboard(); + + /* + /* At this point the early return is commented + /* Based on the UX we actually want to have + /* we can uncoment this and reaturn as early + /* as the custom keyboard is hidden + */ + // return; + } + + if (Keyboard.isVisible()) { + Keyboard.dismiss(); + } + + callback(...args); + }), [keyboardContext, ...dependencies]); +}; + +export const ExtraKeyboard = () => { + const keyb = useAnimatedKeyboard({isStatusBarTranslucentAndroid: true}); + const defaultKeyboardHeight = Platform.select({ios: 291, default: 240}); + const context = useExtraKeyboardContext(); + const insets = useSafeAreaInsets(); + const offset = useOffetForCurrentScreen(); + + const maxKeyboardHeight = useDerivedValue(() => { + if (keyb.state.value === KeyboardState.OPEN) { + const keyboardOffset = keyb.height.value < 70 ? 0 : offset; // When using a hw keyboard + return keyb.height.value + keyboardOffset; + } + + return defaultKeyboardHeight; + }); + + const animatedStyle = useAnimatedStyle(() => { + let height = keyb.height.value + offset; + if (keyb.height.value < 70) { + height = 0; // When using a hw keyboard + } + if (context?.isExtraKeyboardVisible) { + height = withTiming(maxKeyboardHeight.value, {duration: 250}); + } else if (keyb.state.value === KeyboardState.CLOSED || keyb.state.value === KeyboardState.UNKNOWN) { + height = withTiming(0, {duration: 250}); + } + + return { + height, + marginBottom: withTiming((keyb.state.value === KeyboardState.CLOSED || keyb.state.value === KeyboardState.CLOSING || keyb.state.value === KeyboardState.UNKNOWN) ? insets.bottom : 0, {duration: 250}), + }; + }, [context, insets.bottom, offset]); + + return ( + + {context?.isExtraKeyboardVisible && context.component} + + ); +}; diff --git a/app/hooks/device.ts b/app/hooks/device.ts index 72266b0b3..9eb98ffd8 100644 --- a/app/hooks/device.ts +++ b/app/hooks/device.ts @@ -3,12 +3,11 @@ import RNUtils, {type WindowDimensions} from '@mattermost/rnutils'; import React, {type RefObject, useEffect, useRef, useState, useContext} from 'react'; -import {AppState, DeviceEventEmitter, Keyboard, NativeEventEmitter, Platform, View} from 'react-native'; +import {AppState, 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'; import type {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; const utilsEmitter = new NativeEventEmitter(RNUtils); @@ -51,43 +50,14 @@ export function useIsTablet() { return isTablet && !isSplit; } -export function useKeyboardHeightWithDuration(keyboardTracker?: RefObject) { +export function useKeyboardHeightWithDuration() { const [keyboardHeight, setKeyboardHeight] = useState({height: 0, duration: 0}); const updateTimeout = useRef(null); const insets = useSafeAreaInsets(); - // This is a magic number. With tracking view, to properly get the final position, this had to be added. - const KEYBOARD_TRACKINGVIEW_SEPARATION = 4; - - const updateValue = (height: number, duration: number) => { - if (updateTimeout.current != null) { - clearTimeout(updateTimeout.current); - updateTimeout.current = null; - } - updateTimeout.current = setTimeout(() => { - setKeyboardHeight({height, duration}); - updateTimeout.current = null; - }, 200); - }; - useEffect(() => { const show = Keyboard.addListener(Platform.select({ios: 'keyboardWillShow', default: 'keyboardDidShow'}), async (event) => { - if (!keyboardTracker?.current) { - setKeyboardHeight({height: event.endCoordinates.height, duration: event.duration}); - } - }); - - const tracker = DeviceEventEmitter.addListener('MattermostKeyboardTrackerView', (event: KeyboardWillShowEventData) => { - const props = event.nativeEvent; - if (keyboardTracker?.current) { - if (props.keyboardHeight) { - updateValue((props.trackingViewHeight + props.keyboardHeight) - KEYBOARD_TRACKINGVIEW_SEPARATION, props.animationDuration); - } else { - updateValue((props.trackingViewHeight + insets.bottom) - KEYBOARD_TRACKINGVIEW_SEPARATION, props.animationDuration); - } - } else { - setKeyboardHeight({height: props.keyboardFrameEndHeight, duration: props.animationDuration}); - } + setKeyboardHeight({height: event.endCoordinates.height, duration: event.duration}); }); const hide = Keyboard.addListener(Platform.select({ios: 'keyboardWillHide', default: 'keyboardDidHide'}), (event) => { @@ -101,15 +71,14 @@ export function useKeyboardHeightWithDuration(keyboardTracker?: RefObject { show.remove(); hide.remove(); - tracker.remove(); }; - }, [keyboardTracker?.current && insets.bottom]); + }, [insets.bottom]); return keyboardHeight; } -export function useKeyboardHeight(keyboardTracker?: RefObject) { - const {height} = useKeyboardHeightWithDuration(keyboardTracker); +export function useKeyboardHeight() { + const {height} = useKeyboardHeightWithDuration(); return height; } @@ -126,7 +95,7 @@ export function useViewPosition(viewRef: RefObject, deps: React.Dependency } }); } - }, [...deps, isTablet, height]); + }, [...deps, isTablet, height, viewRef, modalPosition]); return modalPosition; } diff --git a/app/hooks/keyboard_tracking.ts b/app/hooks/keyboard_tracking.ts deleted file mode 100644 index 3f598da48..000000000 --- a/app/hooks/keyboard_tracking.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {type KeyboardTrackingViewRef} from '@mattermost/keyboard-tracker'; -import {type RefObject, useEffect, useRef} from 'react'; -import {Navigation} from 'react-native-navigation'; - -import NavigationStore from '@store/navigation_store'; - -export const useKeyboardTrackingPaused = (keyboardTrackingRef: RefObject, trackerId: string, screens: string[]) => { - const isPostDraftPaused = useRef(false); - - useEffect(() => { - if (keyboardTrackingRef.current) { - keyboardTrackingRef.current.resumeTracking(trackerId); - } - }, []); - - useEffect(() => { - const onCommandComplete = () => { - const id = NavigationStore.getVisibleScreen(); - if (screens.includes(id) && isPostDraftPaused.current) { - isPostDraftPaused.current = false; - keyboardTrackingRef.current?.resumeTracking(trackerId); - } - }; - - const commandListener = Navigation.events().registerCommandListener(() => { - setTimeout(() => { - const visibleScreen = NavigationStore.getVisibleScreen(); - if (!isPostDraftPaused.current && !screens.includes(visibleScreen)) { - isPostDraftPaused.current = true; - keyboardTrackingRef.current?.pauseTracking(trackerId); - } - }); - }); - - const commandCompletedListener = Navigation.events().registerCommandCompletedListener(() => { - onCommandComplete(); - }); - - const popListener = Navigation.events().registerScreenPoppedListener(() => { - onCommandComplete(); - }); - - return () => { - commandListener.remove(); - commandCompletedListener.remove(); - popListener.remove(); - }; - }, [trackerId]); -}; diff --git a/app/screens/channel/channel.tsx b/app/screens/channel/channel.tsx index 845d20e6b..4c4768c7c 100644 --- a/app/screens/channel/channel.tsx +++ b/app/screens/channel/channel.tsx @@ -1,22 +1,21 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {type KeyboardTrackingViewRef} from 'libraries/@mattermost/keyboard-tracker/src'; -import React, {useCallback, useEffect, useRef, useState} from 'react'; +import RNUtils from '@mattermost/rnutils'; +import React, {useCallback, useEffect, useState} from 'react'; import {type LayoutChangeEvent, StyleSheet, View} from 'react-native'; +import {Navigation} from 'react-native-navigation'; import {type Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'; import {storeLastViewedChannelIdAndServer, removeLastViewedChannelIdAndServer} from '@actions/app/global'; import FloatingCallContainer from '@calls/components/floating_call_container'; import FreezeScreen from '@components/freeze_screen'; import PostDraft from '@components/post_draft'; -import {Screens} from '@constants'; -import {ACCESSORIES_CONTAINER_NATIVE_ID} from '@constants/post_draft'; +import {ExtraKeyboardProvider} from '@context/extra_keyboard'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; import {useChannelSwitch} from '@hooks/channel_switch'; import {useIsTablet} from '@hooks/device'; import {useDefaultHeaderHeight} from '@hooks/header'; -import {useKeyboardTrackingPaused} from '@hooks/keyboard_tracking'; import {useTeamSwitch} from '@hooks/team_switch'; import {popTopScreen} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; @@ -45,7 +44,6 @@ type ChannelProps = { }; const edges: Edge[] = ['left', 'right']; -const trackKeyboardForScreens = [Screens.HOME, Screens.CHANNEL]; const styles = StyleSheet.create({ flex: { @@ -75,16 +73,28 @@ const Channel = ({ const switchingTeam = useTeamSwitch(); const switchingChannels = useChannelSwitch(); const defaultHeight = useDefaultHeaderHeight(); - const postDraftRef = useRef(null); const [containerHeight, setContainerHeight] = useState(0); const shouldRender = !switchingTeam && !switchingChannels && shouldRenderPosts && Boolean(channelId); const handleBack = useCallback(() => { popTopScreen(componentId); }, [componentId]); - useKeyboardTrackingPaused(postDraftRef, channelId, trackKeyboardForScreens); useAndroidHardwareBackHandler(componentId, handleBack); + useEffect(() => { + const listener = { + componentDidAppear: () => { + RNUtils.setSoftKeyboardToAdjustNothing(); + }, + componentDidDisappear: () => { + RNUtils.setSoftKeyboardToAdjustResize(); + }, + }; + const unsubscribe = Navigation.events().registerComponentListener(listener, componentId!); + + return () => unsubscribe.remove(); + }, []); + const marginTop = defaultHeight + (isTablet ? 0 : -insets.top); useEffect(() => { // This is done so that the header renders @@ -132,7 +142,7 @@ const Channel = ({ shouldRenderBookmarks={shouldRender} /> {shouldRender && - <> + - + } {showFloatingCallContainer && shouldRender && { } }; -export default function HomeScreen(props: HomeProps) { +export function HomeScreen(props: HomeProps) { const theme = useTheme(); const intl = useIntl(); const appState = useAppState(); @@ -82,7 +82,7 @@ export default function HomeScreen(props: HomeProps) { return () => { listener.remove(); }; - }, [intl.locale]); + }, [intl]); useEffect(() => { const leaveTeamListener = DeviceEventEmitter.addListener(Events.LEAVE_TEAM, (displayName: string) => { @@ -109,7 +109,7 @@ export default function HomeScreen(props: HomeProps) { archivedChannelListener.remove(); crtToggledListener.remove(); }; - }, [intl.locale]); + }, [intl]); useEffect(() => { if (appState === 'active') { @@ -191,3 +191,5 @@ export default function HomeScreen(props: HomeProps) { ); } + +export default HomeScreen; diff --git a/app/screens/home/recent_mentions/recent_mentions.tsx b/app/screens/home/recent_mentions/recent_mentions.tsx index 904f9a1db..926766b1d 100644 --- a/app/screens/home/recent_mentions/recent_mentions.tsx +++ b/app/screens/home/recent_mentions/recent_mentions.tsx @@ -14,6 +14,7 @@ import DateSeparator from '@components/post_list/date_separator'; import PostWithChannelInfo from '@components/post_with_channel_info'; import RoundedHeaderContext from '@components/rounded_header_context'; import {Events, Screens} from '@constants'; +import {ExtraKeyboardProvider} from '@context/extra_keyboard'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {useCollapsibleHeader} from '@hooks/header'; @@ -162,7 +163,7 @@ const RecentMentionsScreen = ({appsEnabled, customEmojiNames, mentions, currentT }, [appsEnabled, customEmojiNames]); return ( - <> + - + ); }; diff --git a/app/screens/home/saved_messages/saved_messages.tsx b/app/screens/home/saved_messages/saved_messages.tsx index 2ecbee966..9fe96a59d 100644 --- a/app/screens/home/saved_messages/saved_messages.tsx +++ b/app/screens/home/saved_messages/saved_messages.tsx @@ -15,6 +15,7 @@ import DateSeparator from '@components/post_list/date_separator'; import PostWithChannelInfo from '@components/post_with_channel_info'; import RoundedHeaderContext from '@components/rounded_header_context'; import {Events, Screens} from '@constants'; +import {ExtraKeyboardProvider} from '@context/extra_keyboard'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {useCollapsibleHeader} from '@hooks/header'; @@ -164,7 +165,7 @@ function SavedMessages({appsEnabled, posts, currentTimezone, customEmojiNames}: }, [appsEnabled, currentTimezone, customEmojiNames, theme]); return ( - <> + - + ); } diff --git a/app/screens/home/search/results/post_results.tsx b/app/screens/home/search/results/post_results.tsx index 5b0ecd297..e4cd6f06e 100644 --- a/app/screens/home/search/results/post_results.tsx +++ b/app/screens/home/search/results/post_results.tsx @@ -9,6 +9,7 @@ import NoResultsWithTerm from '@components/no_results_with_term'; import DateSeparator from '@components/post_list/date_separator'; import PostWithChannelInfo from '@components/post_with_channel_info'; import {Screens} from '@constants'; +import {ExtraKeyboardProvider} from '@context/extra_keyboard'; import {useTheme} from '@context/theme'; import {convertSearchTermToRegex, parseSearchTerms} from '@utils/markdown'; import {getDateForDateLine, selectOrderedPosts} from '@utils/post_list'; @@ -89,7 +90,7 @@ const PostResults = ({ default: return null; } - }, [appsEnabled, customEmojiNames, searchValue, matches]); + }, [currentTimezone, searchValue, matches, appsEnabled, customEmojiNames]); const noResults = useMemo(() => ( - } - ListEmptyComponent={noResults} - contentContainerStyle={containerStyle} - data={orderedPosts} - indicatorStyle='black' - initialNumToRender={5} + + + } + ListEmptyComponent={noResults} + contentContainerStyle={containerStyle} + data={orderedPosts} + indicatorStyle='black' + initialNumToRender={5} - //@ts-expect-error key not defined in types - listKey={'posts'} - maxToRenderPerBatch={5} - nestedScrollEnabled={true} - refreshing={false} - removeClippedSubviews={true} - renderItem={renderItem} - scrollEventThrottle={16} - scrollToOverflowEnabled={true} - showsVerticalScrollIndicator={true} - testID='search_results.post_list.flat_list' - /> + //@ts-expect-error key not defined in types + listKey={'posts'} + maxToRenderPerBatch={5} + nestedScrollEnabled={true} + refreshing={false} + removeClippedSubviews={true} + renderItem={renderItem} + scrollEventThrottle={16} + scrollToOverflowEnabled={true} + showsVerticalScrollIndicator={true} + testID='search_results.post_list.flat_list' + /> + ); }; diff --git a/app/screens/permalink/permalink.tsx b/app/screens/permalink/permalink.tsx index ff0c56b18..22255dad6 100644 --- a/app/screens/permalink/permalink.tsx +++ b/app/screens/permalink/permalink.tsx @@ -15,6 +15,7 @@ import FormattedText from '@components/formatted_text'; import Loading from '@components/loading'; import PostList from '@components/post_list'; import {Screens} from '@constants'; +import {ExtraKeyboardProvider} from '@context/extra_keyboard'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import DatabaseManager from '@database/manager'; @@ -315,7 +316,7 @@ function Permalink({ ); } else { content = ( - <> + - + ); } diff --git a/app/screens/pinned_messages/pinned_messages.tsx b/app/screens/pinned_messages/pinned_messages.tsx index 6cfadbf9b..36f142cb4 100644 --- a/app/screens/pinned_messages/pinned_messages.tsx +++ b/app/screens/pinned_messages/pinned_messages.tsx @@ -10,6 +10,7 @@ import Loading from '@components/loading'; import DateSeparator from '@components/post_list/date_separator'; import Post from '@components/post_list/post'; import {Events, Screens} from '@constants'; +import {ExtraKeyboardProvider} from '@context/extra_keyboard'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; @@ -152,17 +153,19 @@ function SavedMessages({ style={styles.flex} testID='pinned_messages.screen' > - + + + ); } diff --git a/app/screens/thread/thread.tsx b/app/screens/thread/thread.tsx index 8cb015b3d..435940f38 100644 --- a/app/screens/thread/thread.tsx +++ b/app/screens/thread/thread.tsx @@ -1,9 +1,11 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import RNUtils from '@mattermost/rnutils'; import {uniqueId} from 'lodash'; -import React, {useCallback, useEffect, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useState} from 'react'; import {type LayoutChangeEvent, StyleSheet, View} from 'react-native'; +import {Navigation} from 'react-native-navigation'; import {type Edge, SafeAreaView} from 'react-native-safe-area-context'; import {storeLastViewedThreadIdAndServer, removeLastViewedThreadIdAndServer} from '@actions/app/global'; @@ -12,17 +14,15 @@ import FreezeScreen from '@components/freeze_screen'; import PostDraft from '@components/post_draft'; import RoundedHeaderContext from '@components/rounded_header_context'; import {Screens} from '@constants'; -import {THREAD_ACCESSORIES_CONTAINER_NATIVE_ID} from '@constants/post_draft'; +import {ExtraKeyboardProvider} from '@context/extra_keyboard'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; import useDidUpdate from '@hooks/did_update'; -import {useKeyboardTrackingPaused} from '@hooks/keyboard_tracking'; import {popTopScreen, setButtons} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; import NavigationStore from '@store/navigation_store'; import ThreadPostList from './thread_post_list'; -import type {KeyboardTrackingViewRef} from '@mattermost/keyboard-tracker'; import type PostModel from '@typings/database/models/servers/post'; import type {AvailableScreens} from '@typings/screens/navigation'; @@ -37,7 +37,6 @@ type ThreadProps = { }; const edges: Edge[] = ['left', 'right']; -const trackKeyboardForScreens = [Screens.THREAD]; const styles = StyleSheet.create({ flex: {flex: 1}, @@ -52,16 +51,28 @@ const Thread = ({ isInACall, showIncomingCalls, }: ThreadProps) => { - const postDraftRef = useRef(null); const [containerHeight, setContainerHeight] = useState(0); const close = useCallback(() => { popTopScreen(componentId); }, [componentId]); - useKeyboardTrackingPaused(postDraftRef, rootId, trackKeyboardForScreens); useAndroidHardwareBackHandler(componentId, close); + useEffect(() => { + const listener = { + componentDidAppear: () => { + RNUtils.setSoftKeyboardToAdjustNothing(); + }, + componentDidDisappear: () => { + RNUtils.setSoftKeyboardToAdjustResize(); + }, + }; + const unsubscribe = Navigation.events().registerComponentListener(listener, componentId!); + + return () => unsubscribe.remove(); + }, []); + useEffect(() => { if (isCRTEnabled && rootId) { const id = `${componentId}-${rootId}-${uniqueId()}`; @@ -122,7 +133,7 @@ const Thread = ({ > {Boolean(rootPost) && - <> + - + } {showFloatingCallContainer && { const appState = useAppState(); - const isTablet = useIsTablet(); const serverUrl = useServerUrl(); const theme = useTheme(); const isFetchingThread = useFetchingThreadState(rootPost.id); @@ -87,7 +83,7 @@ const ThreadPostList = ({ oldPostsCount.current = posts.length; markThreadAsRead(serverUrl, teamId, rootPost.id, false); } - }, [isCRTEnabled, posts, rootPost, serverUrl, teamId, thread, appState === 'active']); + }, [isCRTEnabled, posts, rootPost, serverUrl, teamId, thread, appState]); const lastViewedAt = isCRTEnabled ? (thread?.viewedAt ?? 0) : channelLastViewedAt; @@ -116,18 +112,7 @@ const ThreadPostList = ({ /> ); - if (isTablet) { - return postList; - } - - return ( - - {postList} - - ); + return postList; }; export default ThreadPostList; diff --git a/app/store/navigation_store.ts b/app/store/navigation_store.ts index 0c0f815ca..304718e7c 100644 --- a/app/store/navigation_store.ts +++ b/app/store/navigation_store.ts @@ -1,6 +1,8 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {BehaviorSubject} from 'rxjs'; + import type {AvailableScreens} from '@typings/screens/navigation'; class NavigationStore { @@ -9,11 +11,18 @@ class NavigationStore { private visibleTab = 'Home'; private tosOpen = false; + private subject: BehaviorSubject = new BehaviorSubject(undefined); + + getSubject = () => { + return this.subject; + }; + reset = () => { this.screensInStack = []; this.modalsInStack = []; this.visibleTab = 'Home'; this.tosOpen = false; + this.subject.next(undefined); }; addModalToStack = (modalId: AvailableScreens) => { @@ -25,10 +34,12 @@ class NavigationStore { addScreenToStack = (screenId: AvailableScreens) => { this.removeScreenFromStack(screenId); this.screensInStack.unshift(screenId); + this.subject.next(screenId); }; clearScreensFromStack = () => { this.screensInStack = []; + this.subject.next(undefined); }; getModalsInStack = () => this.modalsInStack; @@ -49,6 +60,7 @@ class NavigationStore { const index = this.screensInStack.indexOf(screenId); if (index > -1) { this.screensInStack.splice(0, index); + this.subject.next(screenId); } }; @@ -56,6 +68,7 @@ class NavigationStore { const index = this.screensInStack.indexOf(screenId); if (index > -1) { this.screensInStack.splice(index, 1); + this.subject.next(this.screensInStack[0]); } }; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 72a447e3a..984bda232 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -94,27 +94,6 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - mattermost-keyboard-tracker (0.0.0): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Codegen - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - mattermost-react-native-turbo-log (0.4.0): - CocoaLumberjack (~> 3.8.5) - DoubleConversion @@ -1669,7 +1648,6 @@ DEPENDENCIES: - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - "mattermost-hardware-keyboard (from `../node_modules/@mattermost/hardware-keyboard`)" - - "mattermost-keyboard-tracker (from `../node_modules/@mattermost/keyboard-tracker`)" - "mattermost-react-native-turbo-log (from `../node_modules/@mattermost/react-native-turbo-log`)" - "mattermost-rnutils (from `../node_modules/@mattermost/rnutils`)" - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) @@ -1812,8 +1790,6 @@ EXTERNAL SOURCES: :tag: hermes-2024-06-28-RNv0.74.3-7bda0c267e76d11b68a585f84cfdd65000babf85 mattermost-hardware-keyboard: :path: "../node_modules/@mattermost/hardware-keyboard" - mattermost-keyboard-tracker: - :path: "../node_modules/@mattermost/keyboard-tracker" mattermost-react-native-turbo-log: :path: "../node_modules/@mattermost/react-native-turbo-log" mattermost-rnutils: @@ -2008,7 +1984,6 @@ SPEC CHECKSUMS: JitsiWebRTC: d0ae5fd6a81e771bfd82c2ee6c6bb542ebd65ee8 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 mattermost-hardware-keyboard: b916a168bb3add779bb226b3a62d6ac5ec225a79 - mattermost-keyboard-tracker: b48752a97e731fdae5e8259a4ea0e731e1a05094 mattermost-react-native-turbo-log: c446c7e8a39c131d6bafa6ae6f830c735030ed5e mattermost-rnutils: 01504fb26beb75594f4c6e2150668b27783112f5 RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47 diff --git a/libraries/@mattermost/keyboard-tracker/ios/Extensions/UIResponder+FirstResponder.h b/libraries/@mattermost/keyboard-tracker/ios/Extensions/UIResponder+FirstResponder.h deleted file mode 100644 index 0e8d253e8..000000000 --- a/libraries/@mattermost/keyboard-tracker/ios/Extensions/UIResponder+FirstResponder.h +++ /dev/null @@ -1,6 +0,0 @@ -#import -#import - -@interface UIResponder (CurrentResponder) -+(id)currentFirstResponder; -@end diff --git a/libraries/@mattermost/keyboard-tracker/ios/Extensions/UIResponder+FirstResponder.m b/libraries/@mattermost/keyboard-tracker/ios/Extensions/UIResponder+FirstResponder.m deleted file mode 100644 index 2da036d29..000000000 --- a/libraries/@mattermost/keyboard-tracker/ios/Extensions/UIResponder+FirstResponder.m +++ /dev/null @@ -1,17 +0,0 @@ -#import "UIResponder+FirstResponder.h" - -static __weak id currentFirstResponder; - -@implementation UIResponder (FirstResponder) - -+(id)currentFirstResponder { - currentFirstResponder = nil; - [[UIApplication sharedApplication] sendAction:@selector(findFirstResponder:) to:nil from:nil forEvent:nil]; - return currentFirstResponder; -} - --(void)findFirstResponder:(id)sender { - currentFirstResponder = self; -} - -@end diff --git a/libraries/@mattermost/keyboard-tracker/ios/MattermostKeyboardTrackerView.h b/libraries/@mattermost/keyboard-tracker/ios/MattermostKeyboardTrackerView.h deleted file mode 100644 index 65ebeb4fb..000000000 --- a/libraries/@mattermost/keyboard-tracker/ios/MattermostKeyboardTrackerView.h +++ /dev/null @@ -1,17 +0,0 @@ -// This guard prevent this file to be compiled in the old architecture. -#ifdef RCT_NEW_ARCH_ENABLED -#import -#import - -#ifndef MattermostKeyboardTrackerViewNativeComponent_h -#define MattermostKeyboardTrackerViewNativeComponent_h - -NS_ASSUME_NONNULL_BEGIN - -@interface MattermostKeyboardTrackerView : RCTViewComponentView -@end - -NS_ASSUME_NONNULL_END - -#endif /* MattermostKeyboardTrackerViewNativeComponent_h */ -#endif /* RCT_NEW_ARCH_ENABLED */ diff --git a/libraries/@mattermost/keyboard-tracker/ios/MattermostKeyboardTrackerView.mm b/libraries/@mattermost/keyboard-tracker/ios/MattermostKeyboardTrackerView.mm deleted file mode 100644 index f68571bc9..000000000 --- a/libraries/@mattermost/keyboard-tracker/ios/MattermostKeyboardTrackerView.mm +++ /dev/null @@ -1,169 +0,0 @@ -#ifdef RCT_NEW_ARCH_ENABLED -#import "MattermostKeyboardTrackerView.h" -#import "KeyboardTrackingView.h" - -#import -#import -#import -#import - -#import "RCTFabricComponentsPlugins.h" -#import "RCTConversions.h" - -using namespace facebook::react; - -@interface MattermostKeyboardTrackerView () - -@end - -@implementation MattermostKeyboardTrackerView { - KeyboardTrackingView * _view; -} - -+ (ComponentDescriptorProvider)componentDescriptorProvider -{ - return concreteComponentDescriptorProvider(); -} - --(std::shared_ptr)getEventEmitter { - if (!self->_eventEmitter) { - return nullptr; - } - - assert(std::dynamic_pointer_cast(self->_eventEmitter)); - return std::dynamic_pointer_cast(self->_eventEmitter); -} - -- (instancetype)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) { - static const auto defaultProps = std::make_shared(); - _props = defaultProps; - - _view = [[KeyboardTrackingView alloc] init]; - [self _setOnKeyboardWillShow]; - - self.contentView = _view; - } - - return self; -} - -- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps -{ - const auto &oldViewProps = *std::static_pointer_cast(_props); - const auto &newViewProps = *std::static_pointer_cast(props); - - if (oldViewProps.scrollBehavior != newViewProps.scrollBehavior) { - std::string behaviorString = toString(newViewProps.scrollBehavior); - KeyboardTrackingScrollBehavior scrollBehavior = KeyboardTrackingScrollBehaviorFromString([NSString stringWithUTF8String:behaviorString.c_str()]); - _view.scrollBehavior = scrollBehavior; - } - - if (oldViewProps.revealKeyboardInteractive != newViewProps.revealKeyboardInteractive) { - _view.revealKeyboardInteractive = newViewProps.revealKeyboardInteractive; - } - - if (oldViewProps.manageScrollView != newViewProps.manageScrollView) { - _view.manageScrollView = newViewProps.manageScrollView; - } - - if (oldViewProps.requiresSameParentToManageScrollView != newViewProps.requiresSameParentToManageScrollView) { - _view.requiresSameParentToManageScrollView = newViewProps.requiresSameParentToManageScrollView; - } - - if (oldViewProps.addBottomView != newViewProps.addBottomView) { - _view.addBottomView = newViewProps.addBottomView; - } - - if (oldViewProps.scrollToFocusedInput != newViewProps.scrollToFocusedInput) { - _view.scrollToFocusedInput = newViewProps.scrollToFocusedInput; - } - - if (oldViewProps.allowHitsOutsideBounds != newViewProps.allowHitsOutsideBounds) { - _view.allowHitsOutsideBounds = newViewProps.allowHitsOutsideBounds; - } - - if (oldViewProps.normalList != newViewProps.normalList) { - _view.normalList = newViewProps.normalList; - } - - if (oldViewProps.viewInitialOffsetY != newViewProps.viewInitialOffsetY) { - _view.viewInitialOffsetY = newViewProps.viewInitialOffsetY; - } - - if (oldViewProps.scrollViewNativeID != newViewProps.scrollViewNativeID) { - _view.scrollViewNativeID = RCTNSStringFromString(newViewProps.scrollViewNativeID); - } - - if (oldViewProps.accessoriesContainerID != newViewProps.accessoriesContainerID) { - _view.accessoriesContainerID = RCTNSStringFromString(newViewProps.accessoriesContainerID); - - } - - if (oldViewProps.backgroundColor != newViewProps.backgroundColor) { - [_view setBottomViewBackgroundColor:RCTUIColorFromSharedColor(newViewProps.backgroundColor)]; - } - - [super updateProps:props oldProps:oldProps]; -} - -- (void)handleCommand:(nonnull const NSString *)commandName args:(nonnull const NSArray *)args { - RCTMattermostKeyboardTrackerViewHandleCommand(self, commandName, args); -} - --(void)_setOnKeyboardWillShow{ - _view.onKeyboardWillShow = ^(NSDictionary *body) { - const auto eventEmitter = [self getEventEmitter]; - if (eventEmitter) { - eventEmitter->onKeyboardWillShow(MattermostKeyboardTrackerViewEventEmitter::OnKeyboardWillShow{ - .trackingViewHeight = [body[@"trackingViewHeight"] doubleValue], - .keyboardHeight = [body[@"keyboardHeight"] doubleValue], - .contentTopInset = [body[@"contentTopInset"] doubleValue], - .animationDuration = [body[@"animationDuration"] doubleValue], - .keyboardFrameEndHeight = (CGFloat)[body[@"keyboardFrameEndHeight"] doubleValue], - }); - } - }; -} - -Class MattermostKeyboardTrackerViewCls(void) -{ - return MattermostKeyboardTrackerView.class; -} - -- (void)pauseTracking:(nonnull NSString *)scrollViewNativeID { - [_view pauseTracking:scrollViewNativeID]; -} - -- (void)resetScrollView:(nonnull NSString *)scrollViewNativeID { - [_view resetScrollView:scrollViewNativeID]; -} - -- (void)resumeTracking:(nonnull NSString *)scrollViewNativeID { - [_view resumeTracking:scrollViewNativeID]; -} - -- (void)scrollToStart { - [_view scrollToStart]; -} - - -#pragma utils - -KeyboardTrackingScrollBehavior KeyboardTrackingScrollBehaviorFromString(NSString *string) { - if ([string isEqualToString:@"KeyboardTrackingScrollBehaviorNone"]) { - return KeyboardTrackingScrollBehaviorNone; - } else if ([string isEqualToString:@"KeyboardTrackingScrollBehaviorScrollToBottomInvertedOnly"]) { - return KeyboardTrackingScrollBehaviorScrollToBottomInvertedOnly; - } else if ([string isEqualToString:@"KeyboardTrackingScrollBehaviorFixedOffset"]) { - return KeyboardTrackingScrollBehaviorFixedOffset; - } else { - // Handle the case where the string doesn't match any known value - // You can choose to return a default value or raise an exception - return KeyboardTrackingScrollBehaviorNone; // Default value - } -} - -@end -#endif diff --git a/libraries/@mattermost/keyboard-tracker/ios/ViewManagers/MattermostKeyboardTrackerViewManager.mm b/libraries/@mattermost/keyboard-tracker/ios/ViewManagers/MattermostKeyboardTrackerViewManager.mm deleted file mode 100644 index bd2fc1954..000000000 --- a/libraries/@mattermost/keyboard-tracker/ios/ViewManagers/MattermostKeyboardTrackerViewManager.mm +++ /dev/null @@ -1,127 +0,0 @@ -#import -#import -#import "RCTBridge.h" -#import "KeyboardTrackingView.h" - -@interface MattermostKeyboardTrackerViewManager : RCTViewManager -@property (nonatomic, nullable, strong) RCTDirectEventBlock keyboardWillShow; -@end - -@implementation RCTConvert (KeyboardTrackingScrollBehavior) -RCT_ENUM_CONVERTER(KeyboardTrackingScrollBehavior, (@{ @"KeyboardTrackingScrollBehaviorNone": @(KeyboardTrackingScrollBehaviorNone), - @"KeyboardTrackingScrollBehaviorScrollToBottomInvertedOnly": @(KeyboardTrackingScrollBehaviorScrollToBottomInvertedOnly), - @"KeyboardTrackingScrollBehaviorFixedOffset": @(KeyboardTrackingScrollBehaviorFixedOffset)}), - KeyboardTrackingScrollBehaviorNone, unsignedIntegerValue) -@end - -@implementation MattermostKeyboardTrackerViewManager - -RCT_EXPORT_MODULE(MattermostKeyboardTrackerView) - -#ifndef RCT_NEW_ARCH_ENABLED -- (UIView *)view -{ - return [[KeyboardTrackingView alloc] init]; -} -#endif - -RCT_CUSTOM_VIEW_PROPERTY(scrollBehavior, NSString, KeyboardTrackingView) { - KeyboardTrackingScrollBehavior behavior = [RCTConvert KeyboardTrackingScrollBehavior:json]; - [view setScrollBehavior:behavior]; -} - -RCT_CUSTOM_VIEW_PROPERTY(revealKeyboardInteractive, BOOL, KeyboardTrackingView) { - [view setRevealKeyboardInteractive:json]; -} - -RCT_CUSTOM_VIEW_PROPERTY(manageScrollView, BOOL, KeyboardTrackingView) { - [view setManageScrollView:json]; -} - -RCT_CUSTOM_VIEW_PROPERTY(requiresSameParentToManageScrollView, BOOL, KeyboardTrackingView) { - [view setRequiresSameParentToManageScrollView:json]; -} - -RCT_CUSTOM_VIEW_PROPERTY(addBottomView, BOOL, KeyboardTrackingView) { - [view setAddBottomView:json]; -} - -RCT_CUSTOM_VIEW_PROPERTY(scrollToFocusedInput, BOOL, KeyboardTrackingView) { - [view setScrollToFocusedInput:json]; -} - -RCT_CUSTOM_VIEW_PROPERTY(allowHitsOutsideBounds, BOOL, KeyboardTrackingView) { - [view setAllowHitsOutsideBounds:json]; -} - -RCT_CUSTOM_VIEW_PROPERTY(normalList, BOOL, KeyboardTrackingView) { - [view setNormalList:json]; -} - -RCT_CUSTOM_VIEW_PROPERTY(viewInitialOffsetY, CGFloat, KeyboardTrackingView) { - [view setViewInitialOffsetY:[RCTConvert CGFloat: json]]; -} - -RCT_CUSTOM_VIEW_PROPERTY(scrollViewNativeID, NSString, KeyboardTrackingView) { - [view setScrollViewNativeID:json]; -} - -RCT_CUSTOM_VIEW_PROPERTY(accessoriesContainerID, NSString, KeyboardTrackingView) { - [view setAccessoriesContainerID:json]; -} - -RCT_CUSTOM_VIEW_PROPERTY(backgroundColor, NSString, KeyboardTrackingView) -{ - [view setBottomViewBackgroundColor:[RCTConvert UIColor:json]]; -} - -RCT_EXPORT_VIEW_PROPERTY(onKeyboardWillShow, RCTBubblingEventBlock) - -#pragma Commands - -RCT_EXPORT_METHOD(resetScrollView:(nonnull NSNumber *)reactTag scrollViewNativeID:(NSString *) scrollViewNativeID) { - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - KeyboardTrackingView *view = (KeyboardTrackingView *)viewRegistry[reactTag]; - if (!view || ![view isKindOfClass:[KeyboardTrackingView class]]) { - RCTLogError(@"Error: cannot find KeyboardTrackingView with tag #%@", reactTag); - return; - } - [view resetScrollView:scrollViewNativeID]; - }]; -} - -RCT_EXPORT_METHOD(pauseTracking:(nonnull NSNumber *)reactTag scrollViewNativeID:(NSString*)scrollViewNativeID) { - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - KeyboardTrackingView *view = (KeyboardTrackingView *)viewRegistry[reactTag]; - if (!view || ![view isKindOfClass:[KeyboardTrackingView class]]) { - RCTLogError(@"Error: cannot find KeyboardTrackingView with tag #%@", reactTag); - return; - } - [view pauseTracking:scrollViewNativeID]; - }]; -} - -RCT_EXPORT_METHOD(resumeTracking:(nonnull NSNumber *)reactTag scrollViewNativeID:(NSString*)scrollViewNativeID) { - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - KeyboardTrackingView *view = (KeyboardTrackingView *)viewRegistry[reactTag]; - if (!view || ![view isKindOfClass:[KeyboardTrackingView class]]) { - RCTLogError(@"Error: cannot find KeyboardTrackingView with tag #%@", reactTag); - return; - } - [view resumeTracking:scrollViewNativeID]; - }]; -} - -RCT_EXPORT_METHOD(scrollToStart:(nonnull NSNumber *)reactTag) -{ - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - KeyboardTrackingView *view = (KeyboardTrackingView *)viewRegistry[reactTag]; - if (!view || ![view isKindOfClass:[KeyboardTrackingView class]]) { - RCTLogError(@"Error: cannot find KeyboardTrackingView with tag #%@", reactTag); - return; - } - [view scrollToStart]; - }]; -} - -@end diff --git a/libraries/@mattermost/keyboard-tracker/ios/Views/KeyboardTrackingView.h b/libraries/@mattermost/keyboard-tracker/ios/Views/KeyboardTrackingView.h deleted file mode 100644 index 2c651f0b2..000000000 --- a/libraries/@mattermost/keyboard-tracker/ios/Views/KeyboardTrackingView.h +++ /dev/null @@ -1,50 +0,0 @@ -#import -#import - -#import "ObservingInputAccessoryView.h" - -typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { - KeyboardTrackingScrollBehaviorNone, - KeyboardTrackingScrollBehaviorScrollToBottomInvertedOnly, - KeyboardTrackingScrollBehaviorFixedOffset -}; - -@interface KeyboardTrackingView : UIView -{ - NSMapTable *_inputViewsMap; - ObservingInputAccessoryView *_observingInputAccessoryView; - UIView *_bottomView; - CGFloat _bottomViewHeight; -} - -@property (nonatomic) NSMutableDictionary* _Nullable rctScrollViewsArray; -@property (nonatomic, strong) UIScrollView * _Nullable scrollViewToManage; -@property (nonatomic) BOOL scrollIsInverted; -@property (nonatomic) BOOL revealKeyboardInteractive; -@property (nonatomic) BOOL isDraggingScrollView; -@property (nonatomic) BOOL manageScrollView; -@property (nonatomic) BOOL requiresSameParentToManageScrollView; -@property (nonatomic) NSUInteger deferedInitializeAccessoryViewsCount; -@property (nonatomic) CGFloat originalHeight; -@property (nonatomic) KeyboardTrackingScrollBehavior scrollBehavior; -@property (nonatomic) BOOL addBottomView; -@property (nonatomic) BOOL scrollToFocusedInput; -@property (nonatomic) BOOL allowHitsOutsideBounds; - -@property (nonatomic) BOOL normalList; -@property (nonatomic) NSString* _Nullable scrollViewNativeID; -@property (nonatomic) CGFloat initialOffsetY; -@property (nonatomic) CGFloat viewInitialOffsetY; -@property (nonatomic) BOOL initialOffsetIsSet; -@property (nonatomic) BOOL paused; -@property (nonatomic, strong) UIView* _Nullable accessoriesContainer; -@property (nonatomic) NSString* _Nullable accessoriesContainerID; -@property (nonatomic, copy, nullable) RCTDirectEventBlock onKeyboardWillShow; - --(void)setBottomViewBackgroundColor:(UIColor* _Nullable) color; --(void)resetScrollView:(NSString* _Nullable) scrollViewNativeID; --(void)pauseTracking:(NSString* _Nullable) scrollViewNativeID; --(void)resumeTracking:(NSString* _Nullable) scrollViewNativeID; --(void)scrollToStart; -@end - diff --git a/libraries/@mattermost/keyboard-tracker/ios/Views/KeyboardTrackingView.m b/libraries/@mattermost/keyboard-tracker/ios/Views/KeyboardTrackingView.m deleted file mode 100644 index ed6c062ef..000000000 --- a/libraries/@mattermost/keyboard-tracker/ios/Views/KeyboardTrackingView.m +++ /dev/null @@ -1,622 +0,0 @@ -#import "KeyboardTrackingView.h" -#import -#import - -#import "UIResponder+FirstResponder.h" -#import "ObservingInputAccessoryView.h" - -#import - -NSUInteger const kInputViewKey = 101010; -NSUInteger const kMaxDeferedInitializeAccessoryViews = 15; -NSInteger const kTrackingViewNotFoundErrorCode = 1; -NSInteger const kBottomViewHeight = 34; - -@interface KeyboardTrackingView () -@end - -@implementation KeyboardTrackingView --(instancetype)init -{ - self = [super init]; - - if (self) - { - [self addObserver:self forKeyPath:@"bounds" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; - _inputViewsMap = [NSMapTable weakToWeakObjectsMapTable]; - _deferedInitializeAccessoryViewsCount = 0; - _rctScrollViewsArray = [[NSMutableDictionary alloc] init]; - - _observingInputAccessoryView = [ObservingInputAccessoryView new]; - _observingInputAccessoryView.delegate = self; - - _initialOffsetY = 0; - _initialOffsetIsSet = NO; - _viewInitialOffsetY = 0; - - _manageScrollView = YES; - _allowHitsOutsideBounds = NO; - _requiresSameParentToManageScrollView = YES; - - _bottomViewHeight = kBottomViewHeight; - - self.addBottomView = NO; - self.scrollToFocusedInput = NO; - self.paused = NO; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rctContentDidAppearNotification:) name:RCTContentDidAppearNotification object:nil]; - } - - return self; -} - --(RCTRootView*)getRootView -{ - UIView *view = self; - while (view.superview != nil) - { - view = view.superview; - if ([view isKindOfClass:[RCTRootView class]]) - break; - } - - if ([view isKindOfClass:[RCTRootView class]]) - { - return (RCTRootView*)view; - } - return nil; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { - if (!_allowHitsOutsideBounds) { - return [super hitTest:point withEvent:event]; - } - - if (self.isHidden || self.alpha == 0 || self.clipsToBounds) { - return nil; - } - - UIView *subview = [super hitTest:point withEvent:event]; - if (subview == nil) { - NSArray* allSubviews = [self getBreadthFirstSubviewsForView:self]; - for (UIView *tmpSubview in allSubviews) { - CGPoint pointInSubview = [self convertPoint:point toView:tmpSubview]; - if ([tmpSubview pointInside:pointInSubview withEvent:event]) { - subview = tmpSubview; - break; - } - } - } - - return subview; -} - --(void)layoutSubviews -{ - [super layoutSubviews]; - [self updateBottomViewFrame]; -} - -- (void)initializeAccessoryViewsAndHandleInsets -{ - NSArray* allSubviews = [self getBreadthFirstSubviewsForView:[self getRootView]]; - - for (UIView* subview in allSubviews) - { - if(subview.nativeID) { - NSLog(@"self.accessoriesContainerID %@ %@", self.accessoriesContainerID, subview.nativeID); - } - - if (subview.nativeID && [subview.nativeID isEqualToString:self.accessoriesContainerID]) { - NSLog(@"SuperView ID: %@", subview.nativeID); - _accessoriesContainer = subview; - } - - if(_manageScrollView) - { - if(_scrollViewToManage == nil) - { - if([subview isKindOfClass:[RCTScrollView class]]) - { - RCTScrollView *scrollView = (RCTScrollView*)subview; - - if (subview.nativeID && [subview.nativeID isEqualToString:self.scrollViewNativeID]) { - [_rctScrollViewsArray setObject:scrollView forKey:subview.nativeID]; - _scrollViewToManage = scrollView.scrollView; - } - } - } - } - - if ([subview isKindOfClass:NSClassFromString(@"RCTTextField")]) - { - UITextField *textField = nil; - Ivar backedTextInputIvar = class_getInstanceVariable([subview class], "_backedTextInput"); - if (backedTextInputIvar != NULL) - { - textField = [subview valueForKey:@"_backedTextInput"]; - } - else if([subview isKindOfClass:[UITextField class]]) - { - textField = (UITextField*)subview; - } - [self setupTextField:textField]; - } - else if ([subview isKindOfClass:NSClassFromString(@"RCTUITextField")] && [subview isKindOfClass:[UITextField class]]) - { - [self setupTextField:(UITextField*)subview]; - } - else if ([subview isKindOfClass:NSClassFromString(@"RCTMultilineTextInputView")]) - { - [self setupTextView:[subview valueForKey:@"_backedTextInputView"]]; - } - else if ([subview isKindOfClass:NSClassFromString(@"RCTTextView")]) - { - UITextView *textView = nil; - Ivar backedTextInputIvar = class_getInstanceVariable([subview class], "_backedTextInput"); - if (backedTextInputIvar != NULL) - { - textView = [subview valueForKey:@"_backedTextInput"]; - } - else if([subview isKindOfClass:[UITextView class]]) - { - textView = (UITextView*)subview; - } - [self setupTextView:textView]; - } - else if ([subview isKindOfClass:NSClassFromString(@"RCTUITextView")] && [subview isKindOfClass:[UITextView class]]) - { - [self setupTextView:(UITextView*)subview]; - } - } - - for (RCTScrollView *scrollView in [_rctScrollViewsArray allValues]) - { - if(scrollView.scrollView == _scrollViewToManage) - { - [scrollView removeScrollListener:self]; - [scrollView addScrollListener:self]; - break; - } - } - -#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_10_3 - if (@available(iOS 11.0, *)) { - if (_scrollViewToManage != nil) { - _scrollViewToManage.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; - } - } -#endif - - [self _updateScrollViewInsets]; - - _originalHeight = _observingInputAccessoryView.height; - - [self addBottomViewIfNecessary]; -} - -- (void)resetScrollView:(NSString*) scrollViewNativeID { - for (RCTScrollView *scrollView in [_rctScrollViewsArray allValues]) - { - [scrollView removeScrollListener:self]; - } - [_rctScrollViewsArray removeAllObjects]; - _scrollViewToManage = nil; - _scrollViewNativeID = scrollViewNativeID; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self deferedInitializeAccessoryViewsAndHandleInsets]; - [self scrollToStart]; - }); -} - --(void)pauseTracking:(NSString*) scrollViewNativeID { - if ([self.scrollViewNativeID isEqualToString:scrollViewNativeID]) { - _observingInputAccessoryView.delegate = nil; - self.scrollViewToManage = nil; - self.accessoriesContainer = nil; - self.paused = YES; - } -} - --(void)resumeTracking:(NSString*) scrollViewNativeID { - if ([self.scrollViewNativeID isEqualToString:scrollViewNativeID]) { - self.paused = NO; - _observingInputAccessoryView.delegate = self; - [self deferedInitializeAccessoryViewsAndHandleInsets]; - } -} - -- (void)setupTextView:(UITextView*)textView -{ - if (textView != nil) - { - [textView setInputAccessoryView:_observingInputAccessoryView]; - [textView reloadInputViews]; - [_inputViewsMap setObject:textView forKey:@(kInputViewKey)]; - } -} - -- (void)setupTextField:(UITextField*)textField -{ - if (textField != nil) - { - [textField setInputAccessoryView:_observingInputAccessoryView]; - [textField reloadInputViews]; - [_inputViewsMap setObject:textField forKey:@(kInputViewKey)]; - } -} - --(void) deferedInitializeAccessoryViewsAndHandleInsets -{ - if(self.window == nil) - { - return; - } - - if (_observingInputAccessoryView.height == 0 && self.deferedInitializeAccessoryViewsCount < kMaxDeferedInitializeAccessoryViews) - { - self.deferedInitializeAccessoryViewsCount++; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self deferedInitializeAccessoryViewsAndHandleInsets]; - }); - } - else - { - dispatch_async(dispatch_get_main_queue(), ^{ - [self initializeAccessoryViewsAndHandleInsets]; - }); - } -} - -- (void)willMoveToWindow:(nullable UIWindow *)newWindow -{ - if (newWindow == nil && [ObservingInputAccessoryViewManager sharedInstance].activeObservingInputAccessoryView == _observingInputAccessoryView) - { - [ObservingInputAccessoryViewManager sharedInstance].activeObservingInputAccessoryView = nil; - } - else if (newWindow != nil) - { - [ObservingInputAccessoryViewManager sharedInstance].activeObservingInputAccessoryView = _observingInputAccessoryView; - } -} - --(void)didMoveToWindow -{ - [super didMoveToWindow]; - - self.deferedInitializeAccessoryViewsCount = 0; - - [self deferedInitializeAccessoryViewsAndHandleInsets]; -} - --(void)dealloc -{ - [self removeObserver:self forKeyPath:@"bounds"]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - if (self.paused) { - return; - } - _observingInputAccessoryView.height = self.bounds.size.height; -} - -- (void)observingInputAccessoryViewKeyboardWillDisappear:(ObservingInputAccessoryView *)observingInputAccessoryView -{ - if (self.paused) { - return; - } - - _bottomViewHeight = [self getBottomSafeArea]; - [self updateBottomViewFrame]; -} - -- (NSArray*)getBreadthFirstSubviewsForView:(UIView*)view -{ - if(view == nil) - { - return nil; - } - - NSMutableArray *allSubviews = [NSMutableArray new]; - NSMutableArray *queue = [NSMutableArray new]; - - [allSubviews addObject:view]; - [queue addObject:view]; - - while ([queue count] > 0) { - UIView *current = [queue lastObject]; - [queue removeLastObject]; - - for (UIView *n in current.subviews) - { - [allSubviews addObject:n]; - [queue insertObject:n atIndex:0]; - } - } - return allSubviews; -} - -- (NSArray*)getAllReactSubviewsForView:(UIView*)view -{ - NSMutableArray *allSubviews = [NSMutableArray new]; - for (UIView *subview in view.reactSubviews) - { - [allSubviews addObject:subview]; - [allSubviews addObjectsFromArray:[self getAllReactSubviewsForView:subview]]; - } - return allSubviews; -} - -- (void)_updateScrollViewInsets -{ - if(self.scrollViewToManage != nil && !self.paused) - { - if (!_initialOffsetIsSet) { - self.initialOffsetY = self.scrollViewToManage.contentOffset.y; - _initialOffsetIsSet = YES; - } - - if (_observingInputAccessoryView.keyboardState != KeyboardStateWillHide && _observingInputAccessoryView.keyboardState != KeyboardStateHidden) { - [self.scrollViewToManage setContentOffset:CGPointMake(self.scrollViewToManage.contentOffset.x, self.initialOffsetY) animated:NO]; - } - - UIEdgeInsets insets = self.scrollViewToManage.contentInset; - CGFloat bottomSafeArea = [self getBottomSafeArea]; - CGFloat bottomInset = MAX(self.bounds.size.height, _observingInputAccessoryView.keyboardHeight + _observingInputAccessoryView.height); - - CGFloat originalBottomInset = self.scrollIsInverted ? insets.top : insets.bottom; - CGPoint originalOffset = self.scrollViewToManage.contentOffset; - CGFloat keyboardHeight = _observingInputAccessoryView.keyboardHeight; - - bottomInset += (keyboardHeight == 0 ? bottomSafeArea : 0); - if(self.scrollIsInverted) - { - insets.top = bottomInset; - } - else - { - insets.bottom = keyboardHeight; - } - self.scrollViewToManage.contentInset = insets; - - if(self.scrollBehavior == KeyboardTrackingScrollBehaviorScrollToBottomInvertedOnly && _scrollIsInverted) - { - BOOL firstTime = _observingInputAccessoryView.keyboardHeight == 0 && _observingInputAccessoryView.keyboardState == KeyboardStateHidden; - BOOL willOpen = _observingInputAccessoryView.keyboardHeight != 0 && _observingInputAccessoryView.keyboardState == KeyboardStateHidden; - BOOL isOpen = _observingInputAccessoryView.keyboardHeight != 0 && _observingInputAccessoryView.keyboardState == KeyboardStateShown; - if(firstTime || willOpen || (isOpen && !self.isDraggingScrollView)) - { - [self.scrollViewToManage setContentOffset:CGPointMake(self.scrollViewToManage.contentOffset.x, self.scrollViewToManage.contentOffset.y) animated:!firstTime]; - } - } - else if(self.scrollBehavior == KeyboardTrackingScrollBehaviorFixedOffset && !self.isDraggingScrollView) - { - CGFloat insetsDiff = (bottomInset - originalBottomInset) * (self.scrollIsInverted ? -1 : 1); - self.scrollViewToManage.contentOffset = CGPointMake(originalOffset.x, originalOffset.y + insetsDiff); - } - - CGFloat kHeight = _observingInputAccessoryView.keyboardHeight; - if (kHeight != 0 && (_observingInputAccessoryView.keyboardState == KeyboardStateShown || _observingInputAccessoryView.keyboardState == KeyboardStateWillShow)) { - kHeight -= (bottomSafeArea + _viewInitialOffsetY); - } - - CGFloat positionY = self.normalList ? 0 : kHeight; - CGRect frame = CGRectMake(self.scrollViewToManage.frame.origin.x, positionY, - self.scrollViewToManage.frame.size.width, self.scrollViewToManage.frame.size.height); - self.scrollViewToManage.frame = frame; - - if (self.accessoriesContainer) { - CGFloat containerPositionY = self.normalList ? 0 : kHeight; - self.accessoriesContainer.bounds = CGRectMake(self.accessoriesContainer.bounds.origin.x, containerPositionY, - self.accessoriesContainer.bounds.size.width, self.accessoriesContainer.bounds.size.height); - } - } -} - -#pragma mark - bottom view - --(void)setAddBottomView:(BOOL)addBottomView -{ - _addBottomView = addBottomView; - [self addBottomViewIfNecessary]; -} - --(void)addBottomViewIfNecessary -{ - if (self.addBottomView && _bottomView == nil) - { - _bottomView = [UIView new]; - [self addSubview:_bottomView]; - [self updateBottomViewFrame]; - } - else if (!self.addBottomView && _bottomView != nil) - { - [_bottomView removeFromSuperview]; - _bottomView = nil; - } -} - --(void)updateBottomViewFrame -{ - if (_bottomView != nil) - { - _bottomView.frame = CGRectMake(0, self.frame.size.height, self.frame.size.width, _bottomViewHeight); - } -} - --(void)setBottomViewBackgroundColor:(UIColor*) color { - if (_bottomView != nil) { - _bottomView.backgroundColor = color; - } -} - -#pragma mark - safe area - --(void)safeAreaInsetsDidChange -{ -#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_10_3 - if (@available(iOS 11.0, *)) { - [super safeAreaInsetsDidChange]; - } -#endif - [self updateTransformAndInsets]; -} - --(CGFloat)getBottomSafeArea -{ - CGFloat bottomSafeArea = 0; -#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_10_3 - if (@available(iOS 11.0, *)) { - bottomSafeArea = self.superview ? self.superview.safeAreaInsets.bottom : self.safeAreaInsets.bottom; - } -#endif - return bottomSafeArea; -} - -#pragma RCTRootView notifications - -- (void) rctContentDidAppearNotification:(NSNotification*)notification -{ - dispatch_async(dispatch_get_main_queue(), ^{ - if(notification.object == [self getRootView] && self->_manageScrollView && self->_scrollViewToManage == nil) - { - [self initializeAccessoryViewsAndHandleInsets]; - } - }); -} - -#pragma mark - ObservingInputAccessoryViewDelegate methods - --(void)updateTransformAndInsets -{ - if (self.paused) { - return; - } - - CGFloat bottomSafeArea = [self getBottomSafeArea]; - CGFloat accessoryTranslation = MIN(-bottomSafeArea, -(_observingInputAccessoryView.keyboardHeight - _viewInitialOffsetY)); - - if (_observingInputAccessoryView.keyboardHeight <= bottomSafeArea) { - _bottomViewHeight = [self getBottomSafeArea]; - } else if (_observingInputAccessoryView.keyboardState != KeyboardStateWillHide) { - _bottomViewHeight = 0; - } - [self updateBottomViewFrame]; - - self.transform = CGAffineTransformMakeTranslation(0, accessoryTranslation); - [self _updateScrollViewInsets]; -} - -- (void)performScrollToFocusedInput -{ - if (_scrollViewToManage != nil && self.scrollToFocusedInput) - { - UIResponder *currentFirstResponder = [UIResponder currentFirstResponder]; - if (currentFirstResponder != nil && [currentFirstResponder isKindOfClass:[UIView class]]) - { - UIView *reponderView = (UIView*)currentFirstResponder; - if ([reponderView isDescendantOfView:_scrollViewToManage]) - { - CGRect frame = [_scrollViewToManage convertRect:reponderView.frame fromView:reponderView]; - frame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height + 20); - [_scrollViewToManage scrollRectToVisible:frame animated:NO]; - } - } - } -} - -- (void)observingInputAccessoryViewDidChangeFrame:(ObservingInputAccessoryView*)observingInputAccessoryView -{ - [self updateTransformAndInsets]; -} - --(void) observingInputAccessoryViewKeyboardWillAppear:(ObservingInputAccessoryView *)observingInputAccessoryView keyboardDelta:(CGFloat)keyboardDelta animationDuration:(double)animationDuration keyboardFrameEndHeight:(CGFloat)keyboardFrameEndHeight { - if (observingInputAccessoryView.keyboardHeight > 0) //prevent hiding the bottom view if an external keyboard is in use - { - _bottomViewHeight = 0; - [self updateBottomViewFrame]; - } - - [self performScrollToFocusedInput]; - _onKeyboardWillShow(@{ - @"trackingViewHeight": [NSNumber numberWithDouble:self.bounds.size.height], - @"keyboardHeight": [NSNumber numberWithDouble:[self getKeyboardHeight]], - @"contentTopInset": [NSNumber numberWithDouble:[self getScrollViewTopContentInset]], - @"animationDuration": [NSNumber numberWithDouble:animationDuration * 1000], - @"keyboardFrameEndHeight": [NSNumber numberWithDouble:keyboardFrameEndHeight], - }); -} - -#pragma mark - UIScrollViewDelegate methods - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - if(_observingInputAccessoryView.keyboardState != KeyboardStateHidden || !self.revealKeyboardInteractive) - { - return; - } - - UIView *inputView = [_inputViewsMap objectForKey:@(kInputViewKey)]; - if (inputView != nil && scrollView.contentOffset.y * (self.scrollIsInverted ? -1 : 1) > (self.scrollIsInverted ? scrollView.contentInset.top : scrollView.contentInset.bottom) + 50 && ![inputView isFirstResponder]) - { - for (UIGestureRecognizer *gesture in scrollView.gestureRecognizers) - { - if([gesture isKindOfClass:[UIPanGestureRecognizer class]]) - { - gesture.enabled = NO; - gesture.enabled = YES; - } - } - - [inputView reactFocus]; - } -} - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - self.isDraggingScrollView = YES; - _initialOffsetIsSet = NO; - self.initialOffsetY = scrollView.contentOffset.y; -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset -{ - self.isDraggingScrollView = NO; -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate -{ - self.isDraggingScrollView = NO; - self.initialOffsetY = scrollView.contentOffset.y; -} - -- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { - self.initialOffsetY = scrollView.contentOffset.y; -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { - self.initialOffsetY = scrollView.contentOffset.y; -} - -- (CGFloat)getKeyboardHeight -{ - return _observingInputAccessoryView ? _observingInputAccessoryView.keyboardHeight : 0; -} - --(CGFloat)getScrollViewTopContentInset -{ - return (self.scrollViewToManage != nil) ? -self.scrollViewToManage.contentInset.top : 0; -} - --(void)scrollToStart -{ - if (self.scrollViewToManage != nil) - { - [self.scrollViewToManage setContentOffset:CGPointMake(self.scrollViewToManage.contentOffset.x, -self.scrollViewToManage.contentInset.top) animated:YES]; - } -} -@end diff --git a/libraries/@mattermost/keyboard-tracker/ios/Views/ObservingInputAccessoryView.h b/libraries/@mattermost/keyboard-tracker/ios/Views/ObservingInputAccessoryView.h deleted file mode 100644 index 08dd907bd..000000000 --- a/libraries/@mattermost/keyboard-tracker/ios/Views/ObservingInputAccessoryView.h +++ /dev/null @@ -1,36 +0,0 @@ -#import - -typedef NS_ENUM(NSUInteger, KeyboardState) { - KeyboardStateHidden, - KeyboardStateWillShow, - KeyboardStateShown, - KeyboardStateWillHide -}; - -@class ObservingInputAccessoryView; - -@interface ObservingInputAccessoryViewManager : NSObject; -+(ObservingInputAccessoryViewManager*)sharedInstance; -@property (nonatomic, weak) ObservingInputAccessoryView *activeObservingInputAccessoryView; -@end - -@protocol ObservingInputAccessoryViewDelegate - -- (void)observingInputAccessoryViewDidChangeFrame:(ObservingInputAccessoryView*)observingInputAccessoryView; - -@optional - -- (void) observingInputAccessoryViewKeyboardWillAppear:(ObservingInputAccessoryView *)observingInputAccessoryView keyboardDelta:(CGFloat)keyboardDelta animationDuration:(double)animationDuration keyboardFrameEndHeight:(CGFloat)keyboardFrameEndHeight; -- (void)observingInputAccessoryViewKeyboardWillDisappear:(ObservingInputAccessoryView*)observingInputAccessoryView; - -@end - -@interface ObservingInputAccessoryView : UIView - -@property (nonatomic, weak) id delegate; - -@property (nonatomic) CGFloat height; -@property (nonatomic, readonly) CGFloat keyboardHeight; -@property (nonatomic, readonly) KeyboardState keyboardState; - -@end diff --git a/libraries/@mattermost/keyboard-tracker/ios/Views/ObservingInputAccessoryView.m b/libraries/@mattermost/keyboard-tracker/ios/Views/ObservingInputAccessoryView.m deleted file mode 100644 index 88be971db..000000000 --- a/libraries/@mattermost/keyboard-tracker/ios/Views/ObservingInputAccessoryView.m +++ /dev/null @@ -1,172 +0,0 @@ -#import "ObservingInputAccessoryView.h" - -@implementation ObservingInputAccessoryViewManager - -+(ObservingInputAccessoryViewManager*)sharedInstance -{ - static ObservingInputAccessoryViewManager *instance = nil; - static dispatch_once_t observingInputAccessoryViewManagerOnceToken = 0; - - dispatch_once(&observingInputAccessoryViewManagerOnceToken,^ - { - if (instance == nil) - { - instance = [ObservingInputAccessoryViewManager new]; - } - }); - - return instance; -} - -@end - -@implementation ObservingInputAccessoryView -{ - CGFloat _previousKeyboardHeight; -} - -- (instancetype)init -{ - self = [super init]; - - if(self) - { - self.userInteractionEnabled = NO; - self.translatesAutoresizingMaskIntoConstraints = NO; - self.autoresizingMask = UIViewAutoresizingFlexibleHeight; - - [self registerForKeyboardNotifications]; - } - - return self; -} - -- (void) registerForKeyboardNotifications -{ - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter addObserver:self selector:@selector(_keyboardWillShowNotification:) name:UIKeyboardWillShowNotification object:nil]; - [notificationCenter addObserver:self selector:@selector(_keyboardDidShowNotification:) name:UIKeyboardDidShowNotification object:nil]; - [notificationCenter addObserver:self selector:@selector(_keyboardWillHideNotification:) name:UIKeyboardWillHideNotification object:nil]; - [notificationCenter addObserver:self selector:@selector(_keyboardDidHideNotification:) name:UIKeyboardDidHideNotification object:nil]; - [notificationCenter addObserver:self selector:@selector(_keyboardWillChangeFrameNotification:) name:UIKeyboardWillChangeFrameNotification object:nil]; -} - -- (void)willMoveToSuperview:(UIView *)newSuperview -{ - if (self.superview) - { - [self.superview removeObserver:self forKeyPath:@"center"]; - } - - if (newSuperview != nil) - { - [newSuperview addObserver:self forKeyPath:@"center" options:NSKeyValueObservingOptionNew context:nil]; - } - - [super willMoveToSuperview:newSuperview]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - if ((object == self.superview) && ([keyPath isEqualToString:@"center"])) - { - CGFloat centerY = self.superview.center.y; - - if([keyPath isEqualToString:@"center"]) - { - centerY = [change[NSKeyValueChangeNewKey] CGPointValue].y; - } - - CGFloat boundsH = self.superview.bounds.size.height; - - _previousKeyboardHeight = _keyboardHeight; - _keyboardHeight = MAX(0, self.window.bounds.size.height - (centerY - boundsH / 2) - self.intrinsicContentSize.height); - - [_delegate observingInputAccessoryViewDidChangeFrame:self]; - } -} - --(void)dealloc -{ - [self.superview removeObserver:self forKeyPath:@"center"]; - - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (CGSize)intrinsicContentSize -{ - return CGSizeMake(self.bounds.size.width, _keyboardState == KeyboardStateWillShow || _keyboardState == KeyboardStateWillHide ? 0 : _height); -} - -- (void)setHeight:(CGFloat)height -{ - _height = height; - - [self invalidateIntrinsicContentSize]; -} - -- (void)_keyboardWillShowNotification:(NSNotification*)notification -{ - if (_keyboardState != KeyboardStateShown) { - _keyboardState = KeyboardStateWillShow; - - [self invalidateIntrinsicContentSize]; - - if([_delegate respondsToSelector:@selector(observingInputAccessoryViewKeyboardWillAppear:keyboardDelta:animationDuration:keyboardFrameEndHeight:)]) - { - NSDictionary *userInfo = notification.userInfo; - CGFloat delta = _keyboardHeight - _previousKeyboardHeight; - NSValue *frameEndValue = userInfo[UIKeyboardFrameEndUserInfoKey]; - CGFloat keyboardFrameEnd = [frameEndValue CGRectValue].size.height; - NSNumber *animationDurationValue = userInfo[UIKeyboardAnimationDurationUserInfoKey]; - NSTimeInterval animationDuration = [animationDurationValue doubleValue]; - - [_delegate observingInputAccessoryViewKeyboardWillAppear:self keyboardDelta:delta animationDuration:animationDuration keyboardFrameEndHeight:keyboardFrameEnd]; - } - } -} - -- (void)_keyboardDidShowNotification:(NSNotification*)notification -{ - if (_keyboardState != KeyboardStateShown) { - _keyboardState = KeyboardStateShown; - - [self invalidateIntrinsicContentSize]; - } -} - -- (void)_keyboardWillHideNotification:(NSNotification*)notification -{ - _keyboardState = KeyboardStateWillHide; - - [self invalidateIntrinsicContentSize]; - - if([_delegate respondsToSelector:@selector(observingInputAccessoryViewKeyboardWillDisappear:)]) - { - [_delegate observingInputAccessoryViewKeyboardWillDisappear:self]; - } -} - -- (void)_keyboardDidHideNotification:(NSNotification*)notification -{ - _keyboardState = KeyboardStateHidden; - - [self invalidateIntrinsicContentSize]; -} - -- (void)_keyboardWillChangeFrameNotification:(NSNotification*)notification -{ - if(self.window) - { - return; - } - - CGRect endFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; - _keyboardHeight = [UIScreen mainScreen].bounds.size.height - endFrame.origin.y; - - [_delegate observingInputAccessoryViewDidChangeFrame:self]; - - [self invalidateIntrinsicContentSize]; -} - -@end diff --git a/libraries/@mattermost/keyboard-tracker/mattermost-keyboard-tracker.podspec b/libraries/@mattermost/keyboard-tracker/mattermost-keyboard-tracker.podspec deleted file mode 100644 index 30c5b07cb..000000000 --- a/libraries/@mattermost/keyboard-tracker/mattermost-keyboard-tracker.podspec +++ /dev/null @@ -1,23 +0,0 @@ -require "json" - -package = JSON.parse(File.read(File.join(__dir__, "package.json"))) - -Pod::Spec.new do |s| - s.name = "mattermost-keyboard-tracker" - s.version = package["version"] - s.summary = package["description"] - s.homepage = package["homepage"] - s.license = package["license"] - s.authors = package["author"] - - s.platforms = { :ios => min_ios_version_supported } - s.source = { :git => ".git", :tag => "#{s.version}" } - - s.source_files = "ios/**/*.{h,m,mm}" - - install_modules_dependencies(s) - - s.pod_target_xcconfig = { - "CLANG_CXX_LANGUAGE_STANDARD" => "c++20", - } -end diff --git a/libraries/@mattermost/keyboard-tracker/package.json b/libraries/@mattermost/keyboard-tracker/package.json deleted file mode 100644 index 3b2286f7a..000000000 --- a/libraries/@mattermost/keyboard-tracker/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@mattermost/keyboard-tracker", - "version": "0.0.0", - "description": "component to track the keyboard", - "main": "src/index", - "codegenConfig": { - "name": "MattermostKeyboardTrackerSpec", - "type": "all", - "jsSrcsDir": "src", - "platforms": ["ios"] - }, - "author": "Mattermost, Inc.", - "license": "Apache 2.0", - "private": true, - "homepage": "#readme" -} diff --git a/libraries/@mattermost/keyboard-tracker/src/MattermostKeyboardTrackerViewNativeComponent.ts b/libraries/@mattermost/keyboard-tracker/src/MattermostKeyboardTrackerViewNativeComponent.ts deleted file mode 100644 index 167ad7c1f..000000000 --- a/libraries/@mattermost/keyboard-tracker/src/MattermostKeyboardTrackerViewNativeComponent.ts +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands'; -import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; - -import type {ColorValue, HostComponent, ViewProps} from 'react-native'; -import type {BubblingEventHandler, Double, Float, WithDefault} from 'react-native/Libraries/Types/CodegenTypes'; - -type ScrollBehavior = 'KeyboardTrackingScrollBehaviorNone' | 'KeyboardTrackingScrollBehaviorScrollToBottomInvertedOnly' | 'KeyboardTrackingScrollBehaviorFixedOffset' - -export type NativeKeyboardTrackerProps = Readonly<{ - trackingViewHeight: Double; - keyboardHeight: Double; - contentTopInset: Double; - animationDuration: Double; - keyboardFrameEndHeight: Float; -}> - -export type KeyboardWillShowEventData = { - nativeEvent: { - trackingViewHeight: number; - keyboardHeight: number; - contentTopInset: number; - animationDuration: number; - keyboardFrameEndHeight: number; - }; -} - -export interface NativeProps extends ViewProps { - scrollBehavior?: WithDefault; - revealKeyboardInteractive?: boolean; - manageScrollView?: WithDefault; - requiresSameParentToManageScrollView?: WithDefault; - addBottomView?: boolean; - scrollToFocusedInput?: boolean; - allowHitsOutsideBounds?: boolean; - normalList?: boolean; - viewInitialOffsetY?: WithDefault; - scrollViewNativeID?: string; - accessoriesContainerID?: string; - backgroundColor?: ColorValue; - onKeyboardWillShow?: BubblingEventHandler; -} - -export type MattermostKeyboardTrackerNativeComponentType = HostComponent; - -interface NativeCommands { - readonly resetScrollView: ( - viewRef: React.ElementRef, - scrollViewNativeID: string, - ) => void; - readonly pauseTracking: ( - viewRef: React.ElementRef, - scrollViewNativeID: string, - ) => void; - readonly resumeTracking: ( - viewRef: React.ElementRef, - scrollViewNativeID: string, - ) => void; - readonly scrollToStart: ( - viewRef: React.ElementRef, - ) => void; -} - -export const Commands: NativeCommands = codegenNativeCommands({ - supportedCommands: ['resetScrollView', 'pauseTracking', 'resumeTracking', 'scrollToStart'], -}); - -export default codegenNativeComponent('MattermostKeyboardTrackerView'); diff --git a/libraries/@mattermost/keyboard-tracker/src/index.tsx b/libraries/@mattermost/keyboard-tracker/src/index.tsx deleted file mode 100644 index 88784a4cc..000000000 --- a/libraries/@mattermost/keyboard-tracker/src/index.tsx +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {forwardRef, useCallback, useImperativeHandle, useRef} from 'react'; -import {DeviceEventEmitter} from 'react-native'; - -import MattermostKeyboardTrackerView, {Commands, type KeyboardWillShowEventData, type NativeProps} from './MattermostKeyboardTrackerViewNativeComponent'; - -export * from './MattermostKeyboardTrackerViewNativeComponent'; - -export type KeyboardTrackingViewRef = { - resetScrollView: (scrollViewNativeID: string) => void; - pauseTracking: (scrollViewNativeID: string) => void; - resumeTracking: (scrollViewNativeID: string) => void; - scrollToStart: () => void; -}; - -type Props = NativeProps & { - onKeyboardWillShow?: (e: KeyboardWillShowEventData) => void; -} - -const KeyboardTrackingView = forwardRef((props, ref) => { - const viewRef = useRef(null); - - useImperativeHandle(ref, () => ({ - resetScrollView(scrollViewNativeID: string) { - if (viewRef.current) { - Commands.resumeTracking(viewRef.current, scrollViewNativeID); - } - }, - pauseTracking(scrollViewNativeID: string) { - if (viewRef.current) { - Commands.pauseTracking(viewRef.current, scrollViewNativeID); - } - }, - resumeTracking(scrollViewNativeID: string) { - if (viewRef.current) { - Commands.resumeTracking(viewRef.current, scrollViewNativeID); - } - }, - scrollToStart() { - if (viewRef.current) { - Commands.scrollToStart(viewRef.current); - } - }, - })); - - const _onKeyboardShow = useCallback((e: KeyboardWillShowEventData) => { - DeviceEventEmitter.emit('MattermostKeyboardTrackerView', e); - props.onKeyboardWillShow?.(e); - }, [props.onKeyboardWillShow]); - - return ( - - ); -}); - -KeyboardTrackingView.displayName = 'KeyboardTrackingView'; - -export default KeyboardTrackingView; diff --git a/libraries/@mattermost/rnutils/android/src/main/java/com/mattermost/rnutils/RNUtilsModuleImpl.kt b/libraries/@mattermost/rnutils/android/src/main/java/com/mattermost/rnutils/RNUtilsModuleImpl.kt index d79ae9bef..60c2921e7 100644 --- a/libraries/@mattermost/rnutils/android/src/main/java/com/mattermost/rnutils/RNUtilsModuleImpl.kt +++ b/libraries/@mattermost/rnutils/android/src/main/java/com/mattermost/rnutils/RNUtilsModuleImpl.kt @@ -2,6 +2,8 @@ package com.mattermost.rnutils import android.app.Activity import android.net.Uri +import android.view.WindowManager +import androidx.core.view.OnApplyWindowInsetsListener import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext @@ -13,6 +15,8 @@ import com.mattermost.rnutils.helpers.SaveDataTask import com.mattermost.rnutils.helpers.SplitView class RNUtilsModuleImpl(private val reactContext: ReactApplicationContext) { + private var customInsetsListener: OnApplyWindowInsetsListener? = null + companion object { const val NAME = "RNUtils" @@ -121,4 +125,20 @@ class RNUtilsModuleImpl(private val reactContext: ReactApplicationContext) { fun removeServerNotifications(serverUrl: String?) { serverUrl?.let { Notifications.removeServerNotifications(it) } } + + fun setSoftKeyboardToAdjustNothing() { + val currentActivity: Activity = reactContext.currentActivity ?: return + + currentActivity.runOnUiThread { + currentActivity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) + } + } + + fun setSoftKeyboardToAdjustResize() { + val currentActivity: Activity = reactContext.currentActivity ?: return + + currentActivity.runOnUiThread { + currentActivity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) + } + } } diff --git a/libraries/@mattermost/rnutils/android/src/newarch/RNUtilsModule.kt b/libraries/@mattermost/rnutils/android/src/newarch/RNUtilsModule.kt index a5f8c369c..20f356269 100644 --- a/libraries/@mattermost/rnutils/android/src/newarch/RNUtilsModule.kt +++ b/libraries/@mattermost/rnutils/android/src/newarch/RNUtilsModule.kt @@ -67,4 +67,12 @@ class RNUtilsModule(val reactContext: ReactApplicationContext) : NativeRNUtilsSp override fun removeServerNotifications(serverUrl: String?) { implementation.removeServerNotifications(serverUrl) } + + override fun setSoftKeyboardToAdjustResize() { + implementation.setSoftKeyboardToAdjustResize() + } + + override fun setSoftKeyboardToAdjustNothing() { + implementation.setSoftKeyboardToAdjustNothing() + } } diff --git a/libraries/@mattermost/rnutils/android/src/oldarch/RNUtilsModule.kt b/libraries/@mattermost/rnutils/android/src/oldarch/RNUtilsModule.kt index e28b0ba95..08558b78e 100644 --- a/libraries/@mattermost/rnutils/android/src/oldarch/RNUtilsModule.kt +++ b/libraries/@mattermost/rnutils/android/src/oldarch/RNUtilsModule.kt @@ -98,4 +98,14 @@ class RNUtilsModule(context: ReactApplicationContext) : fun removeServerNotifications(serverUrl: String?) { implementation.removeServerNotifications(serverUrl) } + + @ReactMethod + fun setSoftKeyboardToAdjustResize() { + implementation.setSoftKeyboardToAdjustResize() + } + + @ReactMethod + fun setSoftKeyboardToAdjustNothing() { + implementation.setSoftKeyboardToAdjustNothing() + } } diff --git a/libraries/@mattermost/rnutils/ios/RNUtils.mm b/libraries/@mattermost/rnutils/ios/RNUtils.mm index 489d5f567..9bec52197 100644 --- a/libraries/@mattermost/rnutils/ios/RNUtils.mm +++ b/libraries/@mattermost/rnutils/ios/RNUtils.mm @@ -123,6 +123,14 @@ RCT_EXPORT_METHOD(saveFile:(NSString *)filePath [self saveFile:filePath resolve:resolve reject:reject]; } +RCT_REMAP_METHOD(setSoftKeyboardToAdjustResize, setAdjustResize) { + [self setSoftKeyboardToAdjustResize]; +} + +RCT_REMAP_METHOD(setSoftKeyboardToAdjustNothing, setAdjustNothing) { + [self setSoftKeyboardToAdjustNothing]; +} + // Don't compile this code when we build for the old architecture. #ifdef RCT_NEW_ARCH_ENABLED - (std::shared_ptr)getTurboModule: @@ -216,6 +224,14 @@ RCT_EXPORT_METHOD(saveFile:(NSString *)filePath resolve(@""); } +-(void)setSoftKeyboardToAdjustResize { + // Do nothing as it does not apply to iOS +} + +-(void)setSoftKeyboardToAdjustNothing { + // Do nothing as it does not apply to iOS +} + #pragma helpers -(void)getNotifications:(RCTPromiseResolveBlock)resolve { [[NotificationManager shared] getDeliveredNotificationsWithCompletionHandler:^(NSArray * _Nonnull notifications) { diff --git a/libraries/@mattermost/rnutils/src/NativeRNUtils.ts b/libraries/@mattermost/rnutils/src/NativeRNUtils.ts index 7ab46c89c..c39f8dd23 100644 --- a/libraries/@mattermost/rnutils/src/NativeRNUtils.ts +++ b/libraries/@mattermost/rnutils/src/NativeRNUtils.ts @@ -70,6 +70,9 @@ export interface Spec extends TurboModule { removeChannelNotifications(serverUrl: string, channelId: string): void; removeThreadNotifications(serverUrl: string, threadId: string): void; removeServerNotifications(serverUrl: string): void; + + setSoftKeyboardToAdjustResize(): void; + setSoftKeyboardToAdjustNothing(): void; } export default TurboModuleRegistry.getEnforcing('RNUtils'); diff --git a/package-lock.json b/package-lock.json index 616f26d47..9e778ef84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "mattermost-mobile", - "version": "2.22.0", + "version": "2.23.0", "hasInstallScript": true, "license": "Apache 2.0", "dependencies": { @@ -20,7 +20,6 @@ "@mattermost/calls": "github:mattermost/calls-common#030ff7c0a37ee9b0ccc5fae0b2ea9c0e1c08473f", "@mattermost/compass-icons": "0.1.45", "@mattermost/hardware-keyboard": "file:./libraries/@mattermost/hardware-keyboard", - "@mattermost/keyboard-tracker": "file:./libraries/@mattermost/keyboard-tracker", "@mattermost/react-native-emm": "1.5.0", "@mattermost/react-native-network-client": "1.7.3", "@mattermost/react-native-paste-input": "0.8.0", @@ -176,6 +175,7 @@ }, "libraries/@mattermost/keyboard-tracker": { "version": "0.0.0", + "extraneous": true, "license": "Apache 2.0" }, "libraries/@mattermost/rnshare": { @@ -5880,10 +5880,6 @@ "resolved": "libraries/@mattermost/hardware-keyboard", "link": true }, - "node_modules/@mattermost/keyboard-tracker": { - "resolved": "libraries/@mattermost/keyboard-tracker", - "link": true - }, "node_modules/@mattermost/react-native-emm": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@mattermost/react-native-emm/-/react-native-emm-1.5.0.tgz", diff --git a/package.json b/package.json index 4ecea74ab..f57375c75 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "@mattermost/calls": "github:mattermost/calls-common#030ff7c0a37ee9b0ccc5fae0b2ea9c0e1c08473f", "@mattermost/compass-icons": "0.1.45", "@mattermost/hardware-keyboard": "file:./libraries/@mattermost/hardware-keyboard", - "@mattermost/keyboard-tracker": "file:./libraries/@mattermost/keyboard-tracker", "@mattermost/react-native-emm": "1.5.0", "@mattermost/react-native-network-client": "1.7.3", "@mattermost/react-native-paste-input": "0.8.0", diff --git a/patches/react-native-navigation+7.40.1.patch b/patches/react-native-navigation+7.40.1.patch index 9d32075b3..9a4f83423 100644 --- a/patches/react-native-navigation+7.40.1.patch +++ b/patches/react-native-navigation+7.40.1.patch @@ -296,6 +296,37 @@ index 834d734..3fc00c3 100644 want |= Typeface.ITALIC; } +diff --git a/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/child/ChildController.java b/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/child/ChildController.java +index 3acc41d..ebc8d18 100644 +--- a/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/child/ChildController.java ++++ b/node_modules/react-native-navigation/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/child/ChildController.java +@@ -7,6 +7,7 @@ import android.util.Log; + import android.view.View; + import android.view.ViewGroup; + import android.view.WindowInsets; ++import android.view.WindowManager; + + import com.reactnativenavigation.options.Options; + import com.reactnativenavigation.utils.LogKt; +@@ -100,6 +101,18 @@ public abstract class ChildController extends ViewControlle + } + + protected WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat insets) { ++ Activity activity = getActivity(); ++ Insets ime = insets.getInsets(WindowInsetsCompat.Type.ime()); ++ if (activity != null) { ++ int softInputMode = activity.getWindow().getAttributes().softInputMode; ++ if (softInputMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING && !isRoot()) { ++ view.setPadding(ime.left, ime.top, ime.right, 0); ++ return WindowInsetsCompat.CONSUMED; ++ } ++ } ++ if (!isRoot()) { ++ view.setPadding(0, 0, 0, 0); ++ } + return insets; + } + diff --git a/node_modules/react-native-navigation/lib/android/app/src/reactNative71/java/com/reactnativenavigation/react/ReactGateway.java b/node_modules/react-native-navigation/lib/android/app/src/reactNative71/java/com/reactnativenavigation/react/ReactGateway.java index 035ec31..630b8d4 100644 --- a/node_modules/react-native-navigation/lib/android/app/src/reactNative71/java/com/reactnativenavigation/react/ReactGateway.java diff --git a/test/intl-test-helper.tsx b/test/intl-test-helper.tsx index 159295c14..e39cc1116 100644 --- a/test/intl-test-helper.tsx +++ b/test/intl-test-helper.tsx @@ -7,6 +7,7 @@ import React, {type ReactElement} from 'react'; import {IntlProvider} from 'react-intl'; import {SafeAreaProvider} from 'react-native-safe-area-context'; +import {ExtraKeyboardProvider} from '@context/extra_keyboard'; import ServerUrlProvider from '@context/server'; import {ThemeContext, getDefaultThemeByAppearance} from '@context/theme'; import {getTranslations} from '@i18n'; @@ -63,7 +64,9 @@ export function renderWithEverything(ui: ReactElement, {locale = 'en', database, > - {children} + + {children} +