diff options
author | Eric Bailey <git@esb.lol> | 2025-04-15 08:13:20 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-15 06:13:20 -0700 |
commit | f46336e34142e7f46bb1395f727e303e37b15d41 (patch) | |
tree | dac04a4abd8d100dc9c0d5c65ab7f75f1d3280eb /src/view/com | |
parent | 32c2b69b848050975b698067383dd24f2754cab2 (diff) | |
download | voidsky-f46336e34142e7f46bb1395f727e303e37b15d41.tar.zst |
Replace old ProfileCard with new (#8195)
* Replace usages of old ProfileCard * Replace Pills with Labels component * Replace impl of ProfileCardWithFollowButton * Remove never-used LikesDialog * Handle missing mod opts * Add missing profile hover * use modern button in listmembers * remove follow button from muted accounts list --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Diffstat (limited to 'src/view/com')
-rw-r--r-- | src/view/com/lists/ListMembers.tsx | 85 | ||||
-rw-r--r-- | src/view/com/profile/ProfileCard.tsx | 311 |
2 files changed, 72 insertions, 324 deletions
diff --git a/src/view/com/lists/ListMembers.tsx b/src/view/com/lists/ListMembers.tsx index da32c2d48..dc18cbe5d 100644 --- a/src/view/com/lists/ListMembers.tsx +++ b/src/view/com/lists/ListMembers.tsx @@ -1,22 +1,23 @@ import React, {useCallback} from 'react' import {Dimensions, type StyleProp, View, type ViewStyle} from 'react-native' import {type AppBskyGraphDefs} from '@atproto/api' -import {msg} from '@lingui/macro' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' import {useModalControls} from '#/state/modals' +import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useListMembersQuery} from '#/state/queries/list-members' import {useSession} from '#/state/session' -import {ProfileCard} from '#/view/com/profile/ProfileCard' import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' -import {Button} from '#/view/com/util/forms/Button' import {List, type ListRef} from '#/view/com/util/List' import {ProfileCardFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn' +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' import {ListFooter} from '#/components/Lists' +import * as ProfileCard from '#/components/ProfileCard' import type * as bsky from '#/types/bsky' const LOADING_ITEM = {_reactKey: '__loading__'} @@ -47,11 +48,12 @@ export function ListMembers({ headerOffset?: number desktopFixedHeightOffset?: number }) { + const t = useTheme() const {_} = useLingui() const [isRefreshing, setIsRefreshing] = React.useState(false) - const {isMobile} = useWebMediaQueries() const {openModal} = useModalControls() const {currentAccount} = useSession() + const moderationOpts = useModerationOpts() const { data, @@ -131,23 +133,6 @@ export function ListMembers({ // rendering // = - const renderMemberButton = React.useCallback( - (profile: bsky.profile.AnyProfileView) => { - if (!isOwner) { - return null - } - return ( - <Button - testID={`user-${profile.handle}-editBtn`} - type="default" - label={_(msg({message: 'Edit', context: 'action'}))} - onPress={() => onPressEditMembership(profile)} - /> - ) - }, - [isOwner, onPressEditMembership, _], - ) - const renderItem = React.useCallback( ({item}: {item: any}) => { if (item === EMPTY_ITEM) { @@ -171,26 +156,60 @@ export function ListMembers({ } else if (item === LOADING_ITEM) { return <ProfileCardFeedLoadingPlaceholder /> } + + const profile = (item as AppBskyGraphDefs.ListItemView).subject + if (!moderationOpts) return null + return ( - <ProfileCard - testID={`user-${ - (item as AppBskyGraphDefs.ListItemView).subject.handle - }`} - profile={(item as AppBskyGraphDefs.ListItemView).subject} - renderButton={renderMemberButton} - style={{paddingHorizontal: isMobile ? 8 : 14, paddingVertical: 4}} - noModFilter - /> + <View + style={[a.py_md, a.px_xl, a.border_t, t.atoms.border_contrast_low]}> + <ProfileCard.Link profile={profile}> + <ProfileCard.Outer> + <ProfileCard.Header> + <ProfileCard.Avatar + profile={profile} + moderationOpts={moderationOpts} + /> + <ProfileCard.NameAndHandle + profile={profile} + moderationOpts={moderationOpts} + /> + {isOwner && ( + <Button + testID={`user-${profile.handle}-editBtn`} + label={_(msg({message: 'Edit', context: 'action'}))} + onPress={() => onPressEditMembership(profile)} + size="small" + variant="solid" + color="secondary"> + <ButtonText> + <Trans context="action">Edit</Trans> + </ButtonText> + </Button> + )} + </ProfileCard.Header> + + <ProfileCard.Labels + profile={profile} + moderationOpts={moderationOpts} + /> + + <ProfileCard.Description profile={profile} /> + </ProfileCard.Outer> + </ProfileCard.Link> + </View> ) }, [ - renderMemberButton, renderEmptyState, error, onPressTryAgain, onPressRetryLoadMore, - isMobile, + moderationOpts, + isOwner, + onPressEditMembership, _, + t, ], ) diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index bd09d6514..cee950703 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -1,307 +1,36 @@ -import React from 'react' -import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' -import { - AppBskyActorDefs, - moderateProfile, - ModerationDecision, -} from '@atproto/api' -import {useQueryClient} from '@tanstack/react-query' +import {View} from 'react-native' +import {type AppBskyActorDefs} from '@atproto/api' -import {usePalette} from '#/lib/hooks/usePalette' -import {getModerationCauseKey, isJustAMute} from '#/lib/moderation' -import {makeProfileLink} from '#/lib/routes/links' -import {sanitizeDisplayName} from '#/lib/strings/display-names' -import {sanitizeHandle} from '#/lib/strings/handles' -import {s} from '#/lib/styles' -import {useProfileShadow} from '#/state/cache/profile-shadow' -import {Shadow} from '#/state/cache/types' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {precacheProfile} from '#/state/queries/profile' -import {useSession} from '#/state/session' -import {atoms as a} from '#/alf' -import { - KnownFollowers, - shouldShowKnownFollowers, -} from '#/components/KnownFollowers' -import * as Pills from '#/components/Pills' -import * as bsky from '#/types/bsky' -import {Link} from '../util/Link' -import {Text} from '../util/text/Text' -import {PreviewableUserAvatar} from '../util/UserAvatar' -import {FollowButton} from './FollowButton' - -export function ProfileCard({ - testID, - profile: profileUnshadowed, - noModFilter, - noBg, - noBorder, - renderButton, - onPress, - style, - showKnownFollowers, -}: { - testID?: string - profile: bsky.profile.AnyProfileView - noModFilter?: boolean - noBg?: boolean - noBorder?: boolean - renderButton?: ( - profile: Shadow<bsky.profile.AnyProfileView>, - ) => React.ReactNode - onPress?: () => void - style?: StyleProp<ViewStyle> - showKnownFollowers?: boolean -}) { - const queryClient = useQueryClient() - const pal = usePalette('default') - const profile = useProfileShadow(profileUnshadowed) - const moderationOpts = useModerationOpts() - const isLabeler = profile?.associated?.labeler - - const onBeforePress = React.useCallback(() => { - onPress?.() - precacheProfile(queryClient, profile) - }, [onPress, profile, queryClient]) - - if (!moderationOpts) { - return null - } - const moderation = moderateProfile(profile, moderationOpts) - const modui = moderation.ui('profileList') - if (!noModFilter && modui.filter && !isJustAMute(modui)) { - return null - } - - const knownFollowersVisible = - showKnownFollowers && - shouldShowKnownFollowers(profile.viewer?.knownFollowers) && - moderationOpts - const hasDescription = 'description' in profile - - return ( - <Link - testID={testID} - style={[ - styles.outer, - pal.border, - noBorder && styles.outerNoBorder, - !noBg && pal.view, - style, - ]} - href={makeProfileLink(profile)} - title={profile.handle} - asAnchor - onBeforePress={onBeforePress} - anchorNoUnderline> - <View style={styles.layout}> - <View style={styles.layoutAvi}> - <PreviewableUserAvatar - size={40} - profile={profile} - moderation={moderation.ui('avatar')} - type={isLabeler ? 'labeler' : 'user'} - /> - </View> - <View style={styles.layoutContent}> - <Text - emoji - type="lg" - style={[s.bold, pal.text, a.self_start]} - numberOfLines={1} - lineHeight={1.2}> - {sanitizeDisplayName( - profile.displayName || sanitizeHandle(profile.handle), - moderation.ui('displayName'), - )} - </Text> - <Text emoji type="md" style={[pal.textLight]} numberOfLines={1}> - {sanitizeHandle(profile.handle, '@')} - </Text> - <ProfileCardPills - followedBy={!!profile.viewer?.followedBy} - moderation={moderation} - /> - {!!profile.viewer?.followedBy && <View style={s.flexRow} />} - </View> - {renderButton && !isLabeler ? ( - <View style={styles.layoutButton}>{renderButton(profile)}</View> - ) : undefined} - </View> - {hasDescription || knownFollowersVisible ? ( - <View style={styles.details}> - {hasDescription && profile.description ? ( - <Text emoji style={pal.text} numberOfLines={4}> - {profile.description as string} - </Text> - ) : null} - {knownFollowersVisible ? ( - <View - style={[ - a.flex_row, - a.align_center, - a.gap_sm, - !!hasDescription && a.mt_md, - ]}> - <KnownFollowers - minimal - profile={profile} - moderationOpts={moderationOpts} - /> - </View> - ) : null} - </View> - ) : null} - </Link> - ) -} - -export function ProfileCardPills({ - followedBy, - moderation, -}: { - followedBy: boolean - moderation: ModerationDecision -}) { - const modui = moderation.ui('profileList') - if (!followedBy && !modui.inform && !modui.alert) { - return null - } - - return ( - <Pills.Row style={[a.pt_xs]}> - {followedBy && <Pills.FollowsYou />} - {modui.alerts.map(alert => ( - <Pills.Label key={getModerationCauseKey(alert)} cause={alert} /> - ))} - {modui.informs.map(inform => ( - <Pills.Label key={getModerationCauseKey(inform)} cause={inform} /> - ))} - </Pills.Row> - ) -} +import {atoms as a, useTheme} from '#/alf' +import * as ProfileCard from '#/components/ProfileCard' export function ProfileCardWithFollowBtn({ profile, - noBg, noBorder, - onPress, - onFollow, logContext = 'ProfileCard', - showKnownFollowers, }: { profile: AppBskyActorDefs.ProfileView - noBg?: boolean noBorder?: boolean - onPress?: () => void - onFollow?: () => void logContext?: 'ProfileCard' | 'StarterPackProfilesList' - showKnownFollowers?: boolean }) { - const {currentAccount} = useSession() - const isMe = profile.did === currentAccount?.did + const t = useTheme() + const moderationOpts = useModerationOpts() + + if (!moderationOpts) return null return ( - <ProfileCard - profile={profile} - noBg={noBg} - noBorder={noBorder} - renderButton={ - isMe - ? undefined - : profileShadow => ( - <FollowButton - profile={profileShadow} - logContext={logContext} - onFollow={onFollow} - /> - ) - } - onPress={onPress} - showKnownFollowers={!isMe && showKnownFollowers} - /> + <View + style={[ + a.py_md, + a.px_xl, + !noBorder && [a.border_t, t.atoms.border_contrast_low], + ]}> + <ProfileCard.Default + profile={profile} + moderationOpts={moderationOpts} + logContext={logContext} + /> + </View> ) } - -const styles = StyleSheet.create({ - outer: { - borderTopWidth: StyleSheet.hairlineWidth, - paddingHorizontal: 6, - paddingVertical: 4, - }, - outerNoBorder: { - borderTopWidth: 0, - }, - layout: { - flexDirection: 'row', - alignItems: 'center', - }, - layoutAvi: { - alignSelf: 'flex-start', - width: 54, - paddingLeft: 4, - paddingTop: 10, - }, - avi: { - width: 40, - height: 40, - borderRadius: 20, - resizeMode: 'cover', - }, - layoutContent: { - flex: 1, - paddingRight: 10, - paddingTop: 10, - paddingBottom: 10, - }, - layoutButton: { - paddingRight: 10, - }, - details: { - justifyContent: 'center', - paddingLeft: 54, - paddingRight: 10, - paddingBottom: 10, - }, - pills: { - alignItems: 'flex-start', - flexDirection: 'row', - flexWrap: 'wrap', - columnGap: 6, - rowGap: 2, - }, - pill: { - borderRadius: 4, - paddingHorizontal: 6, - paddingVertical: 2, - justifyContent: 'center', - }, - btn: { - paddingVertical: 7, - borderRadius: 50, - marginLeft: 6, - paddingHorizontal: 14, - }, - - followedBy: { - flexDirection: 'row', - paddingLeft: 54, - paddingRight: 20, - marginBottom: 10, - marginTop: -6, - }, - followedByAviContainer: { - width: 24, - height: 36, - }, - followedByAvi: { - width: 36, - height: 36, - borderRadius: 18, - padding: 2, - }, - followsByDesc: { - flex: 1, - paddingRight: 10, - }, -}) |