[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"
|
"icons:optimize": "svgo -f ./assets/icons"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "^0.19.6",
|
"@atproto/api": "^0.19.8",
|
||||||
"@bitdrift/react-native": "^0.6.8",
|
"@bitdrift/react-native": "^0.6.8",
|
||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
"@bsky.app/alf": "^0.1.7",
|
"@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 {View} from 'react-native'
|
||||||
import {Image} from 'expo-image'
|
import {Image} from 'expo-image'
|
||||||
import {type AppBskyActorDefs, type AppBskyEmbedExternal} from '@atproto/api'
|
import {
|
||||||
import {msg} from '@lingui/core/macro'
|
type AppBskyActorDefs,
|
||||||
import {useLingui} from '@lingui/react'
|
type AppBskyEmbedExternal,
|
||||||
import {Trans} from '@lingui/react/macro'
|
moderateStatus,
|
||||||
|
} from '@atproto/api'
|
||||||
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
@@ -19,8 +21,10 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
|||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo'
|
||||||
import {Globe_Stroke2_Corner0_Rounded} from '#/components/icons/Globe'
|
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 {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRightIcon} from '#/components/icons/SquareArrowTopRight'
|
||||||
import {createStaticClick, SimpleInlineLinkText} from '#/components/Link'
|
import {createStaticClick, SimpleInlineLinkText} from '#/components/Link'
|
||||||
|
import * as Hider from '#/components/moderation/Hider'
|
||||||
import {useGlobalReportDialogControl} from '#/components/moderation/ReportDialog'
|
import {useGlobalReportDialogControl} from '#/components/moderation/ReportDialog'
|
||||||
import * as ProfileCard from '#/components/ProfileCard'
|
import * as ProfileCard from '#/components/ProfileCard'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
@@ -64,7 +68,7 @@ function DialogInner({
|
|||||||
navigation: NavigationProp
|
navigation: NavigationProp
|
||||||
status: AppBskyActorDefs.StatusView
|
status: AppBskyActorDefs.StatusView
|
||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const control = Dialog.useDialogContext()
|
const control = Dialog.useDialogContext()
|
||||||
|
|
||||||
const onPressOpenProfile = useCallback(() => {
|
const onPressOpenProfile = useCallback(() => {
|
||||||
@@ -77,7 +81,7 @@ function DialogInner({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.ScrollableInner
|
<Dialog.ScrollableInner
|
||||||
label={_(msg`${sanitizeHandle(profile.handle)} is live`)}
|
label={l`${sanitizeHandle(profile.handle)} is live`}
|
||||||
contentContainerStyle={[a.pt_0, a.px_0]}
|
contentContainerStyle={[a.pt_0, a.px_0]}
|
||||||
style={[web({maxWidth: 420}), a.overflow_hidden]}>
|
style={[web({maxWidth: 420}), a.overflow_hidden]}>
|
||||||
<LiveStatus
|
<LiveStatus
|
||||||
@@ -105,17 +109,25 @@ export function LiveStatus({
|
|||||||
onPressOpenProfile: () => void
|
onPressOpenProfile: () => void
|
||||||
}) {
|
}) {
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const openLink = useOpenLink()
|
const openLink = useOpenLink()
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
const reportDialogControl = useGlobalReportDialogControl()
|
const reportDialogControl = useGlobalReportDialogControl()
|
||||||
const dialogContext = Dialog.useDialogContext()
|
const dialogContext = Dialog.useDialogContext()
|
||||||
|
const moderation = useMemo(() => {
|
||||||
|
return moderateStatus(profile, moderationOpts!)
|
||||||
|
}, [profile, moderationOpts])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{embed.external.thumb && (
|
{embed.external.thumb && (
|
||||||
|
<Hider.Outer modui={moderation.ui('contentMedia')}>
|
||||||
|
<Hider.Mask>
|
||||||
|
<ModeratedImage />
|
||||||
|
</Hider.Mask>
|
||||||
|
<Hider.Content>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
t.atoms.bg_contrast_25,
|
t.atoms.bg_contrast_25,
|
||||||
@@ -144,6 +156,8 @@ export function LiveStatus({
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
</Hider.Content>
|
||||||
|
</Hider.Outer>
|
||||||
)}
|
)}
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@@ -171,7 +185,7 @@ export function LiveStatus({
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Watch now`)}
|
label={l`Watch now`}
|
||||||
size={platform({native: 'large', web: 'small'})}
|
size={platform({native: 'large', web: 'small'})}
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
@@ -200,7 +214,7 @@ export function LiveStatus({
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Open profile`)}
|
label={l`Open profile`}
|
||||||
size="small"
|
size="small"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
@@ -231,7 +245,7 @@ export function LiveStatus({
|
|||||||
</View>
|
</View>
|
||||||
{status && (
|
{status && (
|
||||||
<SimpleInlineLinkText
|
<SimpleInlineLinkText
|
||||||
label={_(msg`Report this livestream`)}
|
label={l`Report this livestream`}
|
||||||
{...createStaticClick(() => {
|
{...createStaticClick(() => {
|
||||||
function open() {
|
function open() {
|
||||||
reportDialogControl.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,
|
AppBskyEmbedExternal,
|
||||||
AtUri,
|
AtUri,
|
||||||
ComAtprotoRepoPutRecord,
|
ComAtprotoRepoPutRecord,
|
||||||
|
moderateStatus,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {retry} from '@atproto/common-web'
|
import {retry} from '@atproto/common-web'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
@@ -21,6 +22,7 @@ import {
|
|||||||
updateProfileShadow,
|
updateProfileShadow,
|
||||||
useMaybeProfileShadow,
|
useMaybeProfileShadow,
|
||||||
} from '#/state/cache/profile-shadow'
|
} from '#/state/cache/profile-shadow'
|
||||||
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {useAgent, useSession} from '#/state/session'
|
||||||
import {useTickEveryMinute} from '#/state/shell'
|
import {useTickEveryMinute} from '#/state/shell'
|
||||||
import {useDialogContext} from '#/components/Dialog'
|
import {useDialogContext} from '#/components/Dialog'
|
||||||
@@ -37,6 +39,13 @@ export const DEFAULT_ALLOWED_DOMAINS = [
|
|||||||
'bluecast.app',
|
'bluecast.app',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const DEFAULT_STATE = {
|
||||||
|
status: '',
|
||||||
|
isDisabled: false,
|
||||||
|
isActive: false,
|
||||||
|
record: {},
|
||||||
|
} satisfies AppBskyActorDefs.StatusView
|
||||||
|
|
||||||
export type LiveNowConfig = {
|
export type LiveNowConfig = {
|
||||||
canGoLive: boolean
|
canGoLive: boolean
|
||||||
currentAccountAllowedHosts: Set<string>
|
currentAccountAllowedHosts: Set<string>
|
||||||
@@ -87,10 +96,23 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
|
|||||||
const shadowed = useMaybeProfileShadow(actor)
|
const shadowed = useMaybeProfileShadow(actor)
|
||||||
const tick = useTickEveryMinute()
|
const tick = useTickEveryMinute()
|
||||||
const config = useLiveNowConfig()
|
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(() => {
|
return useMemo(() => {
|
||||||
void tick // revalidate every minute
|
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) {
|
if (shadowed && 'status' in shadowed && shadowed.status) {
|
||||||
const isValid = isStatusValidForViewers(shadowed.status, config)
|
const isValid = isStatusValidForViewers(shadowed.status, config)
|
||||||
const isDisabled = shadowed.status.isDisabled || false
|
const isDisabled = shadowed.status.isDisabled || false
|
||||||
@@ -118,14 +140,9 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
|
|||||||
record: shadowed.status.record,
|
record: shadowed.status.record,
|
||||||
} satisfies AppBskyActorDefs.StatusView
|
} satisfies AppBskyActorDefs.StatusView
|
||||||
} else {
|
} else {
|
||||||
return {
|
return DEFAULT_STATE
|
||||||
status: '',
|
|
||||||
isDisabled: false,
|
|
||||||
isActive: false,
|
|
||||||
record: {},
|
|
||||||
} satisfies AppBskyActorDefs.StatusView
|
|
||||||
}
|
}
|
||||||
}, [shadowed, config, tick])
|
}, [shadowed, config, tick, moderation])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isStatusStillActive(timeStr: string | undefined) {
|
export function isStatusStillActive(timeStr: string | undefined) {
|
||||||
|
|||||||
@@ -20,12 +20,12 @@
|
|||||||
"@jridgewell/gen-mapping" "^0.3.0"
|
"@jridgewell/gen-mapping" "^0.3.0"
|
||||||
"@jridgewell/trace-mapping" "^0.3.9"
|
"@jridgewell/trace-mapping" "^0.3.9"
|
||||||
|
|
||||||
"@atproto/api@^0.19.6":
|
"@atproto/api@^0.19.8":
|
||||||
version "0.19.6"
|
version "0.19.8"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.19.6.tgz#c8fae3d792fe429c900ac0ba2609d60b9a89e28b"
|
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.19.8.tgz#ae847abece43f0108535c6305780079e8782ab29"
|
||||||
integrity sha512-8L5dZvGaclB52b8msjtgDNx3uLWUY4PELA7KbFyAWBFVasCceE1txdrscCqDCLN+Fff9+Sm07OIjnjHYJXdETA==
|
integrity sha512-b79kuI3AzEmpLLi9afRNq6T0KFEEVL4d+vHFAtWxeDwS7lfwUOIIngMjAVvwmwC5nJRZIrK8L9d4y7LD8zdvsg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@atproto/common-web" "^0.4.19"
|
"@atproto/common-web" "^0.4.20"
|
||||||
"@atproto/lexicon" "^0.6.2"
|
"@atproto/lexicon" "^0.6.2"
|
||||||
"@atproto/syntax" "^0.5.3"
|
"@atproto/syntax" "^0.5.3"
|
||||||
"@atproto/xrpc" "^0.7.7"
|
"@atproto/xrpc" "^0.7.7"
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
tlds "^1.234.0"
|
tlds "^1.234.0"
|
||||||
zod "^3.23.8"
|
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"
|
version "0.4.19"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.19.tgz#bbd7f84f545ebe73ca3bc00314ccf4ee66e7069e"
|
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.19.tgz#bbd7f84f545ebe73ca3bc00314ccf4ee66e7069e"
|
||||||
integrity sha512-3BTi58p5WpT+9/zb6UZrdsXcfPo5P45UJm0E4iwHLILr+jc37CuBj9JReDSZ4U0i9RTrI3ZkfySyZ9bd+LnMsw==
|
integrity sha512-3BTi58p5WpT+9/zb6UZrdsXcfPo5P45UJm0E4iwHLILr+jc37CuBj9JReDSZ4U0i9RTrI3ZkfySyZ9bd+LnMsw==
|
||||||
@@ -44,6 +44,16 @@
|
|||||||
"@atproto/syntax" "^0.5.1"
|
"@atproto/syntax" "^0.5.1"
|
||||||
zod "^3.23.8"
|
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":
|
"@atproto/lex-data@^0.0.14":
|
||||||
version "0.0.14"
|
version "0.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.14.tgz#2f2f3c64699925a0d4785e5afd0e7731ba1d46c0"
|
resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.14.tgz#2f2f3c64699925a0d4785e5afd0e7731ba1d46c0"
|
||||||
@@ -54,6 +64,16 @@
|
|||||||
uint8arrays "3.0.0"
|
uint8arrays "3.0.0"
|
||||||
unicode-segmenter "^0.14.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":
|
"@atproto/lex-json@^0.0.14":
|
||||||
version "0.0.14"
|
version "0.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/lex-json/-/lex-json-0.0.14.tgz#717e533ab583aa5f580acb2a77d9aa3e7eddaa17"
|
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"
|
"@atproto/lex-data" "^0.0.14"
|
||||||
tslib "^2.8.1"
|
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":
|
"@atproto/lexicon@^0.6.0", "@atproto/lexicon@^0.6.2":
|
||||||
version "0.6.2"
|
version "0.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.6.2.tgz#f6152a2119df953236ca127c4b30e332265e81e7"
|
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.6.2.tgz#f6152a2119df953236ca127c4b30e332265e81e7"
|
||||||
|
|||||||
Reference in New Issue
Block a user