[APP-1775] Handle labels on actor statuses (#9716)
(cherry picked from commit 8c9a11dc3f)
This commit is contained in:
+1
-1
@@ -81,7 +81,7 @@
|
||||
"icons:optimize": "svgo -f ./assets/icons"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.19.6",
|
||||
"@atproto/api": "^0.19.8",
|
||||
"@bitdrift/react-native": "^0.6.8",
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"@bsky.app/alf": "^0.1.7",
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import {useCallback} from 'react'
|
||||
import {useCallback, useMemo} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {Image} from 'expo-image'
|
||||
import {type AppBskyActorDefs, type AppBskyEmbedExternal} from '@atproto/api'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyEmbedExternal,
|
||||
moderateStatus,
|
||||
} from '@atproto/api'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
@@ -19,8 +21,10 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo'
|
||||
import {Globe_Stroke2_Corner0_Rounded} from '#/components/icons/Globe'
|
||||
import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
|
||||
import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRightIcon} from '#/components/icons/SquareArrowTopRight'
|
||||
import {createStaticClick, SimpleInlineLinkText} from '#/components/Link'
|
||||
import * as Hider from '#/components/moderation/Hider'
|
||||
import {useGlobalReportDialogControl} from '#/components/moderation/ReportDialog'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import {Text} from '#/components/Typography'
|
||||
@@ -64,7 +68,7 @@ function DialogInner({
|
||||
navigation: NavigationProp
|
||||
status: AppBskyActorDefs.StatusView
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const {t: l} = useLingui()
|
||||
const control = Dialog.useDialogContext()
|
||||
|
||||
const onPressOpenProfile = useCallback(() => {
|
||||
@@ -77,7 +81,7 @@ function DialogInner({
|
||||
|
||||
return (
|
||||
<Dialog.ScrollableInner
|
||||
label={_(msg`${sanitizeHandle(profile.handle)} is live`)}
|
||||
label={l`${sanitizeHandle(profile.handle)} is live`}
|
||||
contentContainerStyle={[a.pt_0, a.px_0]}
|
||||
style={[web({maxWidth: 420}), a.overflow_hidden]}>
|
||||
<LiveStatus
|
||||
@@ -105,45 +109,55 @@ export function LiveStatus({
|
||||
onPressOpenProfile: () => void
|
||||
}) {
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const {t: l} = useLingui()
|
||||
const t = useTheme()
|
||||
const queryClient = useQueryClient()
|
||||
const openLink = useOpenLink()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const reportDialogControl = useGlobalReportDialogControl()
|
||||
const dialogContext = Dialog.useDialogContext()
|
||||
const moderation = useMemo(() => {
|
||||
return moderateStatus(profile, moderationOpts!)
|
||||
}, [profile, moderationOpts])
|
||||
|
||||
return (
|
||||
<>
|
||||
{embed.external.thumb && (
|
||||
<View
|
||||
style={[
|
||||
t.atoms.bg_contrast_25,
|
||||
a.w_full,
|
||||
a.aspect_card,
|
||||
android([
|
||||
a.overflow_hidden,
|
||||
{
|
||||
borderTopLeftRadius: a.rounded_md.borderRadius,
|
||||
borderTopRightRadius: a.rounded_md.borderRadius,
|
||||
},
|
||||
]),
|
||||
]}>
|
||||
<Image
|
||||
source={embed.external.thumb}
|
||||
contentFit="cover"
|
||||
style={[a.absolute, a.inset_0]}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
<LiveIndicator
|
||||
size="large"
|
||||
style={[
|
||||
a.absolute,
|
||||
{top: tokens.space.lg, left: tokens.space.lg},
|
||||
a.align_start,
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<Hider.Outer modui={moderation.ui('contentMedia')}>
|
||||
<Hider.Mask>
|
||||
<ModeratedImage />
|
||||
</Hider.Mask>
|
||||
<Hider.Content>
|
||||
<View
|
||||
style={[
|
||||
t.atoms.bg_contrast_25,
|
||||
a.w_full,
|
||||
a.aspect_card,
|
||||
android([
|
||||
a.overflow_hidden,
|
||||
{
|
||||
borderTopLeftRadius: a.rounded_md.borderRadius,
|
||||
borderTopRightRadius: a.rounded_md.borderRadius,
|
||||
},
|
||||
]),
|
||||
]}>
|
||||
<Image
|
||||
source={embed.external.thumb}
|
||||
contentFit="cover"
|
||||
style={[a.absolute, a.inset_0]}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
<LiveIndicator
|
||||
size="large"
|
||||
style={[
|
||||
a.absolute,
|
||||
{top: tokens.space.lg, left: tokens.space.lg},
|
||||
a.align_start,
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
</Hider.Content>
|
||||
</Hider.Outer>
|
||||
)}
|
||||
<View
|
||||
style={[
|
||||
@@ -171,7 +185,7 @@ export function LiveStatus({
|
||||
</View>
|
||||
</View>
|
||||
<Button
|
||||
label={_(msg`Watch now`)}
|
||||
label={l`Watch now`}
|
||||
size={platform({native: 'large', web: 'small'})}
|
||||
color="primary"
|
||||
variant="solid"
|
||||
@@ -200,7 +214,7 @@ export function LiveStatus({
|
||||
/>
|
||||
</View>
|
||||
<Button
|
||||
label={_(msg`Open profile`)}
|
||||
label={l`Open profile`}
|
||||
size="small"
|
||||
color="secondary"
|
||||
variant="solid"
|
||||
@@ -231,7 +245,7 @@ export function LiveStatus({
|
||||
</View>
|
||||
{status && (
|
||||
<SimpleInlineLinkText
|
||||
label={_(msg`Report this livestream`)}
|
||||
label={l`Report this livestream`}
|
||||
{...createStaticClick(() => {
|
||||
function open() {
|
||||
reportDialogControl.open({
|
||||
@@ -256,3 +270,58 @@ export function LiveStatus({
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function ModeratedImage() {
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
const hider = Hider.useHider()
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.p_lg,
|
||||
a.py_xl,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<View style={[a.align_center, a.gap_sm, {maxWidth: 200}]}>
|
||||
<ImageIcon size="lg" fill={t.atoms.text_contrast_medium.color} />
|
||||
<Text
|
||||
style={[
|
||||
a.italic,
|
||||
a.leading_snug,
|
||||
a.text_center,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{hider.meta.allowOverride ? (
|
||||
<Trans comment="Image has been moderated and user has the option of showing it temporarily">
|
||||
Image is hidden due to your moderation settings.
|
||||
</Trans>
|
||||
) : (
|
||||
/*
|
||||
* In practice, if `allowOverride` is false, we won't even allow this
|
||||
* dialog to open. That is handled in
|
||||
* `#/features/liveNow/index.tsx`. But for clarity, I've included
|
||||
* this here.
|
||||
*/
|
||||
<Trans comment="Image has been moderated and is not visible to the user">
|
||||
Image is unavailable.
|
||||
</Trans>
|
||||
)}
|
||||
</Text>
|
||||
|
||||
{hider.meta.allowOverride && (
|
||||
<SimpleInlineLinkText
|
||||
label={l`Show anyway`}
|
||||
{...createStaticClick(() => {
|
||||
hider.setIsContentVisible(true)
|
||||
})}>
|
||||
<Trans>Show anyway</Trans>
|
||||
</SimpleInlineLinkText>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
AppBskyEmbedExternal,
|
||||
AtUri,
|
||||
ComAtprotoRepoPutRecord,
|
||||
moderateStatus,
|
||||
} from '@atproto/api'
|
||||
import {retry} from '@atproto/common-web'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
updateProfileShadow,
|
||||
useMaybeProfileShadow,
|
||||
} from '#/state/cache/profile-shadow'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useTickEveryMinute} from '#/state/shell'
|
||||
import {useDialogContext} from '#/components/Dialog'
|
||||
@@ -37,6 +39,13 @@ export const DEFAULT_ALLOWED_DOMAINS = [
|
||||
'bluecast.app',
|
||||
]
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
status: '',
|
||||
isDisabled: false,
|
||||
isActive: false,
|
||||
record: {},
|
||||
} satisfies AppBskyActorDefs.StatusView
|
||||
|
||||
export type LiveNowConfig = {
|
||||
canGoLive: boolean
|
||||
currentAccountAllowedHosts: Set<string>
|
||||
@@ -87,10 +96,23 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
|
||||
const shadowed = useMaybeProfileShadow(actor)
|
||||
const tick = useTickEveryMinute()
|
||||
const config = useLiveNowConfig()
|
||||
const moderationOpts = useModerationOpts()
|
||||
|
||||
const moderation = useMemo(() => {
|
||||
if (!actor || !('status' in actor && actor.status)) return undefined
|
||||
return moderateStatus(actor, moderationOpts!)
|
||||
}, [actor, moderationOpts])
|
||||
|
||||
return useMemo(() => {
|
||||
void tick // revalidate every minute
|
||||
|
||||
/*
|
||||
* Do not even allow Live Now to show if filtered for `contentList`.
|
||||
*/
|
||||
if (moderation && moderation.ui('contentList').filter) {
|
||||
return DEFAULT_STATE
|
||||
}
|
||||
|
||||
if (shadowed && 'status' in shadowed && shadowed.status) {
|
||||
const isValid = isStatusValidForViewers(shadowed.status, config)
|
||||
const isDisabled = shadowed.status.isDisabled || false
|
||||
@@ -118,14 +140,9 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
|
||||
record: shadowed.status.record,
|
||||
} satisfies AppBskyActorDefs.StatusView
|
||||
} else {
|
||||
return {
|
||||
status: '',
|
||||
isDisabled: false,
|
||||
isActive: false,
|
||||
record: {},
|
||||
} satisfies AppBskyActorDefs.StatusView
|
||||
return DEFAULT_STATE
|
||||
}
|
||||
}, [shadowed, config, tick])
|
||||
}, [shadowed, config, tick, moderation])
|
||||
}
|
||||
|
||||
export function isStatusStillActive(timeStr: string | undefined) {
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
"@jridgewell/gen-mapping" "^0.3.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@atproto/api@^0.19.6":
|
||||
version "0.19.6"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.19.6.tgz#c8fae3d792fe429c900ac0ba2609d60b9a89e28b"
|
||||
integrity sha512-8L5dZvGaclB52b8msjtgDNx3uLWUY4PELA7KbFyAWBFVasCceE1txdrscCqDCLN+Fff9+Sm07OIjnjHYJXdETA==
|
||||
"@atproto/api@^0.19.8":
|
||||
version "0.19.8"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.19.8.tgz#ae847abece43f0108535c6305780079e8782ab29"
|
||||
integrity sha512-b79kuI3AzEmpLLi9afRNq6T0KFEEVL4d+vHFAtWxeDwS7lfwUOIIngMjAVvwmwC5nJRZIrK8L9d4y7LD8zdvsg==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.4.19"
|
||||
"@atproto/common-web" "^0.4.20"
|
||||
"@atproto/lexicon" "^0.6.2"
|
||||
"@atproto/syntax" "^0.5.3"
|
||||
"@atproto/xrpc" "^0.7.7"
|
||||
@@ -34,7 +34,7 @@
|
||||
tlds "^1.234.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/common-web@^0.4.18", "@atproto/common-web@^0.4.19":
|
||||
"@atproto/common-web@^0.4.18":
|
||||
version "0.4.19"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.19.tgz#bbd7f84f545ebe73ca3bc00314ccf4ee66e7069e"
|
||||
integrity sha512-3BTi58p5WpT+9/zb6UZrdsXcfPo5P45UJm0E4iwHLILr+jc37CuBj9JReDSZ4U0i9RTrI3ZkfySyZ9bd+LnMsw==
|
||||
@@ -44,6 +44,16 @@
|
||||
"@atproto/syntax" "^0.5.1"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/common-web@^0.4.20":
|
||||
version "0.4.20"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.20.tgz#bb455868e674d45ed1044c68ccccae3c08168d47"
|
||||
integrity sha512-RcsYT28yQgVi/Glb/hHPGpqpzIlKrbMLeldEd7PmmMLWDaJL2j3lb92qytvxjl1yhi2Ssq2TEuMZ2NlWaAbpow==
|
||||
dependencies:
|
||||
"@atproto/lex-data" "^0.0.15"
|
||||
"@atproto/lex-json" "^0.0.15"
|
||||
"@atproto/syntax" "^0.5.3"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/lex-data@^0.0.14":
|
||||
version "0.0.14"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.14.tgz#2f2f3c64699925a0d4785e5afd0e7731ba1d46c0"
|
||||
@@ -54,6 +64,16 @@
|
||||
uint8arrays "3.0.0"
|
||||
unicode-segmenter "^0.14.0"
|
||||
|
||||
"@atproto/lex-data@^0.0.15":
|
||||
version "0.0.15"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.15.tgz#b9a644d71d4a99b32f452250994b5f12276335ef"
|
||||
integrity sha512-ZsbGiaM5S3CnGrcTMbDGON3bLZzCi/Mx9UvcMREKSRujnF68eHgMiXxJqvykP7+QpOX6tYCK93axZkuJVhtSEw==
|
||||
dependencies:
|
||||
multiformats "^9.9.0"
|
||||
tslib "^2.8.1"
|
||||
uint8arrays "3.0.0"
|
||||
unicode-segmenter "^0.14.0"
|
||||
|
||||
"@atproto/lex-json@^0.0.14":
|
||||
version "0.0.14"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-json/-/lex-json-0.0.14.tgz#717e533ab583aa5f580acb2a77d9aa3e7eddaa17"
|
||||
@@ -62,6 +82,14 @@
|
||||
"@atproto/lex-data" "^0.0.14"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@atproto/lex-json@^0.0.15":
|
||||
version "0.0.15"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lex-json/-/lex-json-0.0.15.tgz#34d300e5dfd8a0ec76ca7363f264a488e17c1bd9"
|
||||
integrity sha512-kCLdP629H6GhgPjBTpZibUoqlpmW0hnVfZVwcD4s4Jch1KAqY/QcfL24Ih8wrW0Ok1YvtMIhjk98evdTA2OJcw==
|
||||
dependencies:
|
||||
"@atproto/lex-data" "^0.0.15"
|
||||
tslib "^2.8.1"
|
||||
|
||||
"@atproto/lexicon@^0.6.0", "@atproto/lexicon@^0.6.2":
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.6.2.tgz#f6152a2119df953236ca127c4b30e332265e81e7"
|
||||
|
||||
Reference in New Issue
Block a user