Android task entry fix

This commit is contained in:
2026-03-29 11:42:33 -04:00
parent a60af7de31
commit 586497e1fa
2 changed files with 99 additions and 72 deletions

75
main.js

File diff suppressed because one or more lines are too long

View File

@ -24,8 +24,7 @@ export class TaskEditorModal extends Modal {
private textarea: HTMLTextAreaElement | null = null;
private selectedFile: TFile | null = null;
private fileLabel: HTMLSpanElement | null = null;
private keyboardHandler: (() => void) | null = null;
private keyboardResetHandler: (() => void) | null = null;
private keyboardCleanup: (() => void) | null = null;
constructor(
app: App,
@ -47,42 +46,61 @@ export class TaskEditorModal extends Modal {
const { contentEl } = this;
contentEl.addClass('yaotp-editor-modal');
// On Android the on-screen keyboard doesn't resize the layout viewport, so
// the fixed-position modal container stays full-height and the modal ends
// up centered behind the keyboard. We fix this by:
// 1. On textarea focus (+ a short delay for the keyboard animation),
// read the visual viewport height and shrink the container to that
// size, then align the modal to the top of the container.
// 2. On blur, reset everything.
// We also listen to visualViewport and window resize as supplementary
// triggers in case the keyboard appears/disappears without a focus change.
const adjust = () => {
const vv = window.visualViewport;
const availableHeight = vv ? vv.height : window.innerHeight;
const keyboardHeight = Math.max(0, window.innerHeight - availableHeight);
if (keyboardHeight > 50) {
this.containerEl.style.height = `${availableHeight}px`;
this.containerEl.style.alignItems = 'flex-start';
this.containerEl.style.paddingTop = '8px';
this.modalEl.style.maxHeight = `${availableHeight - 16}px`;
} else {
this.containerEl.style.height = '';
this.containerEl.style.alignItems = '';
this.containerEl.style.paddingTop = '';
this.modalEl.style.maxHeight = '';
}
};
// ── Android keyboard avoidance ────────────────────────────────────────
// Obsidian Android (Capacitor) defaults to adjustNothing keyboard mode,
// meaning the keyboard is a pure overlay: neither window.innerHeight nor
// visualViewport.height changes. Capacitor fires its own keyboard events
// on window with an explicit keyboardHeight so we use those as the primary
// signal, with a visualViewport fallback for other environments.
this.keyboardHandler = adjust;
this.keyboardResetHandler = () => {
const applyLayout = (keyboardHeight: number) => {
const available = window.innerHeight - keyboardHeight;
this.containerEl.style.height = `${available}px`;
this.containerEl.style.alignItems = 'flex-start';
this.containerEl.style.paddingTop = '8px';
this.modalEl.style.maxHeight = `${available - 16}px`;
};
const resetLayout = () => {
this.containerEl.style.height = '';
this.containerEl.style.alignItems = '';
this.containerEl.style.paddingTop = '';
this.modalEl.style.maxHeight = '';
};
window.visualViewport?.addEventListener('resize', adjust);
window.addEventListener('resize', adjust);
// Capacitor events carry keyboardHeight directly.
const onCapacitorShow = (e: Event) => {
const kh: number = (e as any).keyboardHeight
?? (e as any).detail?.keyboardHeight
?? 0;
if (kh > 0) applyLayout(kh);
};
const onCapacitorHide = () => resetLayout();
// visualViewport fallback: works when adjustResize / adjustPan is active.
const onViewport = () => {
const vv = window.visualViewport;
if (!vv) return;
const kh = Math.max(0, window.innerHeight - vv.height - vv.offsetTop);
if (kh > 50) applyLayout(kh);
else resetLayout();
};
window.addEventListener('keyboardWillShow', onCapacitorShow);
window.addEventListener('keyboardDidShow', onCapacitorShow);
window.addEventListener('keyboardWillHide', onCapacitorHide);
window.addEventListener('keyboardDidHide', onCapacitorHide);
window.visualViewport?.addEventListener('resize', onViewport);
window.addEventListener('resize', onViewport);
this.keyboardCleanup = () => {
window.removeEventListener('keyboardWillShow', onCapacitorShow);
window.removeEventListener('keyboardDidShow', onCapacitorShow);
window.removeEventListener('keyboardWillHide', onCapacitorHide);
window.removeEventListener('keyboardDidHide', onCapacitorHide);
window.visualViewport?.removeEventListener('resize', onViewport);
window.removeEventListener('resize', onViewport);
resetLayout();
};
// Title
contentEl.createEl('h2', { text: 'Edit task' });
@ -97,8 +115,11 @@ export class TaskEditorModal extends Modal {
attr: { rows: '8', placeholder: 'Task title\n\nNotes…' },
});
this.textarea.value = initialValue;
this.textarea.addEventListener('focus', () => setTimeout(adjust, 300));
this.textarea.addEventListener('blur', () => setTimeout(adjust, 100));
// Timeout-based fallback: if none of the events above fired yet, read
// the viewport after the keyboard animation completes (~500 ms on most
// devices). blur resets in case the keyboard has closed.
this.textarea.addEventListener('focus', () => setTimeout(onViewport, 500));
this.textarea.addEventListener('blur', () => setTimeout(resetLayout, 100));
// Auto-focus
setTimeout(() => this.textarea?.focus(), 50);
@ -152,13 +173,8 @@ export class TaskEditorModal extends Modal {
}
onClose(): void {
if (this.keyboardHandler) {
window.visualViewport?.removeEventListener('resize', this.keyboardHandler);
window.removeEventListener('resize', this.keyboardHandler);
this.keyboardHandler = null;
}
this.keyboardResetHandler?.();
this.keyboardResetHandler = null;
this.keyboardCleanup?.();
this.keyboardCleanup = null;
this.contentEl.empty();
}