## 📊 نحوه Navigation به هر سرویس ### 1️⃣ **قرآن (Quran)** #### **A. باز کردن سرویس قرآن (صفحه اصلی):** ```dart Navigator.pushNamed(context, RoutesName.quran); ``` ```tsx // از WebView: window.FlutterChannel?.postMessage(JSON.stringify({ type: 'NAVIGATE', payload: { route: '/quran' } })); ``` #### **B. باز کردن صفحه خاص (آیه):** ```dart // Navigation در کد فلاتر: context.pushPage(QuranAyasScreen(ayaId)); // مثال‌ها: context.pushPage(QuranAyasScreen(123)); // آیه 123 context.pushPage(QuranAyasScreen(null)); // از اول context.pushPage(QuranAyasScreen( surah.startAt, // آیه اول سوره )); ``` **ساختار QuranAyasScreen:** ```dart class QuranAyasScreen extends StatefulWidget { final int? startAya; // ⭐ ID آیه final String? autoPlayReciter; // ⭐ پخش خودکار (اختیاری) const QuranAyasScreen( this.startAya, {this.autoPlayReciter, super.key} ); } ``` --- #### **JSON برای WebView:** ```json { "type": "NAVIGATE_QURAN", "payload": { "ayaId": 123, "autoPlay": true, "reciterId": "abdul-basit" } } ``` **یا ساده‌تر:** ```json { "type": "NAVIGATE", "payload": { "route": "/quran", "params": { "ayaId": 123, "autoPlay": true } } } ``` --- ### 2️⃣ **مفاتیح (Mafatih)** #### **A. باز کردن سرویس مفاتیح (صفحه اصلی):** ```dart Navigator.pushNamed(context, RoutesName.mafatih); ``` ```tsx // از WebView: window.FlutterChannel?.postMessage(JSON.stringify({ type: 'NAVIGATE', payload: { route: '/mafatih' } })); ``` --- #### **B. باز کردن دعای خاص:** ```dart // Navigation در کد فلاتر: context.pushPage(MafatihDuasScreen(duaId)); // مثال: context.pushPage(MafatihDuasScreen(45)); // دعای عهد context.pushPage(MafatihDuasScreen(1)); // دعای اول ``` **ساختار MafatihDuasScreen:** ```dart class MafatihDuasScreen extends StatefulWidget { final int? startDuaId; // ⭐ ID دعا final int? startDuaPartId; // ⭐ بخش خاص دعا (اختیاری) const MafatihDuasScreen( this.startDuaId, {this.startDuaPartId, super.key} ); } ``` --- #### **JSON برای WebView:** ```json { "type": "NAVIGATE_MAFATIH", "payload": { "duaId": 45, "duaPartId": 2, "autoPlay": true } } ``` ### 3️⃣ **کتابخانه (Library)** #### **A. باز کردن سرویس کتابخانه (صفحه اصلی):** ```dart Navigator.pushNamed(context, RoutesName.library); ``` ```tsx // از WebView: window.FlutterChannel?.postMessage(JSON.stringify({ type: 'NAVIGATE', payload: { route: '/library' } })); ``` --- #### **B. باز کردن کتاب خاص:** ```dart // Navigation در کد فلاتر: context.pushPage(BookPage(bookSlug)); // یا با named route: Navigator.pushNamed( context, RoutesName.librarySinglePage, arguments: bookSlug, ); ``` **JSON برای WebView:** ```json { "type": "NAVIGATE_LIBRARY", "payload": { "slug": "kitab-al-tawheed" } } ``` **توضیحات:** - `slug`: شناسه یکتای متنی کتاب (مثال: `"kitab-al-tawheed"`) - API Endpoint: `GET /library/v2/books/{slug}/` --- ### 4️⃣ **حسینیه (Hossienieh)** #### **A. باز کردن سرویس حسینیه:** ```dart Navigator.pushNamed(context, RoutesName.hosseinieh); ``` ```tsx // از WebView: window.FlutterChannel?.postMessage(JSON.stringify({ type: 'NAVIGATE', payload: { route: '/hosseinieh' } })); ``` #### **B. باز کردن پلیر با آهنگ:** ```dart // Navigation در کد فلاتر: context.read().openAudio( audios: [song1, song2, song3], songModel: currentSong, showSmallPlayer: true, ); // با تنظیمات پیشرفته (پیشنهادی جدید): context.read().openHosseiniehPlaylist( HosseiniehPlaylist( audios: audios, startIndex: 0, startDuration: 120000, // شروع از دقیقه 2 (میلی‌ثانیه) playbackSpeed: 2.0, // سرعت 2 برابر ), showSmallPlayer: true, ); ``` #### **JSON برای WebView (پیشنهاد جدید):** ```json { "type": "PLAY_HOSSEINIEH", "payload": { "songs": [ { "slug": "song-123", "title": "نوحه محرم", "singers": [{"name": "حاج محمود کریمی"}], "file": {"audio": "https://example.com/audio.mp3"}, "thumbnail": {"sm": "https://example.com/thumb.jpg"} } ], "currentSongSlug": "song-123", "showSmallPlayer": true, "config": { "startTime": 120000, "playbackSpeed": 2.0, "loopMode": "single", "autoPlay": true, "fadeInDuration": 1000, "fadeOutDuration": 1000 } } } ``` #### **توضیح فیلدهای config:** | فیلد | نوع | پیش‌فرض | توضیح | |------|-----|---------|-------| | `startTime` | `number` | `0` | شروع از زمان مشخص (میلی‌ثانیه) - مثل مفاتیح | | `playbackSpeed` | `number` | `1.0` | سرعت پخش (0.5x تا 2.0x) | | `loopMode` | `"none"` \| `"single"` \| `"playlist"` | `"none"` | حالت تکرار | | `autoPlay` | `boolean` | `true` | شروع خودکار پخش | | `fadeInDuration` | `number?` | - | مدت زمان fade-in (میلی‌ثانیه) | | `fadeOutDuration` | `number?` | - | مدت زمان fade-out (میلی‌ثانیه) | | `pitch` | `number?` | `1.0` | تغییر pitch (اختیاری) | | `volume` | `number?` | `1.0` | حجم صدا (0.0 تا 1.0) | #### **مثال‌های کاربردی:** **۱. شروع از دقیقه 2 با سرعت 2 برابر:** ```json { "type": "PLAY_HOSSEINIEH", "payload": { "songs": [...], "currentSongSlug": "song-123", "config": { "startTime": 120000, "playbackSpeed": 2.0 } } } ``` **۲. پخش کامل با fade-in:** ```json { "type": "PLAY_HOSSEINIEH", "payload": { "songs": [...], "currentSongSlug": "song-123", "config": { "fadeInDuration": 2000, "loopMode": "single" } } } ``` **۳. شروع از انتخاب کاربر با تنظیمات پیشرفته:** ```json { "type": "PLAY_HOSSEINIEH", "payload": { "songs": [...], "currentSongSlug": "song-456", "config": { "startTime": 0, "playbackSpeed": 1.5, "volume": 0.8, "autoPlay": true } } } ``` --- ### 6️⃣ **تاک (Talk)** #### **A. باز کردن صفحه اصلی Talk (لیست مشاورین):** ```dart // Navigation در کد فلاتر: Navigator.pushNamed(context, RoutesName.talk); ``` **JSON برای WebView:** ```json { "type": "NAVIGATE_TALK" } ``` **توضیحات:** - باز می‌کند صفحه اصلی سرویس Talk - برای کاربر عادی: لیست مشاورین نمایش داده می‌شود - برای مشاور: داشبورد مشاور نمایش داده می‌شود --- #### **B. باز کردن صفحه جزئیات مشاور:** ```dart // Navigation در کد فلاتر: context.pushPage(ConsultantInfoPage(consultant)); // با استفاده از notification: Navigator.pushNamed( context, '/TalkConsultantPage', arguments: consultantJson, ); ``` **JSON برای WebView:** ```json { "type": "NAVIGATE_TALK", "payload": { "page": "consultant", "username": "consultant_username" } } ``` **توضیحات:** - `username`: نام کاربری یکتای مشاور (مثال: `"ahmad_consultant"`) - صفحه جزئیات شامل: بیوگرافی، نظرات، زمان‌بندی و انواع تماس - از این صفحه کاربر می‌تواند درخواست چت یا تماس بدهد **نمونه Consultant JSON کامل:** ```json { "username": "consultant_username", "fullname": "احمد محمدی", "avatar_url": "https://example.com/avatar.jpg", "slogan": "مشاور خانواده و ازدواج", "bio": "توضیحات کامل مشاور...", "status": "online", "unread_count": 0, "avg_rate": 4.5, "languages": ["fa", "en"], "contact_type": ["chat", "voice", "video"], "categories": [], "topics": ["ازدواج", "خانواده"], "session_duration": 30, "video_call_cost": 50000, "voice_call_cost": 30000, "first_message": "سلام، چطور می‌تونم کمکتون کنم؟", "is_ai": false } ``` --- #### **C. باز کردن صفحه چت با مشاور:** ```dart // Navigation در کد فلاتر: context.pushPage(TalkChatPage(event: callEvent)); // با استفاده از notification: Navigator.pushNamed( context, '/TalkChatPage', arguments: callEventJson, ); ``` **JSON برای WebView:** ```json { "type": "NAVIGATE_TALK", "payload": { "page": "chat", "call_id": 12345, "uuid": "consultant_username", "call_type": "chat", "from_user_username": "consultant_username", "from_user_fullname": "احمد محمدی", "from_user_avatar": "https://example.com/avatar.jpg", "session_duration": 30, "price": 0, "is_from_user": true } } ``` **توضیحات:** - `call_id`: شناسه یکتای چت/تماس (اختیاری) - `uuid`: نام کاربری مشاور - `call_type`: نوع تماس (`"chat"`, `"voice"`, یا `"video"`) - `from_user_*`: اطلاعات فرستنده پیام - `session_duration`: مدت زمان جلسه به دقیقه - `price`: هزینه تماس (برای چت معمولاً 0) - `is_from_user`: آیا از طرف کاربر است یا مشاور **نمونه CallEvent JSON کامل:** ```json { "act": "", "call_id": 12345, "uuid": "consultant_username", "call_type": "chat", "session_duration": 30, "call_direction": "outgoing", "is_from_user": true, "price": 0, "me": { "name": "محمد رضایی", "username": "user_username", "avatar": "https://example.com/user-avatar.jpg" }, "contact": { "name": "احمد محمدی", "username": "consultant_username", "avatar": "https://example.com/consultant-avatar.jpg", "extra": "مشاور خانواده" }, "chat_data": { "init_text": "سلام، نیاز به مشاوره دارم", "first_message": "سلام، چطور می‌تونم کمکتون کنم؟" } } ``` --- #### **D. نکات مهم برای دو طرفه بودن Navigation:** **1. از کاربر به مشاور (User → Consultant):** - کاربر از لیست مشاورین (`UserHomePage`) می‌تواند: - به صفحه جزئیات مشاور (`ConsultantInfoPage`) برود - از آنجا درخواست چت بدهد و به صفحه چت (`TalkChatPage`) برود **2. از مشاور به کاربر (Consultant → User):** - مشاور از داشبورد خود (`ConsultantHomePage`) می‌تواند: - لیست چت‌ها را ببیند (`ChatsPage`) - روی هر چت کلیک کند و به صفحه چت (`TalkChatPage`) برود - درخواست‌های booking را ببیند (`BookingPage`) **3. نوتیفیکیشن‌ها:** - هر دو طرف از طریق notification می‌توانند مستقیماً به چت بروند - Navigation routes: - `/TalkChatPage` → باز کردن صفحه چت - `/TalkConsultantPage` → باز کردن صفحه جزئیات مشاور --- ### 7️⃣ **پلیر کنترل (Player Control Events)** #### **کنترل‌های استاندارد:** ```json { "type": "PLAYER_PAUSE" } { "type": "PLAYER_RESUME" } { "type": "PLAYER_STOP" } { "type": "PLAYER_SEEK", "payload": { "position": 60000 } } { "type": "PLAYER_SET_SPEED", "payload": { "speed": 1.5 } } { "type": "PLAYER_SET_VOLUME", "payload": { "volume": 0.7 } } { "type": "PLAYER_NEXT" } { "type": "PLAYER_PREVIOUS" } ``` --- ### 9️⃣ **Cross-Service Navigation (نویگیشن بین سرویس‌ها)** این event برای انتقال از یک سرویس به سرویس دیگر استفاده می‌شود. هم برای Native Services و هم WebView Services کار می‌کند. --- #### **A. Navigation به سرویس Native:** ```json { "type": "NAVIGATE_TO_SERVICE", "payload": { "service": "quran", "params": { "ayaId": 123, "autoPlay": true } } } ``` **سرویس‌های Native موجود:** - `quran` → سرویس قرآن - `mafatih` → سرویس مفاتیح - `library` → کتابخانه - `hosseinieh` → حسینیه - `talk` → Talk (مشاوره) - `meet` → Meet - `habit` → عادات - `ahkaam` → احکام - `tafsir` → تفسیر --- #### **B. Navigation به WebView Service با URL کامل:** ```json { "type": "NAVIGATE_TO_SERVICE", "payload": { "service": "marriage", "url": "https://marriage.habibapp.com", "path": "/games", "params": { "level": 5, "mode": "challenge" } } } ``` **توضیحات:** - `service`: نام سرویس (برای لاگ و tracking) - `url`: آدرس پایه WebView - `path`: مسیر درون سرویس (اختیاری) - `params`: پارامترهای query string (اختیاری) **URL نهایی:** ``` https://marriage.habibapp.com/games?level=5&mode=challenge ``` --- #### **C. Navigation به WebView با Service Name (از Config):** اگر سرویس از قبل در `ServiceConfigs` تعریف شده باشد: ```json { "type": "NAVIGATE_TO_SERVICE", "payload": { "service": "marriage", "path": "/games", "params": { "level": 5 } } } ``` **توضیحات:** - Flutter از `ServiceConfigs` آدرس پایه را می‌خواند - `path` و `params` به آدرس پایه اضافه می‌شوند --- #### **D. مثال‌های کاربردی:** **۱. از سرویس Marriage به Talk:** ```json { "type": "NAVIGATE_TO_SERVICE", "payload": { "service": "talk", "params": { "page": "consultant", "username": "marriage_consultant" } } } ``` **۲. از Talk به بازی Marriage:** ```json { "type": "NAVIGATE_TO_SERVICE", "payload": { "service": "marriage", "path": "/games/compatibility", "params": { "consultantId": "ahmad_consultant" } } } ``` **۳. از Quran به Library (کتاب تفسیر):** ```json { "type": "NAVIGATE_TO_SERVICE", "payload": { "service": "library", "params": { "slug": "tafsir-al-mizan", "chapter": 5 } } } ``` **۴. از Library به Mafatih:** ```json { "type": "NAVIGATE_TO_SERVICE", "payload": { "service": "mafatih", "params": { "duaId": 45 } } } ``` **۵. از WebView به WebView دیگر:** ```json { "type": "NAVIGATE_TO_SERVICE", "payload": { "service": "health", "url": "https://health.habibapp.com", "path": "/consultation", "params": { "type": "mental_health", "referrer": "marriage" } } } ``` --- #### **E. پیاده‌سازی در Flutter:** ```dart Future handleNavigateToService(Map payload) async { final serviceName = payload['service'] as String; final params = payload['params'] as Map?; final path = payload['path'] as String?; final url = payload['url'] as String?; // Native Services if (_isNativeService(serviceName)) { return _navigateToNativeService(serviceName, params); } // WebView Services final serviceUrl = url ?? _getServiceUrlFromConfig(serviceName); if (serviceUrl != null) { final fullUrl = _buildWebViewUrl(serviceUrl, path, params); return _navigateToWebView(serviceName, fullUrl); } // Service not found throw Exception('Service "$serviceName" not found'); } String _buildWebViewUrl( String baseUrl, String? path, Map? params, ) { final uri = Uri.parse(baseUrl); final pathSegment = path ?? ''; final queryParams = params?.map( (key, value) => MapEntry(key, value.toString()), ) ?? {}; return uri.replace( path: pathSegment, queryParameters: queryParams.isEmpty ? null : queryParams, ).toString(); } ``` --- #### **F. استفاده در React/TypeScript:** ```typescript // utils/navigation.ts interface NavigateToServicePayload { service: string; url?: string; path?: string; params?: Record; } export function navigateToService(payload: NavigateToServicePayload) { window.FlutterChannel?.postMessage(JSON.stringify({ type: 'NAVIGATE_TO_SERVICE', payload, })); } // مثال استفاده: import { navigateToService } from '@/utils/navigation'; // Navigate to Talk navigateToService({ service: 'talk', params: { page: 'consultant', username: 'consultant_username', }, }); // Navigate to another WebView service navigateToService({ service: 'marriage', path: '/games', params: { level: 5 }, }); ``` --- #### **G. Schema کامل:** ```typescript interface NavigateToServiceEvent { type: 'NAVIGATE_TO_SERVICE'; payload: { // Required: نام سرویس service: string; // Optional: برای WebView services url?: string; // آدرس کامل پایه path?: string; // مسیر داخل سرویس params?: Record; // Query parameters // Optional: تنظیمات اضافی options?: { replaceCurrentRoute?: boolean; // جایگزین route فعلی clearStack?: boolean; // پاک کردن history animation?: 'slide' | 'fade' | 'none'; }; }; } ``` --- #### **H. نکات مهم:** 1. **Service Name:** - باید lowercase باشد - فقط حروف، اعداد و underscore - مثال: `marriage`, `health_tips`, `quran_v2` 2. **URL Building:** - اگر `url` وجود نداشته باشد، از `ServiceConfigs` خوانده می‌شود - `path` باید با `/` شروع شود - `params` به صورت خودکار به query string تبدیل می‌شود 3. **Security:** - فقط URLهای مجاز (`habibapp.com`) قابل باز شدن هستند - پارامترها باید sanitize شوند 4. **History Management:** - هر navigation به history stack اضافه می‌شود - کاربر می‌تواند با دکمه back برگردد --- #### **Event‌های ارسال شده از Flutter:** ```typescript // types/player-events.ts interface PlayerStateEvent { type: 'PLAYER_STATE_CHANGED'; payload: { isPlaying: boolean; currentAudio: { id: string; title: string; artist?: string; duration: number; thumbnail?: string; }; currentPosition: number; duration: number; playbackSpeed: number; volume: number; }; } interface PlayerProgressEvent { type: 'PLAYER_PROGRESS'; payload: { position: number; duration: number; buffered: number; }; } interface PlayerErrorEvent { type: 'PLAYER_ERROR'; payload: { code: string; message: string; }; } ``` #### **دریافت در React:** ```tsx // hooks/usePlayerState.ts import { useEffect, useState } from 'react'; export function usePlayerState() { const [isPlaying, setIsPlaying] = useState(false); const [currentAudio, setCurrentAudio] = useState(null); const [position, setPosition] = useState(0); const [duration, setDuration] = useState(0); const [speed, setSpeed] = useState(1.0); useEffect(() => { const handlePlayerState = (event: CustomEvent) => { const { type, payload } = event.detail; switch (type) { case 'PLAYER_STATE_CHANGED': setIsPlaying(payload.isPlaying); setCurrentAudio(payload.currentAudio); setSpeed(payload.playbackSpeed); break; case 'PLAYER_PROGRESS': setPosition(payload.position); setDuration(payload.duration); break; case 'PLAYER_ERROR': console.error('Player error:', payload.message); break; } }; window.addEventListener('playerState', handlePlayerState as EventListener); return () => { window.removeEventListener('playerState', handlePlayerState as EventListener); }; }, []); return { isPlaying, currentAudio, position, duration, speed, }; } ```