mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +00:00
fix: keep the event editor's bottom fields above the keyboard #81
This commit is contained in:
@@ -8,8 +8,9 @@ import {
|
||||
StyleSheet,
|
||||
Platform,
|
||||
Alert as NativeAlert,
|
||||
type LayoutChangeEvent,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import DateTimePicker from '@react-native-community/datetimepicker';
|
||||
import {
|
||||
X,
|
||||
@@ -72,6 +73,8 @@ import { useContactNameResolver } from '../../lib/contact-name-resolver';
|
||||
import { RecurrenceEditor } from './RecurrenceEditor';
|
||||
import { MODAL_ORIENTATIONS } from '../../lib/modal-orientations';
|
||||
import { SafeAreaModal } from '../SafeAreaModal';
|
||||
import { useKeyboardObstruction } from '../../lib/use-keyboard-obstruction';
|
||||
import { editorKeyboardInset } from '../../lib/calendar-editor-keyboard';
|
||||
import ToggleSwitch from '../ToggleSwitch';
|
||||
|
||||
type RecurrenceOption = 'none' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'custom';
|
||||
@@ -185,6 +188,25 @@ export function EventModal({
|
||||
const isEdit = !!event;
|
||||
const resolveContactName = useContactNameResolver();
|
||||
|
||||
// The keyboard covered the bottom fields (location, description; #81).
|
||||
// iOS: the scroll view insets itself and scrolls the focused field into
|
||||
// view (automaticallyAdjustKeyboardInsets). Android: the edge-to-edge
|
||||
// dialog does not resize for the keyboard, so the scroll view ends above
|
||||
// it, and Android's ScrollView brings the focused field back into view
|
||||
// when it shrinks.
|
||||
const insets = useSafeAreaInsets();
|
||||
const keyboard = useKeyboardObstruction(insets.bottom);
|
||||
const fullHeight = React.useRef(0);
|
||||
const [containerHeight, setContainerHeight] = React.useState(0);
|
||||
const onContainerLayout = (e: LayoutChangeEvent) => {
|
||||
const { height } = e.nativeEvent.layout;
|
||||
if (keyboard === 0) fullHeight.current = height;
|
||||
setContainerHeight(height);
|
||||
};
|
||||
const keyboardInset = Platform.OS === 'android'
|
||||
? editorKeyboardInset(keyboard, insets.bottom, fullHeight.current - containerHeight)
|
||||
: 0;
|
||||
|
||||
const [title, setTitle] = React.useState('');
|
||||
const [description, setDescription] = React.useState('');
|
||||
const [allDay, setAllDay] = React.useState(false);
|
||||
@@ -441,7 +463,7 @@ export function EventModal({
|
||||
presentationStyle="fullScreen"
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']} onLayout={onContainerLayout}>
|
||||
<View style={styles.header}>
|
||||
<Pressable
|
||||
onPress={onClose}
|
||||
@@ -467,9 +489,10 @@ export function EventModal({
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
style={styles.body}
|
||||
style={[styles.body, keyboardInset > 0 && { marginBottom: keyboardInset }]}
|
||||
contentContainerStyle={styles.bodyContent}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
automaticallyAdjustKeyboardInsets
|
||||
>
|
||||
<TextInput
|
||||
value={title}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { editorKeyboardInset } from '../calendar-editor-keyboard';
|
||||
|
||||
describe('editorKeyboardInset (#81)', () => {
|
||||
it('ends the scroll area above the keyboard, past the safe-area inset already kept clear', () => {
|
||||
expect(editorKeyboardInset(320, 24, 0)).toBe(296);
|
||||
});
|
||||
|
||||
it('shortens a window that resized for the keyboard by nothing more', () => {
|
||||
expect(editorKeyboardInset(320, 24, 296)).toBe(0);
|
||||
expect(editorKeyboardInset(320, 24, 400)).toBe(0);
|
||||
});
|
||||
|
||||
it('keeps the full height without a keyboard', () => {
|
||||
expect(editorKeyboardInset(0, 24, 0)).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* How far above its normal bottom the event editor's scroll area has to end
|
||||
* so the keyboard covers none of it (#81). `obstruction` is how much of the
|
||||
* screen bottom the keyboard covers (use-keyboard-obstruction),
|
||||
* `bottomInset` the safe-area inset the editor already keeps clear, and
|
||||
* `resizedBy` how much the window itself shrank for the keyboard: an
|
||||
* edge-to-edge Android dialog does not resize at all, a window that does
|
||||
* must not be shortened twice.
|
||||
*/
|
||||
export function editorKeyboardInset(obstruction: number, bottomInset: number, resizedBy: number): number {
|
||||
return Math.max(0, Math.round(obstruction - bottomInset - Math.max(0, resizedBy)));
|
||||
}
|
||||
Reference in New Issue
Block a user