diff --git a/src/lib/labeling/const.ts b/src/lib/labeling/const.ts index 028f3191f5..df0154523b 100644 --- a/src/lib/labeling/const.ts +++ b/src/lib/labeling/const.ts @@ -104,7 +104,7 @@ export const LABEL_VAL_GROUPS: Record = { }, impersonation: { id: 'impersonation', - title: 'Impersonated Content', + title: 'Impersonation', values: ['impersonation'], }, unknown: { diff --git a/src/lib/labeling/helpers.ts b/src/lib/labeling/helpers.ts index f1849a3d0f..b4b5208659 100644 --- a/src/lib/labeling/helpers.ts +++ b/src/lib/labeling/helpers.ts @@ -1,4 +1,3 @@ -import {ComAtprotoLabelDefs} from '@atproto/api' import {LabelValGroup, LABEL_VAL_GROUPS} from './const' export function getLabelValueGroup(labelVal: string): LabelValGroup { diff --git a/src/state/models/content/profile.ts b/src/state/models/content/profile.ts index 8d9c71b39a..45d928c92e 100644 --- a/src/state/models/content/profile.ts +++ b/src/state/models/content/profile.ts @@ -1,6 +1,7 @@ import {makeAutoObservable, runInAction} from 'mobx' import {PickedMedia} from 'lib/media/picker' import { + ComAtprotoLabelDefs, AppBskyActorGetProfile as GetProfile, AppBskyActorProfile, RichText, @@ -41,6 +42,7 @@ export class ProfileModel { followersCount: number = 0 followsCount: number = 0 postsCount: number = 0 + labels?: ComAtprotoLabelDefs.Label[] = undefined viewer = new ProfileViewerModel() // added data @@ -210,6 +212,7 @@ export class ProfileModel { this.followersCount = res.data.followersCount || 0 this.followsCount = res.data.followsCount || 0 this.postsCount = res.data.postsCount || 0 + this.labels = res.data.labels if (res.data.viewer) { Object.assign(this.viewer, res.data.viewer) this.rootStore.me.follows.hydrate(this.did, res.data.viewer.following) diff --git a/src/view/com/discover/SuggestedFollows.tsx b/src/view/com/discover/SuggestedFollows.tsx index e4ada52046..ae5605c5cd 100644 --- a/src/view/com/discover/SuggestedFollows.tsx +++ b/src/view/com/discover/SuggestedFollows.tsx @@ -31,6 +31,7 @@ export const SuggestedFollows = ({ handle={item.handle} displayName={item.displayName} avatar={item.avatar} + labels={item.labels} noBg noBorder description={ diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index e77eae17e1..22a354da09 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -8,7 +8,7 @@ import { View, } from 'react-native' import {AppBskyEmbedImages} from '@atproto/api' -import {AtUri} from '@atproto/api' +import {AtUri, ComAtprotoLabelDefs} from '@atproto/api' import { FontAwesomeIcon, FontAwesomeIconStyle, @@ -38,6 +38,7 @@ interface Author { handle: string displayName?: string avatar?: string + labels?: ComAtprotoLabelDefs.Label[] } export const FeedItem = observer(function FeedItem({ @@ -129,6 +130,7 @@ export const FeedItem = observer(function FeedItem({ handle: item.author.handle, displayName: item.author.displayName, avatar: item.author.avatar, + labels: item.author.labels, }, ] if (item.additional?.length) { @@ -138,6 +140,7 @@ export const FeedItem = observer(function FeedItem({ handle: item2.author.handle, displayName: item2.author.displayName, avatar: item2.author.avatar, + labels: item.author.labels, })), ) } @@ -255,7 +258,11 @@ function CondensedAuthorsList({ href={authors[0].href} title={`@${authors[0].handle}`} asAnchor> - + ) @@ -264,7 +271,11 @@ function CondensedAuthorsList({ {authors.slice(0, MAX_AUTHORS).map(author => ( - + ))} {authors.length > MAX_AUTHORS ? ( @@ -317,7 +328,11 @@ function ExpandedAuthorsList({ style={styles.expandedAuthor} asAnchor> - + ) diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx index 30f8fd4453..9874460e99 100644 --- a/src/view/com/post-thread/PostRepostedBy.tsx +++ b/src/view/com/post-thread/PostRepostedBy.tsx @@ -64,6 +64,7 @@ export const PostRepostedBy = observer(function PostRepostedBy({ handle={item.handle} displayName={item.displayName} avatar={item.avatar} + labels={item.labels} isFollowedBy={!!item.viewer?.followedBy} /> ) diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 43f9b14bc5..6e8758f7e2 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -138,7 +138,11 @@ export const PostThreadItem = observer(function PostThreadItem({ - + @@ -304,13 +308,18 @@ export const PostThreadItem = observer(function PostThreadItem({ - + - + - + - + ) diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx index 54b5a319a4..10da79c5e7 100644 --- a/src/view/com/profile/ProfileFollows.tsx +++ b/src/view/com/profile/ProfileFollows.tsx @@ -64,6 +64,7 @@ export const ProfileFollows = observer(function ProfileFollows({ handle={item.handle} displayName={item.displayName} avatar={item.avatar} + labels={item.labels} isFollowedBy={!!item.viewer?.followedBy} /> ) diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index 1326d3ec33..d520a712fd 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -27,6 +27,7 @@ import {Text} from '../util/text/Text' import {RichText} from '../util/text/RichText' import {UserAvatar} from '../util/UserAvatar' import {UserBanner} from '../util/UserBanner' +import {ProfileHeaderLabels} from '../util/moderation/ProfileHeaderLabels' import {usePalette} from 'lib/hooks/usePalette' import {useAnalytics} from 'lib/analytics' import {NavigationProp} from 'lib/routes/types' @@ -320,6 +321,7 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoaded({ richText={view.descriptionRichText} /> ) : undefined} + {view.viewer.muted ? ( - + diff --git a/src/view/com/search/SearchResults.tsx b/src/view/com/search/SearchResults.tsx index b53965f44c..5d6163d4b0 100644 --- a/src/view/com/search/SearchResults.tsx +++ b/src/view/com/search/SearchResults.tsx @@ -101,6 +101,7 @@ const Profiles = observer(({model}: {model: SearchUIModel}) => { displayName={item.displayName} avatar={item.avatar} description={item.description} + labels={item.labels} /> ))} diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index c46c16da05..d9dd11e05a 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -15,6 +15,7 @@ interface PostMetaOpts { authorAvatar?: string authorHandle: string authorDisplayName: string | undefined + authorHasWarning: boolean postHref: string timestamp: string did?: string @@ -93,7 +94,11 @@ export const PostMeta = observer(function (opts: PostMetaOpts) { {typeof opts.authorAvatar !== 'undefined' && ( - + )} diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index ff741cd349..d18c2d6978 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -44,10 +44,12 @@ function DefaultAvatar({size}: {size: number}) { export function UserAvatar({ size, avatar, + hasWarning, onSelectNewAvatar, }: { size: number avatar?: string | null + hasWarning?: boolean onSelectNewAvatar?: (img: PickedMedia | null) => void }) { const store = useStores() @@ -105,6 +107,22 @@ export function UserAvatar({ }, }, ] + + const warning = React.useMemo(() => { + if (!hasWarning) { + return <> + } + return ( + + + + ) + }, [hasWarning, size, pal]) + // onSelectNewAvatar is only passed as prop on the EditProfile component return onSelectNewAvatar ? ( ) : avatar ? ( - + + + {warning} + ) : ( - + + + {warning} + ) } @@ -165,4 +189,13 @@ const styles = StyleSheet.create({ height: 80, borderRadius: 40, }, + warningIconContainer: { + position: 'absolute', + right: 0, + bottom: 0, + borderRadius: 100, + }, + warningIcon: { + color: colors.red3, + }, }) diff --git a/src/view/com/util/moderation/ProfileHeaderLabels.tsx b/src/view/com/util/moderation/ProfileHeaderLabels.tsx new file mode 100644 index 0000000000..e099f09a73 --- /dev/null +++ b/src/view/com/util/moderation/ProfileHeaderLabels.tsx @@ -0,0 +1,55 @@ +import React from 'react' +import {StyleSheet, View} from 'react-native' +import {ComAtprotoLabelDefs} from '@atproto/api' +import { + FontAwesomeIcon, + FontAwesomeIconStyle, +} from '@fortawesome/react-native-fontawesome' +import {Text} from '../text/Text' +import {usePalette} from 'lib/hooks/usePalette' +import {getLabelValueGroup} from 'lib/labeling/helpers' + +export function ProfileHeaderLabels({ + labels, +}: { + labels: ComAtprotoLabelDefs.Label[] | undefined +}) { + const palErr = usePalette('error') + if (!labels?.length) { + return null + } + return ( + <> + {labels.map((label, i) => { + const labelGroup = getLabelValueGroup(label?.val || '') + return ( + + + + This account has been flagged for{' '} + {labelGroup.title.toLocaleLowerCase()}. + + + ) + })} + + ) +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + gap: 10, + borderWidth: 1, + borderRadius: 6, + paddingHorizontal: 10, + paddingVertical: 8, + }, +}) diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx index 5a8be5a148..94e8372385 100644 --- a/src/view/com/util/post-embeds/QuoteEmbed.tsx +++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx @@ -42,6 +42,7 @@ export function QuoteEmbed({ authorAvatar={quote.author.avatar} authorHandle={quote.author.handle} authorDisplayName={quote.author.displayName} + authorHasWarning={false} postHref={itemHref} timestamp={quote.indexedAt} /> diff --git a/src/view/screens/Search.tsx b/src/view/screens/Search.tsx index e1fb3ec0a1..ed9effd0b5 100644 --- a/src/view/screens/Search.tsx +++ b/src/view/screens/Search.tsx @@ -155,6 +155,7 @@ export const SearchScreen = withAuthRequired( testID={`searchAutoCompleteResult-${item.handle}`} handle={item.handle} displayName={item.displayName} + labels={item.labels} avatar={item.avatar} /> ))} diff --git a/src/view/shell/desktop/Search.tsx b/src/view/shell/desktop/Search.tsx index 1bc12add17..9954719443 100644 --- a/src/view/shell/desktop/Search.tsx +++ b/src/view/shell/desktop/Search.tsx @@ -90,6 +90,7 @@ export const DesktopSearch = observer(function DesktopSearch() { handle={item.handle} displayName={item.displayName} avatar={item.avatar} + labels={item.labels} noBorder={i === 0} /> ))}