+1
-1
assets/icons/image_stroke2_corner0_rounded.svg
+1
-1
assets/icons/image_stroke2_corner0_rounded.svg
···
1
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="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" clip-rule="evenodd"/></svg>
1
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="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" clip-rule="evenodd"/></svg>
+1
-1
src/components/icons/Image.tsx
+1
-1
src/components/icons/Image.tsx
···
1
1
import {createSinglePathSVG} from './TEMPLATE'
2
2
3
3
export const Image_Stroke2_Corner0_Rounded = createSinglePathSVG({
4
-
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',
4
+
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',
5
5
})
+122
-23
src/view/com/composer/Composer.tsx
+122
-23
src/view/com/composer/Composer.tsx
···
9
9
import {
10
10
ActivityIndicator,
11
11
Keyboard,
12
+
LayoutChangeEvent,
12
13
StyleSheet,
13
14
TouchableOpacity,
14
15
View,
···
19
20
} from 'react-native-keyboard-controller'
20
21
import Animated, {
21
22
interpolateColor,
23
+
runOnUI,
22
24
useAnimatedStyle,
23
25
useSharedValue,
24
26
withTiming,
···
169
171
}),
170
172
[insets, isKeyboardVisible],
171
173
)
172
-
173
-
const hasScrolled = useSharedValue(0)
174
-
const scrollHandler = useAnimatedScrollHandler({
175
-
onScroll: event => {
176
-
hasScrolled.value = withTiming(event.contentOffset.y > 0 ? 1 : 0)
177
-
},
178
-
})
179
-
const topBarAnimatedStyle = useAnimatedStyle(() => {
180
-
return {
181
-
borderColor: interpolateColor(
182
-
hasScrolled.value,
183
-
[0, 1],
184
-
['transparent', t.atoms.border_contrast_medium.borderColor],
185
-
),
186
-
}
187
-
})
188
174
189
175
const onPressCancel = useCallback(() => {
190
176
if (graphemeLength > 0 || !gallery.isEmpty) {
···
395
381
[setExtLink],
396
382
)
397
383
384
+
const {
385
+
scrollHandler,
386
+
onScrollViewContentSizeChange,
387
+
onScrollViewLayout,
388
+
topBarAnimatedStyle,
389
+
bottomBarAnimatedStyle,
390
+
} = useAnimatedBorders()
391
+
398
392
return (
399
393
<>
400
394
<KeyboardAvoidingView
401
395
testID="composePostView"
402
396
behavior="padding"
403
397
style={a.flex_1}
404
-
keyboardVerticalOffset={replyTo ? 120 : isAndroid ? 180 : 150}>
398
+
keyboardVerticalOffset={replyTo ? 110 : isAndroid ? 180 : 140}>
405
399
<View
406
400
style={[a.flex_1, viewStyles]}
407
401
aria-modal
···
509
503
<Animated.ScrollView
510
504
onScroll={scrollHandler}
511
505
style={styles.scrollView}
512
-
keyboardShouldPersistTaps="always">
506
+
keyboardShouldPersistTaps="always"
507
+
onContentSizeChange={onScrollViewContentSizeChange}
508
+
onLayout={onScrollViewLayout}>
513
509
{replyTo ? <ComposerReplyTo replyTo={replyTo} /> : undefined}
514
510
515
511
<View
···
575
571
<KeyboardStickyView
576
572
offset={{closed: isIOS ? -insets.bottom : 0, opened: 0}}>
577
573
{replyTo ? null : (
578
-
<ThreadgateBtn threadgate={threadgate} onChange={setThreadgate} />
574
+
<ThreadgateBtn
575
+
threadgate={threadgate}
576
+
onChange={setThreadgate}
577
+
style={bottomBarAnimatedStyle}
578
+
/>
579
579
)}
580
580
<View
581
581
style={[
···
625
625
return useRef<CancelRef>(null)
626
626
}
627
627
628
+
function useAnimatedBorders() {
629
+
const t = useTheme()
630
+
const hasScrolledTop = useSharedValue(0)
631
+
const hasScrolledBottom = useSharedValue(0)
632
+
const contentOffset = useSharedValue(0)
633
+
const scrollViewHeight = useSharedValue(Infinity)
634
+
const contentHeight = useSharedValue(0)
635
+
636
+
/**
637
+
* Make sure to run this on the UI thread!
638
+
*/
639
+
const showHideBottomBorder = useCallback(
640
+
({
641
+
newContentHeight,
642
+
newContentOffset,
643
+
newScrollViewHeight,
644
+
}: {
645
+
newContentHeight?: number
646
+
newContentOffset?: number
647
+
newScrollViewHeight?: number
648
+
}) => {
649
+
'worklet'
650
+
651
+
if (typeof newContentHeight === 'number')
652
+
contentHeight.value = newContentHeight
653
+
if (typeof newContentOffset === 'number')
654
+
contentOffset.value = newContentOffset
655
+
if (typeof newScrollViewHeight === 'number')
656
+
scrollViewHeight.value = newScrollViewHeight
657
+
658
+
hasScrolledBottom.value = withTiming(
659
+
contentHeight.value - contentOffset.value >= scrollViewHeight.value
660
+
? 1
661
+
: 0,
662
+
)
663
+
},
664
+
[contentHeight, contentOffset, scrollViewHeight, hasScrolledBottom],
665
+
)
666
+
667
+
const scrollHandler = useAnimatedScrollHandler({
668
+
onScroll: event => {
669
+
hasScrolledTop.value = withTiming(event.contentOffset.y > 0 ? 1 : 0)
670
+
671
+
// already on UI thread
672
+
showHideBottomBorder({
673
+
newContentOffset: event.contentOffset.y,
674
+
newContentHeight: event.contentSize.height,
675
+
newScrollViewHeight: event.layoutMeasurement.height,
676
+
})
677
+
},
678
+
})
679
+
680
+
const onScrollViewContentSizeChange = useCallback(
681
+
(_width: number, height: number) => {
682
+
runOnUI(showHideBottomBorder)({
683
+
newContentHeight: height,
684
+
})
685
+
},
686
+
[showHideBottomBorder],
687
+
)
688
+
689
+
const onScrollViewLayout = useCallback(
690
+
(evt: LayoutChangeEvent) => {
691
+
runOnUI(showHideBottomBorder)({
692
+
newScrollViewHeight: evt.nativeEvent.layout.height,
693
+
})
694
+
},
695
+
[showHideBottomBorder],
696
+
)
697
+
698
+
const topBarAnimatedStyle = useAnimatedStyle(() => {
699
+
return {
700
+
borderBottomWidth: hairlineWidth,
701
+
borderColor: interpolateColor(
702
+
hasScrolledTop.value,
703
+
[0, 1],
704
+
['transparent', t.atoms.border_contrast_medium.borderColor],
705
+
),
706
+
}
707
+
})
708
+
const bottomBarAnimatedStyle = useAnimatedStyle(() => {
709
+
return {
710
+
borderTopWidth: hairlineWidth,
711
+
borderColor: interpolateColor(
712
+
hasScrolledBottom.value,
713
+
[0, 1],
714
+
['transparent', t.atoms.border_contrast_medium.borderColor],
715
+
),
716
+
}
717
+
})
718
+
719
+
return {
720
+
scrollHandler,
721
+
onScrollViewContentSizeChange,
722
+
onScrollViewLayout,
723
+
topBarAnimatedStyle,
724
+
bottomBarAnimatedStyle,
725
+
}
726
+
}
727
+
628
728
const styles = StyleSheet.create({
629
-
topbar: {
630
-
borderBottomWidth: StyleSheet.hairlineWidth,
631
-
},
729
+
topbar: {},
632
730
topbarDesktop: {
633
731
paddingTop: 10,
634
732
paddingBottom: 10,
···
698
796
bottomBar: {
699
797
flexDirection: 'row',
700
798
paddingVertical: 4,
701
-
paddingLeft: 8,
799
+
// should be 8 but due to visual alignment we have to fudge it
800
+
paddingLeft: 7,
702
801
paddingRight: 16,
703
802
alignItems: 'center',
704
803
borderTopWidth: hairlineWidth,
+6
-3
src/view/com/composer/threadgate/ThreadgateBtn.tsx
+6
-3
src/view/com/composer/threadgate/ThreadgateBtn.tsx
···
1
1
import React from 'react'
2
-
import {Keyboard, View} from 'react-native'
2
+
import {Keyboard, StyleProp, ViewStyle} from 'react-native'
3
+
import Animated, {AnimatedStyle} from 'react-native-reanimated'
3
4
import {msg} from '@lingui/macro'
4
5
import {useLingui} from '@lingui/react'
5
6
···
16
17
export function ThreadgateBtn({
17
18
threadgate,
18
19
onChange,
20
+
style,
19
21
}: {
20
22
threadgate: ThreadgateSetting[]
21
23
onChange: (v: ThreadgateSetting[]) => void
24
+
style?: StyleProp<AnimatedStyle<ViewStyle>>
22
25
}) {
23
26
const {track} = useAnalytics()
24
27
const {_} = useLingui()
···
46
49
: _(msg`Some people can reply`)
47
50
48
51
return (
49
-
<View style={[a.flex_row, a.py_xs, a.px_sm, t.atoms.bg]}>
52
+
<Animated.View style={[a.flex_row, a.p_sm, t.atoms.bg, style]}>
50
53
<Button
51
54
variant="solid"
52
55
color="secondary"
···
59
62
/>
60
63
<ButtonText>{label}</ButtonText>
61
64
</Button>
62
-
</View>
65
+
</Animated.View>
63
66
)
64
67
}