fix(mobile): incorrect height of keyboard toolbar in ios (#8653)

### What Changes:
- Fix keyboard height calculation with the `KeyboardToolBarConfig.useScreenHeight = true`. More detials in: https://github.com/toeverything/blocksuite/pull/8645
- Add safe bottom padding for shrinked keyboard toolbar
- Add `VirtualKeyboard` polyfill. Close [AF-1573](https://linear.app/affine-design/issue/AF-1573/virtualkeyboard-polyfill-with-capacitor)
  - Since the `@capacitor/keyboard` dose not implement the `Keyboard.show()` method, this polyfill is not enabled now.
This commit is contained in:
L-Sun 2024-11-01 05:48:30 +00:00
parent 10b1f233d9
commit 1973cea035
No known key found for this signature in database
GPG Key ID: D5C252102D2B576F
9 changed files with 198 additions and 5 deletions

View File

@ -13,6 +13,7 @@ def capacitor_pods
pod 'CapacitorCordova', :path => '../../../../../node_modules/@capacitor/ios'
pod 'CapacitorApp', :path => '../../../../../node_modules/@capacitor/app'
pod 'CapacitorBrowser', :path => '../../../../../node_modules/@capacitor/browser'
pod 'CapacitorKeyboard', :path => '../../../../../node_modules/@capacitor/keyboard'
end
target 'App' do

View File

@ -6,6 +6,8 @@ PODS:
- CapacitorBrowser (6.0.3):
- Capacitor
- CapacitorCordova (6.1.2)
- CapacitorKeyboard (6.0.2):
- Capacitor
- CryptoSwift (1.8.3)
DEPENDENCIES:
@ -13,6 +15,7 @@ DEPENDENCIES:
- "CapacitorApp (from `../../../../../node_modules/@capacitor/app`)"
- "CapacitorBrowser (from `../../../../../node_modules/@capacitor/browser`)"
- "CapacitorCordova (from `../../../../../node_modules/@capacitor/ios`)"
- "CapacitorKeyboard (from `../../../../../node_modules/@capacitor/keyboard`)"
- CryptoSwift (~> 1.8.3)
SPEC REPOS:
@ -28,14 +31,17 @@ EXTERNAL SOURCES:
:path: "../../../../../node_modules/@capacitor/browser"
CapacitorCordova:
:path: "../../../../../node_modules/@capacitor/ios"
CapacitorKeyboard:
:path: "../../../../../node_modules/@capacitor/keyboard"
SPEC CHECKSUMS:
Capacitor: 679f9673fdf30597493a6362a5d5bf233d46abc2
CapacitorApp: 0bc633b4eae40a1f32cd2834788fad3bc42da6a1
CapacitorBrowser: aab1ed943b01c0365c4810538a8b3477e2d9f72e
CapacitorCordova: f48c89f96c319101cd2f0ce8a2b7449b5fb8b3dd
CapacitorKeyboard: 2700f9b18687be021e28b5a09b59eb151a46d5e0
CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483
PODFILE CHECKSUM: 763e3dac392c17bcf42dab97a9225ea234e8416a
PODFILE CHECKSUM: 1b0d3fe81862c0e9ce712ddd0c5a0accd0097698
COCOAPODS: 1.15.2
COCOAPODS: 1.16.1

View File

@ -1,4 +1,5 @@
import type { CapacitorConfig } from '@capacitor/cli';
import { KeyboardResize } from '@capacitor/keyboard';
const config: CapacitorConfig = {
appId: 'app.affine.pro',
@ -17,6 +18,9 @@ const config: CapacitorConfig = {
CapacitorHttp: {
enabled: true,
},
Keyboard: {
resize: KeyboardResize.Native,
},
},
};

View File

@ -19,6 +19,7 @@
"@capacitor/browser": "^6.0.3",
"@capacitor/core": "^6.1.2",
"@capacitor/ios": "^6.1.2",
"@capacitor/keyboard": "^6.0.2",
"@sentry/react": "^8.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@ -1,3 +1,6 @@
import '@affine/core/bootstrap/browser';
import '@affine/component/theme';
import '@affine/core/mobile/styles/mobile.css';
// TODO(@L-Sun) Uncomment this when the `show` method implement by `@capacitor/keyboard` in ios
// import './virtual-keyboard';

View File

@ -0,0 +1,152 @@
import type { PluginListenerHandle } from '@capacitor/core/types/definitions';
import { Keyboard } from '@capacitor/keyboard';
type VirtualKeyboardCallback =
| (<K extends keyof VirtualKeyboardEventMap>(
this: VirtualKeyboard,
ev: VirtualKeyboardEventMap[K]
) => any)
| EventListenerOrEventListenerObject;
class NavigatorVirtualKeyboard implements VirtualKeyboard {
private readonly _boundingRect = new DOMRect();
private readonly _overlaysContent = false;
private _listeners = new Map<
string,
Set<{
cb: VirtualKeyboardCallback;
options?: boolean | AddEventListenerOptions;
}>
>();
private _capacitorListenerHandles: PluginListenerHandle[] = [];
private async _bindListener() {
const updateBoundingRect = (info?: { keyboardHeight: number }) => {
this.boundingRect.x = 0;
this.boundingRect.y = info ? window.innerHeight - info.keyboardHeight : 0;
this.boundingRect.width = window.innerWidth;
this.boundingRect.height = info ? info.keyboardHeight : 0;
this.dispatchEvent(new Event('geometrychange'));
};
this._capacitorListenerHandles = [
await Keyboard.addListener('keyboardDidShow', updateBoundingRect),
await Keyboard.addListener('keyboardDidHide', updateBoundingRect),
];
}
dispatchEvent = (event: Event) => {
const listeners = this._listeners.get(event.type);
if (listeners) {
for (const l of listeners) {
if (typeof l.cb === 'function') {
l.cb.call(this, event);
} else {
l.cb.handleEvent(event);
}
}
}
return !(event.cancelable && event.defaultPrevented);
};
constructor() {
this._bindListener();
}
destroy() {
this._capacitorListenerHandles.forEach(handle => handle.remove());
}
get boundingRect(): DOMRect {
return this._boundingRect;
}
get overlaysContent(): boolean {
return this._overlaysContent;
}
set overlaysContent(_: boolean) {
console.warn(
'overlaysContent is read-only in polyfill based on @capacitor/keyboard'
);
}
hide() {
Keyboard.hide();
}
show() {
Keyboard.show();
}
ongeometrychange: ((this: VirtualKeyboard, ev: Event) => any) | null = null;
addEventListener<K extends keyof VirtualKeyboardEventMap>(
type: K,
listener: VirtualKeyboardCallback,
options?: boolean | AddEventListenerOptions
) {
if (!this._listeners.has(type)) {
this._listeners.set(type, new Set());
}
const listeners = this._listeners.get(type);
if (!listeners) return;
listeners.add({ cb: listener, options });
}
removeEventListener<K extends keyof VirtualKeyboardEventMap>(
type: K,
listener: VirtualKeyboardCallback,
options?: boolean | EventListenerOptions
) {
const listeners = this._listeners.get(type);
if (!listeners) return;
const sameCapture = (
a?: boolean | AddEventListenerOptions,
b?: boolean | EventListenerOptions
) => {
if (a === undefined && b === undefined) {
return true;
}
if (typeof a === 'boolean' && typeof b === 'boolean') {
return a === b;
}
if (typeof a === 'object' && typeof b === 'object') {
return a.capture === b.capture;
}
if (typeof a === 'object' && typeof b === 'boolean') {
return a.capture === b;
}
if (typeof a === 'boolean' && typeof b === 'object') {
return a === b.capture;
}
return false;
};
let target = null;
for (const l of listeners) {
if (l.cb === listener && sameCapture(l.options, options)) {
target = l;
break;
}
}
if (target) {
listeners.delete(target);
}
}
}
// @ts-expect-error polyfill
navigator.virtualKeyboard = new NavigatorVirtualKeyboard();

View File

@ -10,6 +10,11 @@ import {
LifeCycleWatcher,
StdIdentifier,
} from '@blocksuite/affine/block-std';
import type {
RootBlockConfig,
TelemetryEventMap,
ThemeExtension,
} from '@blocksuite/affine/blocks';
import {
ColorScheme,
EdgelessBuiltInManager,
@ -18,9 +23,7 @@ import {
EditorSettingExtension,
FontLoaderService,
PageRootBlockSpec,
type TelemetryEventMap,
TelemetryProvider,
type ThemeExtension,
ThemeExtensionIdentifier,
} from '@blocksuite/affine/blocks';
import {
@ -39,6 +42,7 @@ import { combineLatest, map } from 'rxjs';
import { getFontConfigExtension } from '../font-extension';
import { createDatabaseOptionsConfig } from './database-block';
import { createKeyboardToolbarConfig } from './widgets/keyboard-toolbar';
import { createLinkedWidgetConfig } from './widgets/linked';
import { createToolbarMoreMenuConfig } from './widgets/toolbar';
@ -144,7 +148,8 @@ function getEditorConfigExtension(
linkedWidget: createLinkedWidgetConfig(framework),
toolbarMoreMenu: createToolbarMoreMenuConfig(framework),
databaseOptions: createDatabaseOptionsConfig(framework),
}),
keyboardToolbar: createKeyboardToolbarConfig(),
} satisfies RootBlockConfig),
];
}

