use contentInset

This commit is contained in:
Samuel Newman
2025-06-23 15:52:31 +03:00
parent 96e9ed33d2
commit e39f130f6e
2 changed files with 86 additions and 25 deletions
@@ -26,7 +26,7 @@ import {
} from '#/state/messages/message-drafts' } from '#/state/messages/message-drafts'
import {type EmojiPickerPosition} from '#/view/com/composer/text-input/web/EmojiPicker' import {type EmojiPickerPosition} from '#/view/com/composer/text-input/web/EmojiPicker'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {android, atoms as a, useTheme} from '#/alf' import {android, atoms as a, useTheme, web} from '#/alf'
import {useSharedInputStyles} from '#/components/forms/TextField' import {useSharedInputStyles} from '#/components/forms/TextField'
import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
import {useExtractEmbedFromFacets} from './MessageInputEmbed' import {useExtractEmbedFromFacets} from './MessageInputEmbed'
@@ -132,7 +132,7 @@ export function MessageInput({
})) }))
return ( return (
<View style={[a.px_md, a.pb_sm, a.pt_xs]}> <View style={[a.px_md, a.pb_sm, web([a.pt_xs])]}>
{children} {children}
<View <View
style={[ style={[
@@ -4,11 +4,13 @@ import {useKeyboardHandler} from 'react-native-keyboard-controller'
import Animated, { import Animated, {
runOnJS, runOnJS,
scrollTo, scrollTo,
useAnimatedProps,
useAnimatedRef, useAnimatedRef,
useAnimatedStyle, useAnimatedStyle,
useSharedValue, useSharedValue,
} from 'react-native-reanimated' } from 'react-native-reanimated'
import {type ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook/commonTypes' import {type ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook/commonTypes'
import {LinearGradient} from 'expo-linear-gradient'
import { import {
type $Typed, type $Typed,
type AppBskyEmbedRecord, type AppBskyEmbedRecord,
@@ -24,7 +26,7 @@ import {
isBskyPostUrl, isBskyPostUrl,
} from '#/lib/strings/url-helpers' } from '#/lib/strings/url-helpers'
import {logger} from '#/logger' import {logger} from '#/logger'
import {isNative} from '#/platform/detection' import {isIOS, isNative} from '#/platform/detection'
import {isWeb} from '#/platform/detection' import {isWeb} from '#/platform/detection'
import { import {
type ActiveConvoStates, type ActiveConvoStates,
@@ -43,10 +45,12 @@ import {
EmojiPicker, EmojiPicker,
type EmojiPickerState, type EmojiPickerState,
} from '#/view/com/composer/text-input/web/EmojiPicker' } from '#/view/com/composer/text-input/web/EmojiPicker'
import {List, type ListMethods} from '#/view/com/util/List' import {List, type ListMethods, type ListProps} from '#/view/com/util/List'
import {ChatDisabled} from '#/screens/Messages/components/ChatDisabled' import {ChatDisabled} from '#/screens/Messages/components/ChatDisabled'
import {MessageInput} from '#/screens/Messages/components/MessageInput' import {MessageInput} from '#/screens/Messages/components/MessageInput'
import {MessageListError} from '#/screens/Messages/components/MessageListError' import {MessageListError} from '#/screens/Messages/components/MessageListError'
import {atoms as a, useTheme} from '#/alf'
import {transparentifyColor} from '#/alf/util/colorGeneration'
import {ChatEmptyPill} from '#/components/dms/ChatEmptyPill' import {ChatEmptyPill} from '#/components/dms/ChatEmptyPill'
import {MessageItem} from '#/components/dms/MessageItem' import {MessageItem} from '#/components/dms/MessageItem'
import {NewMessagesPill} from '#/components/dms/NewMessagesPill' import {NewMessagesPill} from '#/components/dms/NewMessagesPill'
@@ -102,6 +106,7 @@ export function MessagesList({
footer?: React.ReactNode footer?: React.ReactNode
hasAcceptOverride?: boolean hasAcceptOverride?: boolean
}) { }) {
const t = useTheme()
const convoState = useConvoActive() const convoState = useConvoActive()
const agent = useAgent() const agent = useAgent()
const getPost = useGetPost() const getPost = useGetPost()
@@ -134,6 +139,9 @@ export function MessagesList({
const prevContentHeight = useRef(0) const prevContentHeight = useRef(0)
const prevItemCount = useRef(0) const prevItemCount = useRef(0)
// Keep track of the message input height
const messageInputHeight = useSharedValue(52)
// -- Keep track of background state and positioning for new pill // -- Keep track of background state and positioning for new pill
const layoutHeight = useSharedValue(0) const layoutHeight = useSharedValue(0)
const didBackground = useRef(false) const didBackground = useRef(false)
@@ -294,10 +302,41 @@ export function MessagesList({
}, },
[footerHeight], [footerHeight],
) )
const animatedListStyle = useAnimatedStyle(() => {
if (!isIOS) {
return {
marginBottom: Math.max(
keyboardHeight.get(),
footerHeight + messageInputHeight.get(),
),
}
}
return {}
})
const animatedListStyle = useAnimatedStyle(() => ({ const animatedProps: Partial<ListProps> = useAnimatedProps(() => {
marginBottom: Math.max(keyboardHeight.get(), footerHeight), if (isIOS) {
})) return {
scrollIndicatorInsets: {
top: 0,
left: 0,
right: 1,
bottom:
Math.max(keyboardHeight.get(), footerHeight) +
messageInputHeight.get(),
},
contentInset: {
top: 0,
left: 0,
right: 0,
bottom:
Math.max(keyboardHeight.get(), footerHeight) +
messageInputHeight.get(),
},
}
}
return {}
})
const animatedStickyViewStyle = useAnimatedStyle(() => ({ const animatedStickyViewStyle = useAnimatedStyle(() => ({
transform: [{translateY: -Math.max(keyboardHeight.get(), footerHeight)}], transform: [{translateY: -Math.max(keyboardHeight.get(), footerHeight)}],
@@ -431,9 +470,7 @@ export function MessagesList({
maxToRenderPerBatch={isWeb ? 32 : 62} maxToRenderPerBatch={isWeb ? 32 : 62}
keyboardDismissMode="on-drag" keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="handled" keyboardShouldPersistTaps="handled"
maintainVisibleContentPosition={{ maintainVisibleContentPosition={{minIndexForVisible: 0}}
minIndexForVisible: 0,
}}
removeClippedSubviews={false} removeClippedSubviews={false}
sideBorders={false} sideBorders={false}
onContentSizeChange={onContentSizeChange} onContentSizeChange={onContentSizeChange}
@@ -444,28 +481,52 @@ export function MessagesList({
ListHeaderComponent={ ListHeaderComponent={
<MaybeLoader isLoading={convoState.isFetchingHistory} /> <MaybeLoader isLoading={convoState.isFetchingHistory} />
} }
contentInset={{bottom: 0}} animatedProps={animatedProps}
contentContainerStyle={{paddingBottom: 0}} automaticallyAdjustKeyboardInsets={false}
scrollIndicatorInsets={{bottom: 0}} automaticallyAdjustContentInsets={false}
/> />
</ScrollProvider> </ScrollProvider>
<Animated.View style={animatedStickyViewStyle}> <Animated.View
style={[
a.absolute,
{bottom: 0, left: 0, right: 0},
animatedStickyViewStyle,
]}>
<LinearGradient
key={t.name} // android does not update when you change the colors. sigh.
start={[0.5, 0]}
end={[0.5, 1]}
colors={[
transparentifyColor(t.atoms.bg.backgroundColor, 0),
t.atoms.bg.backgroundColor,
]}
locations={[0.15, 0.4]}
style={[a.absolute, a.inset_0]}
/>
{convoState.status === ConvoStatus.Disabled ? ( {convoState.status === ConvoStatus.Disabled ? (
<ChatDisabled /> <ChatDisabled />
) : blocked ? ( ) : blocked ? (
footer footer
) : ( ) : (
<ConversationFooter <View
convoState={convoState} onLayout={evt => {
hasAcceptOverride={hasAcceptOverride}> const height = evt.nativeEvent.layout.height
<MessageInput if (height) {
onSendMessage={onSendMessage} messageInputHeight.set(height)
hasEmbed={!!embedUri} }
setEmbed={setEmbed} }}>
openEmojiPicker={onOpenEmojiPicker}> <ConversationFooter
<MessageInputEmbed embedUri={embedUri} setEmbed={setEmbed} /> convoState={convoState}
</MessageInput> hasAcceptOverride={hasAcceptOverride}>
</ConversationFooter> <MessageInput
onSendMessage={onSendMessage}
hasEmbed={!!embedUri}
setEmbed={setEmbed}
openEmojiPicker={onOpenEmojiPicker}>
<MessageInputEmbed embedUri={embedUri} setEmbed={setEmbed} />
</MessageInput>
</ConversationFooter>
</View>
)} )}
</Animated.View> </Animated.View>