diff --git a/lib/main.dart b/lib/main.dart index 160f894..e988cea 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,5 @@ import 'dart:io'; import 'package:firebase_core/firebase_core.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -13,23 +12,15 @@ import 'package:proxibuy/presentation/comment/bloc/comment_bloc.dart'; // این import 'package:proxibuy/presentation/notification_preferences/bloc/notification_preferences_bloc.dart'; import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart'; import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart'; -import 'package:proxibuy/services/background_service.dart'; import 'package:proxibuy/services/mqtt_service.dart'; import 'core/config/app_colors.dart'; import 'package:proxibuy/presentation/pages/splash_screen.dart'; void main() async { - - Future getFcmToken() async { - FirebaseMessaging messaging = FirebaseMessaging.instance; - String? token = await messaging.getToken(); - print("🔥 Firebase Messaging Token: $token"); - return token; - } - WidgetsFlutterBinding.ensureInitialized(); - await initializeService(); + // Background service را بعداً initialize خواهیم کرد + // await initializeService(); HttpOverrides.global = MyHttpOverrides(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); diff --git a/lib/presentation/pages/offers_page.dart b/lib/presentation/pages/offers_page.dart index 0ba60b7..904291e 100644 --- a/lib/presentation/pages/offers_page.dart +++ b/lib/presentation/pages/offers_page.dart @@ -9,7 +9,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_background_service/flutter_background_service.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:geolocator/geolocator.dart'; +import 'package:geolocator/geolocator.dart' as geo; +import 'package:permission_handler/permission_handler.dart'; import 'package:proxibuy/core/config/api_config.dart'; import 'package:proxibuy/core/config/app_colors.dart'; import 'package:proxibuy/core/gen/assets.gen.dart'; @@ -40,7 +41,9 @@ class _OffersPageState extends State { List _selectedCategories = []; StreamSubscription? _locationServiceSubscription; StreamSubscription? _connectivitySubscription; + Timer? _permissionCheckTimer; bool _isGpsEnabled = false; + bool _isNotificationEnabled = false; bool _isConnectedToInternet = true; @override @@ -50,12 +53,14 @@ class _OffersPageState extends State { _initializePage(); _initConnectivityListener(); _fetchInitialReservations(); + _startPermissionMonitoring(); } @override void dispose() { _locationServiceSubscription?.cancel(); _connectivitySubscription?.cancel(); + _permissionCheckTimer?.cancel(); super.dispose(); } @@ -170,25 +175,63 @@ class _OffersPageState extends State { void _initLocationListener() { _checkInitialGpsStatus(); _locationServiceSubscription = - Geolocator.getServiceStatusStream().listen((status) { - final isEnabled = status == ServiceStatus.enabled; - if (mounted && _isGpsEnabled != isEnabled) { + geo.Geolocator.getServiceStatusStream().listen((status) async { + final isServiceEnabled = status == geo.ServiceStatus.enabled; + + // Also check permission when service status changes + bool hasPermission = false; + if (isServiceEnabled) { + try { + geo.LocationPermission permission = await geo.Geolocator.checkPermission(); + hasPermission = permission == geo.LocationPermission.whileInUse || + permission == geo.LocationPermission.always; + } catch (e) { + debugPrint("Error checking permission in listener: $e"); + } + } + + final isGpsEnabledNew = isServiceEnabled && hasPermission; + + if (mounted && _isGpsEnabled != isGpsEnabledNew) { setState(() { - _isGpsEnabled = isEnabled; + _isGpsEnabled = isGpsEnabledNew; }); - if (!isEnabled) { + if (!isGpsEnabledNew) { context.read().add(ClearOffers()); + + // Show GPS dialog if GPS becomes disabled + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + showGPSDialog(context); + } + }); } } }); } Future _checkInitialGpsStatus() async { - final status = await Geolocator.isLocationServiceEnabled(); - if (mounted) { - setState(() { - _isGpsEnabled = status; - }); + try { + // Check if location service is enabled + final serviceEnabled = await geo.Geolocator.isLocationServiceEnabled(); + + // Check location permission + geo.LocationPermission permission = await geo.Geolocator.checkPermission(); + bool hasPermission = permission == geo.LocationPermission.whileInUse || + permission == geo.LocationPermission.always; + + if (mounted) { + setState(() { + _isGpsEnabled = serviceEnabled && hasPermission; + }); + } + } catch (e) { + debugPrint("Error checking GPS status: $e"); + if (mounted) { + setState(() { + _isGpsEnabled = false; + }); + } } } @@ -204,6 +247,55 @@ class _OffersPageState extends State { } } + void _startPermissionMonitoring() { + // Initial check + _checkAllPermissions(); + + // Periodic check every 5 seconds + _permissionCheckTimer = Timer.periodic(const Duration(seconds: 5), (timer) { + _checkAllPermissions(); + }); + } + + Future _checkAllPermissions() async { + if (!mounted) return; + + // Check GPS permission and service + await _checkInitialGpsStatus(); + + // Check notification permission + await _checkInitialNotificationStatus(); + } + + Future _checkInitialNotificationStatus() async { + try { + final status = await Permission.notification.status; + final isEnabled = status.isGranted; + + if (mounted && _isNotificationEnabled != isEnabled) { + setState(() { + _isNotificationEnabled = isEnabled; + }); + + // Show notification dialog if permission is revoked + if (!isEnabled) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + showNotificationPermissionDialog(context); + } + }); + } + } + } catch (e) { + debugPrint("Error checking notification status: $e"); + if (mounted) { + setState(() { + _isNotificationEnabled = false; + }); + } + } + } + Future _handleRefresh() { final completer = Completer(); final service = FlutterBackgroundService(); @@ -530,7 +622,7 @@ class OffersView extends StatelessWidget { const SizedBox(height: 60), ElevatedButton( onPressed: () async { - await Geolocator.openLocationSettings(); + await geo.Geolocator.openLocationSettings(); }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.confirm, diff --git a/lib/presentation/pages/splash_screen.dart b/lib/presentation/pages/splash_screen.dart index de16d76..df7a4e6 100644 --- a/lib/presentation/pages/splash_screen.dart +++ b/lib/presentation/pages/splash_screen.dart @@ -3,6 +3,7 @@ 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:geolocator/geolocator.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:proxibuy/core/config/app_colors.dart'; import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart'; @@ -10,6 +11,7 @@ import 'package:proxibuy/presentation/pages/onboarding_page.dart'; import 'package:proxibuy/presentation/pages/offers_page.dart'; import 'package:proxibuy/core/gen/assets.gen.dart'; import 'package:proxibuy/services/mqtt_service.dart'; +import 'package:proxibuy/services/background_service.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; class SplashScreen extends StatefulWidget { @@ -212,11 +214,35 @@ class _SplashScreenState extends State { } Future _requestPermissions() async { - await Permission.notification.request(); + try { + // Request notification permission + await Permission.notification.request(); - var status = await Permission.location.request(); - if (status.isGranted) { - await Permission.locationAlways.request(); + // Check and request location permission + LocationPermission permission = await Geolocator.checkPermission(); + + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + } + + // Only request "always" permission if basic location is granted + if (permission == LocationPermission.whileInUse || + permission == LocationPermission.always) { + // Request background location permission using permission_handler + await Permission.locationAlways.request(); + } + + debugPrint("Location permission status: $permission"); + + // حالا که permissions گرفته شد، background service را راه‌اندازی کنیم + try { + await initializeService(); + debugPrint("Background Service initialized successfully"); + } catch (e) { + debugPrint("Error initializing Background Service: $e"); + } + } catch (e) { + debugPrint("Error requesting permissions: $e"); } } } \ No newline at end of file diff --git a/lib/presentation/widgets/gps_dialog.dart b/lib/presentation/widgets/gps_dialog.dart index 33ce6fe..f8de155 100644 --- a/lib/presentation/widgets/gps_dialog.dart +++ b/lib/presentation/widgets/gps_dialog.dart @@ -5,110 +5,124 @@ import 'package:proxibuy/core/config/app_colors.dart'; import 'package:proxibuy/core/gen/assets.gen.dart'; Future showGPSDialog(BuildContext context) async { - bool isLocationEnabled = await Geolocator.isLocationServiceEnabled(); - if (!isLocationEnabled) { - await showDialog( - // ignore: use_build_context_synchronously - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return Dialog( - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), - elevation: 10, - backgroundColor: Colors.white, - child: Stack( - clipBehavior: Clip.none, - alignment: Alignment.topCenter, - children: [ - Padding( - padding: const EdgeInsets.only( - top: 40, left: 20, right: 20, bottom: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 10), - const Padding( - padding: EdgeInsets.all(15.0), - child: Text( - "فعالسازی موقعیت مکانی (GPS)", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - const SizedBox(height: 5), - const Text( - "برای اینکه بتونیم تخفیف‌های اطرافت رو سریع بهت اطلاع بدیم، اجازه بده به موقعیت مکانیت دسترسی داشته باشیم.", - style: TextStyle( - color: AppColors.hint, - fontSize: 16, - ), - textAlign: TextAlign.start, - ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () => Navigator.of(context).pop(), - child: Text("الان نه",style: TextStyle(color: AppColors.primary,fontWeight: FontWeight.bold),), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primary, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide(color: AppColors.border), - ), - padding: const EdgeInsets.symmetric( - horizontal: 45, vertical: 7), - ), - onPressed: () async { - await Geolocator.openLocationSettings(); - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - }, - child: const Text( - "فعالسازی", - style: TextStyle(color: Colors.white), - ), + try { + bool isLocationEnabled = await Geolocator.isLocationServiceEnabled(); + LocationPermission permission = await Geolocator.checkPermission(); + bool hasPermission = permission == LocationPermission.whileInUse || + permission == LocationPermission.always; + + if (!isLocationEnabled || !hasPermission) { + await showDialog( + // ignore: use_build_context_synchronously + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), + elevation: 10, + backgroundColor: Colors.white, + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.topCenter, + children: [ + Padding( + padding: const EdgeInsets.only( + top: 40, left: 20, right: 20, bottom: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 10), + const Padding( + padding: EdgeInsets.all(15.0), + child: Text( + "فعالسازی موقعیت مکانی (GPS)", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, ), - ], - ), - ], - ), - ), - Positioned( - top: -40, - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - // ignore: deprecated_member_use - color: Colors.black.withOpacity(0.3), - blurRadius: 8, - offset: const Offset(0, 4), + ), + ), + const SizedBox(height: 5), + Text( + !isLocationEnabled + ? "برای اینکه بتونیم تخفیف‌های اطرافت رو سریع بهت اطلاع بدیم، لطفاً GPS رو فعال کن." + : "برای اینکه بتونیم تخفیف‌های اطرافت رو سریع بهت اطلاع بدیم، اجازه بده به موقعیت مکانیت دسترسی داشته باشیم.", + style: const TextStyle( + color: AppColors.hint, + fontSize: 16, + ), + textAlign: TextAlign.start, + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Text("الان نه",style: TextStyle(color: AppColors.primary,fontWeight: FontWeight.bold),), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide(color: AppColors.border), + ), + padding: const EdgeInsets.symmetric( + horizontal: 45, vertical: 7), + ), + onPressed: () async { + if (!isLocationEnabled) { + await Geolocator.openLocationSettings(); + } else if (!hasPermission) { + await Geolocator.requestPermission(); + } + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + }, + child: Text( + !isLocationEnabled ? "فعالسازی GPS" : "اجازه دسترسی", + style: const TextStyle(color: Colors.white), + ), + ), + ], ), ], ), - child: CircleAvatar( - backgroundColor: Colors.white, - radius: 40, - child: Padding( - padding: const EdgeInsets.all(12.0), - child: SvgPicture.asset(Assets.icons.globalSearch.path), + ), + Positioned( + top: -40, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + // ignore: deprecated_member_use + color: Colors.black.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: CircleAvatar( + backgroundColor: Colors.white, + radius: 40, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: SvgPicture.asset(Assets.icons.globalSearch.path), + ), ), ), ), - ), - ], - ), - ); - }, - ); + ], + ), + ); + }, + ); + } + } catch (e) { + debugPrint("Error showing GPS dialog: $e"); } } \ No newline at end of file diff --git a/lib/services/background_service.dart b/lib/services/background_service.dart index 0ea7f04..ab56816 100644 --- a/lib/services/background_service.dart +++ b/lib/services/background_service.dart @@ -2,11 +2,9 @@ import 'dart:async'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_background_service/flutter_background_service.dart'; -import 'package:flutter_background_service_android/flutter_background_service_android.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:geolocator/geolocator.dart'; -import 'package:permission_handler/permission_handler.dart'; import 'package:proxibuy/services/mqtt_service.dart'; const notificationChannelId = 'proxibuy_foreground_service'; @@ -31,15 +29,30 @@ void onStart(ServiceInstance service) async { } Future sendGpsData() async { - var locationStatus = await Permission.location.status; - if (!locationStatus.isGranted) { - debugPrint("Background Service: Location permission not granted."); - return; - } - try { + // Check location service status + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + debugPrint("Background Service: Location service is disabled."); + return; + } + + // Check location permission + LocationPermission permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + debugPrint("Background Service: Location permission denied."); + return; + } + + if (permission == LocationPermission.deniedForever) { + debugPrint("Background Service: Location permission denied forever."); + return; + } + final position = await Geolocator.getCurrentPosition( - desiredAccuracy: LocationAccuracy.high); + locationSettings: const LocationSettings( + accuracy: LocationAccuracy.high, + )); final token = await storage.read(key: 'accessToken'); final userID = await storage.read(key: 'userID'); diff --git a/lib/services/background_tasks.dart b/lib/services/background_tasks.dart index 79511cf..1bcbe28 100644 --- a/lib/services/background_tasks.dart +++ b/lib/services/background_tasks.dart @@ -2,11 +2,9 @@ import 'dart:async'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_background_service/flutter_background_service.dart'; -import 'package:flutter_background_service_android/flutter_background_service_android.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:geolocator/geolocator.dart'; -import 'package:permission_handler/permission_handler.dart'; import 'package:proxibuy/services/mqtt_service.dart'; const notificationChannelId = 'proxibuy_foreground_service'; @@ -41,27 +39,42 @@ void onStart(ServiceInstance service) async { Timer.periodic(const Duration(seconds: 30), (timer) async { debugPrint("✅ Background Service is running and sending location..."); - var locationStatus = await Permission.location.status; - var locationAlwaysStatus = await Permission.locationAlways.status; - if (!locationStatus.isGranted || !locationAlwaysStatus.isGranted) { - debugPrint("Background Service: Permissions not granted. Task skipped."); - return; - } - - final position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high); - final token = await storage.read(key: 'accessToken'); - final userID = await storage.read(key: 'userID'); - - if (token != null && token.isNotEmpty && userID != null) { - if (!mqttService.isConnected) { - await mqttService.connect(token); + try { + // Check location service status + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + debugPrint("Background Service: Location service is disabled."); + return; } - - if (mqttService.isConnected) { - final payload = {"userID": userID, "lat": position.latitude, "lng": position.longitude}; - mqttService.publish("proxybuy/sendGps", payload); - debugPrint("Background Service: GPS sent successfully."); + + // Check location permission using Geolocator + LocationPermission permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied || + permission == LocationPermission.deniedForever) { + debugPrint("Background Service: Location permission not granted. Status: $permission"); + return; } + + final position = await Geolocator.getCurrentPosition( + locationSettings: const LocationSettings( + accuracy: LocationAccuracy.high, + )); + final token = await storage.read(key: 'accessToken'); + final userID = await storage.read(key: 'userID'); + + if (token != null && token.isNotEmpty && userID != null) { + if (!mqttService.isConnected) { + await mqttService.connect(token); + } + + if (mqttService.isConnected) { + final payload = {"userID": userID, "lat": position.latitude, "lng": position.longitude}; + mqttService.publish("proxybuy/sendGps", payload); + debugPrint("Background Service: GPS sent successfully."); + } + } + } catch (e) { + debugPrint("❌ Background Service Error: $e"); } }); }