Update composer to new attachment style

This commit is contained in:
Samuel Newman
2026-05-20 17:10:47 +03:00
parent 46a4f6cbee
commit 0c46d58a5d
3 changed files with 179 additions and 140 deletions
@@ -145,8 +145,6 @@ export function MessageComposer({
return ( return (
<ComposerContainer> <ComposerContainer>
{children}
<View <View
collapsable={false} collapsable={false}
ref={native( ref={native(
@@ -162,6 +160,8 @@ export function MessageComposer({
style={[a.flex_1, a.rounded_xl, {minHeight: MIN_HEIGHT}]} style={[a.flex_1, a.rounded_xl, {minHeight: MIN_HEIGHT}]}
tintColor={t.palette.contrast_50} tintColor={t.palette.contrast_50}
fallbackStyle={[t.atoms.bg_contrast_50]}> fallbackStyle={[t.atoms.bg_contrast_50]}>
{children}
<View style={[a.flex_1]}>
{IS_WEB && ( {IS_WEB && (
<EmojiPicker.Root <EmojiPicker.Root
onEmojiSelect={emoji => onEmojiSelect={emoji =>
@@ -236,6 +236,7 @@ export function MessageComposer({
handleSubmit() handleSubmit()
}} }}
/> />
</View>
</GlassView> </GlassView>
<SubmitButton onPress={handleSubmit} disabled={submitDisabled} /> <SubmitButton onPress={handleSubmit} disabled={submitDisabled} />
</GlassContainer> </GlassContainer>
@@ -158,7 +158,6 @@ export function MessageInput({
return ( return (
<ComposerContainer> <ComposerContainer>
{children}
<GlassContainer <GlassContainer
style={[a.flex_row, a.align_end, a.gap_sm]} style={[a.flex_row, a.align_end, a.gap_sm]}
spacing={tokens.space.xs}> spacing={tokens.space.xs}>
@@ -168,6 +167,7 @@ export function MessageInput({
style={[a.flex_1, a.rounded_xl, {minHeight: MIN_HEIGHT}]} style={[a.flex_1, a.rounded_xl, {minHeight: MIN_HEIGHT}]}
tintColor={t.palette.contrast_50} tintColor={t.palette.contrast_50}
fallbackStyle={[t.atoms.bg_contrast_50]}> fallbackStyle={[t.atoms.bg_contrast_50]}>
{children}
<AnimatedTextInput <AnimatedTextInput
nativeID={textInputId} nativeID={textInputId}
accessibilityLabel={l`Message input field`} accessibilityLabel={l`Message input field`}
@@ -7,10 +7,10 @@ import {
moderatePost, moderatePost,
RichText as RichTextAPI, RichText as RichTextAPI,
} from '@atproto/api' } from '@atproto/api'
import {msg} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native' import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native'
import {HITSLOP_20} from '#/lib/constants'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
import { import {
type CommonNavigatorParams, type CommonNavigatorParams,
@@ -25,8 +25,8 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {usePostQuery} from '#/state/queries/post' import {usePostQuery} from '#/state/queries/post'
import {PostMeta} from '#/view/com/util/PostMeta' import {PostMeta} from '#/view/com/util/PostMeta'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button' import {Button} from '#/components/Button'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as MediaPreview from '#/components/MediaPreview' import * as MediaPreview from '#/components/MediaPreview'
import {ContentHider} from '#/components/moderation/ContentHider' import {ContentHider} from '#/components/moderation/ContentHider'
@@ -41,10 +41,10 @@ export function useMessageEmbed() {
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const embedFromParams = route.params.embed const embedFromParams = route.params.embed
const [embedUri, setEmbed] = useState(embedFromParams) const [embedUri, setEmbedUri] = useState(embedFromParams)
if (embedFromParams && embedUri !== embedFromParams) { if (embedFromParams && embedUri !== embedFromParams) {
setEmbed(embedFromParams) setEmbedUri(embedFromParams)
} }
return { return {
@@ -53,7 +53,7 @@ export function useMessageEmbed() {
(embedUrl: string | undefined) => { (embedUrl: string | undefined) => {
if (!embedUrl) { if (!embedUrl) {
navigation.setParams({embed: ''}) navigation.setParams({embed: ''})
setEmbed(undefined) setEmbedUri(undefined)
return return
} }
@@ -63,7 +63,7 @@ export function useMessageEmbed() {
const [_0, user, _1, rkey] = url.split('/').filter(Boolean) const [_0, user, _1, rkey] = url.split('/').filter(Boolean)
const uri = makeRecordUri(user, 'app.bsky.feed.post', rkey) const uri = makeRecordUri(user, 'app.bsky.feed.post', rkey)
setEmbed(uri) setEmbedUri(uri)
}, },
[embedFromParams, navigation], [embedFromParams, navigation],
), ),
@@ -103,7 +103,7 @@ export function MessageInputEmbed({
setEmbed: (embedUrl: string | undefined) => void setEmbed: (embedUrl: string | undefined) => void
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {t: l} = useLingui()
const {data: post, status} = usePostQuery(embedUri) const {data: post, status} = usePostQuery(embedUri)
@@ -138,25 +138,30 @@ export function MessageInputEmbed({
return null return null
} }
let content = null const onRemove = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setEmbed(undefined)
}
switch (status) { switch (status) {
case 'pending': // case 'success':
content = ( case 'pending': {
<View return (
style={[a.flex_1, {minHeight: 64}, a.justify_center, a.align_center]}> <SimpleContainer onRemove={onRemove}>
<Loader /> <Loader />
</View> </SimpleContainer>
) )
break }
case 'error': case 'error': {
content = ( return (
<View <SimpleContainer onRemove={onRemove}>
style={[a.flex_1, {minHeight: 64}, a.justify_center, a.align_center]}> <Text style={[a.text_center, t.atoms.text_contrast_medium, a.italic]}>
<Text style={a.text_center}>Could not fetch post</Text> <Trans>Could not fetch post</Trans>
</View> </Text>
</SimpleContainer>
) )
break }
case 'success': case 'success': {
const itemUrip = new AtUri(post.uri) const itemUrip = new AtUri(post.uri)
const itemHref = makeProfileLink(post.author, 'post', itemUrip.rkey) const itemHref = makeProfileLink(post.author, 'post', itemUrip.rkey)
@@ -164,30 +169,41 @@ export function MessageInputEmbed({
return null return null
} }
content = ( return (
<View <View
style={[ style={[
a.flex_1, a.flex_1,
t.atoms.bg, t.atoms.border_contrast_high,
t.atoms.border_contrast_low,
a.rounded_md, a.rounded_md,
a.border, a.border,
a.p_sm, a.p_sm,
a.mb_sm, a.mt_sm,
]} a.mx_sm,
pointerEvents="none"> ]}>
<View style={[a.flex_1, a.flex_row, a.gap_sm]}>
<PostMeta <PostMeta
showAvatar showAvatar
author={post.author} author={post.author}
moderation={moderation} moderation={moderation}
timestamp={post.indexedAt} timestamp={post.indexedAt}
postHref={itemHref} postHref={itemHref}
style={a.flex_0} linkDisabled
/> />
<Button
label={l`Remove embed`}
onPress={onRemove}
style={[a.px_2xs, {transform: [{translateY: -2}]}]}
hitSlop={HITSLOP_20}>
<XIcon size="xs" style={t.atoms.text_contrast_high} />
</Button>
</View>
<ContentHider modui={moderation.ui('contentView')}> <ContentHider modui={moderation.ui('contentView')}>
<PostAlerts modui={moderation.ui('contentView')} style={a.py_xs} /> <PostAlerts
modui={moderation.ui('contentView')}
style={a.py_xs}
size="sm"
/>
{rt.text && ( {rt.text && (
<View style={a.mt_xs}>
<RichText <RichText
enableTags enableTags
testID="postText" testID="postText"
@@ -196,30 +212,52 @@ export function MessageInputEmbed({
authorHandle={post.author.handle} authorHandle={post.author.handle}
numberOfLines={3} numberOfLines={3}
/> />
</View>
)} )}
<MediaPreview.Embed embed={post.embed} style={a.mt_sm} /> <MediaPreview.Embed embed={post.embed} style={a.mt_sm} />
</ContentHider> </ContentHider>
</View> </View>
) )
break }
}
} }
function SimpleContainer({
children,
onRemove,
}: {
children: React.ReactNode
onRemove?: () => void
}) {
const t = useTheme()
const {t: l} = useLingui()
return ( return (
<View style={[a.flex_row, a.gap_sm]}> <View
{content} style={[
a.flex_1,
{minHeight: 80},
a.justify_center,
a.align_center,
t.atoms.border_contrast_high,
a.rounded_md,
a.border,
a.mt_sm,
a.mx_sm,
]}>
{children}
{onRemove && (
<Button <Button
label={_(msg`Remove embed`)} label={l`Remove embed`}
onPress={() => { onPress={onRemove}
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) style={[
setEmbed(undefined) a.absolute,
}} {top: 10, right: 8},
size="tiny" a.px_2xs,
variant="solid" {transform: [{translateY: -2}]},
color="secondary" ]}
shape="round"> hitSlop={HITSLOP_20}>
<ButtonIcon icon={X} /> <XIcon size="xs" style={t.atoms.text_contrast_high} />
</Button> </Button>
)}
</View> </View>
) )
} }