about summary refs log tree commit diff
path: root/src/screens/Messages
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens/Messages')
-rw-r--r--src/screens/Messages/ChatList.tsx2
-rw-r--r--src/screens/Messages/Conversation.tsx26
-rw-r--r--src/screens/Messages/Settings.tsx2
-rw-r--r--src/screens/Messages/components/ChatDisabled.tsx2
-rw-r--r--src/screens/Messages/components/MessageInput.tsx21
-rw-r--r--src/screens/Messages/components/MessageInput.web.tsx4
-rw-r--r--src/screens/Messages/components/MessageInputEmbed.tsx2
-rw-r--r--src/screens/Messages/components/MessagesList.tsx36
8 files changed, 63 insertions, 32 deletions
diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx
index 45b3bf14f..4f2bd251f 100644
--- a/src/screens/Messages/ChatList.tsx
+++ b/src/screens/Messages/ChatList.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useEffect, useMemo, useState} from 'react'
+import {useCallback, useEffect, useMemo, useState} from 'react'
 import {View} from 'react-native'
 import {ChatBskyConvoDefs} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
diff --git a/src/screens/Messages/Conversation.tsx b/src/screens/Messages/Conversation.tsx
index e2e646a3d..ee09adaf0 100644
--- a/src/screens/Messages/Conversation.tsx
+++ b/src/screens/Messages/Conversation.tsx
@@ -4,10 +4,11 @@ import {useKeyboardController} from 'react-native-keyboard-controller'
 import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
-import {useFocusEffect} from '@react-navigation/native'
+import {useFocusEffect, useNavigation} from '@react-navigation/native'
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
 
-import {CommonNavigatorParams} from '#/lib/routes/types'
+import {useEmail} from '#/lib/hooks/useEmail'
+import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
 import {isWeb} from '#/platform/detection'
 import {useProfileShadow} from '#/state/cache/profile-shadow'
 import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo'
@@ -19,6 +20,8 @@ import {useSetMinimalShellMode} from '#/state/shell'
 import {CenteredView} from '#/view/com/util/Views'
 import {MessagesList} from '#/screens/Messages/components/MessagesList'
 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
+import {useDialogControl} from '#/components/Dialog'
+import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
 import {MessagesListBlockedFooter} from '#/components/dms/MessagesListBlockedFooter'
 import {MessagesListHeader} from '#/components/dms/MessagesListHeader'
 import {Error} from '#/components/Error'
@@ -161,8 +164,12 @@ function InnerReady({
   hasScrolled: boolean
   setHasScrolled: React.Dispatch<React.SetStateAction<boolean>>
 }) {
+  const {_} = useLingui()
   const convoState = useConvo()
+  const navigation = useNavigation<NavigationProp>()
   const recipient = useProfileShadow(recipientUnshadowed)
+  const verifyEmailControl = useDialogControl()
+  const {needsEmailVerification} = useEmail()
 
   const moderation = React.useMemo(() => {
     return moderateProfile(recipient, moderationOpts)
@@ -179,6 +186,12 @@ function InnerReady({
     }
   }, [moderation])
 
+  React.useEffect(() => {
+    if (needsEmailVerification) {
+      verifyEmailControl.open()
+    }
+  }, [needsEmailVerification, verifyEmailControl])
+
   return (
     <>
       <MessagesListHeader
@@ -201,6 +214,15 @@ function InnerReady({
           }
         />
       )}
+      <VerifyEmailDialog
+        reasonText={_(
+          msg`Before you may message another user, you must first verify your email.`,
+        )}
+        control={verifyEmailControl}
+        onCloseWithoutVerifying={() => {
+          navigation.navigate('Home')
+        }}
+      />
     </>
   )
 }
diff --git a/src/screens/Messages/Settings.tsx b/src/screens/Messages/Settings.tsx
index 93e3bc400..50b1c4cc9 100644
--- a/src/screens/Messages/Settings.tsx
+++ b/src/screens/Messages/Settings.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback} from 'react'
+import {useCallback} from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
diff --git a/src/screens/Messages/components/ChatDisabled.tsx b/src/screens/Messages/components/ChatDisabled.tsx
index c768d2504..5e9f57fa5 100644
--- a/src/screens/Messages/components/ChatDisabled.tsx
+++ b/src/screens/Messages/components/ChatDisabled.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useState} from 'react'
+import {useCallback, useState} from 'react'
 import {View} from 'react-native'
 import {ComAtprotoModerationDefs} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
