Revert "use KeyboardPadding over KeyboardAvoidingView"
This reverts commit 5122c20e56.
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {useKeyboardHandler} from 'react-native-keyboard-controller'
|
||||||
|
import Animated, {
|
||||||
|
useAnimatedStyle,
|
||||||
|
useSharedValue,
|
||||||
|
} from 'react-native-reanimated'
|
||||||
|
|
||||||
|
export function KeyboardPadding({maxHeight}: {maxHeight?: number}) {
|
||||||
|
const keyboardHeight = useSharedValue(0)
|
||||||
|
|
||||||
|
useKeyboardHandler(
|
||||||
|
{
|
||||||
|
onMove: e => {
|
||||||
|
'worklet'
|
||||||
|
|
||||||
|
if (maxHeight && e.height > maxHeight) {
|
||||||
|
keyboardHeight.value = maxHeight
|
||||||
|
} else {
|
||||||
|
keyboardHeight.value = e.height
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[maxHeight],
|
||||||
|
)
|
||||||
|
|
||||||
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
|
height: keyboardHeight.value,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return <Animated.View style={animatedStyle} />
|
||||||
|
}
|
||||||
@@ -1,40 +1,3 @@
|
|||||||
import React from 'react'
|
export function KeyboardPadding({maxHeight: _}: {maxHeight?: number}) {
|
||||||
import {useKeyboardHandler} from 'react-native-keyboard-controller'
|
return null
|
||||||
import Animated, {
|
|
||||||
useAnimatedStyle,
|
|
||||||
useSharedValue,
|
|
||||||
} from 'react-native-reanimated'
|
|
||||||
|
|
||||||
import {isAndroid} from 'platform/detection'
|
|
||||||
|
|
||||||
export function KeyboardPadding({androidOnly = true}: {androidOnly?: boolean}) {
|
|
||||||
if (!isAndroid && androidOnly) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return <KeyboardPaddingInner />
|
|
||||||
}
|
|
||||||
|
|
||||||
function KeyboardPaddingInner({maxHeight}: {maxHeight?: number}) {
|
|
||||||
const keyboardHeight = useSharedValue(0)
|
|
||||||
|
|
||||||
useKeyboardHandler(
|
|
||||||
{
|
|
||||||
onMove: e => {
|
|
||||||
'worklet'
|
|
||||||
|
|
||||||
if (maxHeight && e.height > maxHeight) {
|
|
||||||
keyboardHeight.value = maxHeight
|
|
||||||
} else {
|
|
||||||
keyboardHeight.value = e.height
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[maxHeight],
|
|
||||||
)
|
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => ({
|
|
||||||
height: keyboardHeight.value,
|
|
||||||
}))
|
|
||||||
|
|
||||||
return <Animated.View style={animatedStyle} />
|
|
||||||
}
|
}
|
||||||
|
|||||||
+174
-162
@@ -13,7 +13,10 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {KeyboardStickyView} from 'react-native-keyboard-controller'
|
import {
|
||||||
|
KeyboardAvoidingView,
|
||||||
|
KeyboardStickyView,
|
||||||
|
} from 'react-native-keyboard-controller'
|
||||||
import Animated, {
|
import Animated, {
|
||||||
interpolateColor,
|
interpolateColor,
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
@@ -52,13 +55,14 @@ import {useComposerControls} from '#/state/shell/composer'
|
|||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import * as apilib from 'lib/api/index'
|
import * as apilib from 'lib/api/index'
|
||||||
import {HITSLOP_10, MAX_GRAPHEME_LENGTH} from 'lib/constants'
|
import {HITSLOP_10, MAX_GRAPHEME_LENGTH} from 'lib/constants'
|
||||||
|
import {useIsKeyboardVisible} from 'lib/hooks/useIsKeyboardVisible'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {cleanError} from 'lib/strings/errors'
|
import {cleanError} from 'lib/strings/errors'
|
||||||
import {insertMentionAt} from 'lib/strings/mention-manip'
|
import {insertMentionAt} from 'lib/strings/mention-manip'
|
||||||
import {shortenLinks} from 'lib/strings/rich-text-manip'
|
import {shortenLinks} from 'lib/strings/rich-text-manip'
|
||||||
import {colors, gradients, s} from 'lib/styles'
|
import {colors, gradients, s} from 'lib/styles'
|
||||||
import {isIOS, isNative, isWeb} from 'platform/detection'
|
import {isAndroid, isIOS, isNative, isWeb} from 'platform/detection'
|
||||||
import {useDialogStateControlContext} from 'state/dialogs'
|
import {useDialogStateControlContext} from 'state/dialogs'
|
||||||
import {GalleryModel} from 'state/models/media/gallery'
|
import {GalleryModel} from 'state/models/media/gallery'
|
||||||
import {ComposerOpts} from 'state/shell/composer'
|
import {ComposerOpts} from 'state/shell/composer'
|
||||||
@@ -66,7 +70,6 @@ import {ComposerReplyTo} from 'view/com/composer/ComposerReplyTo'
|
|||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
|
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
|
||||||
import {KeyboardPadding} from '#/components/KeyboardPadding'
|
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {QuoteEmbed, QuoteX} from '../util/post-embeds/QuoteEmbed'
|
import {QuoteEmbed, QuoteX} from '../util/post-embeds/QuoteEmbed'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
@@ -122,6 +125,7 @@ export const ComposePost = observer(function ComposePost({
|
|||||||
const {closeAllDialogs} = useDialogStateControlContext()
|
const {closeAllDialogs} = useDialogStateControlContext()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
|
||||||
|
const [isKeyboardVisible] = useIsKeyboardVisible({iosUseWillEvents: true})
|
||||||
const [isProcessing, setIsProcessing] = useState(false)
|
const [isProcessing, setIsProcessing] = useState(false)
|
||||||
const [processingState, setProcessingState] = useState('')
|
const [processingState, setProcessingState] = useState('')
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
@@ -157,6 +161,13 @@ export const ComposePost = observer(function ComposePost({
|
|||||||
}, [closeComposer])
|
}, [closeComposer])
|
||||||
|
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
|
const viewStyles = useMemo(
|
||||||
|
() => ({
|
||||||
|
paddingBottom:
|
||||||
|
isAndroid || (isIOS && !isKeyboardVisible) ? insets.bottom : 0,
|
||||||
|
}),
|
||||||
|
[insets, isKeyboardVisible],
|
||||||
|
)
|
||||||
|
|
||||||
const hasScrolled = useSharedValue(0)
|
const hasScrolled = useSharedValue(0)
|
||||||
const scrollHandler = useAnimatedScrollHandler({
|
const scrollHandler = useAnimatedScrollHandler({
|
||||||
@@ -390,174 +401,175 @@ export const ComposePost = observer(function ComposePost({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View
|
<KeyboardAvoidingView
|
||||||
style={[s.flex1]}
|
|
||||||
testID="composePostView"
|
testID="composePostView"
|
||||||
aria-modal
|
behavior="padding"
|
||||||
accessibilityViewIsModal>
|
style={s.flex1}
|
||||||
<Animated.View
|
keyboardVerticalOffset={replyTo ? 60 : isAndroid ? 120 : 100}>
|
||||||
style={[
|
<View style={[s.flex1, viewStyles]} aria-modal accessibilityViewIsModal>
|
||||||
styles.topbar,
|
<Animated.View
|
||||||
topBarAnimatedStyle,
|
|
||||||
isWeb && isTabletOrDesktop && styles.topbarDesktop,
|
|
||||||
]}>
|
|
||||||
<TouchableOpacity
|
|
||||||
testID="composerDiscardButton"
|
|
||||||
onPress={onPressCancel}
|
|
||||||
onAccessibilityEscape={onPressCancel}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Cancel`)}
|
|
||||||
accessibilityHint={_(
|
|
||||||
msg`Closes post composer and discards post draft`,
|
|
||||||
)}
|
|
||||||
hitSlop={HITSLOP_10}>
|
|
||||||
<Text style={[pal.link, s.f18]}>
|
|
||||||
<Trans>Cancel</Trans>
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<View style={s.flex1} />
|
|
||||||
{isProcessing ? (
|
|
||||||
<>
|
|
||||||
<Text style={pal.textLight}>{processingState}</Text>
|
|
||||||
<View style={styles.postBtn}>
|
|
||||||
<ActivityIndicator />
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<LabelsBtn
|
|
||||||
labels={labels}
|
|
||||||
onChange={setLabels}
|
|
||||||
hasMedia={hasMedia}
|
|
||||||
/>
|
|
||||||
{canPost ? (
|
|
||||||
<TouchableOpacity
|
|
||||||
testID="composerPublishBtn"
|
|
||||||
onPress={onPressPublish}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={
|
|
||||||
replyTo ? _(msg`Publish reply`) : _(msg`Publish post`)
|
|
||||||
}
|
|
||||||
accessibilityHint="">
|
|
||||||
<LinearGradient
|
|
||||||
colors={[
|
|
||||||
gradients.blueLight.start,
|
|
||||||
gradients.blueLight.end,
|
|
||||||
]}
|
|
||||||
start={{x: 0, y: 0}}
|
|
||||||
end={{x: 1, y: 1}}
|
|
||||||
style={styles.postBtn}>
|
|
||||||
<Text style={[s.white, s.f16, s.bold]}>
|
|
||||||
{replyTo ? (
|
|
||||||
<Trans context="action">Reply</Trans>
|
|
||||||
) : (
|
|
||||||
<Trans context="action">Post</Trans>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</LinearGradient>
|
|
||||||
</TouchableOpacity>
|
|
||||||
) : (
|
|
||||||
<View style={[styles.postBtn, pal.btn]}>
|
|
||||||
<Text style={[pal.textLight, s.f16, s.bold]}>
|
|
||||||
<Trans context="action">Post</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Animated.View>
|
|
||||||
{isAltTextRequiredAndMissing && (
|
|
||||||
<View style={[styles.reminderLine, pal.viewLight]}>
|
|
||||||
<View style={styles.errorIcon}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="exclamation"
|
|
||||||
style={{color: colors.red4}}
|
|
||||||
size={10}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Text style={[pal.text, s.flex1]}>
|
|
||||||
<Trans>One or more images is missing alt text.</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
{error !== '' && (
|
|
||||||
<View style={styles.errorLine}>
|
|
||||||
<View style={styles.errorIcon}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="exclamation"
|
|
||||||
style={{color: colors.red4}}
|
|
||||||
size={10}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Text style={[s.red4, s.flex1]}>{error}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<Animated.ScrollView
|
|
||||||
onScroll={scrollHandler}
|
|
||||||
style={styles.scrollView}
|
|
||||||
keyboardShouldPersistTaps="always">
|
|
||||||
{replyTo ? <ComposerReplyTo replyTo={replyTo} /> : undefined}
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={[
|
style={[
|
||||||
styles.textInputLayout,
|
styles.topbar,
|
||||||
isNative && styles.textInputLayoutMobile,
|
topBarAnimatedStyle,
|
||||||
|
isWeb && isTabletOrDesktop && styles.topbarDesktop,
|
||||||
]}>
|
]}>
|
||||||
<UserAvatar
|
<TouchableOpacity
|
||||||
avatar={currentProfile?.avatar}
|
testID="composerDiscardButton"
|
||||||
size={50}
|
onPress={onPressCancel}
|
||||||
type={currentProfile?.associated?.labeler ? 'labeler' : 'user'}
|
onAccessibilityEscape={onPressCancel}
|
||||||
/>
|
accessibilityRole="button"
|
||||||
<TextInput
|
accessibilityLabel={_(msg`Cancel`)}
|
||||||
ref={textInput}
|
|
||||||
richtext={richtext}
|
|
||||||
placeholder={selectTextInputPlaceholder}
|
|
||||||
autoFocus={true}
|
|
||||||
setRichText={setRichText}
|
|
||||||
onPhotoPasted={onPhotoPasted}
|
|
||||||
onPressPublish={onPressPublish}
|
|
||||||
onNewLink={onNewLink}
|
|
||||||
onError={setError}
|
|
||||||
accessible={true}
|
|
||||||
accessibilityLabel={_(msg`Write post`)}
|
|
||||||
accessibilityHint={_(
|
accessibilityHint={_(
|
||||||
msg`Compose posts up to ${MAX_GRAPHEME_LENGTH} characters in length`,
|
msg`Closes post composer and discards post draft`,
|
||||||
)}
|
)}
|
||||||
/>
|
hitSlop={HITSLOP_10}>
|
||||||
</View>
|
<Text style={[pal.link, s.f18]}>
|
||||||
|
<Trans>Cancel</Trans>
|
||||||
<Gallery gallery={gallery} />
|
</Text>
|
||||||
{gallery.isEmpty && extLink && (
|
</TouchableOpacity>
|
||||||
<View style={a.relative}>
|
<View style={s.flex1} />
|
||||||
<ExternalEmbed
|
{isProcessing ? (
|
||||||
link={extLink}
|
<>
|
||||||
gif={extGif}
|
<Text style={pal.textLight}>{processingState}</Text>
|
||||||
onRemove={() => {
|
<View style={styles.postBtn}>
|
||||||
setExtLink(undefined)
|
<ActivityIndicator />
|
||||||
setExtGif(undefined)
|
</View>
|
||||||
}}
|
</>
|
||||||
/>
|
) : (
|
||||||
<GifAltText
|
<>
|
||||||
link={extLink}
|
<LabelsBtn
|
||||||
gif={extGif}
|
labels={labels}
|
||||||
onSubmit={handleChangeGifAltText}
|
onChange={setLabels}
|
||||||
/>
|
hasMedia={hasMedia}
|
||||||
|
/>
|
||||||
|
{canPost ? (
|
||||||
|
<TouchableOpacity
|
||||||
|
testID="composerPublishBtn"
|
||||||
|
onPress={onPressPublish}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel={
|
||||||
|
replyTo ? _(msg`Publish reply`) : _(msg`Publish post`)
|
||||||
|
}
|
||||||
|
accessibilityHint="">
|
||||||
|
<LinearGradient
|
||||||
|
colors={[
|
||||||
|
gradients.blueLight.start,
|
||||||
|
gradients.blueLight.end,
|
||||||
|
]}
|
||||||
|
start={{x: 0, y: 0}}
|
||||||
|
end={{x: 1, y: 1}}
|
||||||
|
style={styles.postBtn}>
|
||||||
|
<Text style={[s.white, s.f16, s.bold]}>
|
||||||
|
{replyTo ? (
|
||||||
|
<Trans context="action">Reply</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans context="action">Post</Trans>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
</TouchableOpacity>
|
||||||
|
) : (
|
||||||
|
<View style={[styles.postBtn, pal.btn]}>
|
||||||
|
<Text style={[pal.textLight, s.f16, s.bold]}>
|
||||||
|
<Trans context="action">Post</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Animated.View>
|
||||||
|
{isAltTextRequiredAndMissing && (
|
||||||
|
<View style={[styles.reminderLine, pal.viewLight]}>
|
||||||
|
<View style={styles.errorIcon}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="exclamation"
|
||||||
|
style={{color: colors.red4}}
|
||||||
|
size={10}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Text style={[pal.text, s.flex1]}>
|
||||||
|
<Trans>One or more images is missing alt text.</Trans>
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{quote ? (
|
{error !== '' && (
|
||||||
<View style={[s.mt5, isWeb && s.mb10]}>
|
<View style={styles.errorLine}>
|
||||||
<View style={{pointerEvents: 'none'}}>
|
<View style={styles.errorIcon}>
|
||||||
<QuoteEmbed quote={quote} />
|
<FontAwesomeIcon
|
||||||
|
icon="exclamation"
|
||||||
|
style={{color: colors.red4}}
|
||||||
|
size={10}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
{quote.uri !== initQuote?.uri && (
|
<Text style={[s.red4, s.flex1]}>{error}</Text>
|
||||||
<QuoteX onRemove={() => setQuote(undefined)} />
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
) : undefined}
|
)}
|
||||||
</Animated.ScrollView>
|
<Animated.ScrollView
|
||||||
<SuggestedLanguage text={richtext.text} />
|
onScroll={scrollHandler}
|
||||||
<KeyboardPadding androidOnly={false} />
|
style={styles.scrollView}
|
||||||
</View>
|
keyboardShouldPersistTaps="always">
|
||||||
|
{replyTo ? <ComposerReplyTo replyTo={replyTo} /> : undefined}
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.textInputLayout,
|
||||||
|
isNative && styles.textInputLayoutMobile,
|
||||||
|
]}>
|
||||||
|
<UserAvatar
|
||||||
|
avatar={currentProfile?.avatar}
|
||||||
|
size={50}
|
||||||
|
type={currentProfile?.associated?.labeler ? 'labeler' : 'user'}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
ref={textInput}
|
||||||
|
richtext={richtext}
|
||||||
|
placeholder={selectTextInputPlaceholder}
|
||||||
|
autoFocus={true}
|
||||||
|
setRichText={setRichText}
|
||||||
|
onPhotoPasted={onPhotoPasted}
|
||||||
|
onPressPublish={onPressPublish}
|
||||||
|
onNewLink={onNewLink}
|
||||||
|
onError={setError}
|
||||||
|
accessible={true}
|
||||||
|
accessibilityLabel={_(msg`Write post`)}
|
||||||
|
accessibilityHint={_(
|
||||||
|
msg`Compose posts up to ${MAX_GRAPHEME_LENGTH} characters in length`,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Gallery gallery={gallery} />
|
||||||
|
{gallery.isEmpty && extLink && (
|
||||||
|
<View style={a.relative}>
|
||||||
|
<ExternalEmbed
|
||||||
|
link={extLink}
|
||||||
|
gif={extGif}
|
||||||
|
onRemove={() => {
|
||||||
|
setExtLink(undefined)
|
||||||
|
setExtGif(undefined)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<GifAltText
|
||||||
|
link={extLink}
|
||||||
|
gif={extGif}
|
||||||
|
onSubmit={handleChangeGifAltText}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{quote ? (
|
||||||
|
<View style={[s.mt5, isWeb && s.mb10]}>
|
||||||
|
<View style={{pointerEvents: 'none'}}>
|
||||||
|
<QuoteEmbed quote={quote} />
|
||||||
|
</View>
|
||||||
|
{quote.uri !== initQuote?.uri && (
|
||||||
|
<QuoteX onRemove={() => setQuote(undefined)} />
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
) : undefined}
|
||||||
|
</Animated.ScrollView>
|
||||||
|
<SuggestedLanguage text={richtext.text} />
|
||||||
|
</View>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
<KeyboardStickyView
|
<KeyboardStickyView
|
||||||
offset={{closed: isIOS ? -insets.bottom : 0, opened: 0}}>
|
offset={{closed: isIOS ? -insets.bottom : 0, opened: 0}}>
|
||||||
{replyTo ? null : (
|
{replyTo ? null : (
|
||||||
|
|||||||
Reference in New Issue
Block a user