use RNKC for composer keyboard avoiding view

This commit is contained in:
Samuel Newman
2026-06-26 18:49:34 +03:00
parent 66250e45cc
commit a20afa9e43
2 changed files with 35 additions and 50 deletions
-12
View File
@@ -1969,18 +1969,6 @@
} }
}, },
"src/view/com/composer/Composer.tsx": { "src/view/com/composer/Composer.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
},
"@typescript-eslint/no-floating-promises": {
"count": 1
},
"@typescript-eslint/no-misused-promises": {
"count": 4
},
"@typescript-eslint/no-unsafe-member-access": {
"count": 2
},
"react-hooks/immutability": { "react-hooks/immutability": {
"count": 2 "count": 2
}, },
+35 -38
View File
@@ -13,7 +13,6 @@ import {
ActivityIndicator, ActivityIndicator,
BackHandler, BackHandler,
Keyboard, Keyboard,
KeyboardAvoidingView,
type LayoutChangeEvent, type LayoutChangeEvent,
ScrollView, ScrollView,
type StyleProp, type StyleProp,
@@ -21,6 +20,7 @@ import {
View, View,
type ViewStyle, type ViewStyle,
} from 'react-native' } from 'react-native'
import {KeyboardAvoidingView} from 'react-native-keyboard-controller'
// @ts-expect-error no type definition // @ts-expect-error no type definition
import ProgressCircle from 'react-native-progress/Circle' import ProgressCircle from 'react-native-progress/Circle'
import Animated, { import Animated, {
@@ -71,7 +71,6 @@ import {
SUPPORTED_MIME_TYPES, SUPPORTED_MIME_TYPES,
type SupportedMimeTypes, type SupportedMimeTypes,
} from '#/lib/constants' } from '#/lib/constants'
import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {createVideoTelemetry} from '#/lib/media/video/telemetry' import {createVideoTelemetry} from '#/lib/media/video/telemetry'
import {mimeToExt} from '#/lib/media/video/util' import {mimeToExt} from '#/lib/media/video/util'
@@ -286,7 +285,6 @@ export const ComposePost = ({
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const [isKeyboardVisible] = useIsKeyboardVisible({iosUseWillEvents: true})
const [isPublishing, setIsPublishing] = useState(false) const [isPublishing, setIsPublishing] = useState(false)
const [publishingStage, setPublishingStage] = useState('') const [publishingStage, setPublishingStage] = useState('')
const [error, setError] = useState('') const [error, setError] = useState('')
@@ -641,7 +639,7 @@ export const ComposePost = ({
// This is async but we don't await - videos process in the background // This is async but we don't await - videos process in the background
for (const [postIndex, videoInfo] of restoredVideos) { for (const [postIndex, videoInfo] of restoredVideos) {
const postId = posts[postIndex].id const postId = posts[postIndex].id
restoreVideo(postId, videoInfo) void restoreVideo(postId, videoInfo)
} }
}, },
[composerDispatch, restoreVideo, ax], [composerDispatch, restoreVideo, ax],
@@ -798,17 +796,9 @@ export const ComposePost = ({
const viewStyles = useMemo( const viewStyles = useMemo(
() => ({ () => ({
paddingTop: IS_ANDROID ? insets.top : 0, paddingTop: IS_ANDROID ? insets.top : 0,
paddingBottom: paddingBottom: insets.bottom,
// iOS - when keyboard is closed, keep the bottom bar in the safe area
(IS_IOS && !isKeyboardVisible) ||
// Android - Android >=35 KeyboardAvoidingView adds double padding when
// keyboard is closed, so we subtract that in the offset and add it back
// here when the keyboard is open
(IS_ANDROID && isKeyboardVisible)
? insets.bottom
: 0,
}), }),
[insets, isKeyboardVisible], [insets.top, insets.bottom],
) )
const onPressCancel = useCallback(() => { const onPressCancel = useCallback(() => {
@@ -1042,13 +1032,13 @@ export const ComposePost = ({
posts, posts,
} }
} }
} catch (waitErr: any) { } catch (waitErr) {
logger.info(`composer: waiting for app view failed`, { logger.info(`composer: waiting for app view failed`, {
safeMessage: waitErr, safeMessage: waitErr,
}) })
} }
} catch (e: any) { } catch (e) {
logger.error(e, { logger.error(e instanceof Error ? e : String(e), {
message: `Composer: create post failed`, message: `Composer: create post failed`,
hasImages: filteredThread.posts.some( hasImages: filteredThread.posts.some(
p => p =>
@@ -1057,7 +1047,7 @@ export const ComposePost = ({
), ),
}) })
let err = cleanError(e.message) let err = e instanceof Error ? cleanError(e.message) : String(e)
if ( if (
e instanceof apilib.ReplyDeletedError || e instanceof apilib.ReplyDeletedError ||
err.includes('not locate record') err.includes('not locate record')
@@ -1359,8 +1349,8 @@ export const ComposePost = ({
publishingStage={publishingStage} publishingStage={publishingStage}
topBarAnimatedStyle={topBarAnimatedStyle} topBarAnimatedStyle={topBarAnimatedStyle}
onCancel={onPressCancel} onCancel={onPressCancel}
onPublish={onPressPublish} onPublish={() => void onPressPublish()}
onSelectDraft={handleSelectDraft} onSelectDraft={draft => void handleSelectDraft(draft)}
onSaveDraft={saveCurrentDraft} onSaveDraft={saveCurrentDraft}
onDiscard={handleClearComposer} onDiscard={handleClearComposer}
isEmpty={isComposerEmpty} isEmpty={isComposerEmpty}
@@ -1473,7 +1463,7 @@ export const ComposePost = ({
{allPostsWithinLimit && ( {allPostsWithinLimit && (
<Prompt.Action <Prompt.Action
cta={composerState.draftId ? l`Save changes` : l`Save draft`} cta={composerState.draftId ? l`Save changes` : l`Save draft`}
onPress={handleSaveDraft} onPress={() => void handleSaveDraft()}
color="primary" color="primary"
/> />
)} )}
@@ -1633,7 +1623,7 @@ let ComposerPost = memo(function ComposerPost({
postId: post.id, postId: post.id,
}) })
}} }}
onPhotoPasted={onPhotoPasted} onPhotoPasted={uri => void onPhotoPasted(uri)}
onNewLink={onNewLink} onNewLink={onNewLink}
onError={onError} onError={onError}
onPressPublish={onPublish} onPressPublish={onPublish}
@@ -2124,7 +2114,7 @@ function ComposerFooter({
}), }),
).catch(e => { ).catch(e => {
logger.error(`createComposerImage failed`, { logger.error(`createComposerImage failed`, {
safeMessage: e.message, safeMessage: e instanceof Error ? e.message : String(e),
}) })
}) })
@@ -2361,24 +2351,31 @@ function useScrollTracker({
} }
function useKeyboardVerticalOffset() { function useKeyboardVerticalOffset() {
const {top, bottom} = useSafeAreaInsets() const insets = useSafeAreaInsets()
// Android etc // the keyboardavoidingview has bottom padding to avoid being obscured by the safe area when keyboard is closed.
if (!IS_IOS) { // however, this leads to a gap when the keyboard is open. we account for that by subtracting the bottom inset when open.
// need to account for the edge-to-edge nav bar let keyboardVerticalOffset = insets.bottom * -1
return bottom * -1
// iOS requires a bit of extra offset to account for the native sheet not being at the top of the screen
if (IS_IOS) {
// they ditched the gap behaviour on 26
if (IS_LIQUID_GLASS) {
keyboardVerticalOffset += insets.top
}
// iPhone SE
else if (insets.top === 20) {
keyboardVerticalOffset += 40
}
// all other iPhones on <26
else {
keyboardVerticalOffset += insets.top + 10
}
} }
// they ditched the gap behaviour on 26 return keyboardVerticalOffset
if (IS_LIQUID_GLASS) {
return top
}
// iPhone SE
if (top === 20) return 40
// all other iPhones on <26
return top + 10
} }
async function whenAppViewReady( async function whenAppViewReady(