View File

@ -0,0 +1,11 @@
import { globalVars } from '@affine/core/mobile/styles/mobile.css';
import { type KeyboardToolbarConfig } from '@blocksuite/affine/blocks';
export function createKeyboardToolbarConfig(): Partial<KeyboardToolbarConfig> {
return {
// TODO(@L-Sun): check android following the PR
// https://github.com/toeverything/blocksuite/pull/8645
useScreenHeight: BUILD_CONFIG.isIOS,
safeBottomPadding: BUILD_CONFIG.isIOS ? globalVars.appTabHeight : '0px',
};
}

View File

@ -606,6 +606,7 @@ __metadata:
"@capacitor/cli": "npm:^6.1.2"
"@capacitor/core": "npm:^6.1.2"
"@capacitor/ios": "npm:^6.1.2"
"@capacitor/keyboard": "npm:^6.0.2"
"@sentry/react": "npm:^8.0.0"
"@types/react": "npm:^18.2.75"
"@types/react-dom": "npm:^18.2.24"
@ -2991,6 +2992,15 @@ __metadata:
languageName: node
linkType: hard
"@capacitor/keyboard@npm:^6.0.2":
version: 6.0.2
resolution: "@capacitor/keyboard@npm:6.0.2"
peerDependencies:
"@capacitor/core": ^6.0.0
checksum: 10/cef4cce5c703fd67ceed3f04736a6513e05423006a5b4571d88d07a5849b79fc9f1ec98759606b7498ec1b604e5ee7987e5e30e61e9835538a29238ffbe3b036
languageName: node
linkType: hard
"@chromatic-com/storybook@npm:^3.0.0":
version: 3.0.0
resolution: "@chromatic-com/storybook@npm:3.0.0"