From 8569e2e389e756ec2df6c547e894689500d1c111 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 31 May 2024 07:05:52 +0300 Subject: [PATCH] Add follow button to feed item avatar (#3560) * add follow button to feed item avatar * remove confirmation * add confirmation (just system alert) * Shrink the avi follow indicator a smidge * gate the follow button * remove from your own posts * add to post thread item * hide the follow button locally to component * Use native dropdown * Add follow btn to notifications and search * UI tweaks * Hide on PWI * Add toast for confirmation * Check gate last * compiler * Rm unused * Use names --------- Co-authored-by: Paul Frazee Co-authored-by: Eric Bailey Co-authored-by: Dan Abramov --- .../personPlus_stroke2_corner0_rounded.svg | 1 + src/components/Button.tsx | 2 +- src/components/dms/ConvoMenu.tsx | 8 +- src/components/icons/Person.tsx | 12 ++ src/components/icons/PersonCheck.tsx | 5 - src/components/icons/PersonX.tsx | 5 - src/lib/statsig/events.ts | 2 + src/lib/statsig/gates.ts | 1 + src/view/com/post-thread/PostThreadItem.tsx | 17 ++- src/view/com/post/Post.tsx | 17 +-- src/view/com/posts/AviFollowButton.tsx | 115 ++++++++++++++++++ src/view/com/posts/AviFollowButton.web.tsx | 1 + src/view/com/posts/FeedItem.tsx | 21 ++-- src/view/com/profile/ProfileMenu.tsx | 6 +- 14 files changed, 177 insertions(+), 36 deletions(-) create mode 100644 assets/icons/personPlus_stroke2_corner0_rounded.svg delete mode 100644 src/components/icons/PersonCheck.tsx delete mode 100644 src/components/icons/PersonX.tsx create mode 100644 src/view/com/posts/AviFollowButton.tsx create mode 100644 src/view/com/posts/AviFollowButton.web.tsx diff --git a/assets/icons/personPlus_stroke2_corner0_rounded.svg b/assets/icons/personPlus_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..118268bf97 --- /dev/null +++ b/assets/icons/personPlus_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/src/components/Button.tsx b/src/components/Button.tsx index e22faa060c..982f422134 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -64,7 +64,7 @@ type NonTextElements = export type ButtonProps = Pick< PressableProps, - 'disabled' | 'onPress' | 'testID' | 'onLongPress' + 'disabled' | 'onPress' | 'testID' | 'onLongPress' | 'hitSlop' > & AccessibilityProps & VariantProps & { diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx index 79ca34f17e..3f680120b8 100644 --- a/src/components/dms/ConvoMenu.tsx +++ b/src/components/dms/ConvoMenu.tsx @@ -26,9 +26,11 @@ import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeft} from '#/components import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' -import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person' -import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheck} from '#/components/icons/PersonCheck' -import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/PersonX' +import { + Person_Stroke2_Corner0_Rounded as Person, + PersonCheck_Stroke2_Corner0_Rounded as PersonCheck, + PersonX_Stroke2_Corner0_Rounded as PersonX, +} from '#/components/icons/Person' import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' import * as Menu from '#/components/Menu' import * as Prompt from '#/components/Prompt' diff --git a/src/components/icons/Person.tsx b/src/components/icons/Person.tsx index 6d09148c9d..7fceada533 100644 --- a/src/components/icons/Person.tsx +++ b/src/components/icons/Person.tsx @@ -3,3 +3,15 @@ import {createSinglePathSVG} from './TEMPLATE' export const Person_Stroke2_Corner0_Rounded = createSinglePathSVG({ path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.678 19h12.644c-.71-2.909-3.092-5-6.322-5s-5.613 2.091-6.322 5Zm-2.174.906C3.917 15.521 7.242 12 12 12c4.758 0 8.083 3.521 8.496 7.906A1 1 0 0 1 19.5 21h-15a1 1 0 0 1-.996-1.094Z', }) + +export const PersonCheck_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.679 19c.709-2.902 3.079-5 6.321-5a6.69 6.69 0 0 1 2.612.51 1 1 0 0 0 .776-1.844A8.687 8.687 0 0 0 12 12c-4.3 0-7.447 2.884-8.304 6.696-.29 1.29.767 2.304 1.902 2.304H11a1 1 0 1 0 0-2H5.679Zm14.835-4.857a1 1 0 0 1 .344 1.371l-3 5a1 1 0 0 1-1.458.286l-2-1.5a1 1 0 0 1 1.2-1.6l1.113.835 2.43-4.05a1 1 0 0 1 1.372-.342Z', +}) + +export const PersonX_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.679 19c.709-2.902 3.079-5 6.321-5 .302 0 .595.018.878.053a1 1 0 0 0 .243-1.985A9.235 9.235 0 0 0 12 12c-4.3 0-7.447 2.884-8.304 6.696-.29 1.29.767 2.304 1.902 2.304H12a1 1 0 1 0 0-2H5.679Zm9.614-3.707a1 1 0 0 1 1.414 0L18 16.586l1.293-1.293a1 1 0 0 1 1.414 1.414L19.414 18l1.293 1.293a1 1 0 0 1-1.414 1.414L18 19.414l-1.293 1.293a1 1 0 0 1-1.414-1.414L16.586 18l-1.293-1.293a1 1 0 0 1 0-1.414Z', +}) + +export const PersonPlus_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.678 19c.71-2.909 3.092-5 6.322-5 .621 0 1.206.077 1.748.218a1 1 0 1 0 .504-1.936A8.931 8.931 0 0 0 12 12c-4.758 0-8.083 3.521-8.496 7.906A1 1 0 0 0 4.5 21H11a1 1 0 1 0 0-2H5.678ZM18 14a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2h-2v2a1 1 0 1 1-2 0v-2h-2a1 1 0 1 1 0-2h2v-2a1 1 0 0 1 1-1Z', +}) diff --git a/src/components/icons/PersonCheck.tsx b/src/components/icons/PersonCheck.tsx deleted file mode 100644 index 097271d89a..0000000000 --- a/src/components/icons/PersonCheck.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import {createSinglePathSVG} from './TEMPLATE' - -export const PersonCheck_Stroke2_Corner0_Rounded = createSinglePathSVG({ - path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.679 19c.709-2.902 3.079-5 6.321-5a6.69 6.69 0 0 1 2.612.51 1 1 0 0 0 .776-1.844A8.687 8.687 0 0 0 12 12c-4.3 0-7.447 2.884-8.304 6.696-.29 1.29.767 2.304 1.902 2.304H11a1 1 0 1 0 0-2H5.679Zm14.835-4.857a1 1 0 0 1 .344 1.371l-3 5a1 1 0 0 1-1.458.286l-2-1.5a1 1 0 0 1 1.2-1.6l1.113.835 2.43-4.05a1 1 0 0 1 1.372-.342Z', -}) diff --git a/src/components/icons/PersonX.tsx b/src/components/icons/PersonX.tsx deleted file mode 100644 index a015e13765..0000000000 --- a/src/components/icons/PersonX.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import {createSinglePathSVG} from './TEMPLATE' - -export const PersonX_Stroke2_Corner0_Rounded = createSinglePathSVG({ - path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.679 19c.709-2.902 3.079-5 6.321-5 .302 0 .595.018.878.053a1 1 0 0 0 .243-1.985A9.235 9.235 0 0 0 12 12c-4.3 0-7.447 2.884-8.304 6.696-.29 1.29.767 2.304 1.902 2.304H12a1 1 0 1 0 0-2H5.679Zm9.614-3.707a1 1 0 0 1 1.414 0L18 16.586l1.293-1.293a1 1 0 0 1 1.414 1.414L19.414 18l1.293 1.293a1 1 0 0 1-1.414 1.414L18 19.414l-1.293 1.293a1 1 0 0 1-1.414-1.414L16.586 18l-1.293-1.293a1 1 0 0 1 0-1.414Z', -}) diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts index 42f4737f82..00444c18c4 100644 --- a/src/lib/statsig/events.ts +++ b/src/lib/statsig/events.ts @@ -115,6 +115,7 @@ export type LogEvents = { | 'ProfileHeaderSuggestedFollows' | 'ProfileMenu' | 'ProfileHoverCard' + | 'AvatarButton' } 'profile:unfollow': { logContext: @@ -126,6 +127,7 @@ export type LogEvents = { | 'ProfileMenu' | 'ProfileHoverCard' | 'Chat' + | 'AvatarButton' } 'chat:create': { logContext: 'ProfileHeader' | 'NewChatDialog' diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 2721871f35..4481935f77 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -1,4 +1,5 @@ export type Gate = // Keep this alphabetic please. | 'request_notifications_permission_after_onboarding_v2' + | 'show_avi_follow_button' | 'show_follow_back_label_v2' diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 9d2985f155..981b4e72f5 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -40,6 +40,7 @@ import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' import {PostAlerts} from '../../../components/moderation/PostAlerts' import {PostHider} from '../../../components/moderation/PostHider' import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers' +import {AviFollowButton} from '../posts/AviFollowButton' import {WhoCanReply} from '../threadgate/WhoCanReply' import {ErrorMessage} from '../util/error/ErrorMessage' import {Link, TextLink} from '../util/Link' @@ -470,12 +471,16 @@ let PostThreadItemLoaded = ({ {/* If we are in threaded mode, the avatar is rendered in PostMeta */} {!isThreadedChild && ( - + + + {showChildReplyLine && ( } - + + + (null) + const navigation = useNavigation() + + const name = sanitizeDisplayName( + profile.displayName || profile.handle, + moderation.ui('displayName'), + ) + const isFollowing = + profile.viewer?.following || + profile.did === followed || + profile.did === currentAccount?.did + + function onPress() { + follow() + setFollowed(profile.did) + Toast.show(_(msg`Following ${name}`)) + } + + const items: DropdownItem[] = [ + { + label: _(msg`View profile`), + onPress: () => { + navigation.navigate('Profile', {name: profile.did}) + }, + icon: { + ios: { + name: 'arrow.up.right.square', + }, + android: '', + web: ['far', 'arrow-up-right-from-square'], + }, + }, + { + label: _(msg`Follow ${name}`), + onPress: onPress, + icon: { + ios: { + name: 'person.badge.plus', + }, + android: '', + web: ['far', 'user-plus'], + }, + }, + ] + + return hasSession && gate('show_avi_follow_button') ? ( + + {children} + + {!isFollowing && ( + + )} + + ) : ( + children + ) +} diff --git a/src/view/com/posts/AviFollowButton.web.tsx b/src/view/com/posts/AviFollowButton.web.tsx new file mode 100644 index 0000000000..6ad3c9f1fd --- /dev/null +++ b/src/view/com/posts/AviFollowButton.web.tsx @@ -0,0 +1 @@ +export {Fragment as AviFollowButton} from 'react' diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 8077c29683..b10ffe19fa 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -41,6 +41,7 @@ import {PostEmbeds} from '../util/post-embeds' import {PostMeta} from '../util/PostMeta' import {Text} from '../util/text/Text' import {PreviewableUserAvatar} from '../util/UserAvatar' +import {AviFollowButton} from './AviFollowButton' interface FeedItemProps { record: AppBskyFeedPost.Record @@ -284,13 +285,15 @@ let FeedItemInner = ({ - + + + {isThreadParent && (