diff --git a/assets/icons/image_stroke2_corner0_rounded.svg b/assets/icons/image_stroke2_corner0_rounded.svg
index 389020b0d1..3363e186db 100644
--- a/assets/icons/image_stroke2_corner0_rounded.svg
+++ b/assets/icons/image_stroke2_corner0_rounded.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/src/components/icons/Image.tsx b/src/components/icons/Image.tsx
index 03702a0f46..eac296ad42 100644
--- a/src/components/icons/Image.tsx
+++ b/src/components/icons/Image.tsx
@@ -1,5 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Image_Stroke2_Corner0_Rounded = createSinglePathSVG({
- path: 'M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5Zm16 0H5v7.213l1.246-.932.044-.03a3 3 0 0 1 3.863.454c1.468 1.58 2.941 2.749 4.847 2.749 1.703 0 2.855-.555 4-1.618V5Zm0 10.357c-1.112.697-2.386 1.097-4 1.097-2.81 0-4.796-1.755-6.313-3.388a1 1 0 0 0-1.269-.164L5 14.712V19h14v-3.643ZM15 8a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-3 1a3 3 0 1 1 6 0 3 3 0 0 1-6 0Z',
+ path: 'M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 1v7.213l1.246-.932.044-.03a3 3 0 0 1 3.863.454c1.468 1.58 2.941 2.749 4.847 2.749 1.703 0 2.855-.555 4-1.618V5H5Zm14 10.357c-1.112.697-2.386 1.097-4 1.097-2.81 0-4.796-1.755-6.313-3.388a1 1 0 0 0-1.269-.164L5 14.712V19h14v-3.643ZM15 8a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-3 1a3 3 0 1 1 6 0 3 3 0 0 1-6 0Z',
})
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index b1c020a105..ad79cdb58c 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -9,6 +9,7 @@ import React, {
import {
ActivityIndicator,
Keyboard,
+ LayoutChangeEvent,
StyleSheet,
TouchableOpacity,
View,
@@ -19,6 +20,7 @@ import {
} from 'react-native-keyboard-controller'
import Animated, {
interpolateColor,
+ runOnUI,
useAnimatedStyle,
useSharedValue,
withTiming,
@@ -170,22 +172,6 @@ export const ComposePost = observer(function ComposePost({
[insets, isKeyboardVisible],
)
- const hasScrolled = useSharedValue(0)
- const scrollHandler = useAnimatedScrollHandler({
- onScroll: event => {
- hasScrolled.value = withTiming(event.contentOffset.y > 0 ? 1 : 0)
- },
- })
- const topBarAnimatedStyle = useAnimatedStyle(() => {
- return {
- borderColor: interpolateColor(
- hasScrolled.value,
- [0, 1],
- ['transparent', t.atoms.border_contrast_medium.borderColor],
- ),
- }
- })
-
const onPressCancel = useCallback(() => {
if (graphemeLength > 0 || !gallery.isEmpty) {
closeAllDialogs()
@@ -395,13 +381,21 @@ export const ComposePost = observer(function ComposePost({
[setExtLink],
)
+ const {
+ scrollHandler,
+ onScrollViewContentSizeChange,
+ onScrollViewLayout,
+ topBarAnimatedStyle,
+ bottomBarAnimatedStyle,
+ } = useAnimatedBorders()
+
return (
<>
+ keyboardVerticalOffset={replyTo ? 110 : isAndroid ? 180 : 140}>
+ keyboardShouldPersistTaps="always"
+ onContentSizeChange={onScrollViewContentSizeChange}
+ onLayout={onScrollViewLayout}>
{replyTo ? : undefined}
{replyTo ? null : (
-
+
)}
(null)
}
+function useAnimatedBorders() {
+ const t = useTheme()
+ const hasScrolledTop = useSharedValue(0)
+ const hasScrolledBottom = useSharedValue(0)
+ const contentOffset = useSharedValue(0)
+ const scrollViewHeight = useSharedValue(Infinity)
+ const contentHeight = useSharedValue(0)
+
+ /**
+ * Make sure to run this on the UI thread!
+ */
+ const showHideBottomBorder = useCallback(
+ ({
+ newContentHeight,
+ newContentOffset,
+ newScrollViewHeight,
+ }: {
+ newContentHeight?: number
+ newContentOffset?: number
+ newScrollViewHeight?: number
+ }) => {
+ 'worklet'
+
+ if (typeof newContentHeight === 'number')
+ contentHeight.value = newContentHeight
+ if (typeof newContentOffset === 'number')
+ contentOffset.value = newContentOffset
+ if (typeof newScrollViewHeight === 'number')
+ scrollViewHeight.value = newScrollViewHeight
+
+ hasScrolledBottom.value = withTiming(
+ contentHeight.value - contentOffset.value >= scrollViewHeight.value
+ ? 1
+ : 0,
+ )
+ },
+ [contentHeight, contentOffset, scrollViewHeight, hasScrolledBottom],
+ )
+
+ const scrollHandler = useAnimatedScrollHandler({
+ onScroll: event => {
+ hasScrolledTop.value = withTiming(event.contentOffset.y > 0 ? 1 : 0)
+
+ // already on UI thread
+ showHideBottomBorder({
+ newContentOffset: event.contentOffset.y,
+ newContentHeight: event.contentSize.height,
+ newScrollViewHeight: event.layoutMeasurement.height,
+ })
+ },
+ })
+
+ const onScrollViewContentSizeChange = useCallback(
+ (_width: number, height: number) => {
+ runOnUI(showHideBottomBorder)({
+ newContentHeight: height,
+ })
+ },
+ [showHideBottomBorder],
+ )
+
+ const onScrollViewLayout = useCallback(
+ (evt: LayoutChangeEvent) => {
+ runOnUI(showHideBottomBorder)({
+ newScrollViewHeight: evt.nativeEvent.layout.height,
+ })
+ },
+ [showHideBottomBorder],
+ )
+
+ const topBarAnimatedStyle = useAnimatedStyle(() => {
+ return {
+ borderBottomWidth: hairlineWidth,
+ borderColor: interpolateColor(
+ hasScrolledTop.value,
+ [0, 1],
+ ['transparent', t.atoms.border_contrast_medium.borderColor],
+ ),
+ }
+ })
+ const bottomBarAnimatedStyle = useAnimatedStyle(() => {
+ return {
+ borderTopWidth: hairlineWidth,
+ borderColor: interpolateColor(
+ hasScrolledBottom.value,
+ [0, 1],
+ ['transparent', t.atoms.border_contrast_medium.borderColor],
+ ),
+ }
+ })
+
+ return {
+ scrollHandler,
+ onScrollViewContentSizeChange,
+ onScrollViewLayout,
+ topBarAnimatedStyle,
+ bottomBarAnimatedStyle,
+ }
+}
+
const styles = StyleSheet.create({
- topbar: {
- borderBottomWidth: StyleSheet.hairlineWidth,
- },
+ topbar: {},
topbarDesktop: {
paddingTop: 10,
paddingBottom: 10,
@@ -698,7 +796,8 @@ const styles = StyleSheet.create({
bottomBar: {
flexDirection: 'row',
paddingVertical: 4,
- paddingLeft: 8,
+ // should be 8 but due to visual alignment we have to fudge it
+ paddingLeft: 7,
paddingRight: 16,
alignItems: 'center',
borderTopWidth: hairlineWidth,
diff --git a/src/view/com/composer/threadgate/ThreadgateBtn.tsx b/src/view/com/composer/threadgate/ThreadgateBtn.tsx
index afc9f5bfad..2aefdfbbf3 100644
--- a/src/view/com/composer/threadgate/ThreadgateBtn.tsx
+++ b/src/view/com/composer/threadgate/ThreadgateBtn.tsx
@@ -1,5 +1,6 @@
import React from 'react'
-import {Keyboard, View} from 'react-native'
+import {Keyboard, StyleProp, ViewStyle} from 'react-native'
+import Animated, {AnimatedStyle} from 'react-native-reanimated'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -16,9 +17,11 @@ import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
export function ThreadgateBtn({
threadgate,
onChange,
+ style,
}: {
threadgate: ThreadgateSetting[]
onChange: (v: ThreadgateSetting[]) => void
+ style?: StyleProp>
}) {
const {track} = useAnalytics()
const {_} = useLingui()
@@ -46,7 +49,7 @@ export function ThreadgateBtn({
: _(msg`Some people can reply`)
return (
-
+
{label}
-
+
)
}