bsky-app/src/view/com/util/post-ctrls/PostCtrls.tsx
Alice f6685419d8
Fix aria-label on the Share button (#3159)
It had the same one as the Like button for some reason
2024-03-09 13:07:56 -08:00

267 lines
7.4 KiB
TypeScript

import React, {memo, useCallback} from 'react'
import {
StyleProp,
StyleSheet,
TouchableOpacity,
View,
ViewStyle,
} from 'react-native'
import {
AppBskyFeedDefs,
AppBskyFeedPost,
AtUri,
RichText as RichTextAPI,
} from '@atproto/api'
import {Text} from '../text/Text'
import {PostDropdownBtn} from '../forms/PostDropdownBtn'
import {HeartIcon, HeartIconSolid, CommentBottomArrow} from 'lib/icons'
import {s} from 'lib/styles'
import {pluralize} from 'lib/strings/helpers'
import {useTheme} from 'lib/ThemeContext'
import {RepostButton} from './RepostButton'
import {Haptics} from 'lib/haptics'
import {HITSLOP_10, HITSLOP_20} from 'lib/constants'
import {useModalControls} from '#/state/modals'
import {
usePostLikeMutationQueue,
usePostRepostMutationQueue,
} from '#/state/queries/post'
import {useComposerControls} from '#/state/shell/composer'
import {Shadow} from '#/state/cache/types'
import {useRequireAuth} from '#/state/session'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox'
import {toShareUrl} from 'lib/strings/url-helpers'
import {shareUrl} from 'lib/sharing'
import {makeProfileLink} from 'lib/routes/links'
let PostCtrls = ({
big,
post,
record,
richText,
showAppealLabelItem,
style,
onPressReply,
}: {
big?: boolean
post: Shadow<AppBskyFeedDefs.PostView>
record: AppBskyFeedPost.Record
richText: RichTextAPI
showAppealLabelItem?: boolean
style?: StyleProp<ViewStyle>
onPressReply: () => void
}): React.ReactNode => {
const theme = useTheme()
const {_} = useLingui()
const {openComposer} = useComposerControls()
const {closeModal} = useModalControls()
const [queueLike, queueUnlike] = usePostLikeMutationQueue(post)
const [queueRepost, queueUnrepost] = usePostRepostMutationQueue(post)
const requireAuth = useRequireAuth()
const defaultCtrlColor = React.useMemo(
() => ({
color: theme.palette.default.postCtrl,
}),
[theme],
) as StyleProp<ViewStyle>
const onPressToggleLike = React.useCallback(async () => {
try {
if (!post.viewer?.like) {
Haptics.default()
await queueLike()
} else {
await queueUnlike()
}
} catch (e: any) {
if (e?.name !== 'AbortError') {
throw e
}
}
}, [post.viewer?.like, queueLike, queueUnlike])
const onRepost = useCallback(async () => {
closeModal()
try {
if (!post.viewer?.repost) {
Haptics.default()
await queueRepost()
} else {
await queueUnrepost()
}
} catch (e: any) {
if (e?.name !== 'AbortError') {
throw e
}
}
}, [post.viewer?.repost, queueRepost, queueUnrepost, closeModal])
const onQuote = useCallback(() => {
closeModal()
openComposer({
quote: {
uri: post.uri,
cid: post.cid,
text: record.text,
author: post.author,
indexedAt: post.indexedAt,
},
})
Haptics.default()
}, [
post.uri,
post.cid,
post.author,
post.indexedAt,
record.text,
openComposer,
closeModal,
])
const onShare = useCallback(() => {
const urip = new AtUri(post.uri)
const href = makeProfileLink(post.author, 'post', urip.rkey)
const url = toShareUrl(href)
shareUrl(url)
}, [post.uri, post.author])
return (
<View style={[styles.ctrls, style]}>
<View
style={[
big ? styles.ctrlBig : styles.ctrl,
post.viewer?.replyDisabled ? {opacity: 0.5} : undefined,
]}>
<TouchableOpacity
testID="replyBtn"
style={[styles.btn, !big && styles.btnPad, {paddingLeft: 0}]}
onPress={() => {
if (!post.viewer?.replyDisabled) {
requireAuth(() => onPressReply())
}
}}
accessibilityRole="button"
accessibilityLabel={`Reply (${post.replyCount} ${
post.replyCount === 1 ? 'reply' : 'replies'
})`}
accessibilityHint=""
hitSlop={big ? HITSLOP_20 : HITSLOP_10}>
<CommentBottomArrow
style={[defaultCtrlColor, big ? s.mt2 : styles.mt1]}
strokeWidth={3}
size={big ? 20 : 15}
/>
{typeof post.replyCount !== 'undefined' && post.replyCount > 0 ? (
<Text style={[defaultCtrlColor, s.ml5, s.f15]}>
{post.replyCount}
</Text>
) : undefined}
</TouchableOpacity>
</View>
<View style={big ? styles.ctrlBig : styles.ctrl}>
<RepostButton
big={big}
isReposted={!!post.viewer?.repost}
repostCount={post.repostCount}
onRepost={onRepost}
onQuote={onQuote}
/>
</View>
<View style={big ? styles.ctrlBig : styles.ctrl}>
<TouchableOpacity
testID="likeBtn"
style={[styles.btn, !big && styles.btnPad]}
onPress={() => {
requireAuth(() => onPressToggleLike())
}}
accessibilityRole="button"
accessibilityLabel={`${
post.viewer?.like ? _(msg`Unlike`) : _(msg`Like`)
} (${post.likeCount} ${pluralize(post.likeCount || 0, 'like')})`}
accessibilityHint=""
hitSlop={big ? HITSLOP_20 : HITSLOP_10}>
{post.viewer?.like ? (
<HeartIconSolid style={s.likeColor} size={big ? 22 : 16} />
) : (
<HeartIcon
style={[defaultCtrlColor, big ? styles.mt1 : undefined]}
strokeWidth={3}
size={big ? 20 : 16}
/>
)}
{typeof post.likeCount !== 'undefined' && post.likeCount > 0 ? (
<Text
testID="likeCount"
style={
post.viewer?.like
? [s.bold, s.likeColor, s.f15, s.ml5]
: [defaultCtrlColor, s.f15, s.ml5]
}>
{post.likeCount}
</Text>
) : undefined}
</TouchableOpacity>
</View>
{big && (
<View style={styles.ctrlBig}>
<TouchableOpacity
testID="shareBtn"
style={[styles.btn]}
onPress={onShare}
accessibilityRole="button"
accessibilityLabel={`${_(msg`Share`)}`}
accessibilityHint=""
hitSlop={big ? HITSLOP_20 : HITSLOP_10}>
<ArrowOutOfBox style={[defaultCtrlColor, styles.mt1]} width={22} />
</TouchableOpacity>
</View>
)}
<View style={big ? styles.ctrlBig : styles.ctrl}>
<PostDropdownBtn
testID="postDropdownBtn"
postAuthor={post.author}
postCid={post.cid}
postUri={post.uri}
record={record}
richText={richText}
showAppealLabelItem={showAppealLabelItem}
style={styles.btnPad}
hitSlop={big ? HITSLOP_20 : HITSLOP_10}
/>
</View>
</View>
)
}
PostCtrls = memo(PostCtrls)
export {PostCtrls}
const styles = StyleSheet.create({
ctrls: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
ctrl: {
flex: 1,
alignItems: 'flex-start',
},
ctrlBig: {
alignItems: 'center',
},
btn: {
flexDirection: 'row',
alignItems: 'center',
},
btnPad: {
paddingTop: 5,
paddingBottom: 5,
paddingLeft: 5,
paddingRight: 5,
},
mt1: {
marginTop: 1,
},
})