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

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