Browse Source

feat(webview): support Flutter initial_config handshake and dynamic safe area insets

master
mortezaei 2 weeks ago
parent
commit
52c597a0d2
  1. 67
      src/lib/view-paddings.ts
  2. 7
      src/types/window.d.ts

67
src/lib/view-paddings.ts

@ -56,16 +56,22 @@ function readEdges(source: EdgeSource): ViewPaddings {
}
/**
* فضای امن هر ضلع = بیشینهی viewInsets و safeArea همان ضلع.
* منطبق بر منطق مستند: navigationBarHeight = max(insets, padding).
* هیچ عدد ثابتی استفاده نمیشود؛ همهچیز از دیتای دریافتی محاسبه میشود.
* مقادیر safe-area در `initial_config` (فیلد safeArea) از فلاتر در واحد منطقی
* (CSS px / dp) میآیند و مستقیماً برای CSS درستاند. اما مسیر قدیمی
* `get_view_paddings` مقادیر را در پیکسل فیزیکی (× devicePixelRatio) میفرستد؛
* این تابع آنها را به px منطقی برمیگرداند تا با CSS همخوان شوند.
*/
function mergeEdges(insets: ViewPaddings, safe: ViewPaddings): ViewPaddings {
function toLogical(edges: ViewPaddings): ViewPaddings {
const ratio =
typeof window !== "undefined" && window.devicePixelRatio > 0
? window.devicePixelRatio
: 1;
return {
top: Math.max(insets.top, safe.top),
bottom: Math.max(insets.bottom, safe.bottom),
left: Math.max(insets.left, safe.left),
right: Math.max(insets.right, safe.right),
top: edges.top / ratio,
bottom: edges.bottom / ratio,
left: edges.left / ratio,
right: edges.right / ratio,
};
}
@ -83,6 +89,7 @@ class ViewPaddingsBridge {
private listeners: Array<(paddings: ViewPaddings) => void> = [];
private configListeners: Array<(config: InitialConfig) => void> = [];
private flutterUnsubscribe?: () => void;
private hasInitialConfig = false;
constructor() {
this.init();
@ -108,14 +115,34 @@ class ViewPaddingsBridge {
this.flutterUnsubscribe = win.addFlutterResponseListener((event) => {
if (!event || event.success === false || !event.data) return;
if (event.action === "initial_config") {
switch (event.action) {
case "initial_config":
this.applyInitialConfig(event.data);
this.hasInitialConfig = true;
return;
// به‌روزرسانی فضای امن هنگام چرخش/تغییر notch (px منطقی).
case "safe_area_changed":
this.applySafeArea(readEdges(event.data));
return;
// ارتفاع کیبورد به‌صورت زنده (px منطقی).
case "keyboard_changed": {
const height = num(event.data.height);
this.config.keyboardHeight = height;
this.applyKeyboard(height);
return;
}
// سازگاری عقب‌رو: پاسخ ساده‌ی get_view_paddings که فقط لبه‌ها را دارد
if (event.action === "get_view_paddings") {
this.applyEdges(readEdges(event.data));
// سازگاری عقب‌رو: پاسخ ساده‌ی get_view_paddings (px فیزیکی، فقط لبه‌ها).
// اگر initial_config رسیده باشد، آن مرجع است و این نادیده گرفته می‌شود.
case "get_view_paddings":
if (this.hasInitialConfig) return;
this.applyEdges(toLogical(readEdges(event.data)));
return;
default:
return;
}
});
@ -144,13 +171,16 @@ class ViewPaddingsBridge {
private applyInitialConfig(data: any) {
const viewInsets = readEdges(data?.viewInsets);
const safeArea = readEdges(data?.safeArea);
const paddings = mergeEdges(viewInsets, safeArea);
// فیلد safeArea از فلاتر در px منطقی است و واحد درست برای CSS؛ همین مبنا
// برای --safe-* است. (فیلد viewInsets در این payload در px فیزیکی و عملاً
// تکرار همان viewPadding است، پس در محاسبه‌ی فضای امن استفاده نمی‌شود.)
const paddings = safeArea;
this.config = {
paddings,
viewInsets,
safeArea,
keyboardHeight: viewInsets.bottom,
keyboardHeight: 0,
layout: data?.layout
? {
breakpoint: String(data.layout.breakpoint ?? ""),
@ -176,7 +206,14 @@ class ViewPaddingsBridge {
};
this.applyEdges(paddings);
this.applyKeyboard(viewInsets.bottom);
this.applyKeyboard(0);
this.notifyConfigListeners();
}
private applySafeArea(safeArea: ViewPaddings) {
this.config.safeArea = safeArea;
this.config.paddings = safeArea;
this.applyEdges(safeArea);
this.notifyConfigListeners();
}

7
src/types/window.d.ts

@ -13,11 +13,16 @@ declare global {
// get_location
latitude?: number;
longitude?: number;
// get_view_paddings (flat edges)
// get_view_paddings / safe_area_changed (flat edges)
top?: number;
bottom?: number;
left?: number;
right?: number;
// keyboard_changed
height?: number;
visible?: boolean;
// orientation_changed
orientation?: string;
message?: string;
timestamp?: number;
// initial_config

Loading…
Cancel
Save