mattermost-mobile-upstream/app/utils/keyboard.ts
yasser khan 6f8f87b081
fix: Product fixes to run e2e tests (#10081)
fix: extract product fixes from Detox PR and stabilize related E2E

Move Saved, thread follow, bookmarks image_url, tables, search persist,
create-channel retry, and sidebar padding onto their own branch so they
can land or revert independently of the E2E harness.

Also includes:
- iOS search clear/Cancel overlap and resting margin fixes
- Saved messages subscription distinctUntilChanged guards (no flicker)
- Table horizontal inset derived from a single padding constant
- Manual time entry commits on parse (not only blur/submit)
- Presence sheet dismiss-before-status-update (Android Fabric crash)
- Bounded keyboard dismiss on Android edge-to-edge only; iOS unbound
- Create-channel retry leaves header button enabled on failure
- E2E flakiness: Maestro attach_logs/subflows, bookmark Edit wait,
  status sheet timing, classification lock transport retries,
  apiCreatePost throws on network errors, flow-header contract in CI
- Unit/integration coverage and CodeRabbit follow-ups for the above
2026-08-26 16:12:37 +05:30

63 lines
2.3 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Keyboard} from 'react-native';
import {KeyboardController} from 'react-native-keyboard-controller';
import * as Device from '@constants/device';
const DISMISS_ANIMATION_TIMEOUT = 250;
// Resolve on whichever comes first, and always clear the timer.
const withTimeout = async (promise: Promise<void>, ms: number) => {
let timer: ReturnType<typeof setTimeout> | undefined;
try {
await Promise.race([promise, new Promise<void>((resolve) => {
timer = setTimeout(resolve, ms);
})]);
} finally {
clearTimeout(timer);
}
};
/**
* Dismisses the keyboard using platform-specific implementation.
* - iOS: Uses KeyboardController.dismiss() which provides better control
* - Android: Uses React Native's Keyboard.dismiss() since KeyboardProvider
* is not used on Android (to avoid layout issues)
*/
export const dismissKeyboard = async (): Promise<void> => {
if (Device.isEdgeToEdge) {
const dismissed = KeyboardController.dismiss({animated: false});
// Android only: this promise settles on a `keyboardDidHide` event, and a missed
// one leaves the library's internal state saying "open" so it never settles at
// all. Callers await this before navigating (showPostOptions does), so a missed
// event stops a long press from opening post options. iOS keeps the unbounded
// await — there the event is what tells us the keyboard has left the screen.
if (Device.isAndroidEdgeToEdge) {
await withTimeout(dismissed, DISMISS_ANIMATION_TIMEOUT);
return;
}
await dismissed;
return;
}
Keyboard.dismiss();
await new Promise((resolve) => setTimeout(resolve, DISMISS_ANIMATION_TIMEOUT));
};
/**
* Checks if the keyboard is currently visible.
* - iOS: Uses KeyboardController.isVisible() for accurate keyboard state
* - Android: Uses React Native's Keyboard.isVisible() since KeyboardProvider
* is not used on Android (to avoid layout issues)
*/
export const isKeyboardVisible = (): boolean => {
if (Device.isEdgeToEdge) {
return KeyboardController.isVisible();
}
return Keyboard.isVisible();
};