You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
14 KiB
14 KiB
📐 اطلاعات Layout و Responsive که باید از Flutter به WebView ارسال شود
🎯 خلاصه اجرایی
این سند تمام اطلاعاتی که Flutter باید به WebView ارسال کند تا صفحه بهدرستی نمایش داده شود را مستند میکند.
📊 ۱. Responsive Breakpoints
تعریف در Flutter:
// lib/app/application.dart (خط 123-126)
ResponsiveBreakpoints.builder(
breakpoints: const [
Breakpoint(start: 0, end: 800, name: MOBILE),
Breakpoint(start: 800, end: 1600, name: TABLET),
],
)
استفاده در کد:
// lib/core/util/extensions/context_extensions.dart
bool get isMobile => breakpoints.isMobile; // width < 800
bool get isTablet => breakpoints.isTablet; // width >= 800
bool get isDesktop => breakpoints.isDesktop; // width >= 1600 (غیرفعال)
double get screenWidth => breakpoints.screenWidth;
✅ باید به WebView ارسال شود:
{
"layout": {
"breakpoint": "mobile" | "tablet" | "desktop",
"screenWidth": 375,
"isMobile": true,
"isTablet": false
}
}
📱 تاثیر در UI:
| بخش | Mobile (< 800px) | Tablet (>= 800px) |
|---|---|---|
| Padding افقی | 16px |
64px |
| Search Bar | عرض کامل | عرض محدود با padding 64px |
| Chat List | Single column | Single column با padding 64px |
| TabBar | Centered، غیر اسکرول | Left-aligned، scrollable |
| Calendar Grid | width / 120 columns |
width / 120 columns |
| Chat TextField | padding: 16px | padding: 64px horizontal |
📏 ۲. Safe Area & System Insets
تعریف در Flutter:
// lib/core/util/extensions/context_extensions.dart
MediaQueryData get mq => MediaQuery.of(context);
EdgeInsets get insets => mq.viewInsets; // Keyboard, etc
EdgeInsets get padding => mq.viewPadding; // Status bar, notch, home indicator
double get navigationBarHeight => max(insets.bottom, padding.bottom);
✅ باید به WebView ارسال شود:
{
"safeArea": {
"top": 44, // Status bar / notch
"bottom": 34, // Home indicator (iPhone X+)
"left": 0,
"right": 0
},
"viewInsets": {
"top": 0,
"bottom": 336, // Keyboard height (تغییر میکند)
"left": 0,
"right": 0
},
"navigationBarHeight": 34 // max(viewInsets.bottom, safeArea.bottom)
}
📱 تاثیر در UI:
// مثالهای استفاده در کد:
// 1. Chat Page - Floating Action Button
bottom: 16 + context.padding.bottom, // +34px on iPhone X
right: context.isMobile ? 16 : 64,
// 2. Chat TextField - Bottom padding
EdgeInsets.only(
bottom: 16 + max(insets.bottom, padding.bottom),
)
// 3. Reviews Tab - List padding
padding.addMore(bottom: context.padding.bottom),
// 4. Call Screen - Bottom spacing
SizedBox(height: 16 + context.padding.bottom),
// 5. Become Provider Page - Top padding
SizedBox(height: context.padding.top + 8),
🎨 پیادهسازی در React:
// استفاده از CSS env() برای safe-area
<div
style={{
paddingTop: `max(16px, env(safe-area-inset-top))`,
paddingBottom: `max(16px, env(safe-area-inset-bottom))`,
paddingLeft: `env(safe-area-inset-left)`,
paddingRight: `env(safe-area-inset-right)`,
}}
>
اما بهتر است از Flutter دریافت شود چون:
- Flutter دسترسی دقیقتر به system metrics دارد
env()در همه WebViewها پشتیبانی نمیشود- keyboard height در CSS قابل دسترسی نیست
🔄 ۳. تغییرات Real-time (باید لیسن شوند)
۱. Keyboard Visibility
// زمانی که کیبورد باز/بسته میشود:
MediaQuery.of(context).viewInsets.bottom
Event به WebView:
{
"type": "KEYBOARD_CHANGED",
"payload": {
"visible": true,
"height": 336,
"animationDuration": 250
}
}
استفاده در React:
useEffect(() => {
window.addEventListener('message', (event) => {
if (event.data.type === 'KEYBOARD_CHANGED') {
const { height } = event.data.payload;
// تنظیم bottom padding چت
setChatBottomPadding(height + 16);
}
});
}, []);
۲. Screen Rotation / Size Change
// تغییر orientation یا resize
MediaQuery.of(context).size
Event به WebView:
{
"type": "SCREEN_SIZE_CHANGED",
"payload": {
"width": 812,
"height": 375,
"orientation": "landscape",
"breakpoint": "mobile"
}
}
۳. Safe Area Changes
// تغییر وقتی که status bar مخفی/نمایش داده میشود
MediaQuery.of(context).viewPadding
Event به WebView:
{
"type": "SAFE_AREA_CHANGED",
"payload": {
"top": 44,
"bottom": 34,
"left": 0,
"right": 0
}
}
🖥️ ۴. Design Sizes (Figma)
// lib/core/util/design_size.dart
static const Size mobileSize = Size(375, 814);
static const Size tabletSize = Size(834, 1194);
✅ باید به WebView ارسال شود:
{
"designSize": {
"mobile": { "width": 375, "height": 814 },
"tablet": { "width": 834, "height": 1194 }
}
}
استفاده در React:
- برای محاسبه scaling factor
- Responsive font sizes
- Component sizing
const scaleFactor = window.innerWidth / (isMobile ? 375 : 834);
const fontSize = 16 * scaleFactor;
🎯 ۵. Platform & Environment Info
// اطلاعات پلتفرم
Platform.isIOS
Platform.isAndroid
✅ باید به WebView ارسال شود:
{
"platform": {
"os": "ios" | "android" | "web",
"version": "17.2",
"model": "iPhone 14 Pro",
"isPhysicalDevice": true,
"hasNotch": true,
"hasDynamicIsland": true
}
}
📱 تاثیر در UI:
- iOS: Cupertino-style transitions
- Android: Material ripples
- Safe area handling مختلف
- Font rendering
📦 ۶. JSON کامل Initial Config
این JSON باید یکبار هنگام load WebView ارسال شود:
{
"version": "1.0.0",
"timestamp": 1234567890,
"layout": {
"breakpoint": "mobile",
"screenWidth": 375,
"screenHeight": 812,
"isMobile": true,
"isTablet": false,
"isDesktop": false
},
"safeArea": {
"top": 44,
"bottom": 34,
"left": 0,
"right": 0
},
"viewInsets": {
"top": 0,
"bottom": 0,
"left": 0,
"right": 0
},
"designSize": {
"mobile": { "width": 375, "height": 814 },
"tablet": { "width": 834, "height": 1194 }
},
"platform": {
"os": "ios",
"version": "17.2",
"model": "iPhone 14 Pro",
"isPhysicalDevice": true,
"hasNotch": true,
"hasDynamicIsland": true
},
"locale": {
"languageCode": "en",
"countryCode": "US",
"isRtl": false
},
"theme": {
"mode": "light",
"primaryColor": "#FFB236"
}
}
🔄 ۷. Events که باید به WebView ارسال شوند
۱. Layout Changed
{
"type": "LAYOUT_CHANGED",
"payload": {
"breakpoint": "tablet",
"screenWidth": 820,
"isMobile": false
}
}
۲. Keyboard Changed
{
"type": "KEYBOARD_CHANGED",
"payload": {
"visible": true,
"height": 336,
"animationDuration": 250
}
}
۳. Safe Area Changed
{
"type": "SAFE_AREA_CHANGED",
"payload": {
"top": 0,
"bottom": 0,
"left": 0,
"right": 0
}
}
۴. Orientation Changed
{
"type": "ORIENTATION_CHANGED",
"payload": {
"orientation": "landscape",
"screenWidth": 812,
"screenHeight": 375
}
}
💻 کد Flutter برای ارسال اطلاعات به WebView
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class TalkWebViewBridge {
final WebViewController controller;
BuildContext context;
TalkWebViewBridge(this.controller, this.context) {
_sendInitialConfig();
_listenToChanges();
}
// ارسال اولیه config
void _sendInitialConfig() {
final config = jsonEncode({
'version': '1.0.0',
'timestamp': DateTime.now().millisecondsSinceEpoch,
'layout': {
'breakpoint': _getBreakpoint(),
'screenWidth': MediaQuery.of(context).size.width,
'screenHeight': MediaQuery.of(context).size.height,
'isMobile': context.isMobile,
'isTablet': context.isTablet,
'isDesktop': context.isDesktop,
},
'safeArea': {
'top': MediaQuery.of(context).padding.top,
'bottom': MediaQuery.of(context).padding.bottom,
'left': MediaQuery.of(context).padding.left,
'right': MediaQuery.of(context).padding.right,
},
'viewInsets': {
'top': MediaQuery.of(context).viewInsets.top,
'bottom': MediaQuery.of(context).viewInsets.bottom,
'left': MediaQuery.of(context).viewInsets.left,
'right': MediaQuery.of(context).viewInsets.right,
},
'designSize': {
'mobile': {'width': 375, 'height': 814},
'tablet': {'width': 834, 'height': 1194},
},
'platform': {
'os': Platform.operatingSystem,
'version': Platform.operatingSystemVersion,
},
'locale': {
'languageCode': context.languageCode,
'isRtl': context.isRtl,
},
});
controller.runJavaScript('''
window.dispatchEvent(new CustomEvent('flutterConfig', {
detail: $config
}));
''');
}
// لیسن به تغییرات keyboard
void _listenToChanges() {
// در هر frame چک میکنیم که آیا viewInsets تغییر کرده
WidgetsBinding.instance.addPostFrameCallback((_) {
final currentInsets = MediaQuery.of(context).viewInsets;
if (_lastInsets?.bottom != currentInsets.bottom) {
_sendKeyboardChanged(currentInsets.bottom);
_lastInsets = currentInsets;
}
_listenToChanges(); // تکرار
});
}
EdgeInsets? _lastInsets;
void _sendKeyboardChanged(double height) {
final event = jsonEncode({
'type': 'KEYBOARD_CHANGED',
'payload': {
'visible': height > 0,
'height': height,
'animationDuration': 250,
},
});
controller.runJavaScript('''
window.dispatchEvent(new CustomEvent('flutterEvent', {
detail: $event
}));
''');
}
String _getBreakpoint() {
if (context.isMobile) return 'mobile';
if (context.isTablet) return 'tablet';
return 'desktop';
}
}
💻 کد React برای دریافت اطلاعات
// hooks/useFlutterLayout.ts
import { useEffect, useState } from 'react';
interface FlutterLayout {
breakpoint: 'mobile' | 'tablet' | 'desktop';
screenWidth: number;
screenHeight: number;
isMobile: boolean;
isTablet: boolean;
safeArea: {
top: number;
bottom: number;
left: number;
right: number;
};
viewInsets: {
bottom: number;
};
}
export function useFlutterLayout() {
const [layout, setLayout] = useState<FlutterLayout>({
breakpoint: 'mobile',
screenWidth: 375,
screenHeight: 812,
isMobile: true,
isTablet: false,
safeArea: { top: 0, bottom: 0, left: 0, right: 0 },
viewInsets: { bottom: 0 },
});
useEffect(() => {
// دریافت config اولیه
const handleConfig = (event: CustomEvent) => {
const config = event.detail;
setLayout({
breakpoint: config.layout.breakpoint,
screenWidth: config.layout.screenWidth,
screenHeight: config.layout.screenHeight,
isMobile: config.layout.isMobile,
isTablet: config.layout.isTablet,
safeArea: config.safeArea,
viewInsets: config.viewInsets,
});
};
// دریافت events
const handleEvent = (event: CustomEvent) => {
const { type, payload } = event.detail;
if (type === 'KEYBOARD_CHANGED') {
setLayout(prev => ({
...prev,
viewInsets: {
...prev.viewInsets,
bottom: payload.height,
},
}));
}
if (type === 'SAFE_AREA_CHANGED') {
setLayout(prev => ({
...prev,
safeArea: payload,
}));
}
};
window.addEventListener('flutterConfig', handleConfig as EventListener);
window.addEventListener('flutterEvent', handleEvent as EventListener);
return () => {
window.removeEventListener('flutterConfig', handleConfig as EventListener);
window.removeEventListener('flutterEvent', handleEvent as EventListener);
};
}, []);
return layout;
}
استفاده در Component:
function ChatPage() {
const layout = useFlutterLayout();
return (
<div
style={{
paddingTop: layout.safeArea.top,
paddingBottom: Math.max(16, layout.safeArea.bottom, layout.viewInsets.bottom),
paddingLeft: layout.isMobile ? 16 : 64,
paddingRight: layout.isMobile ? 16 : 64,
}}
>
{/* محتوای چت */}
</div>
);
}
📋 Checklist پیادهسازی
Flutter Side:
- ایجاد
TalkWebViewBridgeclass - ارسال initial config هنگام load WebView
- لیسن به
MediaQuery.of(context).viewInsetsبرای keyboard - لیسن به
MediaQuery.of(context).sizeبرای rotation - ارسال events به WebView از طریق
runJavaScript
React Side:
- ایجاد
useFlutterLayouthook - لیسن به
flutterConfigevent - لیسن به
flutterEventevent - استفاده از layout info در components
- تست در موبایل/تبلت
- تست با keyboard باز/بسته
- تست در landscape/portrait
🎯 خلاصه
اطلاعاتی که یکبار ارسال میشود:
- ✅ Breakpoint (mobile/tablet)
- ✅ Screen size
- ✅ Safe area
- ✅ Design sizes
- ✅ Platform info
- ✅ Locale/direction
اطلاعاتی که real-time ارسال میشود:
- 🔄 Keyboard height (viewInsets.bottom)
- 🔄 Safe area changes
- 🔄 Screen size changes (rotation)
- 🔄 Breakpoint changes
تاثیر اصلی در UI:
- Horizontal padding: mobile=16px, tablet=64px
- Bottom padding: safeArea.bottom + viewInsets.bottom
- Chat TextField: adjust for keyboard
- Floating buttons: offset by safe area