about summary refs log tree commit diff
path: root/src/view/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com')
-rw-r--r--src/view/com/auth/LoggedOut.tsx42
-rw-r--r--src/view/com/feeds/FeedSourceCard.tsx3
-rw-r--r--src/view/com/notifications/FeedItem.tsx87
-rw-r--r--src/view/com/profile/FollowButton.tsx11
-rw-r--r--src/view/com/profile/ProfileCard.tsx6
-rw-r--r--src/view/com/profile/ProfileSubpageHeader.tsx10
6 files changed, 126 insertions, 33 deletions
diff --git a/src/view/com/auth/LoggedOut.tsx b/src/view/com/auth/LoggedOut.tsx
index c8c81dd77..29127ec45 100644
--- a/src/view/com/auth/LoggedOut.tsx
+++ b/src/view/com/auth/LoggedOut.tsx
@@ -7,7 +7,6 @@ import {useNavigation} from '@react-navigation/native'
 
 import {useAnalytics} from '#/lib/analytics/analytics'
 import {usePalette} from '#/lib/hooks/usePalette'
-import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {logEvent} from '#/lib/statsig/statsig'
 import {s} from '#/lib/styles'
 import {isIOS, isNative} from '#/platform/detection'
@@ -22,13 +21,16 @@ import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
 import {Text} from '#/view/com/util/text/Text'
 import {Login} from '#/screens/Login'
 import {Signup} from '#/screens/Signup'
+import {LandingScreen} from '#/screens/StarterPack/StarterPackLandingScreen'
 import {SplashScreen} from './SplashScreen'
 
 enum ScreenState {
   S_LoginOrCreateAccount,
   S_Login,
   S_CreateAccount,
+  S_StarterPack,
 }
+export {ScreenState as LoggedOutScreenState}
 
 export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
   const {hasSession} = useSession()
@@ -37,18 +39,21 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
   const setMinimalShellMode = useSetMinimalShellMode()
   const {screen} = useAnalytics()
   const {requestedAccountSwitchTo} = useLoggedOutView()
-  const [screenState, setScreenState] = React.useState<ScreenState>(
-    requestedAccountSwitchTo
-      ? requestedAccountSwitchTo === 'new'
-        ? ScreenState.S_CreateAccount
-        : ScreenState.S_Login
-      : ScreenState.S_LoginOrCreateAccount,
-  )
-  const {isMobile} = useWebMediaQueries()
+  const [screenState, setScreenState] = React.useState<ScreenState>(() => {
+    if (requestedAccountSwitchTo === 'new') {
+      return ScreenState.S_CreateAccount
+    } else if (requestedAccountSwitchTo === 'starterpack') {
+      return ScreenState.S_StarterPack
+    } else if (requestedAccountSwitchTo != null) {
+      return ScreenState.S_Login
+    } else {
+      return ScreenState.S_LoginOrCreateAccount
+    }
+  })
   const {clearRequestedAccount} = useLoggedOutViewControls()
   const navigation = useNavigation<NavigationProp>()
-  const isFirstScreen = screenState === ScreenState.S_LoginOrCreateAccount
 