diff --git a/src/screens/Messages/components/MessageInput.tsx b/src/screens/Messages/components/MessageInput.tsx
index 21d6e574e..85509211b 100644
--- a/src/screens/Messages/components/MessageInput.tsx
+++ b/src/screens/Messages/components/MessageInput.tsx
@@ -18,6 +18,7 @@ import Graphemer from 'graphemer'
 
 import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
 import {useHaptics} from '#/lib/haptics'
+import {useEmail} from '#/lib/hooks/useEmail'
 import {isIOS} from '#/platform/detection'
 import {
   useMessageDraft,
@@ -61,10 +62,15 @@ export function MessageInput({
   const [message, setMessage] = React.useState(getDraft)
   const inputRef = useAnimatedRef<TextInput>()
 
+  const {needsEmailVerification} = useEmail()
+
   useSaveMessageDraft(message)
   useExtractEmbedFromFacets(message, setEmbed)
 
   const onSubmit = React.useCallback(() => {
+    if (needsEmailVerification) {
+      return
+    }
     if (!hasEmbed && message.trim() === '') {
       return
     }
@@ -84,6 +90,7 @@ export function MessageInput({
       inputRef.current?.focus()
     }, 100)
   }, [
+    needsEmailVerification,
     hasEmbed,
     message,
     clearDraft,
@@ -101,22 +108,22 @@ export function MessageInput({
         const measurement = measure(inputRef)
         if (!measurement) return
 
-        const max = windowHeight - -keyboardHeight.value - topInset - 150
+        const max = windowHeight - -keyboardHeight.get() - topInset - 150
         const availableSpace = max - measurement.height
 
-        maxHeight.value = max
-        isInputScrollable.value = availableSpace < 30
+        maxHeight.set(max)
+        isInputScrollable.set(availableSpace < 30)
       },
     },
     [windowHeight, topInset],
   )
 
   const animatedStyle = useAnimatedStyle(() => ({
-    maxHeight: maxHeight.value,
+    maxHeight: maxHeight.get(),
   }))
 
   const animatedProps = useAnimatedProps(() => ({
-    scrollEnabled: isInputScrollable.value,
+    scrollEnabled: isInputScrollable.get(),
   }))
 
   return (
@@ -159,6 +166,7 @@ export function MessageInput({
           ref={inputRef}
           hitSlop={HITSLOP_10}
           animatedProps={animatedProps}
+          editable={!needsEmailVerification}
         />
         <Pressable
           accessibilityRole="button"
@@ -171,7 +179,8 @@ export function MessageInput({
             a.justify_center,
             {height: 30, width: 30, backgroundColor: t.palette.primary_500},
           ]}
-          onPress={onSubmit}>
+          onPress={onSubmit}
+          disabled={needsEmailVerification}>
           <PaperPlane fill={t.palette.white} style={[a.relative, {left: 1}]} />
         </Pressable>
       </View>
diff --git a/src/screens/Messages/components/MessageInput.web.tsx b/src/screens/Messages/components/MessageInput.web.tsx
index b15cd2492..72e0382a9 100644
--- a/src/screens/Messages/components/MessageInput.web.tsx
+++ b/src/screens/Messages/components/MessageInput.web.tsx
@@ -38,7 +38,7 @@ export function MessageInput({
   children?: React.ReactNode
   openEmojiPicker?: (pos: EmojiPickerPosition) => void
 }) {
-  const {isTabletOrDesktop} = useWebMediaQueries()
+  const {isMobile} = useWebMediaQueries()
   const {_} = useLingui()
   const t = useTheme()
   const {getDraft, clearDraft} = useMessageDraft()
@@ -212,7 +212,7 @@ export function MessageInput({
           onChange={onChange}
           // On mobile web phones, we want to keep the same behavior as the native app. Do not submit the message
           // in these cases.
-          onKeyDown={isTouchDevice && isTabletOrDesktop ? undefined : onKeyDown}
+          onKeyDown={isTouchDevice && isMobile ? undefined : onKeyDown}
         />
         <Pressable
           accessibilityRole="button"
diff --git a/src/screens/Messages/components/MessageInputEmbed.tsx b/src/screens/Messages/components/MessageInputEmbed.tsx
index 2d1551019..6df0ef2fc 100644
--- a/src/screens/Messages/components/MessageInputEmbed.tsx
+++ b/src/screens/Messages/components/MessageInputEmbed.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useEffect, useMemo, useState} from 'react'
+import {useCallback, useEffect, useMemo, useState} from 'react'
 import {LayoutAnimation, View} from 'react-native'
 import {
   AppBskyFeedPost,
diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx
index 9db4f07b6..9f67929a3 100644
--- a/src/screens/Messages/components/MessagesList.tsx
+++ b/src/screens/Messages/components/MessagesList.tsx
@@ -145,7 +145,7 @@ export function MessagesList({
     (_: number, height: number) => {
       // Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the
       // previous off whenever we add new content to the previous offset whenever we add new content to the list.
-      if (isWeb && isAtTop.value && hasScrolled) {
+      if (isWeb && isAtTop.get() && hasScrolled) {
         flatListRef.current?.scrollToOffset({
           offset: height - prevContentHeight.current,
           animated: false,
@@ -153,7 +153,7 @@ export function MessagesList({
       }
 
       // This number _must_ be the height of the MaybeLoader component
-      if (height > 50 && isAtBottom.value) {
+      if (height > 50 && isAtBottom.get()) {
         // If the size of the content is changing by more than the height of the screen, then we don't
         // want to scroll further than the start of all the new content. Since we are storing the previous offset,
         // we can just scroll the user to that offset and add a little bit of padding. We'll also show the pill
@@ -161,7 +161,7 @@ export function MessagesList({
         if (
           didBackground.current &&
           hasScrolled &&
-          height - prevContentHeight.current > layoutHeight.value - 50 &&
+          height - prevContentHeight.current > layoutHeight.get() - 50 &&
           convoState.items.length - prevItemCount.current > 1
         ) {
           flatListRef.current?.scrollToOffset({
@@ -209,7 +209,7 @@ export function MessagesList({
   )
 
   const onStartReached = useCallback(() => {
-    if (hasScrolled && prevContentHeight.current > layoutHeight.value) {
+    if (hasScrolled && prevContentHeight.current > layoutHeight.get()) {
       convoState.fetchMessageHistory()
     }
   }, [convoState, hasScrolled, layoutHeight])
@@ -217,18 +217,18 @@ export function MessagesList({
   const onScroll = React.useCallback(
     (e: ReanimatedScrollEvent) => {
       'worklet'
-      layoutHeight.value = e.layoutMeasurement.height
+      layoutHeight.set(e.layoutMeasurement.height)
       const bottomOffset = e.contentOffset.y + e.layoutMeasurement.height
 
       // Most apps have a little bit of space the user can scroll past while still automatically scrolling ot the bottom
       // when a new message is added, hence the 100 pixel offset
-      isAtBottom.value = e.contentSize.height - 100 < bottomOffset
-      isAtTop.value = e.contentOffset.y <= 1
+      isAtBottom.set(e.contentSize.height - 100 < bottomOffset)
+      isAtTop.set(e.contentOffset.y <= 1)
 
       if (
         newMessagesPill.show &&
         (e.contentOffset.y > newMessagesPill.startContentOffset + 200 ||
-          isAtBottom.value)
+          isAtBottom.get())
       ) {
         runOnJS(setNewMessagesPill)({
           show: false,
@@ -256,28 +256,28 @@ export function MessagesList({
       // Immediate updates - like opening the emoji picker - will have a duration of zero. In those cases, we should
       // just update the height here instead of having the `onMove` event do it (that event will not fire!)
       if (e.duration === 0) {
-        layoutScrollWithoutAnimation.value = true
-        keyboardHeight.value = e.height
+        layoutScrollWithoutAnimation.set(true)
+        keyboardHeight.set(e.height)
       } else {
-        keyboardIsOpening.value = true
+        keyboardIsOpening.set(true)
       }
     },
     onMove: e => {
       'worklet'
-      keyboardHeight.value = e.height
+      keyboardHeight.set(e.height)
       if (e.height > bottomOffset) {
         scrollTo(flatListRef, 0, 1e7, false)
       }
     },
     onEnd: () => {
       'worklet'
-      keyboardIsOpening.value = false
+      keyboardIsOpening.set(false)
     },
   })
 
   const animatedListStyle = useAnimatedStyle(() => ({
     marginBottom:
-      keyboardHeight.value > bottomOffset ? keyboardHeight.value : bottomOffset,
+      keyboardHeight.get() > bottomOffset ? keyboardHeight.get() : bottomOffset,
   }))
 
   // -- Message sending
@@ -363,13 +363,13 @@ export function MessagesList({
   // -- List layout changes (opening emoji keyboard, etc.)
   const onListLayout = React.useCallback(
     (e: LayoutChangeEvent) => {
-      layoutHeight.value = e.nativeEvent.layout.height
+      layoutHeight.set(e.nativeEvent.layout.height)
 
-      if (isWeb || !keyboardIsOpening.value) {
+      if (isWeb || !keyboardIsOpening.get()) {
         flatListRef.current?.scrollToEnd({
-          animated: !layoutScrollWithoutAnimation.value,
+          animated: !layoutScrollWithoutAnimation.get(),
         })
-        layoutScrollWithoutAnimation.value = false
+        layoutScrollWithoutAnimation.set(false)
       }
     },
     [