diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 1692a6b1a2..08c5929934 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1969,18 +1969,6 @@ } }, "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": { "count": 2 }, diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 92b21ac52c..534a739702 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -13,7 +13,6 @@ import { ActivityIndicator, BackHandler, Keyboard, - KeyboardAvoidingView, type LayoutChangeEvent, ScrollView, type StyleProp, @@ -21,6 +20,7 @@ import { View, type ViewStyle, } from 'react-native' +import {KeyboardAvoidingView} from 'react-native-keyboard-controller' // @ts-expect-error no type definition import ProgressCircle from 'react-native-progress/Circle' import Animated, { @@ -71,7 +71,6 @@ import { SUPPORTED_MIME_TYPES, type SupportedMimeTypes, } from '#/lib/constants' -import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {createVideoTelemetry} from '#/lib/media/video/telemetry' import {mimeToExt} from '#/lib/media/video/util' @@ -286,7 +285,6 @@ export const ComposePost = ({ const {data: preferences} = usePreferencesQuery() const navigation = useNavigation() - const [isKeyboardVisible] = useIsKeyboardVisible({iosUseWillEvents: true}) const [isPublishing, setIsPublishing] = useState(false) const [publishingStage, setPublishingStage] = 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 for (const [postIndex, videoInfo] of restoredVideos) { const postId = posts[postIndex].id - restoreVideo(postId, videoInfo) + void restoreVideo(postId, videoInfo) } }, [composerDispatch, restoreVideo, ax], @@ -798,17 +796,9 @@ export const ComposePost = ({ const viewStyles = useMemo( () => ({ paddingTop: IS_ANDROID ? insets.top : 0, - paddingBottom: - // 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, + paddingBottom: insets.bottom, }), - [insets, isKeyboardVisible], + [insets.top, insets.bottom], ) const onPressCancel = useCallback(() => { @@ -1042,13 +1032,13 @@ export const ComposePost = ({ posts, } } - } catch (waitErr: any) { + } catch (waitErr) { logger.info(`composer: waiting for app view failed`, { safeMessage: waitErr, }) } - } catch (e: any) { - logger.error(e, { + } catch (e) { + logger.error(e instanceof Error ? e : String(e), { message: `Composer: create post failed`, hasImages: filteredThread.posts.some( p => @@ -1057,7 +1047,7 @@ export const ComposePost = ({ ), }) - let err = cleanError(e.message) + let err = e instanceof Error ? cleanError(e.message) : String(e) if ( e instanceof apilib.ReplyDeletedError || err.includes('not locate record') @@ -1359,8 +1349,8 @@ export const ComposePost = ({ publishingStage={publishingStage} topBarAnimatedStyle={topBarAnimatedStyle} onCancel={onPressCancel} - onPublish={onPressPublish} - onSelectDraft={handleSelectDraft} + onPublish={() => void onPressPublish()} + onSelectDraft={draft => void handleSelectDraft(draft)} onSaveDraft={saveCurrentDraft} onDiscard={handleClearComposer} isEmpty={isComposerEmpty} @@ -1473,7 +1463,7 @@ export const ComposePost = ({ {allPostsWithinLimit && ( void handleSaveDraft()} color="primary" /> )} @@ -1633,7 +1623,7 @@ let ComposerPost = memo(function ComposerPost({ postId: post.id, }) }} - onPhotoPasted={onPhotoPasted} + onPhotoPasted={uri => void onPhotoPasted(uri)} onNewLink={onNewLink} onError={onError} onPressPublish={onPublish} @@ -2124,7 +2114,7 @@ function ComposerFooter({ }), ).catch(e => { logger.error(`createComposerImage failed`, { - safeMessage: e.message, + safeMessage: e instanceof Error ? e.message : String(e), }) }) @@ -2361,24 +2351,31 @@ function useScrollTracker({ } function useKeyboardVerticalOffset() { - const {top, bottom} = useSafeAreaInsets() + const insets = useSafeAreaInsets() - // Android etc - if (!IS_IOS) { - // need to account for the edge-to-edge nav bar - return bottom * -1 + // the keyboardavoidingview has bottom padding to avoid being obscured by the safe area when keyboard is closed. + // however, this leads to a gap when the keyboard is open. we account for that by subtracting the bottom inset when open. + let keyboardVerticalOffset = insets.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 - if (IS_LIQUID_GLASS) { - return top - } - - // iPhone SE - if (top === 20) return 40 - - // all other iPhones on <26 - return top + 10 + return keyboardVerticalOffset } async function whenAppViewReady(