From c3f88e0a48bdf22831736ad3d44222e7c4418486 Mon Sep 17 00:00:00 2001
From: Samuel Newman <mozzius@protonmail.com>
Date: Sat, 24 May 2025 02:02:38 +0300
Subject: Share menu (#7840)

* move post ctrls to #/components

* restructure post controls, basic share menu

* add border radius to searchable people list for android

* Revert "add border radius to searchable people list for android"

This reverts commit 417449086e25b82f5683b12f6405d972f48ce50e.

* add copy link to native share menu

* reorg files again

* open native share menu on long press

* Translation comments

Thanks @surfdude29

* abs path

* update type imports, remove forwardRef

* rm react import

* equal spacing of buttons, extract disco debug

* add better icon

* add right offset to share button for visual alignment

* Add recent chats to share menu (#7853)

* add recent chats to share menu

* Update RecentChats.tsx

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Update RecentChats.tsx

* add fading edge on andriod

* tweak scrollview

* Add metrics and A/B alt icon to share menu (#8401)

* add metrics

* add a/b tested alt icon

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* More descriptive share text/icon on web (#7854)

* more descriptive share text on web

* revert dev mode changes

* add missing import

* use modified share icon everywhere

* Add back conflicting changes

---------

Co-authored-by: Eric Bailey <git@esb.lol>

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
Co-authored-by: Eric Bailey <git@esb.lol>
---
 .../PostControls/ShareMenu/RecentChats.tsx         | 200 +++++++++++++++++++++
 1 file changed, 200 insertions(+)
 create mode 100644 src/components/PostControls/ShareMenu/RecentChats.tsx

(limited to 'src/components/PostControls/ShareMenu/RecentChats.tsx')

diff --git a/src/components/PostControls/ShareMenu/RecentChats.tsx b/src/components/PostControls/ShareMenu/RecentChats.tsx
new file mode 100644
index 000000000..ca5d0029e
--- /dev/null
+++ b/src/components/PostControls/ShareMenu/RecentChats.tsx
@@ -0,0 +1,200 @@
+import {ScrollView, View} from 'react-native'
+import {moderateProfile, type ModerationOpts} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useNavigation} from '@react-navigation/native'
+
+import {type NavigationProp} from '#/lib/routes/types'
+import {sanitizeDisplayName} from '#/lib/strings/display-names'
+import {sanitizeHandle} from '#/lib/strings/handles'
+import {logger} from '#/logger'
+import {useModerationOpts} from '#/state/preferences/moderation-opts'
+import {useListConvosQuery} from '#/state/queries/messages/list-conversations'
+import {useSession} from '#/state/session'
+import {UserAvatar} from '#/view/com/util/UserAvatar'
+import {atoms as a, tokens, useTheme} from '#/alf'
+import {Button} from '#/components/Button'
+import {useDialogContext} from '#/components/Dialog'
+import {Text} from '#/components/Typography'
+import {useSimpleVerificationState} from '#/components/verification'
+import {VerificationCheck} from '#/components/verification/VerificationCheck'
+import type * as bsky from '#/types/bsky'
+
+export function RecentChats({postUri}: {postUri: string}) {
+  const control = useDialogContext()
+  const {_} = useLingui()
+  const {currentAccount} = useSession()
+  const {data} = useListConvosQuery({status: 'accepted'})
+  const convos = data?.pages[0]?.convos?.slice(0, 10)
+  const moderationOpts = useModerationOpts()
+  const navigation = useNavigation<NavigationProp>()
+
+  const onSelectChat = (convoId: string) => {
+    control.close(() => {
+      logger.metric('share:press:recentDm', {}, {statsig: true})
+      navigation.navigate('MessagesConversation', {
+        conversation: convoId,
+        embed: postUri,
+      })
+    })
+  }
+
+  if (!moderationOpts) return null
+
+  return (
+    <View
+      style={[a.relative, a.flex_1, {marginHorizontal: tokens.space.md * -1}]}>
+      <ScrollView
+        horizontal
+        style={[a.flex_1, a.pt_2xs, {minHeight: 98}]}
+        contentContainerStyle={[a.gap_sm, a.px_md]}
+        showsHorizontalScrollIndicator={false}
+        fadingEdgeLength={64}
+        nestedScrollEnabled>
+        {convos && convos.length > 0 ? (
+          convos.map(convo => {
+            const otherMember = convo.members.find(
+              member => member.did !== currentAccount?.did,
+            )
+
+            if (!otherMember || otherMember.handle === 'missing.invalid')
+              return null
+
+            return (
+              <RecentChatItem
+                key={convo.id}
+                profile={otherMember}
+                onPress={() => onSelectChat(convo.id)}
+                moderationOpts={moderationOpts}
+              />
+            )
+          })
+        ) : (
+          <>
+            <ConvoSkeleton />
+            <ConvoSkeleton />
+            <ConvoSkeleton />
+            <ConvoSkeleton />
+            <ConvoSkeleton />
+          </>
+        )}
+      </ScrollView>
+      {convos && convos.length === 0 && <NoConvos />}
+    </View>
+  )
+}
+
+const WIDTH = 80
+
+function RecentChatItem({
+  profile,
+  onPress,
+  moderationOpts,
+}: {
+  profile: bsky.profile.AnyProfileView
+  onPress: () => void
+  moderationOpts: ModerationOpts
+}) {
+  const {_} = useLingui()
+  const t = useTheme()
+
+  const moderation = moderateProfile(profile, moderationOpts)
+  const name = sanitizeDisplayName(
+    profile.displayName || sanitizeHandle(profile.handle),
+    moderation.ui('displayName'),
+  )
+  const verification = useSimpleVerificationState({profile})
+
+  return (
+    <Button
+      onPress={onPress}
+      label={_(msg`Send post to ${name}`)}
+      style={[
+        a.flex_col,
+        {width: WIDTH},
+        a.gap_sm,
+        a.justify_start,
+        a.align_center,
+      ]}>
+      <UserAvatar
+        avatar={profile.avatar}
+        size={WIDTH - 8}
+        type={profile.associated?.labeler ? 'labeler' : 'user'}
+        moderation={moderation.ui('avatar')}
+      />
+      <View style={[a.flex_row, a.align_center, a.justify_center, a.w_full]}>
+        <Text
+          emoji
+          style={[a.text_xs, a.leading_snug, t.atoms.text_contrast_medium]}
+          numberOfLines={1}>
+          {name}
+        </Text>
+        {verification.showBadge && (
+          <View style={[a.pl_2xs]}>
+            <VerificationCheck
+              width={10}
+              verifier={verification.role === 'verifier'}
+            />
+          </View>
+        )}
+      </View>
+    </Button>
+  )
+}
+
+function ConvoSkeleton() {
+  const t = useTheme()
+  return (
+    <View
+      style={[
+        a.flex_col,
+        {width: WIDTH, height: WIDTH + 15},
+        a.gap_xs,
+        a.justify_start,
+        a.align_center,
+      ]}>
+      <View
+        style={[
+          t.atoms.bg_contrast_50,
+          {width: WIDTH - 8, height: WIDTH - 8},
+          a.rounded_full,
+        ]}
+      />
+      <View
+        style={[
+          t.atoms.bg_contrast_50,
+          {width: WIDTH - 8, height: 10},
+          a.rounded_xs,
+        ]}
+      />
+    </View>
+  )
+}
+
+function NoConvos() {
+  const t = useTheme()
+
+  return (
+    <View
+      style={[
+        a.absolute,
+        a.inset_0,
+        a.justify_center,
+        a.align_center,
+        a.px_2xl,
+      ]}>
+      <View
+        style={[a.absolute, a.inset_0, t.atoms.bg_contrast_25, {opacity: 0.5}]}
+      />
+      <Text
+        style={[
+          a.text_sm,
+          t.atoms.text_contrast_high,
+          a.text_center,
+          a.font_bold,
+        ]}>
+        <Trans>Start a conversation, and it will appear here.</Trans>
+      </Text>
+    </View>
+  )
+}
-- 
cgit 1.4.1