+  const isFirstScreen = screenState === ScreenState.S_LoginOrCreateAccount
   React.useEffect(() => {
     screen('Login')
     setMinimalShellMode(true)
@@ -66,18 +71,9 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
   }, [navigation])
 
   return (
-    <View
-      testID="noSessionView"
-      style={[
-        s.hContentRegion,
-        pal.view,
-        {
-          // only needed if dismiss button is present
-          paddingTop: onDismiss && isMobile ? 40 : 0,
-        },
-      ]}>
+    <View testID="noSessionView" style={[s.hContentRegion, pal.view]}>
       <ErrorBoundary>
-        {onDismiss ? (
+        {onDismiss && screenState === ScreenState.S_LoginOrCreateAccount ? (
           <Pressable
             accessibilityHint={_(msg`Go back`)}
             accessibilityLabel={_(msg`Go back`)}
@@ -132,7 +128,9 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
           </Pressable>
         ) : null}
 
-        {screenState === ScreenState.S_LoginOrCreateAccount ? (
+        {screenState === ScreenState.S_StarterPack ? (
+          <LandingScreen setScreenState={setScreenState} />
+        ) : screenState === ScreenState.S_LoginOrCreateAccount ? (
           <SplashScreen
             onPressSignin={() => {
               setScreenState(ScreenState.S_Login)
diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx
index a61789434..d216849c5 100644
--- a/src/view/com/feeds/FeedSourceCard.tsx
+++ b/src/view/com/feeds/FeedSourceCard.tsx
@@ -329,6 +329,9 @@ const styles = StyleSheet.create({
     flex: 1,
     gap: 14,
   },
+  border: {
+    borderTopWidth: hairlineWidth,
+  },
   headerContainer: {
     flexDirection: 'row',
   },
diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx
index 9cd7a2917..2f8d65a1d 100644
--- a/src/view/com/notifications/FeedItem.tsx
+++ b/src/view/com/notifications/FeedItem.tsx
@@ -52,7 +52,16 @@ import {TimeElapsed} from '../util/TimeElapsed'
 import {PreviewableUserAvatar, UserAvatar} from '../util/UserAvatar'
 
 import hairlineWidth = StyleSheet.hairlineWidth
+import {useNavigation} from '@react-navigation/native'
+
 import {parseTenorGif} from '#/lib/strings/embed-player'
+import {logger} from '#/logger'
+import {NavigationProp} from 'lib/routes/types'
+import {DM_SERVICE_HEADERS} from 'state/queries/messages/const'
+import {useAgent} from 'state/session'
+import {Button, ButtonText} from '#/components/Button'
+import {StarterPack} from '#/components/icons/StarterPack'
+import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
 
 const MAX_AUTHORS = 5
 
@@ -89,7 +98,10 @@ let FeedItem = ({
     } else if (item.type === 'reply') {
       const urip = new AtUri(item.notification.uri)
       return `/profile/${urip.host}/post/${urip.rkey}`
-    } else if (item.type === 'feedgen-like') {
+    } else if (
+      item.type === 'feedgen-like' ||
+      item.type === 'starterpack-joined'
+    ) {
       if (item.subjectUri) {
         const urip = new AtUri(item.subjectUri)
         return `/profile/${urip.host}/feed/${urip.rkey}`
@@ -176,6 +188,13 @@ let FeedItem = ({
     icon = <PersonPlusIcon size="xl" style={{color: t.palette.primary_500}} />
   } else if (item.type === 'feedgen-like') {
     action = _(msg`liked your custom feed`)
+  } else if (item.type === 'starterpack-joined') {
+    icon = (
+      <View style={{height: 30, width: 30}}>
+        <StarterPack width={30} gradient="sky" />
+      </View>
+    )
+    action = _(msg`signed up with your starter pack`)
   } else {
     return null
   }
@@ -289,6 +308,20 @@ let FeedItem = ({
             showLikes
           />
         ) : null}
+        {item.type === 'starterpack-joined' ? (
+          <View>
+            <View
+              style={[
+                a.border,
+                a.p_sm,
+                a.rounded_sm,
+                a.mt_sm,
+                t.atoms.border_contrast_low,
+              ]}>
+              <StarterPackCard starterPack={item.subject} />
+            </View>
+          </View>
+        ) : null}
       </View>
     </Link>
   )
@@ -319,14 +352,63 @@ function ExpandListPressable({
   }
 }
 
+function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileViewBasic}) {
+  const {_} = useLingui()
+  const agent = useAgent()
+  const navigation = useNavigation<NavigationProp>()
+  const [isLoading, setIsLoading] = React.useState(false)
+
+  if (
+    profile.associated?.chat?.allowIncoming === 'none' ||
+    (profile.associated?.chat?.allowIncoming === 'following' &&
+      !profile.viewer?.followedBy)
+  ) {
+    return null
+  }
+
+  return (
+    <Button
+      label={_(msg`Say hello!`)}
+      variant="ghost"
+      color="primary"
+      size="xsmall"
+      style={[a.self_center, {marginLeft: 'auto'}]}
+      disabled={isLoading}
+      onPress={async () => {
+        try {
+          setIsLoading(true)
+          const res = await agent.api.chat.bsky.convo.getConvoForMembers(
+            {
+              members: [profile.did, agent.session!.did!],
+            },
+            {headers: DM_SERVICE_HEADERS},
+          )
+          navigation.navigate('MessagesConversation', {
+            conversation: res.data.convo.id,
+          })
+        } catch (e) {
+          logger.error('Failed to get conversation', {safeMessage: e})
+        } finally {
+          setIsLoading(false)
+        }
+      }}>
+      <ButtonText>
+        <Trans>Say hello!</Trans>
+      </ButtonText>
+    </Button>
+  )
+}
+
 function CondensedAuthorsList({
   visible,
   authors,
   onToggleAuthorsExpanded,
+  showDmButton = true,
 }: {
   visible: boolean
   authors: Author[]
   onToggleAuthorsExpanded: () => void
+  showDmButton?: boolean
 }) {
   const pal = usePalette('default')
   const {_} = useLingui()
@@ -355,7 +437,7 @@ function CondensedAuthorsList({
   }
   if (authors.length === 1) {
     return (
-      <View style={styles.avis}>
+      <View style={[styles.avis]}>
         <PreviewableUserAvatar
           size={35}
           profile={authors[0].profile}
@@ -363,6 +445,7 @@ function CondensedAuthorsList({
           type={authors[0].profile.associated?.labeler ? 'labeler' : 'user'}
           accessible={false}
         />
+        {showDmButton ? <SayHelloBtn profile={authors[0].profile} /> : null}
       </View>
     )
   }
diff --git a/src/view/com/profile/FollowButton.tsx b/src/view/com/profile/FollowButton.tsx
index 7b090ffeb..8e63da85b 100644
--- a/src/view/com/profile/FollowButton.tsx
+++ b/src/view/com/profile/FollowButton.tsx
@@ -1,12 +1,13 @@
 import React from 'react'
 import {StyleProp, TextStyle, View} from 'react-native'
 import {AppBskyActorDefs} from '@atproto/api'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {Shadow} from '#/state/cache/types'
+import {useProfileFollowMutationQueue} from '#/state/queries/profile'
 import {Button, ButtonType} from '../util/forms/Button'
 import * as Toast from '../util/Toast'
-import {useProfileFollowMutationQueue} from '#/state/queries/profile'
-import {Shadow} from '#/state/cache/types'
-import {useLingui} from '@lingui/react'
-import {msg} from '@lingui/macro'
 
 export function FollowButton({
   unfollowedType = 'inverted',
@@ -19,7 +20,7 @@ export function FollowButton({
   followedType?: ButtonType
   profile: Shadow<AppBskyActorDefs.ProfileViewBasic>
   labelStyle?: StyleProp<TextStyle>
-  logContext: 'ProfileCard'
+  logContext: 'ProfileCard' | 'StarterPackProfilesList'
 }) {
   const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
     profile,
diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx
index a3cd5ca1b..d7ed0dd6a 100644
--- a/src/view/com/profile/ProfileCard.tsx
+++ b/src/view/com/profile/ProfileCard.tsx
@@ -251,12 +251,14 @@ export function ProfileCardWithFollowBtn({
   noBorder,
   followers,
   onPress,
+  logContext = 'ProfileCard',
 }: {
   profile: AppBskyActorDefs.ProfileViewBasic
   noBg?: boolean
   noBorder?: boolean
   followers?: AppBskyActorDefs.ProfileView[] | undefined
   onPress?: () => void
+  logContext?: 'ProfileCard' | 'StarterPackProfilesList'
 }) {
   const {currentAccount} = useSession()
   const isMe = profile.did === currentAccount?.did
@@ -271,7 +273,7 @@ export function ProfileCardWithFollowBtn({
         isMe
           ? undefined
           : profileShadow => (
-              <FollowButton profile={profileShadow} logContext="ProfileCard" />
+              <FollowButton profile={profileShadow} logContext={logContext} />
             )
       }
       onPress={onPress}
@@ -314,6 +316,7 @@ const styles = StyleSheet.create({
     paddingRight: 10,
   },
   details: {
+    justifyContent: 'center',
     paddingLeft: 54,
     paddingRight: 10,
     paddingBottom: 10,
@@ -339,7 +342,6 @@ const styles = StyleSheet.create({
 
   followedBy: {
     flexDirection: 'row',
-    alignItems: 'center',
     paddingLeft: 54,
     paddingRight: 20,
     marginBottom: 10,
diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx
index edc6b75f9..ac5febcda 100644
--- a/src/view/com/profile/ProfileSubpageHeader.tsx
+++ b/src/view/com/profile/ProfileSubpageHeader.tsx
@@ -21,7 +21,9 @@ import {Text} from '../util/text/Text'
 import {UserAvatar, UserAvatarType} from '../util/UserAvatar'
 import {CenteredView} from '../util/Views'
 import hairlineWidth = StyleSheet.hairlineWidth
+
 import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
+import {StarterPack} from '#/components/icons/StarterPack'
 
 export function ProfileSubpageHeader({
   isLoading,
@@ -44,7 +46,7 @@ export function ProfileSubpageHeader({
         handle: string
       }
     | undefined
-  avatarType: UserAvatarType
+  avatarType: UserAvatarType | 'starter-pack'
 }>) {
   const setDrawerOpen = useSetDrawerOpen()
   const navigation = useNavigation<NavigationProp>()
@@ -127,7 +129,11 @@ export function ProfileSubpageHeader({
           accessibilityLabel={_(msg`View the avatar`)}
           accessibilityHint=""
           style={{width: 58}}>
-          <UserAvatar type={avatarType} size={58} avatar={avatar} />
+          {avatarType === 'starter-pack' ? (
+            <StarterPack width={58} gradient="sky" />
+          ) : (
+            <UserAvatar type={avatarType} size={58} avatar={avatar} />
+          )}
         </Pressable>
         <View style={{flex: 1}}>
           {isLoading ? (