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})
+ }>
+
+
+
>
)}