diff --git a/src/screens/Messages/components/MessageInput.tsx b/src/screens/Messages/components/MessageInput.tsx index 85509211b1..1a05deea0a 100644 --- a/src/screens/Messages/components/MessageInput.tsx +++ b/src/screens/Messages/components/MessageInput.tsx @@ -29,6 +29,7 @@ import * as Toast from '#/view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' import {useSharedInputStyles} from '#/components/forms/TextField' import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' +import {MessageInputBackground} from './MessageInputBackground' import {useExtractEmbedFromFacets} from './MessageInputEmbed' const AnimatedTextInput = Animated.createAnimatedComponent(TextInput) @@ -127,7 +128,7 @@ export function MessageInput({ })) return ( - + {children} setIsFocused(true)} onBlur={() => setIsFocused(false)} ref={inputRef} @@ -184,6 +185,6 @@ export function MessageInput({ - + ) } diff --git a/src/screens/Messages/components/MessageInputBackground.ios.tsx b/src/screens/Messages/components/MessageInputBackground.ios.tsx new file mode 100644 index 0000000000..a9acaf59f2 --- /dev/null +++ b/src/screens/Messages/components/MessageInputBackground.ios.tsx @@ -0,0 +1,25 @@ +import {View} from 'react-native' +import {BlurView} from 'expo-blur' + +import {atoms as a, useTheme, ViewStyleProp} from '#/alf' + +export function MessageInputBackground({ + style, + children, +}: React.PropsWithChildren) { + const t = useTheme() + return ( + + + {children} + + ) +} diff --git a/src/screens/Messages/components/MessageInputBackground.tsx b/src/screens/Messages/components/MessageInputBackground.tsx new file mode 100644 index 0000000000..1ef44f4306 --- /dev/null +++ b/src/screens/Messages/components/MessageInputBackground.tsx @@ -0,0 +1,11 @@ +import {View} from 'react-native' + +import {useTheme, ViewStyleProp} from '#/alf' + +export function MessageInputBackground({ + style, + children, +}: React.PropsWithChildren) { + const t = useTheme() + return {children} +} diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx index 071ce1cd71..6cbdcaef5c 100644 --- a/src/screens/Messages/components/MessagesList.tsx +++ b/src/screens/Messages/components/MessagesList.tsx @@ -4,15 +4,14 @@ import {useKeyboardHandler} from 'react-native-keyboard-controller' import Animated, { runOnJS, scrollTo, + useAnimatedProps, useAnimatedRef, useAnimatedStyle, useSharedValue, } from 'react-native-reanimated' import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook/commonTypes' -import {useSafeAreaInsets} from 'react-native-safe-area-context' import {AppBskyEmbedRecord, AppBskyRichtextFacet, RichText} from '@atproto/api' -import {clamp} from '#/lib/numbers' import {ScrollProvider} from '#/lib/ScrollContext' import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' import { @@ -20,20 +19,22 @@ import { isBskyPostUrl, } from '#/lib/strings/url-helpers' import {logger} from '#/logger' -import {isNative} from '#/platform/detection' +import {isIOS, isNative} from '#/platform/detection' import {isWeb} from '#/platform/detection' import {isConvoActive, useConvoActive} from '#/state/messages/convo' import {ConvoItem, ConvoStatus} from '#/state/messages/convo/types' import {useGetPost} from '#/state/queries/post' import {useAgent} from '#/state/session' +import {useShellLayout} from '#/state/shell/shell-layout' import { EmojiPicker, EmojiPickerState, } from '#/view/com/composer/text-input/web/EmojiPicker.web' -import {List, ListMethods} from '#/view/com/util/List' +import {List, ListMethods, ListProps} from '#/view/com/util/List' import {ChatDisabled} from '#/screens/Messages/components/ChatDisabled' import {MessageInput} from '#/screens/Messages/components/MessageInput' import {MessageListError} from '#/screens/Messages/components/MessageListError' +import {atoms as a} from '#/alf' import {ChatEmptyPill} from '#/components/dms/ChatEmptyPill' import {MessageItem} from '#/components/dms/MessageItem' import {NewMessagesPill} from '#/components/dms/NewMessagesPill' @@ -90,6 +91,8 @@ export function MessagesList({ const agent = useAgent() const getPost = useGetPost() const {embedUri, setEmbed} = useMessageEmbed() + const {footerHeight} = useShellLayout() + const messageInputHeight = useSharedValue(52) const flatListRef = useAnimatedRef() @@ -237,9 +240,6 @@ export function MessagesList({ ) // -- Keyboard animation handling - const {bottom: bottomInset} = useSafeAreaInsets() - const bottomOffset = isWeb ? 0 : clamp(60 + bottomInset, 60, 75) - const keyboardHeight = useSharedValue(0) const keyboardIsOpening = useSharedValue(false) @@ -263,28 +263,68 @@ export function MessagesList({ onMove: e => { 'worklet' keyboardHeight.set(e.height) - if (e.height > bottomOffset) { + if (isIOS || e.height > footerHeight.get() + messageInputHeight.get()) { scrollTo(flatListRef, 0, 1e7, false) } }, + onInteractive: e => { + 'worklet' + keyboardHeight.set(e.height) + }, onEnd: e => { 'worklet' keyboardHeight.set(e.height) - if (e.height > bottomOffset) { + if (isIOS || e.height > footerHeight.get() + messageInputHeight.get()) { scrollTo(flatListRef, 0, 1e7, false) } keyboardIsOpening.set(false) }, }, - [bottomOffset], + [], ) - const animatedListStyle = useAnimatedStyle(() => ({ - marginBottom: Math.max(keyboardHeight.get(), bottomOffset), - })) + const animatedListStyle = useAnimatedStyle(() => { + if (!isIOS) { + return { + marginBottom: Math.max( + keyboardHeight.get(), + footerHeight.get() + messageInputHeight.get(), + ), + } + } + return {} + }) + + const animatedProps = useAnimatedProps(() => { + if (isIOS) { + return { + scrollIndicatorInsets: { + top: 0, + left: 0, + right: 1, + bottom: + Math.max(keyboardHeight.get(), footerHeight.get()) + + messageInputHeight.get(), + }, + contentInset: { + top: 0, + left: 0, + right: 0, + bottom: + Math.max(keyboardHeight.get(), footerHeight.get()) + + messageInputHeight.get(), + }, + } satisfies Partial + } + return {} + }) const animatedStickyViewStyle = useAnimatedStyle(() => ({ - transform: [{translateY: -Math.max(keyboardHeight.get(), bottomOffset)}], + transform: [ + { + translateY: -Math.max(keyboardHeight.get(), footerHeight.get()), + }, + ], })) // -- Message sending @@ -406,11 +446,12 @@ export function MessagesList({ disableFullWindowScroll={true} disableVirtualization={true} style={animatedListStyle} + animatedProps={animatedProps} // The extra two items account for the header and the footer components initialNumToRender={isNative ? 32 : 62} maxToRenderPerBatch={isWeb ? 32 : 62} - keyboardDismissMode="on-drag" - keyboardShouldPersistTaps="handled" + keyboardDismissMode={isIOS ? 'interactive' : 'on-drag'} + keyboardShouldPersistTaps={isIOS ? 'always' : 'handled'} maintainVisibleContentPosition={{ minIndexForVisible: 0, }} @@ -424,9 +465,15 @@ export function MessagesList({ ListHeaderComponent={ } + automaticallyAdjustContentInsets={false} /> - + {convoState.status === ConvoStatus.Disabled ? ( ) : blocked ? ( @@ -436,13 +483,20 @@ export function MessagesList({ {isConvoActive(convoState) && !convoState.isFetchingHistory && convoState.items.length === 0 && } - setEmojiPickerState({isOpen: true, pos})}> - - + + messageInputHeight.set(evt.nativeEvent.layout.height) + }> + + setEmojiPickerState({isOpen: true, pos}) + }> + + + )}