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

📐 اطلاعات 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:

  • ایجاد TalkWebViewBridge class
  • ارسال initial config هنگام load WebView
  • لیسن به MediaQuery.of(context).viewInsets برای keyboard
  • لیسن به MediaQuery.of(context).size برای rotation
  • ارسال events به WebView از طریق runJavaScript

React Side:

  • ایجاد useFlutterLayout hook
  • لیسن به flutterConfig event
  • لیسن به flutterEvent event
  • استفاده از layout info در components
  • تست در موبایل/تبلت
  • تست با keyboard باز/بسته
  • تست در landscape/portrait

🎯 خلاصه

اطلاعاتی که یکبار ارسال می‌شود:

  1. Breakpoint (mobile/tablet)
  2. Screen size
  3. Safe area
  4. Design sizes
  5. Platform info
  6. Locale/direction

اطلاعاتی که real-time ارسال می‌شود:

  1. 🔄 Keyboard height (viewInsets.bottom)
  2. 🔄 Safe area changes
  3. 🔄 Screen size changes (rotation)
  4. 🔄 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