Post alignment (#5580)

Co-authored-by: Hailey <me@haileyok.com>
This commit is contained in:
Eric Bailey
2024-10-04 15:33:17 -05:00
committed by GitHub
parent d17da8474b
commit 1cedd4a3c4
3 changed files with 170 additions and 264 deletions
@@ -48,7 +48,7 @@ export function PostThreadComposePrompt({
accessibilityHint={_(msg`Opens composer`)}
style={[
gtMobile ? a.py_xs : {paddingTop: 8, paddingBottom: 11},
gtMobile ? {paddingLeft: 6, paddingRight: 6} : a.px_sm,
a.px_sm,
a.border_t,
t.atoms.border_contrast_low,
t.atoms.bg,
@@ -1,14 +1,9 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {AppBskyActorDefs} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {s} from '#/lib/styles'
import {logger} from '#/logger'
import {Shadow, useProfileShadow} from '#/state/cache/profile-shadow'
import {
@@ -16,8 +11,11 @@ import {
useProfileQuery,
} from '#/state/queries/profile'
import {useRequireAuth} from '#/state/session'
import {Text} from '#/view/com/util/text/Text'
import * as Toast from '#/view/com/util/Toast'
import {atoms as a, useBreakpoints} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
export function PostThreadFollowBtn({did}: {did: string}) {
const {data: profile, isLoading} = useProfileQuery({did})
@@ -36,9 +34,7 @@ function PostThreadFollowBtnLoaded({
}) {
const navigation = useNavigation()
const {_} = useLingui()
const pal = usePalette('default')
const palInverted = usePalette('inverted')
const {isTabletOrDesktop} = useWebMediaQueries()
const {gtMobile} = useBreakpoints()
const profile: Shadow<AppBskyActorDefs.ProfileViewBasic> =
useProfileShadow(profileUnshadowed)
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
@@ -113,51 +109,32 @@ function PostThreadFollowBtnLoaded({
if (!showFollowBtn) return null
return (
<View style={{width: isTabletOrDesktop ? 130 : 120}}>
<View style={styles.btnOuter}>
<TouchableOpacity
testID="followBtn"
onPress={onPress}
style={[styles.btn, !isFollowing ? palInverted.view : pal.viewLight]}
accessibilityRole="button"
accessibilityLabel={_(msg`Follow ${profile.handle}`)}
accessibilityHint={_(
msg`Shows posts from ${profile.handle} in your feed`,
)}>
{isTabletOrDesktop && (
<FontAwesomeIcon
icon={!isFollowing ? 'plus' : 'check'}
style={[!isFollowing ? palInverted.text : pal.text, s.mr5]}
/>
)}
<Text
type="button"
style={[!isFollowing ? palInverted.text : pal.text, s.bold]}
numberOfLines={1}>
{!isFollowing ? (
isFollowedBy ? (
<Trans>Follow Back</Trans>
) : (
<Trans>Follow</Trans>
)
) : (
<Trans>Following</Trans>
)}
</Text>
</TouchableOpacity>
</View>
</View>
<Button
testID="followBtn"
label={_(msg`Follow ${profile.handle}`)}
onPress={onPress}
size="small"
variant="solid"
color="secondary_inverted"
style={[a.rounded_full]}>
{gtMobile && (
<ButtonIcon
icon={isFollowing ? Check : Plus}
position="left"
size="sm"
/>
)}
<ButtonText>
{!isFollowing ? (
isFollowedBy ? (
<Trans>Follow Back</Trans>
) : (
<Trans>Follow</Trans>
)
) : (
<Trans>Following</Trans>
)}
</ButtonText>
</Button>
)
}
const styles = StyleSheet.create({
btnOuter: {
marginLeft: 'auto',
},
btn: {
flexDirection: 'row',
borderRadius: 50,
paddingVertical: 8,
paddingHorizontal: 14,
},
})
+137 -208
View File
@@ -14,14 +14,12 @@ import {useLingui} from '@lingui/react'
import {MAX_POST_LINES} from '#/lib/constants'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {makeProfileLink} from '#/lib/routes/links'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {countLines} from '#/lib/strings/helpers'
import {niceDate} from '#/lib/strings/time'
import {s} from '#/lib/styles'
import {isWeb} from '#/platform/detection'
import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow'
import {useLanguagePrefs} from '#/state/preferences'
import {useOpenLink} from '#/state/preferences/in-app-browser'
@@ -30,9 +28,10 @@ import {useSession} from '#/state/session'
import {useComposerControls} from '#/state/shell/composer'
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn'
import {atoms as a} from '#/alf'
import {atoms as a, useTheme} from '#/alf'
import {AppModerationCause} from '#/components/Pills'
import {RichText} from '#/components/RichText'
import {Text as NewText} from '#/components/Typography'
import {ContentHider} from '../../../components/moderation/ContentHider'
import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe'
import {PostAlerts} from '../../../components/moderation/PostAlerts'
@@ -180,6 +179,7 @@ let PostThreadItemLoaded = ({
hideTopBorder?: boolean
threadgateRecord?: AppBskyFeedThreadgate.Record
}): React.ReactNode => {
const t = useTheme()
const pal = usePalette('default')
const {_, i18n} = useLingui()
const langPrefs = useLanguagePrefs()
@@ -268,8 +268,14 @@ let PostThreadItemLoaded = ({
return (
<>
{rootUri !== post.uri && (
<View style={{paddingLeft: 16, flexDirection: 'row', height: 16}}>
<View style={{width: 38}}>
<View
style={[
a.pl_lg,
a.flex_row,
a.pb_xs,
{height: a.pt_lg.paddingTop},
]}>
<View style={{width: 42}}>
<View
style={[
styles.replyLine,
@@ -286,88 +292,74 @@ let PostThreadItemLoaded = ({
<View
testID={`postThreadItem-by-${post.author.handle}`}
style={[
styles.outer,
styles.outerHighlighted,
pal.border,
pal.view,
rootUri === post.uri && styles.outerHighlightedRoot,
hideTopBorder && styles.noTopBorder,
]}
accessible={false}>
<View style={[styles.layout]}>
<View style={[styles.layoutAvi, {paddingBottom: 8}]}>
<PreviewableUserAvatar
size={42}
profile={post.author}
moderation={moderation.ui('avatar')}
type={post.author.associated?.labeler ? 'labeler' : 'user'}
/>
</View>
<View style={styles.layoutContent}>
<View
style={[styles.meta, styles.metaExpandedLine1, {zIndex: 1}]}>
<Link style={s.flex1} href={authorHref} title={authorTitle}>
<Text
emoji
type="xl-bold"
style={[pal.text, a.self_start]}
numberOfLines={1}
lineHeight={1.2}>
{sanitizeDisplayName(
post.author.displayName ||
sanitizeHandle(post.author.handle),
moderation.ui('displayName'),
)}
</Text>
</Link>
</View>
<View style={styles.meta}>
<Link style={s.flex1} href={authorHref} title={authorTitle}>
<Text
emoji
type="md"
style={[pal.textLight]}
numberOfLines={1}>
{sanitizeHandle(post.author.handle, '@')}
</Text>
</Link>
</View>
a.px_lg,
t.atoms.border_contrast_low,
// root post styles
rootUri === post.uri && [a.pt_lg],
]}>
<View style={[a.flex_row, a.gap_md, a.pb_md]}>
<PreviewableUserAvatar
size={42}
profile={post.author}
moderation={moderation.ui('avatar')}
type={post.author.associated?.labeler ? 'labeler' : 'user'}
/>
<View style={[a.flex_1]}>
<Link style={s.flex1} href={authorHref} title={authorTitle}>
<NewText
emoji
style={[a.text_lg, a.font_bold, a.leading_snug, a.self_start]}
numberOfLines={1}>
{sanitizeDisplayName(
post.author.displayName ||
sanitizeHandle(post.author.handle),
moderation.ui('displayName'),
)}
</NewText>
</Link>
<Link style={s.flex1} href={authorHref} title={authorTitle}>
<NewText
emoji
style={[
a.text_md,
a.leading_snug,
t.atoms.text_contrast_medium,
]}
numberOfLines={1}>
{sanitizeHandle(post.author.handle, '@')}
</NewText>
</Link>
</View>
{currentAccount?.did !== post.author.did && (
<PostThreadFollowBtn did={post.author.did} />
<View>
<PostThreadFollowBtn did={post.author.did} />
</View>
)}
</View>
<View style={[s.pl10, s.pr10, s.pb10]}>
<LabelsOnMyPost post={post} />
<View style={[a.pb_sm]}>
<LabelsOnMyPost post={post} style={[a.pb_sm]} />
<ContentHider
modui={moderation.ui('contentView')}
ignoreMute
style={styles.contentHider}
childContainerStyle={styles.contentHiderChild}>
childContainerStyle={[a.pt_sm]}>
<PostAlerts
modui={moderation.ui('contentView')}
size="lg"
includeMute
style={[a.pt_2xs, a.pb_sm]}
style={[a.pb_sm]}
additionalCauses={additionalPostAlerts}
/>
{richText?.text ? (
<View
style={[
styles.postTextContainer,
styles.postTextLargeContainer,
]}>
<RichText
enableTags
selectable
value={richText}
style={[a.flex_1, a.text_xl]}
authorHandle={post.author.handle}
/>
</View>
<RichText
enableTags
selectable
value={richText}
style={[a.flex_1, a.text_xl]}
authorHandle={post.author.handle}
/>
) : undefined}
{post.embed && (
<View style={[a.pb_sm]}>
<View style={[a.py_xs]}>
<PostEmbeds
embed={post.embed}
moderation={moderation}
@@ -386,68 +378,73 @@ let PostThreadItemLoaded = ({
post.likeCount !== 0 ||
post.quoteCount !== 0 ? (
// Show this section unless we're *sure* it has no engagement.
<View style={[styles.expandedInfo, pal.border]}>
<View
style={[
a.flex_row,
a.align_center,
a.gap_lg,
a.border_t,
a.border_b,
a.mt_md,
a.py_md,
t.atoms.border_contrast_low,
]}>
{post.repostCount != null && post.repostCount !== 0 ? (
<Link
style={styles.expandedInfoItem}
href={repostsHref}
title={repostsTitle}>
<Text
<Link href={repostsHref} title={repostsTitle}>
<NewText
testID="repostCount-expanded"
type="lg"
style={pal.textLight}>
<Text type="xl-bold" style={pal.text}>
style={[a.text_md, t.atoms.text_contrast_medium]}>
<NewText style={[a.text_md, a.font_bold, t.atoms.text]}>
{formatCount(i18n, post.repostCount)}
</Text>{' '}
</NewText>{' '}
<Plural
value={post.repostCount}
one="repost"
other="reposts"
/>
</Text>
</NewText>
</Link>
) : null}
{post.quoteCount != null &&
post.quoteCount !== 0 &&
!post.viewer?.embeddingDisabled ? (
<Link
style={styles.expandedInfoItem}
href={quotesHref}
title={quotesTitle}>
<Text
<Link href={quotesHref} title={quotesTitle}>
<NewText
testID="quoteCount-expanded"
type="lg"
style={pal.textLight}>
<Text type="xl-bold" style={pal.text}>
style={[a.text_md, t.atoms.text_contrast_medium]}>
<NewText style={[a.text_md, a.font_bold, t.atoms.text]}>
{formatCount(i18n, post.quoteCount)}
</Text>{' '}
</NewText>{' '}
<Plural
value={post.quoteCount}
one="quote"
other="quotes"
/>
</Text>
</NewText>
</Link>
) : null}
{post.likeCount != null && post.likeCount !== 0 ? (
<Link
style={styles.expandedInfoItem}
href={likesHref}
title={likesTitle}>
<Text
<Link href={likesHref} title={likesTitle}>
<NewText
testID="likeCount-expanded"
type="lg"
style={pal.textLight}>
<Text type="xl-bold" style={pal.text}>
style={[a.text_md, t.atoms.text_contrast_medium]}>
<NewText style={[a.text_md, a.font_bold, t.atoms.text]}>
{formatCount(i18n, post.likeCount)}
</Text>{' '}
</NewText>{' '}
<Plural value={post.likeCount} one="like" other="likes" />
</Text>
</NewText>
</Link>
) : null}
</View>
) : null}
<View style={[s.pl10, s.pr10]}>
<View
style={[
a.pt_sm,
a.pb_2xs,
{
marginLeft: -5,
},
]}>
<PostCtrls
big
post={post}
@@ -481,9 +478,8 @@ let PostThreadItemLoaded = ({
testID={`postThreadItem-by-${post.author.handle}`}
href={postHref}
disabled={overrideBlur}
style={[pal.view]}
modui={moderation.ui('contentList')}
iconSize={isThreadedChild ? 26 : 38}
iconSize={isThreadedChild ? 24 : 42}
iconStyles={
isThreadedChild ? {marginRight: 4} : {marginLeft: 2, marginRight: 2}
}
@@ -496,7 +492,7 @@ let PostThreadItemLoaded = ({
paddingLeft: 8,
height: isThreadedChildAdjacentTop ? 8 : 16,
}}>
<View style={{width: 38}}>
<View style={{width: 42}}>
{!isThreadedChild && showParentReplyLine && (
<View
style={[
@@ -514,7 +510,9 @@ let PostThreadItemLoaded = ({
<View
style={[
styles.layout,
a.flex_row,
a.px_sm,
a.gap_md,
{
paddingBottom:
showChildReplyLine && !isThreadedChild
@@ -526,9 +524,9 @@ let PostThreadItemLoaded = ({
]}>
{/* If we are in threaded mode, the avatar is rendered in PostMeta */}
{!isThreadedChild && (
<View style={styles.layoutAvi}>
<View>
<PreviewableUserAvatar
size={38}
size={42}
profile={post.author}
moderation={moderation.ui('avatar')}
type={post.author.associated?.labeler ? 'labeler' : 'user'}
@@ -549,12 +547,7 @@ let PostThreadItemLoaded = ({
</View>
)}
<View
style={
isThreadedChild
? styles.layoutContentThreaded
: styles.layoutContent
}>
<View style={[a.flex_1]}>
<PostMeta
author={post.author}
moderation={moderation}
@@ -563,20 +556,16 @@ let PostThreadItemLoaded = ({
showAvatar={isThreadedChild}
avatarModeration={moderation.ui('avatar')}
avatarSize={24}
style={
isThreadedChild && {
paddingBottom: isWeb ? 5 : 4,
}
}
style={[a.pb_xs]}
/>
<LabelsOnMyPost post={post} />
<LabelsOnMyPost post={post} style={[a.pb_xs]} />
<PostAlerts
modui={moderation.ui('contentList')}
style={[a.pt_2xs, a.pb_2xs]}
style={[a.pb_2xs]}
additionalCauses={additionalPostAlerts}
/>
{richText?.text ? (
<View style={styles.postTextContainer}>
<View style={[a.pb_2xs, a.pr_sm]}>
<RichText
enableTags
value={richText}
@@ -659,29 +648,31 @@ function PostOuterWrapper({
hasPrecedingItem: boolean
hideTopBorder?: boolean
}>) {
const {isMobile} = useWebMediaQueries()
const pal = usePalette('default')
const t = useTheme()
if (treeView && depth > 0) {
return (
<View
style={[
pal.border,
a.flex_row,
a.px_sm,
t.atoms.border_contrast_low,
styles.cursor,
{
flexDirection: 'row',
paddingHorizontal: isMobile ? 10 : 6,
borderTopWidth: depth === 1 ? StyleSheet.hairlineWidth : 0,
borderTopWidth: depth === 1 ? a.border_t.borderTopWidth : 0,
},
]}>
{Array.from(Array(depth - 1)).map((_, n: number) => (
<View
key={`${post.uri}-padding-${n}`}
style={{
borderLeftWidth: 2,
borderLeftColor: pal.colors.border,
marginLeft: isMobile ? 6 : 12,
paddingLeft: isMobile ? 6 : 8,
}}
style={[
a.ml_sm,
t.atoms.border_contrast_low,
{
borderLeftWidth: 2,
paddingLeft: a.pl_sm.paddingLeft - 2, // minus border
},
]}
/>
))}
<View style={{flex: 1}}>{children}</View>
@@ -691,8 +682,9 @@ function PostOuterWrapper({
return (
<View
style={[
styles.outer,
pal.border,
a.border_t,
a.px_sm,
t.atoms.border_contrast_low,
showParentReplyLine && hasPrecedingItem && styles.noTopBorder,
hideTopBorder && styles.noTopBorder,
styles.cursor,
@@ -713,6 +705,7 @@ function ExpandedPostDetails({
needsTranslation: boolean
translatorUrl: string
}) {
const t = useTheme()
const pal = usePalette('default')
const {_, i18n} = useLingui()
const openLink = useOpenLink()
@@ -723,31 +716,25 @@ function ExpandedPostDetails({
}, [openLink, translatorUrl])
return (
<View
style={[
a.flex_row,
a.align_center,
a.flex_wrap,
a.gap_xs,
s.mt2,
s.mb10,
]}>
<Text style={[a.text_sm, pal.textLight]}>
<View style={[a.flex_row, a.align_center, a.flex_wrap, a.gap_sm, a.pt_md]}>
<NewText style={[a.text_sm, t.atoms.text_contrast_medium]}>
{niceDate(i18n, post.indexedAt)}
</Text>
</NewText>
{isRootPost && (
<WhoCanReply post={post} isThreadAuthor={isThreadAuthor} />
)}
{needsTranslation && (
<>
<Text style={[a.text_sm, pal.textLight]}>&middot;</Text>
<NewText style={[a.text_sm, t.atoms.text_contrast_medium]}>
&middot;
</NewText>
<Text
<NewText
style={[a.text_sm, pal.link]}
title={_(msg`Translate`)}
onPress={onTranslatePress}>
<Trans>Translate</Trans>
</Text>
</NewText>
</>
)}
</View>
@@ -773,31 +760,9 @@ const styles = StyleSheet.create({
borderTopWidth: StyleSheet.hairlineWidth,
paddingLeft: 8,
},
outerHighlighted: {
borderTopWidth: 0,
paddingTop: 4,
paddingLeft: 8,
paddingRight: 8,
},
outerHighlightedRoot: {
borderTopWidth: StyleSheet.hairlineWidth,
paddingTop: 16,
},
noTopBorder: {
borderTopWidth: 0,
},
layout: {
flexDirection: 'row',
paddingHorizontal: 8,
},
layoutAvi: {},
layoutContent: {
flex: 1,
marginLeft: 10,
},
layoutContentThreaded: {
flex: 1,
},
meta: {
flexDirection: 'row',
paddingVertical: 2,
@@ -805,42 +770,6 @@ const styles = StyleSheet.create({
metaExpandedLine1: {
paddingVertical: 0,
},
alert: {
marginBottom: 6,
},
postTextContainer: {
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
paddingBottom: 4,
paddingRight: 10,
overflow: 'hidden',
},
postTextLargeContainer: {
paddingHorizontal: 0,
paddingRight: 0,
paddingBottom: 10,
},
translateLink: {
marginBottom: 6,
},
contentHider: {
marginBottom: 6,
},
contentHiderChild: {
marginTop: 6,
},
expandedInfo: {
flexDirection: 'row',
padding: 10,
borderTopWidth: StyleSheet.hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
marginTop: 5,
marginBottom: 10,
},
expandedInfoItem: {
marginRight: 10,
},
loadMore: {
flexDirection: 'row',
alignItems: 'center',