diff options
Diffstat (limited to 'src/view/com')
-rw-r--r-- | src/view/com/composer/Composer.tsx | 6 | ||||
-rw-r--r-- | src/view/com/modals/Confirm.tsx | 3 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThread.tsx | 64 | ||||
-rw-r--r-- | src/view/com/posts/FeedSlice.tsx | 4 | ||||
-rw-r--r-- | src/view/com/profile/ProfileCard.tsx | 7 | ||||
-rw-r--r-- | src/view/com/profile/ProfileHeader.tsx | 625 |
6 files changed, 439 insertions, 270 deletions
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index c30d881ec..5ccc229d6 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -190,11 +190,7 @@ export const ComposePost = observer(function ComposePost({ const canPost = graphemeLength <= MAX_GRAPHEME_LENGTH - const selectTextInputPlaceholder = replyTo - ? 'Write your reply' - : gallery.isEmpty - ? 'Write a comment' - : "What's up?" + const selectTextInputPlaceholder = replyTo ? 'Write your reply' : "What's up?" const canSelectImages = gallery.size < 4 const viewStyles = { diff --git a/src/view/com/modals/Confirm.tsx b/src/view/com/modals/Confirm.tsx index 63877fe5d..6f7b062cf 100644 --- a/src/view/com/modals/Confirm.tsx +++ b/src/view/com/modals/Confirm.tsx @@ -11,6 +11,7 @@ import {s, colors} from 'lib/styles' import {ErrorMessage} from '../util/error/ErrorMessage' import {cleanError} from 'lib/strings/errors' import {usePalette} from 'lib/hooks/usePalette' +import {isDesktopWeb} from 'platform/detection' export const snapPoints = [300] @@ -77,7 +78,7 @@ const styles = StyleSheet.create({ container: { flex: 1, padding: 10, - paddingBottom: 60, + paddingBottom: isDesktopWeb ? 0 : 60, }, title: { textAlign: 'center', diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index 6e387b8d0..fe1822acb 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -7,6 +7,7 @@ import { TouchableOpacity, View, } from 'react-native' +import {AppBskyFeedDefs} from '@atproto/api' import {CenteredView, FlatList} from '../util/Views' import { PostThreadModel, @@ -27,11 +28,17 @@ import {useNavigation} from '@react-navigation/native' import {NavigationProp} from 'lib/routes/types' const REPLY_PROMPT = {_reactKey: '__reply__', _isHighlightedPost: false} +const DELETED = {_reactKey: '__deleted__', _isHighlightedPost: false} +const BLOCKED = {_reactKey: '__blocked__', _isHighlightedPost: false} const BOTTOM_COMPONENT = { _reactKey: '__bottom_component__', _isHighlightedPost: false, } -type YieldedItem = PostThreadItemModel | typeof REPLY_PROMPT +type YieldedItem = + | PostThreadItemModel + | typeof REPLY_PROMPT + | typeof DELETED + | typeof BLOCKED export const PostThread = observer(function PostThread({ uri, @@ -103,6 +110,22 @@ export const PostThread = observer(function PostThread({ ({item}: {item: YieldedItem}) => { if (item === REPLY_PROMPT) { return <ComposePrompt onPressCompose={onPressReply} /> + } else if (item === DELETED) { + return ( + <View style={[pal.border, pal.viewLight, styles.missingItem]}> + <Text type="lg-bold" style={pal.textLight}> + Deleted post. + </Text> + </View> + ) + } else if (item === BLOCKED) { + return ( + <View style={[pal.border, pal.viewLight, styles.missingItem]}> + <Text type="lg-bold" style={pal.textLight}> + Blocked post. + </Text> + </View> + ) } else if (item === BOTTOM_COMPONENT) { // HACK // due to some complexities with how flatlist works, this is the easiest way @@ -177,6 +200,30 @@ export const PostThread = observer(function PostThread({ </CenteredView> ) } + if (view.isBlocked) { + return ( + <CenteredView> + <View style={[pal.view, pal.border, styles.notFoundContainer]}> + <Text type="title-lg" style={[pal.text, s.mb5]}> + Post hidden + </Text> + <Text type="md" style={[pal.text, s.mb10]}> + You have blocked the author or you have been blocked by the author. + </Text> + <TouchableOpacity onPress={onPressBack}> + <Text type="2xl" style={pal.link}> + <FontAwesomeIcon + icon="angle-left" + style={[pal.link as FontAwesomeIconStyle, s.mr5]} + size={14} + /> + Back + </Text> + </TouchableOpacity> + </View> + </CenteredView> + ) + } // loaded // = @@ -208,8 +255,10 @@ function* flattenThread( isAscending = false, ): Generator<YieldedItem, void> { if (post.parent) { - if ('notFound' in post.parent && post.parent.notFound) { - // TODO render not found + if (AppBskyFeedDefs.isNotFoundPost(post.parent)) { + yield DELETED + } else if (AppBskyFeedDefs.isBlockedPost(post.parent)) { + yield BLOCKED } else { yield* flattenThread(post.parent as PostThreadItemModel, true) } @@ -220,8 +269,8 @@ function* flattenThread( } if (post.replies?.length) { for (const reply of post.replies) { - if ('notFound' in reply && reply.notFound) { - // TODO render not found + if (AppBskyFeedDefs.isNotFoundPost(reply)) { + yield DELETED } else { yield* flattenThread(reply as PostThreadItemModel) } @@ -238,6 +287,11 @@ const styles = StyleSheet.create({ paddingVertical: 14, borderRadius: 6, }, + missingItem: { + borderTop: 1, + paddingHorizontal: 18, + paddingVertical: 18, + }, bottomBorder: { borderBottomWidth: 1, }, diff --git a/src/view/com/posts/FeedSlice.tsx b/src/view/com/posts/FeedSlice.tsx index 651b69bff..5a191ac10 100644 --- a/src/view/com/posts/FeedSlice.tsx +++ b/src/view/com/posts/FeedSlice.tsx @@ -7,6 +7,7 @@ import {Text} from '../util/text/Text' import Svg, {Circle, Line} from 'react-native-svg' import {FeedItem} from './FeedItem' import {usePalette} from 'lib/hooks/usePalette' +import {ModerationBehaviorCode} from 'lib/labeling/types' export function FeedSlice({ slice, @@ -17,6 +18,9 @@ export function FeedSlice({ showFollowBtn?: boolean ignoreMuteFor?: string }) { + if (slice.moderation.list.behavior === ModerationBehaviorCode.Hide) { + return null + } if (slice.isThread && slice.items.length > 3) { const last = slice.items.length - 1 return ( diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index 154344388..66c172141 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -23,6 +23,7 @@ export const ProfileCard = observer( noBg, noBorder, followers, + overrideModeration, renderButton, }: { testID?: string @@ -30,6 +31,7 @@ export const ProfileCard = observer( noBg?: boolean noBorder?: boolean followers?: AppBskyActorDefs.ProfileView[] | undefined + overrideModeration?: boolean renderButton?: () => JSX.Element }) => { const store = useStores() @@ -40,7 +42,10 @@ export const ProfileCard = observer( getProfileViewBasicLabelInfo(profile), ) - if (moderation.list.behavior === ModerationBehaviorCode.Hide) { + if ( + moderation.list.behavior === ModerationBehaviorCode.Hide && + !overrideModeration + ) { return null } diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index d1104d184..719b84e20 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -96,281 +96,377 @@ export const ProfileHeader = observer( }, ) -const ProfileHeaderLoaded = observer(function ProfileHeaderLoaded({ - view, - onRefreshAll, - hideBackButton = false, -}: Props) { - const pal = usePalette('default') - const store = useStores() - const navigation = useNavigation<NavigationProp>() - const {track} = useAnalytics() - - const onPressBack = React.useCallback(() => { - navigation.goBack() - }, [navigation]) - - const onPressAvi = React.useCallback(() => { - if (view.avatar) { - store.shell.openLightbox(new ProfileImageLightbox(view)) - } - }, [store, view]) - - const onPressToggleFollow = React.useCallback(() => { - view?.toggleFollowing().then( - () => { - Toast.show( - `${ - view.viewer.following ? 'Following' : 'No longer following' - } ${sanitizeDisplayName(view.displayName || view.handle)}`, - ) - }, - err => store.log.error('Failed to toggle follow', err), - ) - }, [view, store]) - - const onPressEditProfile = React.useCallback(() => { - track('ProfileHeader:EditProfileButtonClicked') - store.shell.openModal({ - name: 'edit-profile', - profileView: view, - onUpdate: onRefreshAll, - }) - }, [track, store, view, onRefreshAll]) - - const onPressFollowers = React.useCallback(() => { - track('ProfileHeader:FollowersButtonClicked') - navigation.push('ProfileFollowers', {name: view.handle}) - }, [track, navigation, view]) - - const onPressFollows = React.useCallback(() => { - track('ProfileHeader:FollowsButtonClicked') - navigation.push('ProfileFollows', {name: view.handle}) - }, [track, navigation, view]) - - const onPressShare = React.useCallback(async () => { - track('ProfileHeader:ShareButtonClicked') - const url = toShareUrl(`/profile/${view.handle}`) - shareUrl(url) - }, [track, view]) - - const onPressMuteAccount = React.useCallback(async () => { - track('ProfileHeader:MuteAccountButtonClicked') - try { - await view.muteAccount() - Toast.show('Account muted') - } catch (e: any) { - store.log.error('Failed to mute account', e) - Toast.show(`There was an issue! ${e.toString()}`) - } - }, [track, view, store]) - - const onPressUnmuteAccount = React.useCallback(async () => { - track('ProfileHeader:UnmuteAccountButtonClicked') - try { - await view.unmuteAccount() - Toast.show('Account unmuted') - } catch (e: any) { - store.log.error('Failed to unmute account', e) - Toast.show(`There was an issue! ${e.toString()}`) - } - }, [track, view, store]) - - const onPressReportAccount = React.useCallback(() => { - track('ProfileHeader:ReportAccountButtonClicked') - store.shell.openModal({ - name: 'report-account', - did: view.did, - }) - }, [track, store, view]) - - const isMe = React.useMemo( - () => store.me.did === view.did, - [store.me.did, view.did], - ) - const dropdownItems: DropdownItem[] = React.useMemo(() => { - let items: DropdownItem[] = [ - { - testID: 'profileHeaderDropdownSahreBtn', - label: 'Share', - onPress: onPressShare, - }, - ] - if (!isMe) { - items.push({ - testID: 'profileHeaderDropdownMuteBtn', - label: view.viewer.muted ? 'Unmute Account' : 'Mute Account', - onPress: view.viewer.muted ? onPressUnmuteAccount : onPressMuteAccount, +const ProfileHeaderLoaded = observer( + ({view, onRefreshAll, hideBackButton = false}: Props) => { + const pal = usePalette('default') + const store = useStores() + const navigation = useNavigation<NavigationProp>() + const {track} = useAnalytics() + + const onPressBack = React.useCallback(() => { + navigation.goBack() + }, [navigation]) + + const onPressAvi = React.useCallback(() => { + if (view.avatar) { + store.shell.openLightbox(new ProfileImageLightbox(view)) + } + }, [store, view]) + + const onPressToggleFollow = React.useCallback(() => { + view?.toggleFollowing().then( + () => { + Toast.show( + `${ + view.viewer.following ? 'Following' : 'No longer following' + } ${sanitizeDisplayName(view.displayName || view.handle)}`, + ) + }, + err => store.log.error('Failed to toggle follow', err), + ) + }, [view, store]) + + const onPressEditProfile = React.useCallback(() => { + track('ProfileHeader:EditProfileButtonClicked') + store.shell.openModal({ + name: 'edit-profile', + profileView: view, + onUpdate: onRefreshAll, + }) + }, [track, store, view, onRefreshAll]) + + const onPressFollowers = React.useCallback(() => { + track('ProfileHeader:FollowersButtonClicked') + navigation.push('ProfileFollowers', {name: view.handle}) + }, [track, navigation, view]) + + const onPressFollows = React.useCallback(() => { + track('ProfileHeader:FollowsButtonClicked') + navigation.push('ProfileFollows', {name: view.handle}) + }, [track, navigation, view]) + + const onPressShare = React.useCallback(async () => { + track('ProfileHeader:ShareButtonClicked') + const url = toShareUrl(`/profile/${view.handle}`) + shareUrl(url) + }, [track, view]) + + const onPressMuteAccount = React.useCallback(async () => { + track('ProfileHeader:MuteAccountButtonClicked') + try { + await view.muteAccount() + Toast.show('Account muted') + } catch (e: any) { + store.log.error('Failed to mute account', e) + Toast.show(`There was an issue! ${e.toString()}`) + } + }, [track, view, store]) + + const onPressUnmuteAccount = React.useCallback(async () => { + track('ProfileHeader:UnmuteAccountButtonClicked') + try { + await view.unmuteAccount() + Toast.show('Account unmuted') + } catch (e: any) { + store.log.error('Failed to unmute account', e) + Toast.show(`There was an issue! ${e.toString()}`) + } + }, [track, view, store]) + + const onPressBlockAccount = React.useCallback(async () => { + track('ProfileHeader:BlockAccountButtonClicked') + store.shell.openModal({ + name: 'confirm', + title: 'Block Account', + message: + 'Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you. You will not see their content and they will be prevented from seeing yours.', + onPressConfirm: async () => { + try { + await view.blockAccount() + onRefreshAll() + Toast.show('Account blocked') + } catch (e: any) { + store.log.error('Failed to block account', e) + Toast.show(`There was an issue! ${e.toString()}`) + } + }, }) - items.push({ - testID: 'profileHeaderDropdownReportBtn', - label: 'Report Account', - onPress: onPressReportAccount, + }, [track, view, store, onRefreshAll]) + + const onPressUnblockAccount = React.useCallback(async () => { + track('ProfileHeader:UnblockAccountButtonClicked') + store.shell.openModal({ + name: 'confirm', + title: 'Unblock Account', + message: + 'The account will be able to interact with you after unblocking. (You can always block again in the future.)', + onPressConfirm: async () => { + try { + await view.unblockAccount() + onRefreshAll() + Toast.show('Account unblocked') + } catch (e: any) { + store.log.error('Failed to block unaccount', e) + Toast.show(`There was an issue! ${e.toString()}`) + } + }, }) - } - return items - }, [ - isMe, - view.viewer.muted, - onPressShare, - onPressUnmuteAccount, - onPressMuteAccount, - onPressReportAccount, - ]) - return ( - <View style={pal.view}> - <UserBanner banner={view.banner} moderation={view.moderation.avatar} /> - <View style={styles.content}> - <View style={[styles.buttonsLine]}> - {isMe ? ( - <TouchableOpacity - testID="profileHeaderEditProfileButton" - onPress={onPressEditProfile} - style={[styles.btn, styles.mainBtn, pal.btn]}> - <Text type="button" style={pal.text}> - Edit Profile - </Text> - </TouchableOpacity> - ) : ( + }, [track, view, store, onRefreshAll]) + + const onPressReportAccount = React.useCallback(() => { + track('ProfileHeader:ReportAccountButtonClicked') + store.shell.openModal({ + name: 'report-account', + did: view.did, + }) + }, [track, store, view]) + + const isMe = React.useMemo( + () => store.me.did === view.did, + [store.me.did, view.did], + ) + const dropdownItems: DropdownItem[] = React.useMemo(() => { + let items: DropdownItem[] = [ + { + testID: 'profileHeaderDropdownShareBtn', + label: 'Share', + onPress: onPressShare, + }, + ] + if (!isMe) { + items.push({sep: true}) + if (!view.viewer.blocking) { + items.push({ + testID: 'profileHeaderDropdownMuteBtn', + label: view.viewer.muted ? 'Unmute Account' : 'Mute Account', + onPress: view.viewer.muted + ? onPressUnmuteAccount + : onPressMuteAccount, + }) + } + items.push({ + testID: 'profileHeaderDropdownBlockBtn', + label: view.viewer.blocking ? 'Unblock Account' : 'Block Account', + onPress: view.viewer.blocking + ? onPressUnblockAccount + : onPressBlockAccount, + }) + items.push({ + testID: 'profileHeaderDropdownReportBtn', + label: 'Report Account', + onPress: onPressReportAccount, + }) + } + return items + }, [ + isMe, + view.viewer.muted, + view.viewer.blocking, + onPressShare, + onPressUnmuteAccount, + onPressMuteAccount, + onPressUnblockAccount, + onPressBlockAccount, + onPressReportAccount, + ]) + + const blockHide = !isMe && (view.viewer.blocking || view.viewer.blockedBy) + + return ( + <View style={pal.view}> + <UserBanner banner={view.banner} moderation={view.moderation.avatar} /> + <View style={styles.content}> + <View style={[styles.buttonsLine]}> + {isMe ? ( + <TouchableOpacity + testID="profileHeaderEditProfileButton" + onPress={onPressEditProfile} + style={[styles.btn, styles.mainBtn, pal.btn]}> + <Text type="button" style={pal.text}> + Edit Profile + </Text> + </TouchableOpacity> + ) : view.viewer.blocking ? ( + <TouchableOpacity + testID="unblockBtn" + onPress={onPressUnblockAccount} + style={[styles.btn, styles.mainBtn, pal.btn]}> + <Text type="button" style={[pal.text, s.bold]}> + Unblock + </Text> + </TouchableOpacity> + ) : !view.viewer.blockedBy ? ( + <> + {store.me.follows.getFollowState(view.did) === + FollowState.Following ? ( + <TouchableOpacity + testID="unfollowBtn" + onPress={onPressToggleFollow} + style={[styles.btn, styles.mainBtn, pal.btn]}> + <FontAwesomeIcon + icon="check" + style={[pal.text, s.mr5]} + size={14} + /> + <Text type="button" style={pal.text}> + Following + </Text> + </TouchableOpacity> + ) : ( + <TouchableOpacity + testID="followBtn" + onPress={onPressToggleFollow} + style={[styles.btn, styles.primaryBtn]}> + <FontAwesomeIcon + icon="plus" + style={[s.white as FontAwesomeIconStyle, s.mr5]} + /> + <Text type="button" style={[s.white, s.bold]}> + Follow + </Text> + </TouchableOpacity> + )} + </> + ) : null} + {dropdownItems?.length ? ( + <DropdownButton + testID="profileHeaderDropdownBtn" + type="bare" + items={dropdownItems} + style={[styles.btn, styles.secondaryBtn, pal.btn]}> + <FontAwesomeIcon icon="ellipsis" style={[pal.text]} /> + </DropdownButton> + ) : undefined} + </View> + <View> + <Text + testID="profileHeaderDisplayName" + type="title-2xl" + style={[pal.text, styles.title]}> + {sanitizeDisplayName(view.displayName || view.handle)} + </Text> + </View> + <View style={styles.handleLine}> + {view.viewer.followedBy && !blockHide ? ( + <View style={[styles.pill, pal.btn, s.mr5]}> + <Text type="xs" style={[pal.text]}> + Follows you + </Text> + </View> + ) : undefined} + <Text style={pal.textLight}>@{view.handle}</Text> + </View> + {!blockHide && ( <> - {store.me.follows.getFollowState(view.did) === - FollowState.Following ? ( + <View style={styles.metricsLine}> <TouchableOpacity - testID="unfollowBtn" - onPress={onPressToggleFollow} - style={[styles.btn, styles.mainBtn, pal.btn]}> - <FontAwesomeIcon - icon="check" - style={[pal.text, s.mr5]} - size={14} - /> - <Text type="button" style={pal.text}> - Following + testID="profileHeaderFollowersButton" + style={[s.flexRow, s.mr10]} + onPress={onPressFollowers}> + <Text type="md" style={[s.bold, s.mr2, pal.text]}> + {view.followersCount} + </Text> + <Text type="md" style={[pal.textLight]}> + {pluralize(view.followersCount, 'follower')} </Text> </TouchableOpacity> - ) : ( <TouchableOpacity - testID="followBtn" - onPress={onPressToggleFollow} - style={[styles.btn, styles.primaryBtn]}> - <FontAwesomeIcon - icon="plus" - style={[s.white as FontAwesomeIconStyle, s.mr5]} - /> - <Text type="button" style={[s.white, s.bold]}> - Follow + testID="profileHeaderFollowsButton" + style={[s.flexRow, s.mr10]} + onPress={onPressFollows}> + <Text type="md" style={[s.bold, s.mr2, pal.text]}> + {view.followsCount} + </Text> + <Text type="md" style={[pal.textLight]}> + following </Text> </TouchableOpacity> - )} + <View style={[s.flexRow, s.mr10]}> + <Text type="md" style={[s.bold, s.mr2, pal.text]}> + {view.postsCount} + </Text> + <Text type="md" style={[pal.textLight]}> + {pluralize(view.postsCount, 'post')} + </Text> + </View> + </View> + {view.descriptionRichText ? ( + <RichText + testID="profileHeaderDescription" + style={[styles.description, pal.text]} + numberOfLines={15} + richText={view.descriptionRichText} + /> + ) : undefined} </> )} - {dropdownItems?.length ? ( - <DropdownButton - testID="profileHeaderDropdownBtn" - type="bare" - items={dropdownItems} - style={[styles.btn, styles.secondaryBtn, pal.btn]}> - <FontAwesomeIcon icon="ellipsis" style={[pal.text]} /> - </DropdownButton> - ) : undefined} - </View> - <View> - <Text - testID="profileHeaderDisplayName" - type="title-2xl" - style={[pal.text, styles.title]}> - {sanitizeDisplayName(view.displayName || view.handle)} - </Text> - </View> - <View style={styles.handleLine}> - {view.viewer.followedBy ? ( - <View style={[styles.pill, pal.btn, s.mr5]}> - <Text type="xs" style={[pal.text]}> - Follows you - </Text> - </View> - ) : undefined} - <Text style={pal.textLight}>@{view.handle}</Text> - </View> - <View style={styles.metricsLine}> - <TouchableOpacity - testID="profileHeaderFollowersButton" - style={[s.flexRow, s.mr10]} - onPress={onPressFollowers}> - <Text type="md" style={[s.bold, s.mr2, pal.text]}> - {view.followersCount} - </Text> - <Text type="md" style={[pal.textLight]}> - {pluralize(view.followersCount, 'follower')} - </Text> - </TouchableOpacity> - <TouchableOpacity - testID="profileHeaderFollowsButton" - style={[s.flexRow, s.mr10]} - onPress={onPressFollows}> - <Text type="md" style={[s.bold, s.mr2, pal.text]}> - {view.followsCount} - </Text> - <Text type="md" style={[pal.textLight]}> - following - </Text> - </TouchableOpacity> - <View style={[s.flexRow, s.mr10]}> - <Text type="md" style={[s.bold, s.mr2, pal.text]}> - {view.postsCount} - </Text> - <Text type="md" style={[pal.textLight]}> - {pluralize(view.postsCount, 'post')} - </Text> + <ProfileHeaderWarnings moderation={view.moderation.view} /> + <View style={styles.moderationLines}> + {view.viewer.blocking ? ( + <View + testID="profileHeaderBlockedNotice" + style={[styles.moderationNotice, pal.view, pal.border]}> + <FontAwesomeIcon icon="ban" style={[pal.text, s.mr5]} /> + <Text type="md" style={[s.mr2, pal.text]}> + Account blocked + </Text> + </View> + ) : view.viewer.muted ? ( + <View + testID="profileHeaderMutedNotice" + style={[styles.moderationNotice, pal.view, pal.border]}> + <FontAwesomeIcon + icon={['far', 'eye-slash']} + style={[pal.text, s.mr5]} + /> + <Text type="md" style={[s.mr2, pal.text]}> + Account muted + </Text> + </View> + ) : undefined} + {view.viewer.blockedBy && ( + <View + testID="profileHeaderBlockedNotice" + style={[styles.moderationNotice, pal.view, pal.border]}> + <FontAwesomeIcon icon="ban" style={[pal.text, s.mr5]} /> + <Text type="md" style={[s.mr2, pal.text]}> + This account has blocked you + </Text> + </View> + )} </View> </View> - {view.descriptionRichText ? ( - <RichText - testID="profileHeaderDescription" - style={[styles.description, pal.text]} - numberOfLines={15} - richText={view.descriptionRichText} - /> - ) : undefined} - <ProfileHeaderWarnings moderation={view.moderation.view} /> - {view.viewer.muted ? ( + {!isDesktopWeb && !hideBackButton && ( + <TouchableWithoutFeedback + onPress={onPressBack} + hitSlop={BACK_HITSLOP}> + <View style={styles.backBtnWrapper}> + <BlurView style={styles.backBtn} blurType="dark"> + <FontAwesomeIcon size={18} icon="angle-left" style={s.white} /> + </BlurView> + </View> + </TouchableWithoutFeedback> + )} + <TouchableWithoutFeedback + testID="profileHeaderAviButton" + onPress={onPressAvi}> <View - testID="profileHeaderMutedNotice" - style={[styles.detailLine, pal.btn, s.p5]}> - <FontAwesomeIcon - icon={['far', 'eye-slash']} - style={[pal.text, s.mr5]} + style={[ + pal.view, + {borderColor: pal.colors.background}, + styles.avi, + ]}> + <UserAvatar + size={80} + avatar={view.avatar} + moderation={view.moderation.avatar} /> - <Text type="md" style={[s.mr2, pal.text]}> - Account muted - </Text> - </View> - ) : undefined} - </View> - {!isDesktopWeb && !hideBackButton && ( - <TouchableWithoutFeedback onPress={onPressBack} hitSlop={BACK_HITSLOP}> - <View style={styles.backBtnWrapper}> - <BlurView style={styles.backBtn} blurType="dark"> - <FontAwesomeIcon size={18} icon="angle-left" style={s.white} /> - </BlurView> </View> </TouchableWithoutFeedback> - )} - <TouchableWithoutFeedback - testID="profileHeaderAviButton" - onPress={onPressAvi}> - <View - style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}> - <UserAvatar - size={80} - avatar={view.avatar} - moderation={view.moderation.avatar} - /> - </View> - </TouchableWithoutFeedback> - </View> - ) -}) + </View> + ) + }, +) const styles = StyleSheet.create({ banner: { @@ -460,6 +556,19 @@ const styles = StyleSheet.create({ paddingVertical: 2, }, + moderationLines: { + gap: 6, + }, + + moderationNotice: { + flexDirection: 'row', + alignItems: 'center', + borderWidth: 1, + borderRadius: 8, + paddingHorizontal: 12, + paddingVertical: 10, + }, + br40: {borderRadius: 40}, br50: {borderRadius: 50}, }) |