about summary refs log tree commit diff
path: root/src/view/com/post-thread
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/post-thread')
-rw-r--r--src/view/com/post-thread/PostThread.tsx44
-rw-r--r--src/view/com/post-thread/PostThreadComposePrompt.tsx76
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx7
3 files changed, 77 insertions, 50 deletions
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index 5bec9ced1..94cc04f54 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -1,8 +1,7 @@
 import React, {memo, useRef, useState} from 'react'
-import {StyleSheet, useWindowDimensions, View} from 'react-native'
-import {runOnJS} from 'react-native-reanimated'
+import {useWindowDimensions, View} from 'react-native'
+import {runOnJS, useAnimatedStyle} from 'react-native-reanimated'
 import Animated from 'react-native-reanimated'
-import {useSafeAreaInsets} from 'react-native-safe-area-context'
 import {
   AppBskyFeedDefs,
   type AppBskyFeedThreadgate,
@@ -13,11 +12,9 @@ import {useLingui} from '@lingui/react'
 
 import {HITSLOP_10} from '#/lib/constants'
 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
-import {useMinimalShellFabTransform} from '#/lib/hooks/useMinimalShellTransform'
 import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
 import {useSetTitle} from '#/lib/hooks/useSetTitle'
 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
-import {clamp} from '#/lib/numbers'
 import {ScrollProvider} from '#/lib/ScrollContext'
 import {sanitizeDisplayName} from '#/lib/strings/display-names'
 import {cleanError} from '#/lib/strings/errors'
@@ -37,6 +34,7 @@ import {
 import {useSetThreadViewPreferencesMutation} from '#/state/queries/preferences'
 import {usePreferencesQuery} from '#/state/queries/preferences'
 import {useSession} from '#/state/session'
+import {useShellLayout} from '#/state/shell/shell-layout'
 import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
 import {useUnstablePostSource} from '#/state/unstable-post-source'
 import {List, type ListMethods} from '#/view/com/util/List'
@@ -301,11 +299,14 @@ export function PostThread({uri}: {uri: string}) {
       // maintainVisibleContentPosition and onContentSizeChange
       // to "hold onto" the correct row instead of the first one.
 
+      /*
+       * This is basically `!!parents.length`, see notes on `isParentLoading`
+       */
       if (!highlightedPost.ctx.isParentLoading && !deferParents) {
         // When progressively revealing parents, rendering a placeholder
         // here will cause scrolling jumps. Don't add it unless you test it.
         // QT'ing this thread is a great way to test all the scrolling hacks:
-        // https://bsky.app/profile/www.mozzius.dev/post/3kjqhblh6qk2o
+        // https://bsky.app/profile/samuel.bsky.team/post/3kjqhblh6qk2o
 
         // Everything is loaded
         let startIndex = Math.max(0, parents.length - maxParents)
@@ -581,6 +582,9 @@ export function PostThread({uri}: {uri: string}) {
           onEndReached={onEndReached}
           onEndReachedThreshold={2}
           onScrollToTop={onScrollToTop}
+          /**
+           * @see https://reactnative.dev/docs/scrollview#maintainvisiblecontentposition
+           */
           maintainVisibleContentPosition={
             isNative && hasParents
               ? MAINTAIN_VISIBLE_CONTENT_POSITION
@@ -729,17 +733,16 @@ let ThreadMenu = ({
 ThreadMenu = memo(ThreadMenu)
 
 function MobileComposePrompt({onPressReply}: {onPressReply: () => unknown}) {
-  const safeAreaInsets = useSafeAreaInsets()
-  const fabMinimalShellTransform = useMinimalShellFabTransform()
+  const {footerHeight} = useShellLayout()
+
+  const animatedStyle = useAnimatedStyle(() => {
+    return {
+      bottom: footerHeight.get(),
+    }
+  })
+
   return (
-    <Animated.View
-      style={[
-        styles.prompt,
-        fabMinimalShellTransform,
-        {
-          bottom: clamp(safeAreaInsets.bottom, 13, 60),
-        },
-      ]}>
+    <Animated.View style={[a.fixed, a.left_0, a.right_0, animatedStyle]}>
       <PostThreadComposePrompt onPressCompose={onPressReply} />
     </Animated.View>
   )
@@ -904,12 +907,3 @@ function hasBranchingReplies(node?: ThreadNode) {
   }
   return true
 }
-
-const styles = StyleSheet.create({
-  prompt: {
-    // @ts-ignore web-only
-    position: isWeb ? 'fixed' : 'absolute',
-    left: 0,
-    right: 0,
-  },
-})
diff --git a/src/view/com/post-thread/PostThreadComposePrompt.tsx b/src/view/com/post-thread/PostThreadComposePrompt.tsx
index 40acff376..f45b16085 100644
--- a/src/view/com/post-thread/PostThreadComposePrompt.tsx
+++ b/src/view/com/post-thread/PostThreadComposePrompt.tsx
@@ -1,20 +1,25 @@
-import {View} from 'react-native'
+import {type StyleProp, View, type ViewStyle} from 'react-native'
+import {LinearGradient} from 'expo-linear-gradient'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {PressableScale} from '#/lib/custom-animations/PressableScale'
 import {useHaptics} from '#/lib/haptics'
+import {useHideBottomBarBorderForScreen} from '#/lib/hooks/useHideBottomBarBorder'
 import {useProfileQuery} from '#/state/queries/profile'
 import {useSession} from '#/state/session'
 import {UserAvatar} from '#/view/com/util/UserAvatar'
-import {atoms as a, ios, useBreakpoints, useTheme} from '#/alf'
+import {atoms as a, ios, native, useBreakpoints, useTheme} from '#/alf'
+import {transparentifyColor} from '#/alf/util/colorGeneration'
 import {useInteractionState} from '#/components/hooks/useInteractionState'
 import {Text} from '#/components/Typography'
 
 export function PostThreadComposePrompt({
   onPressCompose,
+  style,
 }: {
   onPressCompose: () => void
+  style?: StyleProp<ViewStyle>
 }) {
   const {currentAccount} = useSession()
   const {data: profile} = useProfileQuery({did: currentAccount?.did})
@@ -28,29 +33,49 @@ export function PostThreadComposePrompt({
     onOut: onHoverOut,
   } = useInteractionState()
 
+  useHideBottomBarBorderForScreen()
+
   return (
-    <PressableScale
-      accessibilityRole="button"
-      accessibilityLabel={_(msg`Compose reply`)}
-      accessibilityHint={_(msg`Opens composer`)}
+    <View
       style={[
-        gtMobile ? a.py_xs : {paddingTop: 8, paddingBottom: 11},
-        a.px_sm,
-        a.border_t,
-        t.atoms.border_contrast_low,
-        t.atoms.bg,
-      ]}
-      onPress={() => {
-        onPressCompose()
-        playHaptic('Light')
-      }}
-      onLongPress={ios(() => {
-        onPressCompose()
-        playHaptic('Heavy')
-      })}
-      onHoverIn={onHoverIn}
-      onHoverOut={onHoverOut}>
-      <View
+        gtMobile
+          ? [
+              a.py_xs,
+              a.px_sm,
+              a.border_t,
+              t.atoms.border_contrast_low,
+              t.atoms.bg,
+            ]
+          : [a.px_md, a.pb_2xs],
+        style,
+      ]}>
+      {!gtMobile && (
+        <LinearGradient
+          key={t.name} // android does not update when you change the colors. sigh.
+          start={[0.5, 0]}
+          end={[0.5, 1]}
+          colors={[
+            transparentifyColor(t.atoms.bg.backgroundColor, 0),
+            t.atoms.bg.backgroundColor,
+          ]}
+          locations={[0.15, 0.4]}
+          style={[a.absolute, a.inset_0]}
+        />
+      )}
+      <PressableScale
+        accessibilityRole="button"
+        accessibilityLabel={_(msg`Compose reply`)}
+        accessibilityHint={_(msg`Opens composer`)}
+        onPress={() => {
+          onPressCompose()
+          playHaptic('Light')
+        }}
+        onLongPress={ios(() => {
+          onPressCompose()
+          playHaptic('Heavy')
+        })}
+        onHoverIn={onHoverIn}
+        onHoverOut={onHoverOut}
         style={[
           a.flex_row,
           a.align_center,
@@ -58,6 +83,7 @@ export function PostThreadComposePrompt({
           a.gap_sm,
           a.rounded_full,
           (!gtMobile || hovered) && t.atoms.bg_contrast_25,
+          native([a.border, t.atoms.border_contrast_low]),
           a.transition_color,
         ]}>
         <UserAvatar
@@ -68,7 +94,7 @@ export function PostThreadComposePrompt({
         <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
           <Trans>Write your reply</Trans>
         </Text>
-      </View>
-    </PressableScale>
+      </PressableScale>
+    </View>
   )
 }
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 576b195a0..5184047cb 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -39,6 +39,7 @@ import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback'
 import {useLanguagePrefs} from '#/state/preferences'
 import {type ThreadPost} from '#/state/queries/post-thread'
 import {useSession} from '#/state/session'
+import {type OnPostSuccessData} from '#/state/shell/composer'
 import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
 import {type PostSource} from '#/state/unstable-post-source'
 import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn'
@@ -85,6 +86,7 @@ export function PostThreadItem({
   hasPrecedingItem,
   overrideBlur,
   onPostReply,
+  onPostSuccess,
   hideTopBorder,
   threadgateRecord,
   anchorPostSource,
@@ -103,6 +105,7 @@ export function PostThreadItem({
   hasPrecedingItem: boolean
   overrideBlur: boolean
   onPostReply: (postUri: string | undefined) => void
+  onPostSuccess?: (data: OnPostSuccessData) => void
   hideTopBorder?: boolean
   threadgateRecord?: AppBskyFeedThreadgate.Record
   anchorPostSource?: PostSource
@@ -139,6 +142,7 @@ export function PostThreadItem({
         hasPrecedingItem={hasPrecedingItem}
         overrideBlur={overrideBlur}
         onPostReply={onPostReply}
+        onPostSuccess={onPostSuccess}
         hideTopBorder={hideTopBorder}
         threadgateRecord={threadgateRecord}
         anchorPostSource={anchorPostSource}
@@ -185,6 +189,7 @@ let PostThreadItemLoaded = ({
   hasPrecedingItem,
   overrideBlur,
   onPostReply,
+  onPostSuccess,
   hideTopBorder,
   threadgateRecord,
   anchorPostSource,
@@ -204,6 +209,7 @@ let PostThreadItemLoaded = ({
   hasPrecedingItem: boolean
   overrideBlur: boolean
   onPostReply: (postUri: string | undefined) => void
+  onPostSuccess?: (data: OnPostSuccessData) => void
   hideTopBorder?: boolean
   threadgateRecord?: AppBskyFeedThreadgate.Record
   anchorPostSource?: PostSource
@@ -298,6 +304,7 @@ let PostThreadItemLoaded = ({
         moderation,
       },
       onPost: onPostReply,
+      onPostSuccess: onPostSuccess,
     })
   }