diff --git a/lib.zip b/lib.zip deleted file mode 100644 index e55ed6f..0000000 Binary files a/lib.zip and /dev/null differ diff --git a/lib/main.dart b/lib/main.dart index 6bb49b8..1f29522 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,11 +8,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:get/get.dart'; import 'package:home_widget/home_widget.dart'; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; +// import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/firebase_options.dart'; import 'package:didvan/models/requests/news.dart'; @@ -55,22 +54,20 @@ void main() async { final loader = html.document.getElementById('loading_indicator'); if (loader != null) { loader.remove(); - } + } } try { if (!kIsWeb) { - // ignore: deprecated_member_use HomeWidget.registerBackgroundCallback(_backgroundCallbackHomeWidget); - HomeWidget.registerInteractivityCallback( - _backgroundCallbackHomeWidget); + HomeWidget.registerInteractivityCallback(_backgroundCallbackHomeWidget); await NotificationService.initializeNotification(); if (Platform.isAndroid) { try { await FlutterDownloader.initialize(debug: true, ignoreSsl: true); } catch (e) { - e.printError(); + if (kDebugMode) print("Downloader error: $e"); } } } @@ -80,36 +77,29 @@ void main() async { .timeout( const Duration(seconds: 10), onTimeout: () { - debugPrint( - "Firebase initialization timed out - continuing without Firebase"); - throw TimeoutException( - 'Firebase initialization timeout', const Duration(seconds: 10)); + debugPrint("Firebase initialization timed out - continuing without Firebase"); + throw TimeoutException('Firebase initialization timeout', const Duration(seconds: 10)); }, ); + + if (!kIsWeb) { + await FirebaseApi().initNotification().timeout( + const Duration(seconds: 5), + onTimeout: () { + debugPrint("Firebase notification init timed out - continuing"); + }, + ); + } - await FirebaseApi().initNotification().timeout( - const Duration(seconds: 5), - onTimeout: () { - debugPrint("Firebase notification init timed out - continuing"); - }, - ); } catch (e) { debugPrint("Initialization Error: $e"); debugPrint("App will continue without Firebase services"); } - await SentryFlutter.init( - (options) { - options.dsn = - 'https://a4cfcaa7d67471240d295c25c968d91d@o4508585857384448.ingest.de.sentry.io/4508585886548048'; - options.tracesSampleRate = 1.0; - options.profilesSampleRate = 1.0; - }, - appRunner: () => runApp(const Didvan()), - ); + runApp(const Didvan()); }, (error, stack) { - Sentry.captureException(error, stackTrace: stack); + debugPrint("Error captured: $error"); }, ); } @@ -274,5 +264,3 @@ class _DidvanState extends State with WidgetsBindingObserver { ); } } - -// Developed by Mohamad Mahdi Jebeli - 2025 diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart index 9029a99..4d90c7f 100644 --- a/lib/routes/route_generator.dart +++ b/lib/routes/route_generator.dart @@ -430,15 +430,31 @@ class RouteGenerator { ); case Routes.didvanPlusList: + if (kDebugMode) { + print("📱 Route: didvanPlusList"); + print("📱 Arguments type: ${settings.arguments.runtimeType}"); + print("📱 Arguments: ${settings.arguments}"); + } + if (settings.arguments is List) { + final argsList = settings.arguments as List; + if (kDebugMode) { + print("📱 Arguments list length: ${argsList.length}"); + print("📱 First item type: ${argsList.isNotEmpty ? argsList.first.runtimeType : 'empty'}"); + } + return _createRoute( DidvanPlusListPage( - items: (settings.arguments as List).cast(), + items: argsList.cast(), ), ); } + + if (kDebugMode) { + print("📱 ❌ Invalid arguments type - Expected List, got ${settings.arguments.runtimeType}"); + } return _errorRoute( - 'Invalid arguments for ${settings.name}: Expected List.'); + 'Invalid arguments for ${settings.name}: Expected List, got ${settings.arguments.runtimeType}.'); case Routes.didvanPlusVideo: if (settings.arguments is DidvanPlusModel) { diff --git a/lib/services/app_home_widget/home_widget_repository.dart b/lib/services/app_home_widget/home_widget_repository.dart index b6fa4e8..1a925cc 100644 --- a/lib/services/app_home_widget/home_widget_repository.dart +++ b/lib/services/app_home_widget/home_widget_repository.dart @@ -1,4 +1,6 @@ import 'package:didvan/models/notification_message.dart'; +import 'package:didvan/models/didvan_voice_model.dart'; +import 'package:didvan/models/didvan_plus_model.dart'; import 'package:didvan/services/app_initalizer.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -16,6 +18,29 @@ import '../network/request.dart'; import '../network/request_helper.dart'; class HomeWidgetRepository { + /// Helper method to safely parse ID from notification data + static int? _safeParseId(dynamic id) { + if (id == null) return null; + + String idStr = id.toString().trim(); + + // Check if string contains "null" or is empty + if (idStr.isEmpty || + idStr == 'null' || + idStr.toLowerCase().contains('null')) { + return null; + } + + try { + return int.parse(idStr); + } catch (e) { + if (kDebugMode) { + print("Failed to parse ID: $idStr - Error: $e"); + } + return null; + } + } + static Future fetchWidget() async { // RequestService.token = await StorageService.getValue(key: 'token'); final service = RequestService( @@ -77,8 +102,17 @@ class HomeWidgetRepository { if (row != 0) { String? id = await HomeWidget.getWidgetData("id$row", defaultValue: ""); + + final parsedId = _safeParseId(id); + if (parsedId == null) { + if (kDebugMode) { + print("Invalid widget ID: $id - cannot create WidgetResponse"); + } + return; + } + WidgetResponse data = WidgetResponse( - id: int.parse(id!), + id: parsedId, title: await HomeWidget.getWidgetData("title$row", defaultValue: ""), createdAt: await HomeWidget.getWidgetData("createdAt$row", defaultValue: ""), @@ -200,16 +234,17 @@ class HomeWidgetRepository { } else { switch (localData.type!) { case "infography": - if (localData.id == null || localData.id.toString().isEmpty) { + final infographyId = _safeParseId(localData.id); + if (infographyId == null) { if (kDebugMode) { print( - "WARNING: Infography notification without ID - navigating to home"); + "WARNING: Infography notification without valid ID - navigating to home"); } route = Routes.home; } else { route = Routes.infography; args = { - 'id': int.parse(localData.id.toString()), + 'id': infographyId, 'args': const InfographyRequestArgs(page: 0), 'hasUnmarkConfirmation': false, 'goToComment': openComments @@ -217,36 +252,38 @@ class HomeWidgetRepository { } break; case "news": - if (localData.id == null || localData.id.toString().isEmpty) { + final newsId = _safeParseId(localData.id); + if (newsId == null) { if (kDebugMode) { print( - "WARNING: News notification without ID - navigating to home"); + "WARNING: News notification without valid ID - navigating to home"); } route = Routes.home; } else { route = Routes.newsDetails; args = { - 'id': int.parse(localData.id.toString()), + 'id': newsId, 'args': const NewsRequestArgs(page: 0), 'hasUnmarkConfirmation': false, 'goToComment': openComments }; if (kDebugMode) { - print("News navigation - ID: ${localData.id}"); + print("News navigation - ID: $newsId"); } } break; case "radar": - if (localData.id == null || localData.id.toString().isEmpty) { + final radarId = _safeParseId(localData.id); + if (radarId == null) { if (kDebugMode) { print( - "WARNING: Radar notification without ID - navigating to home"); + "WARNING: Radar notification without valid ID - navigating to home"); } route = Routes.home; } else { route = Routes.radarDetails; args = { - 'id': int.parse(localData.id.toString()), + 'id': radarId, 'args': const RadarRequestArgs(page: 0), 'hasUnmarkConfirmation': false, 'goToComment': openComments @@ -254,49 +291,52 @@ class HomeWidgetRepository { } break; case "studio": - if (localData.id == null || localData.id.toString().isEmpty) { + final studioId = _safeParseId(localData.id); + if (studioId == null) { if (kDebugMode) { print( - "WARNING: Studio notification without ID - navigating to home"); + "WARNING: Studio notification without valid ID - navigating to home"); } route = Routes.home; } else { route = Routes.studioDetails; args = { 'type': 'podcast', - 'id': int.parse(localData.id.toString()), + 'id': studioId, 'goToComment': openComments }; } break; case "video": - if (localData.id == null || localData.id.toString().isEmpty) { + final videoId = _safeParseId(localData.id); + if (videoId == null) { if (kDebugMode) { print( - "WARNING: Video notification without ID - navigating to home"); + "WARNING: Video notification without valid ID - navigating to home"); } route = Routes.home; } else { route = Routes.studioDetails; args = { 'type': 'podcast', - 'id': int.parse(localData.id.toString()), + 'id': videoId, 'goToComment': openComments }; } break; case "podcast": - if (localData.id == null || localData.id.toString().isEmpty) { + final podcastId = _safeParseId(localData.id); + if (podcastId == null) { if (kDebugMode) { print( - "WARNING: Podcast notification without ID - navigating to home"); + "WARNING: Podcast notification without valid ID - navigating to home"); } route = Routes.home; } else { route = Routes.podcasts; args = { 'type': 'podcast', - 'id': int.parse(localData.id.toString()), + 'id': podcastId, 'goToComment': openComments }; } @@ -308,14 +348,133 @@ class HomeWidgetRepository { case "didvanplus": case "didvan_plus": - route = Routes.didvanPlusList; - args = []; + case "didvanPlus": + if (kDebugMode) { + print( + "🎬 Fetching didvan plus list for notification navigation..."); + } + try { + final service = RequestService(RequestHelper.didvanPlus); + await service.httpGet(); + + if (kDebugMode) { + print("🎬 DidvanPlus API Response - Status: ${service.statusCode}"); + print("🎬 DidvanPlus API Response - Success: ${service.isSuccess}"); + } + + if (service.statusCode == 200) { + final rawData = service.data('result'); + + if (kDebugMode) { + print("🎬 Raw Data Type: ${rawData.runtimeType}"); + print("🎬 Raw Data: $rawData"); + } + + List videos = []; + + if (rawData is List && rawData.isNotEmpty) { + videos = rawData + .map((e) => DidvanPlusModel.fromJson( + Map.from(e as Map))) + .toList(); + } else if (rawData is Map) { + videos = [ + DidvanPlusModel.fromJson( + Map.from(rawData)) + ]; + } + + if (kDebugMode) { + print("🎬 Parsed ${videos.length} didvan plus videos"); + for (var video in videos) { + print(" - ${video.title} (ID: ${video.id})"); + } + } + + if (videos.isNotEmpty) { + route = Routes.didvanPlusList; + args = videos; + if (kDebugMode) { + print( + "🎬 ✅ Successfully prepared navigation with ${videos.length} videos"); + print("🎬 Args type: ${args.runtimeType}"); + } + } else { + route = Routes.home; + if (kDebugMode) { + print("🎬 ⚠️ No didvan plus videos found, navigating to home"); + } + } + } else { + route = Routes.home; + if (kDebugMode) { + print( + "🎬 ❌ Failed to fetch didvan plus (status: ${service.statusCode}), navigating to home"); + } + } + } catch (e, stackTrace) { + route = Routes.home; + if (kDebugMode) { + print("🎬 ❌ Error fetching didvan plus: $e"); + print("🎬 Stack trace: $stackTrace"); + } + } break; case "didvanvoice": case "didvan_voice": - route = Routes.didvanVoiceList; - args = []; + case "didvan_Voice": + case "didvanVoice": + case "media": + if (kDebugMode) { + print( + "Fetching didvan voice list for notification navigation..."); + } + try { + final service = RequestService(RequestHelper.didvanVoice); + await service.httpGet(); + if (service.statusCode == 200) { + final rawData = service.data('result'); + List voices = []; + + if (rawData is List && rawData.isNotEmpty) { + voices = rawData + .map((e) => DidvanVoiceModel.fromJson( + Map.from(e as Map))) + .toList(); + } else if (rawData is Map) { + voices = [ + DidvanVoiceModel.fromJson( + Map.from(rawData)) + ]; + } + + if (voices.isNotEmpty) { + route = Routes.didvanVoiceList; + args = voices; + if (kDebugMode) { + print( + "Successfully fetched ${voices.length} didvan voices"); + } + } else { + route = Routes.home; + if (kDebugMode) { + print("No didvan voices found, navigating to home"); + } + } + } else { + route = Routes.home; + if (kDebugMode) { + print( + "Failed to fetch didvan voices (status: ${service.statusCode}), navigating to home"); + } + } + } catch (e) { + route = Routes.home; + if (kDebugMode) { + print("Error fetching didvan voices: $e, navigating to home"); + } + } break; case "startup": case "technology": @@ -418,6 +577,10 @@ class HomeWidgetRepository { if (kDebugMode) { print("Final navigation decision:"); print("Route: $route"); + print("Args type: ${args.runtimeType}"); + if (args is List) { + print("Args list length: ${args.length}"); + } print("Args: $args"); print("==========================="); } @@ -443,6 +606,7 @@ class HomeWidgetRepository { if (navigatorKey.currentState != null) { if (kDebugMode) { print("Navigator is ready, performing navigation to: $route"); + print("With arguments type: ${args.runtimeType}"); } navigatorKey.currentState!.pushNamed(route, arguments: args); } else { diff --git a/lib/services/media/media.dart b/lib/services/media/media.dart index 0f94fc8..2d87a3b 100644 --- a/lib/services/media/media.dart +++ b/lib/services/media/media.dart @@ -35,13 +35,13 @@ class MediaService { static Future _configureAudioSession() async { if (_isAudioSessionConfigured) return; - + try { final session = await AudioSession.instance; - await session.configure(AudioSessionConfiguration( + await session.configure(const AudioSessionConfiguration( avAudioSessionCategory: AVAudioSessionCategory.playback, avAudioSessionMode: AVAudioSessionMode.spokenAudio, - androidAudioAttributes: const AndroidAudioAttributes( + androidAudioAttributes: AndroidAudioAttributes( contentType: AndroidAudioContentType.music, flags: AndroidAudioFlags.none, usage: AndroidAudioUsage.media, @@ -64,9 +64,8 @@ class MediaService { void Function(bool isNext)? onTrackChanged, }) async { try { - // تنظیم AudioSession برای پخش از MEDIA await _configureAudioSession(); - + String tag; tag = '${currentPodcast?.description == 'radar' ? 'radar' : isVoiceMessage ? 'message' : 'podcast'}-$id'; @@ -165,7 +164,7 @@ class MediaService { ) .then((result) { if (result != null && result.files.length > 3) { - return null; + return null; } return result; }); @@ -190,88 +189,93 @@ class MediaService { static Future downloadFile(String url, {final String? name}) async { debugPrint("Attempting to download file from URL: $url"); final basename = name ?? p.basename(url).split('?accessToken=').first; - + PermissionStatus status; if (Platform.isAndroid) { - final androidInfo = await DeviceInfoPlugin().androidInfo; - if (androidInfo.version.sdkInt >= 33) { - debugPrint("Android 13+ detected. Requesting Media permissions."); - // برای Android 13+، بسته به نوع فایل permission مناسب را درخواست می‌کنیم - final extension = basename.toLowerCase(); - if (extension.contains('.mp3') || extension.contains('.wav') || - extension.contains('.m4a') || extension.contains('.aac')) { - status = await Permission.audio.request(); - } else if (extension.contains('.mp4') || extension.contains('.avi') || - extension.contains('.mov')) { - status = await Permission.videos.request(); - } else if (extension.contains('.jpg') || extension.contains('.jpeg') || - extension.contains('.png') || extension.contains('.gif')) { - status = await Permission.photos.request(); - } else { - // برای فایل‌های دیگر، همه permissions رسانه را درخواست می‌کنیم - final audioStatus = await Permission.audio.request(); - final videoStatus = await Permission.videos.request(); - final photoStatus = await Permission.photos.request(); - status = (audioStatus.isGranted || videoStatus.isGranted || photoStatus.isGranted) - ? PermissionStatus.granted - : audioStatus; - } + final androidInfo = await DeviceInfoPlugin().androidInfo; + if (androidInfo.version.sdkInt >= 33) { + debugPrint("Android 13+ detected. Requesting Media permissions."); + final extension = basename.toLowerCase(); + if (extension.contains('.mp3') || + extension.contains('.wav') || + extension.contains('.m4a') || + extension.contains('.aac')) { + status = await Permission.audio.request(); + } else if (extension.contains('.mp4') || + extension.contains('.avi') || + extension.contains('.mov')) { + status = await Permission.videos.request(); + } else if (extension.contains('.jpg') || + extension.contains('.jpeg') || + extension.contains('.png') || + extension.contains('.gif')) { + status = await Permission.photos.request(); } else { - debugPrint("Older Android version detected. Requesting Storage permission."); - status = await Permission.storage.request(); + final audioStatus = await Permission.audio.request(); + final videoStatus = await Permission.videos.request(); + final photoStatus = await Permission.photos.request(); + status = (audioStatus.isGranted || + videoStatus.isGranted || + photoStatus.isGranted) + ? PermissionStatus.granted + : audioStatus; } + } else { + debugPrint( + "Older Android version detected. Requesting Storage permission."); + status = await Permission.storage.request(); + } } else if (Platform.isIOS) { - debugPrint("iOS detected. Requesting Photos permission."); - status = await Permission.photos.request(); + debugPrint("iOS detected. Requesting Photos permission."); + status = await Permission.photos.request(); } else { - status = PermissionStatus.granted; + status = PermissionStatus.granted; } debugPrint("Permission status after request: $status"); if (status.isGranted) { - Directory? dir; - try { - if (Platform.isIOS) { - dir = await getApplicationDocumentsDirectory(); - } else { - dir = await getExternalStorageDirectory(); - } - - if (dir == null) { - debugPrint("Error: Could not determine download directory."); - return null; - } - - final path = dir.path; - debugPrint("Download directory determined: $path"); - - final taskId = await FlutterDownloader.enqueue( - url: url, - savedDir: path, - fileName: basename, - showNotification: true, - openFileFromNotification: true, - saveInPublicStorage: true, - ); - debugPrint("Download successfully enqueued with taskId: $taskId"); - return path; - - } catch (e) { - debugPrint("Error during download process: $e"); - return null; + Directory? dir; + try { + if (Platform.isIOS) { + dir = await getApplicationDocumentsDirectory(); + } else { + dir = await getExternalStorageDirectory(); } + + if (dir == null) { + debugPrint("Error: Could not determine download directory."); + return null; + } + + final path = dir.path; + debugPrint("Download directory determined: $path"); + + final taskId = await FlutterDownloader.enqueue( + url: url, + savedDir: path, + fileName: basename, + showNotification: true, + openFileFromNotification: true, + saveInPublicStorage: true, + ); + debugPrint("Download successfully enqueued with taskId: $taskId"); + return path; + } catch (e) { + debugPrint("Error during download process: $e"); + return null; + } } else if (status.isPermanentlyDenied) { - debugPrint("Storage permission is permanently denied. Opening app settings."); - await openAppSettings(); - return null; + debugPrint( + "Storage permission is permanently denied. Opening app settings."); + await openAppSettings(); + return null; } else { - debugPrint("Storage permission was denied."); - return null; + debugPrint("Storage permission was denied."); + return null; } } - static String downloadFileFromWeb(String url) { final filename = url.split('/').last.split('?').first; html.AnchorElement anchorElement = html.AnchorElement(href: url); @@ -295,4 +299,4 @@ class MediaService { hasDismissButton: false, )); } -} \ No newline at end of file +} diff --git a/lib/services/notification/firebase_api.dart b/lib/services/notification/firebase_api.dart index 93780d8..4ad7fb8 100644 --- a/lib/services/notification/firebase_api.dart +++ b/lib/services/notification/firebase_api.dart @@ -9,104 +9,86 @@ import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; class FirebaseApi { - final _firebaseMessaging = FirebaseMessaging.instance; + // تعریف متغیر به صورت سراسری در کلاس حذف شد تا از کرش زودهنگام جلوگیری شود static String? fcmToken; Future initNotification() async { - try { - if (kIsWeb) { - fcmToken = await _firebaseMessaging.getToken( - vapidKey: - "BMXHGd93t_htpS7c62ceuuLVVmia2cEDmqxp46g9Vt0B3OxNMKIqN9nupsUMtv2Vq8Yy2sQGIqgCm9FxUSKvssU", - ); - } else { - fcmToken = await _firebaseMessaging.getToken(); + // ۱. بررسی پشتیبانی مرورگر (بسیار مهم برای آیفون و سافاری) + if (kIsWeb) { + try { + // اول چک می‌کنیم پشتیبانی میشه یا نه + bool isSupported = await FirebaseMessaging.instance.isSupported(); + if (!isSupported) { + if (kDebugMode) { + print("⚠️ مرورگر از نوتیفیکیشن پشتیبانی نمی‌کند. پروسه نوتیفیکیشن با موفقیت لغو شد تا از کرش جلوگیری شود."); + } + return; // خروج امن از تابع + } + } catch (e) { + if (kDebugMode) print("Error checking FCM support: $e"); + return; // خروج در صورت بروز خطای ناشناخته } - - if (kDebugMode) { - print("fCMToken: $fcmToken"); - } - } catch (e) { - e.printError(); } - _firebaseMessaging.requestPermission( - alert: true, - announcement: true, - badge: true, - carPlay: false, - criticalAlert: true, - provisional: true, - sound: true, - ); + // ۲. حالا که مطمئن شدیم مرورگر پشتیبانی میکنه، فایربیس رو لود میکنیم + try { + final messaging = FirebaseMessaging.instance; - final initMsg = await FirebaseMessaging.instance.getInitialMessage(); + NotificationSettings settings = await messaging.requestPermission( + alert: true, + announcement: true, + badge: true, + carPlay: false, + criticalAlert: true, + provisional: true, + sound: true, + ); - if (initMsg != null) { - if (kDebugMode) { - print("=== APP OPENED FROM NOTIFICATION (TERMINATED) ==="); - print("Message ID: ${initMsg.messageId}"); - print("Raw Data: ${initMsg.data}"); - if (initMsg.notification != null) { - print("Title: ${initMsg.notification!.title}"); - print("Body: ${initMsg.notification!.body}"); + if (settings.authorizationStatus == AuthorizationStatus.authorized || + settings.authorizationStatus == AuthorizationStatus.provisional) { + + if (kIsWeb) { + fcmToken = await messaging.getToken( + vapidKey: "BMXHGd93t_htpS7c62ceuuLVVmia2cEDmqxp46g9Vt0B3OxNMKIqN9nupsUMtv2Vq8Yy2sQGIqgCm9FxUSKvssU", + ); + } else { + fcmToken = await messaging.getToken(); } - print("================================================"); - } - try { + if (kDebugMode) { + print("fCMToken: $fcmToken"); + } + } else { + if (kDebugMode) { + print("User declined or has not accepted notification permissions"); + } + } + } catch (e) { + if (kDebugMode) print("Error initializing notifications: $e"); + } + + // ۳. کدهای مربوط به هندل کردن پیام‌ها + try { + final initMsg = await FirebaseMessaging.instance.getInitialMessage(); + if (initMsg != null) { + if (kDebugMode) print("=== APP OPENED FROM NOTIFICATION (TERMINATED) ==="); NotificationMessage data = NotificationMessage.fromJson(initMsg.data); HomeWidgetRepository.data = data; - if (kDebugMode) { - print("Parsed NotificationMessage: ${data.toJson()}"); - print("Scheduling navigation from terminated state..."); - } - // Schedule navigation to happen after app is fully initialized - // This ensures navigatorKey is ready - // Future.delayed(const Duration(milliseconds: 1500), () async { - // if (kDebugMode) { - // print("Executing delayed navigation from terminated state"); - // } - // await HomeWidgetRepository.decideWhereToGoNotif(); - // await StorageService.delete( - // key: 'notification${AppInitializer.createNotificationId(data)}'); - // }); - } catch (e) { - if (kDebugMode) { - print("Error handling initial message: $e"); - } - e.printError(); } + } catch (e) { + if (kDebugMode) print("Error handling initial message: $e"); } FirebaseMessaging.onMessageOpenedApp.listen((initMsg) async { - if (kDebugMode) { - print("=== APP OPENED FROM NOTIFICATION (BACKGROUND) ==="); - print("Message ID: ${initMsg.messageId}"); - print("Raw Data: ${initMsg.data}"); - if (initMsg.notification != null) { - print("Title: ${initMsg.notification!.title}"); - print("Body: ${initMsg.notification!.body}"); - } - print("================================================"); - } - try { NotificationMessage data = NotificationMessage.fromJson(initMsg.data); HomeWidgetRepository.data = data; - if (kDebugMode) { - print("Parsed NotificationMessage: ${data.toJson()}"); - print("Scheduling navigation from background state..."); - } await Future.delayed(const Duration(milliseconds: 300)); await HomeWidgetRepository.decideWhereToGoNotif(); await StorageService.delete( key: 'notification${AppInitializer.createNotificationId(data)}'); } catch (e) { - if (kDebugMode) { - print("Error handling background message: $e"); - } - e.printError(); + if (kDebugMode) print("Error handling background message: $e"); } }); @@ -116,39 +98,10 @@ class FirebaseApi { void handleMessage(RemoteMessage? message) async { if (message == null) return; - if (kDebugMode) { - print("=== NOTIFICATION RECEIVED (FOREGROUND) ==="); - print("Message ID: ${message.messageId}"); - print("From: ${message.from}"); - print("Sent Time: ${message.sentTime}"); - print("TTL: ${message.ttl}"); - print("Message Type: ${message.messageType}"); - print("Category: ${message.category}"); - - if (message.notification != null) { - print("--- NOTIFICATION PAYLOAD ---"); - print("Title: ${message.notification!.title}"); - print("Body: ${message.notification!.body}"); - print("Android Image: ${message.notification!.android?.imageUrl}"); - print("Apple Image: ${message.notification!.apple?.imageUrl}"); - } - - print("--- DATA PAYLOAD ---"); - print("Raw Data: ${message.data}"); - try { - NotificationData notifData = NotificationData.fromJson(message.data); - print("Parsed Data: ${notifData.toJson()}"); - } catch (e) { - print("Error parsing NotificationData: $e"); - } - - print("=========================================="); - } - try { await NotificationService.showFirebaseNotification(message); } catch (e) { - e.printError(); + if (kDebugMode) print("Error showing local notification: $e"); } } -} +} \ No newline at end of file diff --git a/lib/views/didvan_plus/didvan_plus_list_page.dart b/lib/views/didvan_plus/didvan_plus_list_page.dart index 8de62d8..7811f7a 100644 --- a/lib/views/didvan_plus/didvan_plus_list_page.dart +++ b/lib/views/didvan_plus/didvan_plus_list_page.dart @@ -62,7 +62,7 @@ class DidvanPlusListPage extends StatelessWidget { children: [ Center( child: SvgPicture.asset( - 'lib/assets /images/logos/logo-horizontal-light.svg', + 'lib/assets/images/logos/logo-horizontal-light.svg', height: 55, ), ), @@ -124,6 +124,13 @@ class DidvanPlusListPage extends StatelessWidget { return GestureDetector( onTap: () { debugPrint('🎬 Opening video: ${item.title}'); + debugPrint('🎬 Plus List - Original Image: ${item.image}'); + debugPrint('🎬 Plus List - Original File: ${item.file}'); + + final bool imageHasHttp = item.image.startsWith('http'); + final bool imageHasBaseUrl = item.image.contains(RequestHelper.baseUrl); + debugPrint('🎬 Plus List - Image has HTTP: $imageHasHttp, has BaseURL: $imageHasBaseUrl'); + showDialog( context: context, useSafeArea: false, @@ -156,11 +163,19 @@ class DidvanPlusListPage extends StatelessWidget { child: Stack( fit: StackFit.expand, children: [ - SkeletonImage( - imageUrl: '${RequestHelper.baseUrl}${item.image}', - width: double.infinity, - height: double.infinity, - borderRadius: BorderRadius.circular(20), + Builder( + builder: (context) { + final imageUrl = item.image.startsWith('http') || item.image.contains(RequestHelper.baseUrl) + ? item.image + : '${RequestHelper.baseUrl}${item.image}'; + debugPrint('🖼️ Plus List - Final Image URL: $imageUrl'); + return SkeletonImage( + imageUrl: imageUrl, + width: double.infinity, + height: double.infinity, + borderRadius: BorderRadius.circular(20), + ); + }, ), Center( child: Container( diff --git a/lib/views/didvan_plus/didvan_plus_video_player.dart b/lib/views/didvan_plus/didvan_plus_video_player.dart index 62bdb76..d21639f 100644 --- a/lib/views/didvan_plus/didvan_plus_video_player.dart +++ b/lib/views/didvan_plus/didvan_plus_video_player.dart @@ -29,17 +29,53 @@ class _DidvanPlusVideoPlayerState extends State { } void _initializeVideo() { - final videoUrl = '${RequestHelper.baseUrl}${widget.video.file}'; - final fullUrl = '$videoUrl?accessToken=${RequestService.token}'; + debugPrint('🎥 Didvan Plus Video - Original File: ${widget.video.file}'); + debugPrint('🎥 Didvan Plus Video - Original Image: ${widget.video.image}'); + + // بررسی اینکه آیا لینک از قبل کامل است یا نه + final bool fileHasHttp = widget.video.file.startsWith('http'); + final bool fileHasBaseUrl = widget.video.file.contains(RequestHelper.baseUrl); + + debugPrint('🎥 File has HTTP: $fileHasHttp'); + debugPrint('🎥 File has BaseURL: $fileHasBaseUrl'); + + // برای دیدوان پلاس، لینک مستقیم از سرور استفاده می‌شود + final videoUrl = widget.video.file.startsWith('http') || widget.video.file.contains(RequestHelper.baseUrl) + ? widget.video.file + : '${RequestHelper.baseUrl}${widget.video.file}'; + + debugPrint('🎥 Video URL before token: $videoUrl'); + + // فقط به لینک‌های baseUrl سرور accessToken اضافه می‌شود، نه لینک‌های external مثل S3 + final bool needsToken = videoUrl.contains(RequestHelper.baseUrl); + debugPrint('🎥 Needs access token: $needsToken'); + + final String fullUrl; + if (needsToken) { + final separator = videoUrl.contains('?') ? '&' : '?'; + fullUrl = '$videoUrl${separator}accessToken=${RequestService.token}'; + } else { + fullUrl = videoUrl; // لینک‌های S3 نیازی به token ندارند + } - debugPrint('🎥 Video URL: $fullUrl'); + debugPrint('🎥 Didvan Plus Video - Final URL: $fullUrl'); - _videoController = VideoPlayerController.networkUrl( - Uri.parse(fullUrl), - httpHeaders: { - 'Authorization': 'Bearer ${RequestService.token}', - }, - )..initialize().then((_) { + // برای S3 signed URLs نباید Authorization header اضافه کنیم + if (needsToken) { + _videoController = VideoPlayerController.networkUrl( + Uri.parse(fullUrl), + httpHeaders: { + 'Authorization': 'Bearer ${RequestService.token}', + }, + ); + } else { + // برای S3 بدون Authorization header + _videoController = VideoPlayerController.networkUrl( + Uri.parse(fullUrl), + ); + } + + _videoController.initialize().then((_) { debugPrint('✅ Video initialized successfully'); setState(() { _chewieController = ChewieController( @@ -64,7 +100,9 @@ class _DidvanPlusVideoPlayerState extends State { color: Colors.black, child: Center( child: Image.network( - '${RequestHelper.baseUrl}${widget.video.image}', + widget.video.image.startsWith('http') || widget.video.image.contains(RequestHelper.baseUrl) + ? widget.video.image + : '${RequestHelper.baseUrl}${widget.video.image}', fit: BoxFit.cover, ), ), @@ -126,7 +164,9 @@ class _DidvanPlusVideoPlayerState extends State { fit: StackFit.expand, children: [ Image.network( - '${RequestHelper.baseUrl}${widget.video.image}', + widget.video.image.startsWith('http') || widget.video.image.contains(RequestHelper.baseUrl) + ? widget.video.image + : '${RequestHelper.baseUrl}${widget.video.image}', fit: BoxFit.fitWidth, ), const Center( diff --git a/lib/views/home/main/didvan_voice_list_page.dart b/lib/views/home/main/didvan_voice_list_page.dart index 847b597..d103c6c 100644 --- a/lib/views/home/main/didvan_voice_list_page.dart +++ b/lib/views/home/main/didvan_voice_list_page.dart @@ -133,7 +133,17 @@ class _DidvanVoiceListCard extends StatelessWidget { @override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; - final imageUrl = '${RequestHelper.baseUrl}${voice.image}'; + + debugPrint('🎙️ Voice List - Original Image: ${voice.image}'); + final bool imageHasHttp = voice.image.startsWith('http'); + final bool imageHasBaseUrl = voice.image.contains(RequestHelper.baseUrl); + debugPrint('🎙️ Voice List - Image has HTTP: $imageHasHttp, has BaseURL: $imageHasBaseUrl'); + + final imageUrl = voice.image.startsWith('http') || voice.image.contains(RequestHelper.baseUrl) + ? voice.image + : '${RequestHelper.baseUrl}${voice.image}'; + + debugPrint('🖼️ یک لقمه استراتژی List - Image URL: $imageUrl'); return GestureDetector( onTap: onTap, diff --git a/lib/views/home/main/widgets/didvan_plus_section.dart b/lib/views/home/main/widgets/didvan_plus_section.dart index 1d8823c..a5bf5b0 100644 --- a/lib/views/home/main/widgets/didvan_plus_section.dart +++ b/lib/views/home/main/widgets/didvan_plus_section.dart @@ -36,21 +36,56 @@ class _DidvanPlusSectionState extends State { } void _initializeVideo() { - final videoUrl = '${RequestHelper.baseUrl}${widget.didvanPlus.file}'; - final fullUrl = '$videoUrl?accessToken=${RequestService.token}'; + debugPrint('🎥 Didvan Plus Section - Original File: ${widget.didvanPlus.file}'); + debugPrint('🎥 Didvan Plus Section - Original Image: ${widget.didvanPlus.image}'); + + // بررسی اینکه آیا لینک از قبل کامل است یا نه + final bool fileHasHttp = widget.didvanPlus.file.startsWith('http'); + final bool fileHasBaseUrl = widget.didvanPlus.file.contains(RequestHelper.baseUrl); + + debugPrint('🎥 File has HTTP: $fileHasHttp'); + debugPrint('🎥 File has BaseURL: $fileHasBaseUrl'); + + // برای دیدوان پلاس، لینک مستقیم از سرور استفاده می‌شود + final videoUrl = widget.didvanPlus.file.startsWith('http') || widget.didvanPlus.file.contains(RequestHelper.baseUrl) + ? widget.didvanPlus.file + : '${RequestHelper.baseUrl}${widget.didvanPlus.file}'; + + debugPrint('🎥 Video URL before token: $videoUrl'); + + // فقط به لینک‌های baseUrl سرور accessToken اضافه می‌شود، نه لینک‌های external مثل S3 + final bool needsToken = videoUrl.contains(RequestHelper.baseUrl); + debugPrint('🎥 Needs access token: $needsToken'); + + final String fullUrl; + if (needsToken) { + final separator = videoUrl.contains('?') ? '&' : '?'; + fullUrl = '$videoUrl${separator}accessToken=${RequestService.token}'; + } else { + fullUrl = videoUrl; // لینک‌های S3 نیازی به token ندارند + } - debugPrint('🎥 Didvan Plus Video URL: $fullUrl'); - debugPrint('🎥 Video file path: ${widget.didvanPlus.file}'); + debugPrint('🎥 Didvan Plus Section - Final URL: $fullUrl'); debugPrint('🎥 Base URL: ${RequestHelper.baseUrl}'); debugPrint( '🎥 Token exists: ${RequestService.token != null && RequestService.token!.isNotEmpty}'); - _videoController = VideoPlayerController.networkUrl( - Uri.parse(fullUrl), - httpHeaders: { - 'Authorization': 'Bearer ${RequestService.token}', - }, - )..initialize().then((_) { + // برای S3 signed URLs نباید Authorization header اضافه کنیم + if (needsToken) { + _videoController = VideoPlayerController.networkUrl( + Uri.parse(fullUrl), + httpHeaders: { + 'Authorization': 'Bearer ${RequestService.token}', + }, + ); + } else { + // برای S3 بدون Authorization header + _videoController = VideoPlayerController.networkUrl( + Uri.parse(fullUrl), + ); + } + + _videoController.initialize().then((_) { debugPrint('✅ Video initialized successfully'); setState(() { _chewieController = ChewieController( @@ -75,7 +110,9 @@ class _DidvanPlusSectionState extends State { color: Colors.black, child: Center( child: Image.network( - '${RequestHelper.baseUrl}${widget.didvanPlus.image}', + widget.didvanPlus.image.startsWith('http') || widget.didvanPlus.image.contains(RequestHelper.baseUrl) + ? widget.didvanPlus.image + : '${RequestHelper.baseUrl}${widget.didvanPlus.image}', fit: BoxFit.cover, ), ), @@ -95,8 +132,12 @@ class _DidvanPlusSectionState extends State { super.dispose(); } + @override @override Widget build(BuildContext context) { + debugPrint('🎬 Plus Section Build - Original File: ${widget.didvanPlus.file}'); + debugPrint('🎬 Plus Section Build - Original Image: ${widget.didvanPlus.image}'); + return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( @@ -161,7 +202,9 @@ class _DidvanPlusSectionState extends State { color: Colors.black, child: Center( child: Image.network( - '${RequestHelper.baseUrl}${widget.didvanPlus.image}', + widget.didvanPlus.image.startsWith('http') || widget.didvanPlus.image.contains(RequestHelper.baseUrl) + ? widget.didvanPlus.image + : '${RequestHelper.baseUrl}${widget.didvanPlus.image}', fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; diff --git a/lib/views/home/main/widgets/didvan_voice_detail_card.dart b/lib/views/home/main/widgets/didvan_voice_detail_card.dart index d573b56..248e9ff 100644 --- a/lib/views/home/main/widgets/didvan_voice_detail_card.dart +++ b/lib/views/home/main/widgets/didvan_voice_detail_card.dart @@ -24,7 +24,6 @@ class DidvanVoiceDetailCard extends StatefulWidget { class _DidvanVoiceDetailCardState extends State { late AudioPlayer _audioPlayer; - bool _isInitialized = false; @override void initState() { @@ -34,22 +33,40 @@ class _DidvanVoiceDetailCardState extends State { void _initializeAudio() async { _audioPlayer = VoiceService.audioPlayer; + + debugPrint('🎙️ Detail - Original File: ${widget.didvanVoice.file}'); + // برای یک لقمه استراتژی، لینک مستقیم از سرور استفاده می‌شود - final audioUrl = widget.didvanVoice.file.startsWith('http') + final audioFileUrl = widget.didvanVoice.file.startsWith('http') || widget.didvanVoice.file.contains(RequestHelper.baseUrl) ? widget.didvanVoice.file - : '${RequestHelper.baseUrl}${widget.didvanVoice.file}?accessToken=${RequestService.token}'; + : '${RequestHelper.baseUrl}${widget.didvanVoice.file}'; + + debugPrint('🎙️ Audio URL before token: $audioFileUrl'); + + // فقط به لینک‌های baseUrl سرور accessToken اضافه می‌شود، نه لینک‌های external مثل S3 + final bool needsToken = audioFileUrl.contains(RequestHelper.baseUrl); + debugPrint('🎙️ Needs access token: $needsToken'); + + final String audioUrl; + if (needsToken) { + final separator = audioFileUrl.contains('?') ? '&' : '?'; + audioUrl = '$audioFileUrl${separator}accessToken=${RequestService.token}'; + } else { + audioUrl = audioFileUrl; // لینک‌های S3 نیازی به token ندارند + } - debugPrint('🎙️ Didvan Voice Audio URL: $audioUrl'); + debugPrint('🎙️ یک لقمه استراتژی Detail - Final Audio URL: $audioUrl'); + debugPrint('🎙️ Detail - Original Image: ${widget.didvanVoice.image}'); + + final bool fileHasHttp = widget.didvanVoice.file.startsWith('http'); + final bool fileHasBaseUrl = widget.didvanVoice.file.contains(RequestHelper.baseUrl); + debugPrint('🎙️ Detail - File has HTTP: $fileHasHttp, has BaseURL: $fileHasBaseUrl'); + + debugPrint('🎙️ یک لقمه استراتژی Detail - Final Audio URL: $audioUrl'); try { VoiceService.src = audioUrl; await _audioPlayer.setUrl(audioUrl); - - if (mounted) { - setState(() { - _isInitialized = true; - }); - } } catch (e) { debugPrint('❌ Audio initialization error: $e'); } @@ -70,7 +87,17 @@ class _DidvanVoiceDetailCardState extends State { @override Widget build(BuildContext context) { - final imageUrl = '${RequestHelper.baseUrl}${widget.didvanVoice.image}'; + debugPrint('🎙️ Detail Build - Original Image: ${widget.didvanVoice.image}'); + + final bool imageHasHttp = widget.didvanVoice.image.startsWith('http'); + final bool imageHasBaseUrl = widget.didvanVoice.image.contains(RequestHelper.baseUrl); + debugPrint('🎙️ Detail Build - Image has HTTP: $imageHasHttp, has BaseURL: $imageHasBaseUrl'); + + final imageUrl = widget.didvanVoice.image.startsWith('http') || widget.didvanVoice.image.contains(RequestHelper.baseUrl) + ? widget.didvanVoice.image + : '${RequestHelper.baseUrl}${widget.didvanVoice.image}'; + + debugPrint('🖼️ یک لقمه استراتژی Detail - Image URL: $imageUrl'); return Container( margin: const EdgeInsets.symmetric(horizontal: 16), diff --git a/lib/views/home/main/widgets/didvan_voice_section.dart b/lib/views/home/main/widgets/didvan_voice_section.dart index bd981eb..8004896 100644 --- a/lib/views/home/main/widgets/didvan_voice_section.dart +++ b/lib/views/home/main/widgets/didvan_voice_section.dart @@ -40,11 +40,38 @@ class _DidvanVoiceSectionState extends State { @override Widget build(BuildContext context) { - final imageUrl = '${RequestHelper.baseUrl}${widget.didvanVoice.image}'; + debugPrint('🎙️ Original File: ${widget.didvanVoice.file}'); + debugPrint('🎙️ Original Image: ${widget.didvanVoice.image}'); + + final bool fileHasHttp = widget.didvanVoice.file.startsWith('http'); + final bool fileHasBaseUrl = widget.didvanVoice.file.contains(RequestHelper.baseUrl); + debugPrint('🎙️ File has HTTP: $fileHasHttp, has BaseURL: $fileHasBaseUrl'); + + final imageUrl = widget.didvanVoice.image.startsWith('http') || widget.didvanVoice.image.contains(RequestHelper.baseUrl) + ? widget.didvanVoice.image + : '${RequestHelper.baseUrl}${widget.didvanVoice.image}'; + // برای یک لقمه استراتژی، لینک مستقیم از سرور استفاده می‌شود - final audioUrl = widget.didvanVoice.file.startsWith('http') + final audioFileUrl = widget.didvanVoice.file.startsWith('http') || widget.didvanVoice.file.contains(RequestHelper.baseUrl) ? widget.didvanVoice.file - : '${RequestHelper.baseUrl}${widget.didvanVoice.file}?accessToken=${RequestService.token}'; + : '${RequestHelper.baseUrl}${widget.didvanVoice.file}'; + + debugPrint('🎙️ Audio URL before token: $audioFileUrl'); + + // فقط به لینک‌های baseUrl سرور accessToken اضافه می‌شود، نه لینک‌های external مثل S3 + final bool needsToken = audioFileUrl.contains(RequestHelper.baseUrl); + debugPrint('🎙️ Needs access token: $needsToken'); + + final String audioUrl; + if (needsToken) { + final separator = audioFileUrl.contains('?') ? '&' : '?'; + audioUrl = '$audioFileUrl${separator}accessToken=${RequestService.token}'; + } else { + audioUrl = audioFileUrl; // لینک‌های S3 نیازی به token ندارند + } + + debugPrint('🎙️ یک لقمه استراتژی - Image URL: $imageUrl'); + debugPrint('🎙️ یک لقمه استراتژی - Audio URL: $audioUrl'); return Container( margin: const EdgeInsets.symmetric(horizontal: 16), diff --git a/lib/views/widgets/hoshan_home_app_bar.dart b/lib/views/widgets/hoshan_home_app_bar.dart index fe2c039..84bdf01 100644 --- a/lib/views/widgets/hoshan_home_app_bar.dart +++ b/lib/views/widgets/hoshan_home_app_bar.dart @@ -7,6 +7,7 @@ import 'package:didvan/models/ai/ai_chat_args.dart'; import 'package:didvan/providers/user.dart'; import 'package:didvan/views/ai/history_ai_chat_state.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:persian_number_utility/persian_number_utility.dart'; @@ -308,13 +309,34 @@ class _HistoryDrawerContentState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); + final screenWidth = MediaQuery.of(context).size.width; + + // Calculate responsive width: + // For web: use min of 400px or 30% of screen width + // For mobile: use 75% of screen width + double drawerWidth; + if (kIsWeb) { + if (screenWidth > 1200) { + // Desktop: fixed width of 400px + drawerWidth = 400; + } else if (screenWidth > 768) { + // Tablet: 40% of screen width + drawerWidth = screenWidth * 0.4; + } else { + // Mobile web: 75% of screen width + drawerWidth = screenWidth * 0.75; + } + } else { + // Native mobile: 75% of screen width + drawerWidth = screenWidth * 0.75; + } return Align( alignment: Alignment.centerLeft, child: Material( color: Colors.transparent, child: Container( - width: MediaQuery.of(context).size.width * 0.75, + width: drawerWidth, height: MediaQuery.of(context).size.height, decoration: BoxDecoration( color: DesignConfig.isDark diff --git a/lib/views/widgets/skeleton_image.dart b/lib/views/widgets/skeleton_image.dart index 4272a4f..1e4f4b2 100644 --- a/lib/views/widgets/skeleton_image.dart +++ b/lib/views/widgets/skeleton_image.dart @@ -49,7 +49,7 @@ class SkeletonImage extends StatelessWidget { httpHeaders: {'Authorization': 'Bearer ${RequestService.token}'}, width: width, height: height, - imageUrl: imageUrl.startsWith('http') + imageUrl: imageUrl.startsWith('http') || imageUrl.contains(RequestHelper.baseUrl) ? imageUrl.replaceAll('\n', '') : RequestHelper.baseUrl + imageUrl.replaceAll('\n', ''), placeholder: (context, _) => ShimmerPlaceholder( diff --git a/lib/views/widgets/video/primary_controls.dart b/lib/views/widgets/video/primary_controls.dart index b4ce37c..de8dd1e 100644 --- a/lib/views/widgets/video/primary_controls.dart +++ b/lib/views/widgets/video/primary_controls.dart @@ -22,6 +22,7 @@ class _PrimaryControlsState extends State { bool isAnimating = false; bool isAnimatingForward = false; bool isAnimatingBackward = false; + bool _isLocallyPlaying = false; // bool isSpeedMenuOpen = false; double opacity = 1; @@ -35,8 +36,18 @@ class _PrimaryControlsState extends State { chewieController.videoPlayerController.addListener( () { position.value = chewieController.videoPlayerController.value.position; + if (mounted) { + final isPlaying = + chewieController.videoPlayerController.value.isPlaying; + if (_isLocallyPlaying != isPlaying) { + setState(() { + _isLocallyPlaying = isPlaying; + }); + } + } }, ); + _isLocallyPlaying = chewieController.videoPlayerController.value.isPlaying; } void _startHideControlsTimer() { @@ -54,23 +65,25 @@ class _PrimaryControlsState extends State { @override void dispose() { - _hideControlsTimer?.cancel(); // Clean up the timer + _hideControlsTimer?.cancel(); super.dispose(); } void _handlePlay() { { setState(() { - if (chewieController.isPlaying) { + if (_isLocallyPlaying) { chewieController.pause(); + _isLocallyPlaying = false; opacity = 1; } else { chewieController.play(); + _isLocallyPlaying = true; opacity = 0; } isAnimating = true; }); - _startHideControlsTimer(); // Restart the timer on tap + _startHideControlsTimer(); } } @@ -79,7 +92,7 @@ class _PrimaryControlsState extends State { setState(() { opacity = 1; }); - _startHideControlsTimer(); // Restart the timer on tap + _startHideControlsTimer(); } else { setState(() { opacity = 0; @@ -171,7 +184,7 @@ class _PrimaryControlsState extends State { shape: BoxShape.circle, color: Colors.black.withValues(alpha: 0.4)), child: Icon( - chewieController.isPlaying + _isLocallyPlaying ? CupertinoIcons.pause_fill : CupertinoIcons.play_fill, color: Colors.white, @@ -352,9 +365,9 @@ class _PrimaryControlsState extends State { chewieController.videoPlayerController.value.duration; if (duration.inSeconds == 0) { - return const SizedBox(); // Ya namayesh-e yek loading bar + return const SizedBox(); } - + double maxValue = duration.inMilliseconds.toDouble(); double currentValue = p.inMilliseconds.toDouble(); @@ -364,12 +377,12 @@ class _PrimaryControlsState extends State { data: SliderThemeData( trackHeight: 2, overlayShape: SliderComponentShape.noOverlay, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 8)), + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 8)), child: Slider( min: 0, max: maxValue, - value: currentValue.clamp(0.0, maxValue), // Estefade az clamp + value: currentValue.clamp(0.0, maxValue), onChanged: (value) async { await chewieController.pause(); position.value = Duration(milliseconds: value.round()); @@ -412,7 +425,7 @@ class _PrimaryControlsState extends State { child: InkWell( onTap: () => setState(() { chewieController.toggleFullScreen(); - _startHideControlsTimer(); // Restart the timer on tap + _startHideControlsTimer(); }), child: Icon( chewieController.isFullScreen diff --git a/lib/web_renderer_config.dart b/lib/web_renderer_config.dart new file mode 100644 index 0000000..7e52dc5 --- /dev/null +++ b/lib/web_renderer_config.dart @@ -0,0 +1,10 @@ +// تنظیمات Web Renderer برای جلوگیری از استفاده از CanvasKit و Google CDN + +import 'package:flutter/foundation.dart'; + +void configureWebRenderer() { + if (kIsWeb) { + // این تنظیمات در زمان build به flutter_bootstrap.js منتقل می‌شوند + // فایل flutter_bootstrap.js را ویرایش کرده‌ایم تا از HTML renderer استفاده کند + } +} diff --git a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index d029050..1950885 100644 --- a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -3,8 +3,8 @@ FLUTTER_ROOT=C:\flutter FLUTTER_APPLICATION_PATH=C:\Flutter Projects\didvan-app\didvan-app COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_BUILD_DIR=build -FLUTTER_BUILD_NAME=5.0.1 -FLUTTER_BUILD_NUMBER=7007 +FLUTTER_BUILD_NAME=5.1.0 +FLUTTER_BUILD_NUMBER=7008 DART_OBFUSCATION=false TRACK_WIDGET_CREATION=true TREE_SHAKE_ICONS=false diff --git a/macos/Flutter/ephemeral/flutter_export_environment.sh b/macos/Flutter/ephemeral/flutter_export_environment.sh index 35d1d85..01f6ce9 100644 --- a/macos/Flutter/ephemeral/flutter_export_environment.sh +++ b/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -4,8 +4,8 @@ export "FLUTTER_ROOT=C:\flutter" export "FLUTTER_APPLICATION_PATH=C:\Flutter Projects\didvan-app\didvan-app" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_BUILD_DIR=build" -export "FLUTTER_BUILD_NAME=5.0.1" -export "FLUTTER_BUILD_NUMBER=7007" +export "FLUTTER_BUILD_NAME=5.1.0" +export "FLUTTER_BUILD_NUMBER=7008" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" diff --git a/pubspec.yaml b/pubspec.yaml index 57fcd5d..e6b6206 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 5.0.1+7007 +version: 5.1.0+8008 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/web/flutter_bootstrap.js b/web/flutter_bootstrap.js index 4cab88d..c0de8d4 100644 --- a/web/flutter_bootstrap.js +++ b/web/flutter_bootstrap.js @@ -1,54 +1,11 @@ {{flutter_js}} {{flutter_build_config}} -// Override buildConfig to force local CanvasKit usage -if (window._flutter && window._flutter.buildConfig) { - // Remove engineRevision to prevent CDN usage - delete window._flutter.buildConfig.engineRevision; - // Add useLocalCanvasKit flag - window._flutter.buildConfig.useLocalCanvasKit = true; -} - -// Override the internal canvaskit path resolution function -if (window._flutter && window._flutter.loader) { - const originalLoad = window._flutter.loader.load; - window._flutter.loader.load = function(options) { - options = options || {}; - options.config = options.config || {}; - // Force local CanvasKit - options.config.canvasKitBaseUrl = "/canvaskit/"; - // Disable Google Fonts - options.config.canvasKitForceCpuOnly = false; - return originalLoad.call(this, options); - }; -} - -// Configure Flutter to use local CanvasKit files instead of CDN _flutter.loader.load({ - config: { - // Use local CanvasKit files to avoid issues with filtered domains - canvasKitBaseUrl: "/canvaskit/", - // Don't fetch from Google CDN - canvasKitForceCpuOnly: false, - }, onEntrypointLoaded: async function(engineInitializer) { - try { - let appRunner = await engineInitializer.initializeEngine(); - await appRunner.runApp(); - } catch (error) { - console.error('Failed to initialize Flutter app:', error); - // Fallback: Try with CPU-only rendering if WebGL fails - try { - let appRunner = await engineInitializer.initializeEngine({ - renderer: "html", - }); - await appRunner.runApp(); - } catch (fallbackError) { - console.error('Fallback initialization also failed:', fallbackError); - // Show a user-friendly error message - document.body.innerHTML = '

مشکل در بارگذاری برنامه

لطفا اتصال اینترنت خود را بررسی کنید و صفحه را مجددا بارگذاری کنید.

'; - } - } + const appRunner = await engineInitializer.initializeEngine({ + renderer: "html", + }); + await appRunner.runApp(); } -}); - +}); \ No newline at end of file diff --git a/web/index.html b/web/index.html index d7842ec..4c26b4f 100644 --- a/web/index.html +++ b/web/index.html @@ -1,130 +1,69 @@ - - + + - - - - - - + + + + + + + - - - - + + + + + - - + + + }); + } + - Didvan - - + - @media only screen and (min-width: 600px) { - .indicator { - width: 25vw; - } - } - - + +
+ Loading... +
- -
- -
- \ No newline at end of file