Browse Source

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

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

73
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 { 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 listeners: Array<(paddings: ViewPaddings) => void> = [];
private configListeners: Array<(config: InitialConfig) => void> = []; private configListeners: Array<(config: InitialConfig) => void> = [];
private flutterUnsubscribe?: () => void; private flutterUnsubscribe?: () => void;
private hasInitialConfig = false;
constructor() { constructor() {
this.init(); this.init();
@ -108,14 +115,34 @@ class ViewPaddingsBridge {
this.flutterUnsubscribe = win.addFlutterResponseListener((event) => { this.flutterUnsubscribe = win.addFlutterResponseListener((event) => {
if (!event || event.success === false || !event.data) return; if (!event || event.success === false || !event.data) return;
if (event.action === "initial_config") {
this.applyInitialConfig(event.data);
return;
}
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 (px فیزیکی، فقط لبه‌ها).
// اگر initial_config رسیده باشد، آن مرجع است و این نادیده گرفته می‌شود.
case "get_view_paddings":
if (this.hasInitialConfig) return;
this.applyEdges(toLogical(readEdges(event.data)));
return;
// سازگاری عقب‌رو: پاسخ ساده‌ی get_view_paddings که فقط لبه‌ها را دارد
if (event.action === "get_view_paddings") {
this.applyEdges(readEdges(event.data));
default:
return;
} }
}); });
@ -144,13 +171,16 @@ class ViewPaddingsBridge {
private applyInitialConfig(data: any) { private applyInitialConfig(data: any) {
const viewInsets = readEdges(data?.viewInsets); const viewInsets = readEdges(data?.viewInsets);
const safeArea = readEdges(data?.safeArea); const safeArea = readEdges(data?.safeArea);
const paddings = mergeEdges(viewInsets, safeArea);
// فیلد safeArea از فلاتر در px منطقی است و واحد درست برای CSS؛ همین مبنا
// برای --safe-* است. (فیلد viewInsets در این payload در px فیزیکی و عملاً
// تکرار همان viewPadding است، پس در محاسبه‌ی فضای امن استفاده نمی‌شود.)
const paddings = safeArea;
this.config = { this.config = {
paddings, paddings,
viewInsets, viewInsets,
safeArea, safeArea,
keyboardHeight: viewInsets.bottom,
keyboardHeight: 0,
layout: data?.layout layout: data?.layout
? { ? {
breakpoint: String(data.layout.breakpoint ?? ""), breakpoint: String(data.layout.breakpoint ?? ""),
@ -176,7 +206,14 @@ class ViewPaddingsBridge {
}; };
this.applyEdges(paddings); 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(); this.notifyConfigListeners();
} }

7
src/types/window.d.ts

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

Loading…
Cancel
Save