diff --git a/lib/main.dart b/lib/main.dart index de78257..f1916ed 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,5 @@ +// lib/main.dart + import 'dart:io'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; diff --git a/lib/presentation/pages/offers_page.dart b/lib/presentation/pages/offers_page.dart index dc62343..bf97334 100644 --- a/lib/presentation/pages/offers_page.dart +++ b/lib/presentation/pages/offers_page.dart @@ -43,16 +43,80 @@ class _OffersPageState extends State { Timer? _locationTimer; bool _isSubscribedToOffers = false; bool _isGpsEnabled = false; + bool _isConnectedToInternet = true; @override void initState() { super.initState(); + _checkInitialConnectivity(); _initializePage(); _initConnectivityListener(); _fetchInitialReservations(); } + @override + void dispose() { + _locationServiceSubscription?.cancel(); + _mqttMessageSubscription?.cancel(); + _locationTimer?.cancel(); + _connectivitySubscription?.cancel(); + super.dispose(); + } + + Future _checkInitialConnectivity() async { + final connectivityResult = await Connectivity().checkConnectivity(); + if (!mounted) return; + setState(() { + _isConnectedToInternet = !connectivityResult.contains(ConnectivityResult.none); + }); + } + + Future _initializePage() async { + if (widget.showDialogsOnLoad) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (mounted) { + await showNotificationPermissionDialog(context); + await showGPSDialog(context); + } + }); + } + + await _loadPreferences(); + _initLocationListener(); + _subscribeToUserOffersOnLoad(); + } + + void _initConnectivityListener() { + _connectivitySubscription = + Connectivity().onConnectivityChanged.listen((results) { + final hasConnection = !results.contains(ConnectivityResult.none); + if (mounted && _isConnectedToInternet != hasConnection) { + setState(() { + _isConnectedToInternet = hasConnection; + }); + + if (hasConnection) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('اتصال به اینترنت برقرار شد.'), + backgroundColor: Colors.green, + ), + ); + } else { + context.read().add(ClearOffers()); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('اتصال به اینترنت قطع شد.'), + backgroundColor: Colors.red, + ), + ); + } + } + }); + } + Future _fetchInitialReservations() async { + if (!_isConnectedToInternet) return; try { const storage = FlutterSecureStorage(); final token = await storage.read(key: 'accessToken'); @@ -66,14 +130,11 @@ class _OffersPageState extends State { if (response.statusCode == 200 && mounted) { final List reserves = response.data['reserves']; - final List reservedIds = - reserves - .map( - (reserveData) => - (reserveData['Discount']['ID'] as String?) ?? '', - ) - .where((id) => id.isNotEmpty) - .toList(); + final List reservedIds = reserves + .map((reserveData) => + (reserveData['Discount']['ID'] as String?) ?? '') + .where((id) => id.isNotEmpty) + .toList(); context.read().setReservedIds(reservedIds); } @@ -82,63 +143,6 @@ class _OffersPageState extends State { } } - Future _initializePage() async { - WidgetsBinding.instance.addPostFrameCallback((_) async { - if (mounted) { - await showNotificationPermissionDialog(context); - await showGPSDialog(context); - } - }); - - await _loadPreferences(); - _checkAndConnectMqtt(); - _initLocationListener(); - } - - void _initConnectivityListener() { - _connectivitySubscription = Connectivity().onConnectivityChanged.listen(( - List results, - ) { - if (!results.contains(ConnectivityResult.none)) { - print("ℹ️ Network connection restored."); - _checkAndConnectMqtt(); - } else { - print("ℹ️ Network connection lost."); - } - }); - } - - Future _checkAndConnectMqtt() async { - final mqttService = context.read(); - if (!mqttService.isConnected) { - print("--- OffersPage: MQTT not connected. Attempting to connect..."); - final storage = const FlutterSecureStorage(); - final token = await storage.read(key: 'accessToken'); - if (token != null && token.isNotEmpty) { - try { - await mqttService.connect(token); - if (mqttService.isConnected && mounted) { - _subscribeToUserOffersOnLoad(); - } - } catch (e) { - print("❌ OffersPage: Error connecting to MQTT: $e"); - } - } - } else { - print("--- OffersPage: MQTT already connected."); - _subscribeToUserOffersOnLoad(); - } - } - - @override - void dispose() { - _locationServiceSubscription?.cancel(); - _mqttMessageSubscription?.cancel(); - _locationTimer?.cancel(); - _connectivitySubscription?.cancel(); - super.dispose(); - } - Future _subscribeToUserOffersOnLoad() async { final storage = const FlutterSecureStorage(); final userID = await storage.read(key: 'userID'); @@ -149,22 +153,20 @@ class _OffersPageState extends State { void _initLocationListener() { _checkInitialGpsStatus(); - _locationServiceSubscription = Geolocator.getServiceStatusStream().listen(( - status, - ) { + _locationServiceSubscription = + Geolocator.getServiceStatusStream().listen((status) { final isEnabled = status == ServiceStatus.enabled; if (mounted && _isGpsEnabled != isEnabled) { setState(() { _isGpsEnabled = isEnabled; }); - } - - if (isEnabled) { - _startSendingLocationUpdates(); - } else { - print("❌ Location Service Disabled. Stopping updates."); - _locationTimer?.cancel(); - context.read().add(ClearOffers()); + if (isEnabled) { + _startSendingLocationUpdates(); + } else { + debugPrint("❌ Location Service Disabled. Stopping updates."); + _locationTimer?.cancel(); + context.read().add(ClearOffers()); + } } }); } @@ -182,7 +184,7 @@ class _OffersPageState extends State { } void _startSendingLocationUpdates() { - print("🚀 Starting periodic location updates."); + debugPrint("🚀 Starting periodic location updates."); _locationTimer?.cancel(); _locationTimer = Timer.periodic(const Duration(seconds: 30), (timer) { _sendLocationUpdate(); @@ -191,9 +193,11 @@ class _OffersPageState extends State { } Future _sendLocationUpdate() async { + if (!_isConnectedToInternet || !_isGpsEnabled) return; + final mqttService = context.read(); if (!mqttService.isConnected) { - print("⚠️ MQTT not connected in OffersPage. Cannot send location."); + debugPrint("⚠️ MQTT not connected in OffersPage. Cannot send location."); return; } @@ -205,7 +209,7 @@ class _OffersPageState extends State { if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) { - print("🚫 Location permission denied by user."); + debugPrint("🚫 Location permission denied by user."); return; } @@ -217,15 +221,19 @@ class _OffersPageState extends State { final userID = await storage.read(key: 'userID'); if (userID == null) { - print("⚠️ UserID not found. Cannot send location."); + debugPrint("⚠️ UserID not found. Cannot send location."); return; } - final payload = {"userID": userID, "lat": 32.6685, "lng": 51.6826}; + final payload = { + "userID": userID, + "lat":32.6685, + "lng": 51.6826 + }; mqttService.publish("proxybuy/sendGps", payload); } catch (e) { - print("❌ Error sending location update in OffersPage: $e"); + debugPrint("❌ Error sending location update in OffersPage: $e"); } } @@ -233,6 +241,11 @@ class _OffersPageState extends State { if (_isSubscribedToOffers) return; final mqttService = context.read(); + if (!mqttService.isConnected) { + debugPrint("⚠️ Cannot subscribe. MQTT client is not connected."); + return; + } + final topic = 'user-proxybuy/$userID'; mqttService.subscribe(topic); _isSubscribedToOffers = true; @@ -240,28 +253,25 @@ class _OffersPageState extends State { _mqttMessageSubscription = mqttService.messages.listen((message) { final data = message['data']; - if (data == null) { + if (data == null || data is! List) { if (mounted) { context.read().add(const OffersReceivedFromMqtt([])); } return; } - if (data is List) { - try { - List offers = - data - .whereType>() - .map((json) => OfferModel.fromJson(json)) - .toList(); + try { + List offers = data + .whereType>() + .map((json) => OfferModel.fromJson(json)) + .toList(); - if (mounted) { - context.read().add(OffersReceivedFromMqtt(offers)); - } - } catch (e, stackTrace) { - print("❌ Error parsing offers from MQTT: $e"); - print(stackTrace); + if (mounted) { + context.read().add(OffersReceivedFromMqtt(offers)); } + } catch (e, stackTrace) { + debugPrint("❌ Error parsing offers from MQTT: $e"); + debugPrint(stackTrace.toString()); } }); } @@ -279,52 +289,52 @@ class _OffersPageState extends State { } Widget _buildFavoriteCategoriesSection() { - return Padding( - padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'دسته‌بندی‌های مورد علاقه شما', - style: TextStyle(fontSize: 19, fontWeight: FontWeight.bold), - ), - TextButton( - onPressed: () async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const NotificationPreferencesPage( - loadFavoritesOnStart: true, - ), - ), - ); - - if (!mounted) return; - - context - .read() - .add(ResetSubmissionStatus()); - - _loadPreferences(); - }, - child: Row( - children: [ - SvgPicture.asset(Assets.icons.edit.path), - const SizedBox(width: 4), - const Text( - 'ویرایش', - style: TextStyle(color: AppColors.active), - ), - ], + return Padding( + padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'دسته‌بندی‌های مورد علاقه شما', + style: TextStyle(fontSize: 19, fontWeight: FontWeight.bold), ), - ), - ], - ), - const Divider(height: 1), - const SizedBox(height: 12), - if (_selectedCategories.isEmpty) + TextButton( + onPressed: () async { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const NotificationPreferencesPage( + loadFavoritesOnStart: true, + ), + ), + ); + + if (!mounted) return; + + context + .read() + .add(ResetSubmissionStatus()); + + _loadPreferences(); + }, + child: Row( + children: [ + SvgPicture.asset(Assets.icons.edit.path), + const SizedBox(width: 4), + const Text( + 'ویرایش', + style: TextStyle(color: AppColors.active), + ), + ], + ), + ), + ], + ), + const Divider(height: 1), + const SizedBox(height: 12), + if (_selectedCategories.isEmpty) const Padding( padding: EdgeInsets.only(bottom: 8.0), child: Text( @@ -336,20 +346,19 @@ class _OffersPageState extends State { Wrap( spacing: 8.0, runSpacing: 8.0, - children: - _selectedCategories.map((category) { - return Container( - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 6.0, - ), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(20.0), - ), - child: Text(category), - ); - }).toList(), + children: _selectedCategories.map((category) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 6.0, + ), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(20.0), + ), + child: Text(category), + ); + }).toList(), ), ], ), @@ -372,11 +381,11 @@ class _OffersPageState extends State { child: Assets.icons.logoWithName.svg(height: 40, width: 200), ), actions: [ - IconButton(onPressed: () {}, icon: Assets.icons.notification.svg()), + IconButton( + onPressed: () {}, icon: Assets.icons.notification.svg()), BlocBuilder( builder: (context, state) { final reservedCount = state.reservedProductIds.length; - return Stack( alignment: Alignment.center, children: [ @@ -407,10 +416,8 @@ class _OffersPageState extends State { decoration: BoxDecoration( color: Colors.green, shape: BoxShape.circle, - border: Border.all( - color: Colors.white, - width: 1.5, - ), + border: + Border.all(color: Colors.white, width: 1.5), ), constraints: const BoxConstraints( minWidth: 18, @@ -443,7 +450,10 @@ class _OffersPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildFavoriteCategoriesSection(), - OffersView(isGpsEnabled: _isGpsEnabled), + OffersView( + isGpsEnabled: _isGpsEnabled, + isConnectedToInternet: _isConnectedToInternet, + ), ], ), ), @@ -454,17 +464,26 @@ class _OffersPageState extends State { class OffersView extends StatelessWidget { final bool isGpsEnabled; + final bool isConnectedToInternet; - const OffersView({super.key, required this.isGpsEnabled}); + const OffersView({ + super.key, + required this.isGpsEnabled, + required this.isConnectedToInternet, + }); @override Widget build(BuildContext context) { + if (!isConnectedToInternet) { + return _buildNoInternetUI(context); + } + + if (!isGpsEnabled) { + return _buildGpsActivationUI(context); + } + return BlocBuilder( builder: (context, state) { - if (!isGpsEnabled) { - return _buildGpsActivationUI(context); - } - if (state is OffersInitial) { return const SizedBox( height: 300, @@ -513,12 +532,10 @@ class OffersView extends StatelessWidget { final offersForCategory = groupedOffers[category]!; return CategoryOffersRow( - categoryTitle: category, - offers: offersForCategory, - ) - .animate() - .fade(duration: 500.ms) - .slideY(begin: 0.3, duration: 400.ms, curve: Curves.easeOut); + categoryTitle: category, + offers: offersForCategory, + ).animate().fade(duration: 500.ms).slideY( + begin: 0.3, duration: 400.ms, curve: Curves.easeOut); }, ); } @@ -578,4 +595,28 @@ class OffersView extends StatelessWidget { ), ); } -} + + Widget _buildNoInternetUI(BuildContext context) { + return SizedBox( + height: 300, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.wifi_off_rounded, size: 80, color: Colors.grey[400]), + const SizedBox(height: 20), + const Text( + "اتصال به اینترنت برقرار نیست", + style: TextStyle(fontSize: 18, color: Colors.grey), + ), + const SizedBox(height: 10), + const Text( + "لطفاً اتصال خود را بررسی کرده و دوباره تلاش کنید.", + style: TextStyle(color: Colors.grey), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/pages/product_detail_page.dart b/lib/presentation/pages/product_detail_page.dart index d6820f8..310f2f1 100644 --- a/lib/presentation/pages/product_detail_page.dart +++ b/lib/presentation/pages/product_detail_page.dart @@ -657,23 +657,25 @@ class _ProductDetailViewState extends State { Localizations.override( context: context, locale: const Locale('en'), - child: SlideCountdown( - duration: remainingDuration, - slideDirection: SlideDirection.up, - separator: ':', - style: const TextStyle( - fontSize: 50, - fontWeight: FontWeight.bold, - color: AppColors.countdown, + child: Center( + child: SlideCountdown( + duration: remainingDuration, + slideDirection: SlideDirection.up, + separator: ':', + style: const TextStyle( + fontSize: 50, + fontWeight: FontWeight.bold, + color: AppColors.countdown, + ), + separatorStyle: const TextStyle( + fontSize: 35, + color: AppColors.countdown, + ), + decoration: const BoxDecoration(color: Colors.white), + shouldShowDays: (d) => d.inDays > 0, + shouldShowHours: (d) => d.inHours > 0, + shouldShowMinutes: (d) => d.inSeconds > 0, ), - separatorStyle: const TextStyle( - fontSize: 40, - color: AppColors.countdown, - ), - decoration: const BoxDecoration(color: Colors.white), - shouldShowDays: (d) => d.inDays > 0, - shouldShowHours: (d) => d.inHours > 0, - shouldShowMinutes: (d) => d.inSeconds > 0, ), ), const SizedBox(height: 4), diff --git a/lib/presentation/pages/splash_screen.dart b/lib/presentation/pages/splash_screen.dart index 679ecee..cf5648b 100644 --- a/lib/presentation/pages/splash_screen.dart +++ b/lib/presentation/pages/splash_screen.dart @@ -1,9 +1,10 @@ -// lib/presentation/pages/splash_screen.dart - import 'dart:async'; +import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:proxibuy/core/config/app_colors.dart'; +import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart'; import 'package:proxibuy/presentation/pages/onboarding_page.dart'; import 'package:proxibuy/presentation/pages/offers_page.dart'; import 'package:proxibuy/core/gen/assets.gen.dart'; @@ -17,65 +18,171 @@ class SplashScreen extends StatefulWidget { } class _SplashScreenState extends State { + bool _showRetryUI = false; + String _errorMessage = ""; + bool _isChecking = true; @override void initState() { super.initState(); - // با کمی تاخیر برای نمایش لوگو، فرآیند را شروع می‌کنیم - Timer(const Duration(seconds: 2), _checkAuthAndNavigate); + Future.delayed(const Duration(seconds: 2), _startProcess); } - Future _checkAuthAndNavigate() async { + Future _startProcess() async { + if (!mounted) return; + + final hasInternet = await _checkInternet(); + if (!hasInternet) { + setState(() { + _isChecking = false; + _showRetryUI = true; + _errorMessage = "اتصال به اینترنت برقرار نیست."; + }); + return; + } + + setState(() { + _isChecking = false; + }); + } + + Future _checkInternet() async { + final connectivityResult = await Connectivity().checkConnectivity(); + return !connectivityResult.contains(ConnectivityResult.none); + } + + Future _connectAndNavigate() async { + if (!await _checkInternet()) { + setState(() { + _showRetryUI = true; + _errorMessage = "اتصال اینترنت قطع شد."; + }); + return; + } + + final mqttService = context.read(); final storage = const FlutterSecureStorage(); final token = await storage.read(key: 'accessToken'); if (token != null && token.isNotEmpty) { - // کاربر احراز هویت شده است - debugPrint("--- SplashScreen: User is authenticated. Connecting to MQTT..."); + if (mqttService.isConnected) { + _navigateToOffers(); + return; + } try { - final mqttService = context.read(); - - // ۱. منتظر می‌مانیم تا اتصال کامل برقرار شود - await mqttService.connect(token); - - // ۲. پس از اطمینان از اتصال، به صفحه بعد می‌رویم + await mqttService.connect(token).timeout(const Duration(seconds: 10)); if (mounted && mqttService.isConnected) { - debugPrint("--- SplashScreen: MQTT Connected. Navigating to OffersPage."); - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (_) => const OffersPage()), - ); + _navigateToOffers(); } else if (mounted) { - // اگر به هر دلیلی پس از اتمام متد، اتصال برقرار نبود - debugPrint("--- SplashScreen: MQTT connection failed after attempt. Navigating to Onboarding."); - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (_) => const OnboardingPage()), - ); + setState(() { + _showRetryUI = true; + _errorMessage = "خطا در اتصال به سرور."; + }); } } catch (e) { - debugPrint("❌ SplashScreen: Critical error during MQTT connection: $e"); - if (mounted) { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (_) => const OnboardingPage()), - ); + if (mounted) { + setState(() { + _showRetryUI = true; + _errorMessage = "خطای پیش‌بینی نشده در اتصال."; + }); } } - } else { - // کاربر احراز هویت نشده است - debugPrint("--- SplashScreen: User not authenticated. Navigating to Onboarding."); - if (mounted) { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (_) => const OnboardingPage()), - ); - } } } + void _navigateToOffers() { + if (!mounted) return; + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => const OffersPage(showDialogsOnLoad: true)), + ); + } + + void _navigateToAuth() { + if (!mounted) return; + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => const OnboardingPage()), + ); + } + + void _onRetry() { + setState(() { + _showRetryUI = false; + _errorMessage = ""; + _isChecking = true; + }); + _startProcess(); + } + @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, - body: Center( - child: Assets.icons.logo.svg(height: 160), + body: BlocListener( + listener: (context, state) { + if (_showRetryUI) return; + + if (state is AuthSuccess) { + _connectAndNavigate(); + } else if (state is AuthInitial || state is AuthFailure) { + _navigateToAuth(); + } + }, + child: Center( + child: _buildBody(), + ), + ), + ); + } + + Widget _buildBody() { + if (_showRetryUI) { + return _buildRetryWidget(); + } else { + return _buildLogo(); + } + } + + Widget _buildLogo() { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Assets.icons.logo.svg(height: 160), + if (_isChecking) ...[ + const SizedBox(height: 32), + const CircularProgressIndicator(), + ] + ], + ); + } + + Widget _buildRetryWidget() { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.wifi_off_rounded, size: 80, color: Colors.grey.shade400), + const SizedBox(height: 24), + Text( + _errorMessage, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 18, color: Colors.black87, height: 1.5), + ), + const SizedBox(height: 32), + ElevatedButton( + onPressed: _onRetry, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(50)), + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 12), + ), + child: const Text( + "تلاش مجدد", + style: TextStyle(fontSize: 16), + ), + ) + ], ), ); } diff --git a/lib/services/mqtt_service.dart b/lib/services/mqtt_service.dart index f53b2a7..91dffbf 100644 --- a/lib/services/mqtt_service.dart +++ b/lib/services/mqtt_service.dart @@ -7,7 +7,7 @@ import 'package:mqtt_client/mqtt_client.dart'; import 'package:mqtt_client/mqtt_server_client.dart'; class MqttService { - late MqttServerClient client; + MqttServerClient? client; final String server = '5.75.197.180'; final int port = 1883; final StreamController> _messageStreamController = @@ -15,7 +15,9 @@ class MqttService { Stream> get messages => _messageStreamController.stream; - bool get isConnected => client.connectionStatus?.state == MqttConnectionState.connected; + bool get isConnected { + return client?.connectionStatus?.state == MqttConnectionState.connected; + } Future connect(String token) async { final String clientId = @@ -24,10 +26,10 @@ class MqttService { final String password = token; client = MqttServerClient.withPort(server, clientId, port); - client.logging(on: true); - client.keepAlivePeriod = 60; - client.autoReconnect = true; - client.setProtocolV311(); + client!.logging(on: true); + client!.keepAlivePeriod = 60; + client!.autoReconnect = true; + client!.setProtocolV311(); debugPrint('--- [MQTT] Attempting to connect...'); debugPrint('--- [MQTT] Server: $server:$port'); @@ -38,11 +40,11 @@ class MqttService { .startClean() .authenticateAs(username, password); - client.connectionMessage = connMessage; + client!.connectionMessage = connMessage; - client.onConnected = () { + client!.onConnected = () { debugPrint('✅ [MQTT] Connected successfully.'); - client.updates!.listen((List> c) { + client!.updates!.listen((List> c) { final MqttPublishMessage recMess = c[0].payload as MqttPublishMessage; final String payload = @@ -62,43 +64,43 @@ class MqttService { }); }; - client.onDisconnected = () { + client!.onDisconnected = () { debugPrint('❌ [MQTT] Disconnected.'); }; - client.onAutoReconnect = () { + client!.onAutoReconnect = () { debugPrint('↪️ [MQTT] Auto-reconnecting...'); }; - client.onAutoReconnected = () { + client!.onAutoReconnected = () { debugPrint('✅ [MQTT] Auto-reconnected successfully.'); }; - client.onSubscribed = (String topic) { + client!.onSubscribed = (String topic) { debugPrint('✅ [MQTT] Subscribed to topic: $topic'); }; - client.pongCallback = () { + client!.pongCallback = () { debugPrint('🏓 [MQTT] Ping response received'); }; try { - await client.connect(); + await client!.connect(); } on NoConnectionException catch (e) { debugPrint('❌ [MQTT] Connection failed - No Connection Exception: $e'); - client.disconnect(); + client?.disconnect(); } on SocketException catch (e) { debugPrint('❌ [MQTT] Connection failed - Socket Exception: $e'); - client.disconnect(); + client?.disconnect(); } catch (e) { debugPrint('❌ [MQTT] Connection failed - General Exception: $e'); - client.disconnect(); + client?.disconnect(); } } void subscribe(String topic) { if (isConnected) { - client.subscribe(topic, MqttQos.atLeastOnce); + client?.subscribe(topic, MqttQos.atLeastOnce); } else { debugPrint("⚠️ [MQTT] Cannot subscribe. Client is not connected."); } @@ -115,7 +117,7 @@ class MqttService { debugPrint('>>>>> [MQTT] Payload: $payloadString'); debugPrint('>>>>> ======================= >>>>>'); - client.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!); + client?.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!); } else { debugPrint("⚠️ [MQTT] Cannot publish. Client is not connected."); } @@ -124,6 +126,6 @@ class MqttService { void dispose() { debugPrint("--- [MQTT] Disposing MQTT Service."); _messageStreamController.close(); - client.disconnect(); + client?.disconnect(); } -} \ No newline at end of file +} \ No newline at end of file