diff --git a/src/app/globals.css b/src/app/globals.css
index 61a1716..042edb2 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -2,6 +2,10 @@
:root {
--default-page-background-image: url("/assets/images/home-Checkups-List.svg");
+ --safe-top: 0px;
+ --safe-bottom: 0px;
+ --safe-left: 0px;
+ --safe-right: 0px;
}
@theme inline {
@@ -50,6 +54,8 @@ html:lang(ar) body,
width: 100%;
min-height: 100vh;
padding-inline: 17px;
+ padding-top: var(--safe-top);
+ padding-bottom: var(--safe-bottom);
box-sizing: border-box;
background-color: var(--background);
background-image: var(--default-page-background-image);
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 278017b..9ebb39a 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -143,6 +143,15 @@ export default function RootLayout({
`,
}}
/>
+
diff --git a/src/app/providers.tsx b/src/app/providers.tsx
index 02d8174..0220031 100644
--- a/src/app/providers.tsx
+++ b/src/app/providers.tsx
@@ -2,6 +2,7 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { type ReactNode, useState } from "react";
+import { ViewPaddingsProvider } from "@/components/utils/view-paddings-provider";
type ProvidersProps = {
children: ReactNode;
@@ -23,6 +24,9 @@ export default function Providers({ children }: ProvidersProps) {
);
return (
- {children}
+
+
+ {children}
+
);
}
diff --git a/src/components/ui/sticky-header.tsx b/src/components/ui/sticky-header.tsx
index d26d29e..c8d529d 100644
--- a/src/components/ui/sticky-header.tsx
+++ b/src/components/ui/sticky-header.tsx
@@ -1,4 +1,7 @@
+"use client";
+
import type { ReactNode } from "react";
+import { useViewPaddings } from "@/hooks/use-view-paddings";
type StickyHeaderProps = {
children: ReactNode;
@@ -9,10 +12,13 @@ export default function StickyHeader({
children,
className,
}: StickyHeaderProps) {
+ const { top } = useViewPaddings();
+
return (
+ {children}
+
+ );
+}
diff --git a/src/components/utils/view-paddings-provider.tsx b/src/components/utils/view-paddings-provider.tsx
new file mode 100644
index 0000000..9f5a98f
--- /dev/null
+++ b/src/components/utils/view-paddings-provider.tsx
@@ -0,0 +1,12 @@
+"use client";
+
+import { useEffect } from "react";
+import { viewPaddingsBridge } from "@/lib/view-paddings";
+
+export function ViewPaddingsProvider() {
+ useEffect(() => {
+ viewPaddingsBridge;
+ }, []);
+
+ return null;
+}
diff --git a/src/hooks/use-view-paddings.ts b/src/hooks/use-view-paddings.ts
new file mode 100644
index 0000000..c14167e
--- /dev/null
+++ b/src/hooks/use-view-paddings.ts
@@ -0,0 +1,14 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { viewPaddingsBridge } from "@/lib/view-paddings";
+
+export function useViewPaddings() {
+ const [paddings, setPaddings] = useState(() => viewPaddingsBridge.getPaddings());
+
+ useEffect(() => {
+ return viewPaddingsBridge.subscribe(setPaddings);
+ }, []);
+
+ return paddings;
+}
diff --git a/src/lib/view-paddings.ts b/src/lib/view-paddings.ts
new file mode 100644
index 0000000..e388928
--- /dev/null
+++ b/src/lib/view-paddings.ts
@@ -0,0 +1,92 @@
+interface ViewPaddings {
+ top: number;
+ bottom: number;
+ left: number;
+ right: number;
+}
+
+class ViewPaddingsBridge {
+ private paddings: ViewPaddings = { top: 0, bottom: 0, left: 0, right: 0 };
+ private listeners: Array<(paddings: ViewPaddings) => void> = [];
+ private flutterUnsubscribe?: () => void;
+
+ constructor() {
+ this.init();
+ }
+
+ private init() {
+ if (typeof window === "undefined") return;
+
+ this.setupFlutterListener();
+ this.requestPaddings();
+ }
+
+ private setupFlutterListener() {
+ const win = window as Window & {
+ addFlutterResponseListener?: (
+ listener: (event: any) => void,
+ ) => () => void;
+ };
+
+ const attach = () => {
+ if (typeof win.addFlutterResponseListener !== "function") return false;
+
+ this.flutterUnsubscribe = win.addFlutterResponseListener((event) => {
+ if (event.action === "get_view_paddings" && event.success && event.data) {
+ this.paddings = {
+ top: event.data.top || 0,
+ bottom: event.data.bottom || 0,
+ left: event.data.left || 0,
+ right: event.data.right || 0,
+ };
+ this.applyPaddings();
+ this.notifyListeners();
+ }
+ });
+
+ return true;
+ };
+
+ if (!attach()) {
+ const interval = setInterval(() => {
+ if (attach()) clearInterval(interval);
+ }, 50);
+ }
+ }
+
+ private requestPaddings() {
+ if (typeof window === "undefined") return;
+
+ const app = (window as any).HabibApp;
+ if (app?.postMessage) {
+ app.postMessage(JSON.stringify({ action: "get_view_paddings" }));
+ }
+ }
+
+ private applyPaddings() {
+ if (typeof document === "undefined") return;
+
+ document.documentElement.style.setProperty("--safe-top", `${this.paddings.top}px`);
+ document.documentElement.style.setProperty("--safe-bottom", `${this.paddings.bottom}px`);
+ document.documentElement.style.setProperty("--safe-left", `${this.paddings.left}px`);
+ document.documentElement.style.setProperty("--safe-right", `${this.paddings.right}px`);
+ }
+
+ private notifyListeners() {
+ this.listeners.forEach((listener) => listener(this.paddings));
+ }
+
+ public getPaddings(): ViewPaddings {
+ return { ...this.paddings };
+ }
+
+ public subscribe(listener: (paddings: ViewPaddings) => void): () => void {
+ this.listeners.push(listener);
+ return () => {
+ const index = this.listeners.indexOf(listener);
+ if (index >= 0) this.listeners.splice(index, 1);
+ };
+ }
+}
+
+export const viewPaddingsBridge = new ViewPaddingsBridge();
diff --git a/src/types/window.d.ts b/src/types/window.d.ts
index 27f73c6..355ab32 100644
--- a/src/types/window.d.ts
+++ b/src/types/window.d.ts
@@ -2,7 +2,14 @@ declare global {
interface FlutterResponseEvent {
action: string;
success: boolean;
- data?: { latitude: number; longitude: number };
+ data?: {
+ latitude?: number;
+ longitude?: number;
+ top?: number;
+ bottom?: number;
+ left?: number;
+ right?: number;
+ };
}
interface Window {
diff --git a/src/view-paddings.ts b/src/view-paddings.ts
new file mode 100644
index 0000000..6017712
--- /dev/null
+++ b/src/view-paddings.ts
@@ -0,0 +1,3 @@
+export { useViewPaddings } from "./hooks/use-view-paddings";
+export { viewPaddingsBridge } from "./lib/view-paddings";
+export { FixedBottom } from "./components/utils/fixed-bottom";