Live (#8354)
This commit is contained in:
@@ -6,6 +6,7 @@ import {useLingui} from '@lingui/react'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
import type React from 'react'
|
||||
|
||||
import {useActorStatus} from '#/lib/actor-status'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {forceLTR} from '#/lib/strings/bidi'
|
||||
import {NON_BREAKING_SPACE} from '#/lib/strings/constants'
|
||||
@@ -55,6 +56,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
||||
|
||||
const timestampLabel = niceDate(i18n, opts.timestamp)
|
||||
const verification = useSimpleVerificationState({profile: author})
|
||||
const {isActive: live} = useActorStatus(author)
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -74,6 +76,8 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
||||
profile={author}
|
||||
moderation={opts.moderation?.ui('avatar')}
|
||||
type={author.associated?.labeler ? 'labeler' : 'user'}
|
||||
live={live}
|
||||
hideLiveBadge
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -14,6 +14,9 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useActorStatus} from '#/lib/actor-status'
|
||||
import {isTouchDevice} from '#/lib/browser'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {
|
||||
useCameraPermission,
|
||||
usePhotoLibraryPermission,
|
||||
@@ -22,6 +25,8 @@ import {compressIfNeeded} from '#/lib/media/manip'
|
||||
import {openCamera, openCropper, openPicker} from '#/lib/media/picker'
|
||||
import {type PickerImage} from '#/lib/media/picker.shared'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {logger} from '#/logger'
|
||||
import {isAndroid, isNative, isWeb} from '#/platform/detection'
|
||||
import {
|
||||
@@ -33,6 +38,7 @@ import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache'
|
||||
import {EditImageDialog} from '#/view/com/composer/photos/EditImageDialog'
|
||||
import {HighPriorityImage} from '#/view/com/util/images/Image'
|
||||
import {atoms as a, tokens, useTheme} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
|
||||
import {
|
||||
@@ -42,6 +48,8 @@ import {
|
||||
import {StreamingLive_Stroke2_Corner0_Rounded as LibraryIcon} from '#/components/icons/StreamingLive'
|
||||
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
|
||||
import {Link} from '#/components/Link'
|
||||
import {LiveIndicator} from '#/components/live/LiveIndicator'
|
||||
import {LiveStatusDialog} from '#/components/live/LiveStatusDialog'
|
||||
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||
import * as Menu from '#/components/Menu'
|
||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||
@@ -54,6 +62,8 @@ interface BaseUserAvatarProps {
|
||||
shape?: 'circle' | 'square'
|
||||
size: number
|
||||
avatar?: string | null
|
||||
live?: boolean
|
||||
hideLiveBadge?: boolean
|
||||
}
|
||||
|
||||
interface UserAvatarProps extends BaseUserAvatarProps {
|
||||
@@ -196,27 +206,38 @@ let UserAvatar = ({
|
||||
usePlainRNImage = false,
|
||||
onLoad,
|
||||
style,
|
||||
live,
|
||||
hideLiveBadge,
|
||||
}: UserAvatarProps): React.ReactNode => {
|
||||
const t = useTheme()
|
||||
const backgroundColor = t.palette.contrast_25
|
||||
const finalShape = overrideShape ?? (type === 'user' ? 'circle' : 'square')
|
||||
|
||||
const aviStyle = useMemo(() => {
|
||||
let borderRadius
|
||||
if (finalShape === 'square') {
|
||||
return {
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size > 32 ? 8 : 3,
|
||||
backgroundColor,
|
||||
}
|
||||
borderRadius = size > 32 ? 8 : 3
|
||||
} else {
|
||||
borderRadius = Math.floor(size / 2)
|
||||
}
|
||||
|
||||
return {
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: Math.floor(size / 2),
|
||||
backgroundColor,
|
||||
borderRadius,
|
||||
backgroundColor: t.palette.contrast_25,
|
||||
}
|
||||
}, [finalShape, size, backgroundColor])
|
||||
}, [finalShape, size, t])
|
||||
|
||||
const borderStyle = useMemo(() => {
|
||||
return [
|
||||
{borderRadius: aviStyle.borderRadius},
|
||||
live && {
|
||||
borderColor: t.palette.negative_500,
|
||||
borderWidth: size > 16 ? 2 : 1,
|
||||
opacity: 1,
|
||||
},
|
||||
]
|
||||
}, [aviStyle.borderRadius, live, t, size])
|
||||
|
||||
const alert = useMemo(() => {
|
||||
if (!moderation?.alert) {
|
||||
@@ -277,12 +298,19 @@ let UserAvatar = ({
|
||||
onLoad={onLoad}
|
||||
/>
|
||||
)}
|
||||
<MediaInsetBorder style={[{borderRadius: aviStyle.borderRadius}]} />
|
||||
<MediaInsetBorder style={borderStyle} />
|
||||
{live && size > 16 && !hideLiveBadge && (
|
||||
<LiveIndicator size={size > 32 ? 'small' : 'tiny'} />
|
||||
)}
|
||||
{alert}
|
||||
</View>
|
||||
) : (
|
||||
<View style={containerStyle}>
|
||||
<DefaultAvatar type={type} shape={finalShape} size={size} />
|
||||
<MediaInsetBorder style={borderStyle} />
|
||||
{live && size > 16 && !hideLiveBadge && (
|
||||
<LiveIndicator size={size > 32 ? 'small' : 'tiny'} />
|
||||
)}
|
||||
{alert}
|
||||
</View>
|
||||
)
|
||||
@@ -486,21 +514,32 @@ let PreviewableUserAvatar = ({
|
||||
disableHoverCard,
|
||||
disableNavigation,
|
||||
onBeforePress,
|
||||
live,
|
||||
...rest
|
||||
}: PreviewableUserAvatarProps): React.ReactNode => {
|
||||
const {_} = useLingui()
|
||||
const queryClient = useQueryClient()
|
||||
const status = useActorStatus(profile)
|
||||
const liveControl = useDialogControl()
|
||||
const playHaptic = useHaptics()
|
||||
|
||||
const onPress = React.useCallback(() => {
|
||||
const onPress = useCallback(() => {
|
||||
onBeforePress?.()
|
||||
unstableCacheProfileView(queryClient, profile)
|
||||
}, [profile, queryClient, onBeforePress])
|
||||
|
||||
const onOpenLiveStatus = useCallback(() => {
|
||||
playHaptic('Light')
|
||||
logger.metric('live:card:open', {subject: profile.did, from: 'post'})
|
||||
liveControl.open()
|
||||
}, [liveControl, playHaptic, profile.did])
|
||||
|
||||
const avatarEl = (
|
||||
<UserAvatar
|
||||
avatar={profile.avatar}
|
||||
moderation={moderation}
|
||||
type={profile.associated?.labeler ? 'labeler' : 'user'}
|
||||
live={status.isActive || live}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
@@ -509,9 +548,32 @@ let PreviewableUserAvatar = ({
|
||||
<ProfileHoverCard did={profile.did} disable={disableHoverCard}>
|
||||
{disableNavigation ? (
|
||||
avatarEl
|
||||
) : status.isActive && (isNative || isTouchDevice) ? (
|
||||
<>
|
||||
<Button
|
||||
label={_(
|
||||
msg`${sanitizeDisplayName(
|
||||
profile.displayName || sanitizeHandle(profile.handle),
|
||||
)}'s avatar`,
|
||||
)}
|
||||
accessibilityHint={_(msg`Opens live status dialog`)}
|
||||
onPress={onOpenLiveStatus}>
|
||||
{avatarEl}
|
||||
</Button>
|
||||
<LiveStatusDialog
|
||||
control={liveControl}
|
||||
profile={profile}
|
||||
status={status}
|
||||
embed={status.embed}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Link
|
||||
label={_(msg`${profile.displayName || profile.handle}'s avatar`)}
|
||||
label={_(
|
||||
msg`${sanitizeDisplayName(
|
||||
profile.displayName || sanitizeHandle(profile.handle),
|
||||
)}'s avatar`,
|
||||
)}
|
||||
accessibilityHint={_(msg`Opens this profile`)}
|
||||
to={makeProfileLink({
|
||||
did: profile.did,
|
||||
|
||||
Reference in New Issue
Block a user