Copilot edit

This commit is contained in:
mohamadmahdi jebeli 2025-08-05 11:36:05 +03:30
parent 60c91e8fbb
commit b2c471c26e
6 changed files with 304 additions and 155 deletions

View File

@ -1,6 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_bloc/flutter_bloc.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/notification_preferences/bloc/notification_preferences_bloc.dart';
import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart'; import 'package:proxibuy/presentation/offer/bloc/offer_bloc.dart';
import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.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 'package:proxibuy/services/mqtt_service.dart';
import 'core/config/app_colors.dart'; import 'core/config/app_colors.dart';
import 'package:proxibuy/presentation/pages/splash_screen.dart'; import 'package:proxibuy/presentation/pages/splash_screen.dart';
void main() async { void main() async {
Future<String?> getFcmToken() async {
FirebaseMessaging messaging = FirebaseMessaging.instance;
String? token = await messaging.getToken();
print("🔥 Firebase Messaging Token: $token");
return token;
}
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await initializeService(); // Background service را بعداً initialize خواهیم کرد
// await initializeService();
HttpOverrides.global = MyHttpOverrides(); HttpOverrides.global = MyHttpOverrides();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

View File

@ -9,7 +9,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_background_service/flutter_background_service.dart'; import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/svg.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/api_config.dart';
import 'package:proxibuy/core/config/app_colors.dart'; import 'package:proxibuy/core/config/app_colors.dart';
import 'package:proxibuy/core/gen/assets.gen.dart'; import 'package:proxibuy/core/gen/assets.gen.dart';
@ -40,7 +41,9 @@ class _OffersPageState extends State<OffersPage> {
List<String> _selectedCategories = []; List<String> _selectedCategories = [];
StreamSubscription? _locationServiceSubscription; StreamSubscription? _locationServiceSubscription;
StreamSubscription? _connectivitySubscription; StreamSubscription? _connectivitySubscription;
Timer? _permissionCheckTimer;
bool _isGpsEnabled = false; bool _isGpsEnabled = false;
bool _isNotificationEnabled = false;
bool _isConnectedToInternet = true; bool _isConnectedToInternet = true;
@override @override
@ -50,12 +53,14 @@ class _OffersPageState extends State<OffersPage> {
_initializePage(); _initializePage();
_initConnectivityListener(); _initConnectivityListener();
_fetchInitialReservations(); _fetchInitialReservations();
_startPermissionMonitoring();
} }
@override @override
void dispose() { void dispose() {
_locationServiceSubscription?.cancel(); _locationServiceSubscription?.cancel();
_connectivitySubscription?.cancel(); _connectivitySubscription?.cancel();
_permissionCheckTimer?.cancel();
super.dispose(); super.dispose();
} }
@ -170,25 +175,63 @@ class _OffersPageState extends State<OffersPage> {
void _initLocationListener() { void _initLocationListener() {
_checkInitialGpsStatus(); _checkInitialGpsStatus();
_locationServiceSubscription = _locationServiceSubscription =
Geolocator.getServiceStatusStream().listen((status) { geo.Geolocator.getServiceStatusStream().listen((status) async {
final isEnabled = status == ServiceStatus.enabled; final isServiceEnabled = status == geo.ServiceStatus.enabled;
if (mounted && _isGpsEnabled != isEnabled) {
// 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(() { setState(() {
_isGpsEnabled = isEnabled; _isGpsEnabled = isGpsEnabledNew;
}); });
if (!isEnabled) { if (!isGpsEnabledNew) {
context.read<OffersBloc>().add(ClearOffers()); context.read<OffersBloc>().add(ClearOffers());
// Show GPS dialog if GPS becomes disabled
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
showGPSDialog(context);
}
});
} }
} }
}); });
} }
Future<void> _checkInitialGpsStatus() async { Future<void> _checkInitialGpsStatus() async {
final status = await Geolocator.isLocationServiceEnabled(); try {
if (mounted) { // Check if location service is enabled
setState(() { final serviceEnabled = await geo.Geolocator.isLocationServiceEnabled();
_isGpsEnabled = status;
}); // 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<OffersPage> {
} }
} }
void _startPermissionMonitoring() {
// Initial check
_checkAllPermissions();
// Periodic check every 5 seconds
_permissionCheckTimer = Timer.periodic(const Duration(seconds: 5), (timer) {
_checkAllPermissions();
});
}
Future<void> _checkAllPermissions() async {
if (!mounted) return;
// Check GPS permission and service
await _checkInitialGpsStatus();
// Check notification permission
await _checkInitialNotificationStatus();
}
Future<void> _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<void> _handleRefresh() { Future<void> _handleRefresh() {
final completer = Completer<void>(); final completer = Completer<void>();
final service = FlutterBackgroundService(); final service = FlutterBackgroundService();
@ -530,7 +622,7 @@ class OffersView extends StatelessWidget {
const SizedBox(height: 60), const SizedBox(height: 60),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
await Geolocator.openLocationSettings(); await geo.Geolocator.openLocationSettings();
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.confirm, backgroundColor: AppColors.confirm,

View File

@ -3,6 +3,7 @@ import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:geolocator/geolocator.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:proxibuy/core/config/app_colors.dart'; import 'package:proxibuy/core/config/app_colors.dart';
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.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/presentation/pages/offers_page.dart';
import 'package:proxibuy/core/gen/assets.gen.dart'; import 'package:proxibuy/core/gen/assets.gen.dart';
import 'package:proxibuy/services/mqtt_service.dart'; import 'package:proxibuy/services/mqtt_service.dart';
import 'package:proxibuy/services/background_service.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
class SplashScreen extends StatefulWidget { class SplashScreen extends StatefulWidget {
@ -212,11 +214,35 @@ class _SplashScreenState extends State<SplashScreen> {
} }
Future<void> _requestPermissions() async { Future<void> _requestPermissions() async {
await Permission.notification.request(); try {
// Request notification permission
await Permission.notification.request();
var status = await Permission.location.request(); // Check and request location permission
if (status.isGranted) { LocationPermission permission = await Geolocator.checkPermission();
await Permission.locationAlways.request();
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");
} }
} }
} }

View File

@ -5,110 +5,124 @@ import 'package:proxibuy/core/config/app_colors.dart';
import 'package:proxibuy/core/gen/assets.gen.dart'; import 'package:proxibuy/core/gen/assets.gen.dart';
Future<void> showGPSDialog(BuildContext context) async { Future<void> showGPSDialog(BuildContext context) async {
bool isLocationEnabled = await Geolocator.isLocationServiceEnabled(); try {
if (!isLocationEnabled) { bool isLocationEnabled = await Geolocator.isLocationServiceEnabled();
await showDialog( LocationPermission permission = await Geolocator.checkPermission();
// ignore: use_build_context_synchronously bool hasPermission = permission == LocationPermission.whileInUse ||
context: context, permission == LocationPermission.always;
barrierDismissible: false,
builder: (BuildContext context) { if (!isLocationEnabled || !hasPermission) {
return Dialog( await showDialog(
shape: // ignore: use_build_context_synchronously
RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), context: context,
elevation: 10, barrierDismissible: false,
backgroundColor: Colors.white, builder: (BuildContext context) {
child: Stack( return Dialog(
clipBehavior: Clip.none, shape:
alignment: Alignment.topCenter, RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
children: [ elevation: 10,
Padding( backgroundColor: Colors.white,
padding: const EdgeInsets.only( child: Stack(
top: 40, left: 20, right: 20, bottom: 20), clipBehavior: Clip.none,
child: Column( alignment: Alignment.topCenter,
mainAxisSize: MainAxisSize.min, children: [
children: [ Padding(
const SizedBox(height: 10), padding: const EdgeInsets.only(
const Padding( top: 40, left: 20, right: 20, bottom: 20),
padding: EdgeInsets.all(15.0), child: Column(
child: Text( mainAxisSize: MainAxisSize.min,
"فعالسازی موقعیت مکانی (GPS)", children: [
style: TextStyle( const SizedBox(height: 10),
fontSize: 16, const Padding(
fontWeight: FontWeight.bold, padding: EdgeInsets.all(15.0),
), child: Text(
), "فعالسازی موقعیت مکانی (GPS)",
), style: TextStyle(
const SizedBox(height: 5), fontSize: 16,
const Text( fontWeight: FontWeight.bold,
"برای اینکه بتونیم تخفیف‌های اطرافت رو سریع بهت اطلاع بدیم، اجازه بده به موقعیت مکانیت دسترسی داشته باشیم.",
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),
),
), ),
], ),
), ),
], const SizedBox(height: 5),
), Text(
), !isLocationEnabled
Positioned( ? "برای اینکه بتونیم تخفیف‌های اطرافت رو سریع بهت اطلاع بدیم، لطفاً GPS رو فعال کن."
top: -40, : "برای اینکه بتونیم تخفیف‌های اطرافت رو سریع بهت اطلاع بدیم، اجازه بده به موقعیت مکانیت دسترسی داشته باشیم.",
child: Container( style: const TextStyle(
decoration: BoxDecoration( color: AppColors.hint,
shape: BoxShape.circle, fontSize: 16,
boxShadow: [ ),
BoxShadow( textAlign: TextAlign.start,
// ignore: deprecated_member_use ),
color: Colors.black.withOpacity(0.3), const SizedBox(height: 20),
blurRadius: 8, Row(
offset: const Offset(0, 4), 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, Positioned(
radius: 40, top: -40,
child: Padding( child: Container(
padding: const EdgeInsets.all(12.0), decoration: BoxDecoration(
child: SvgPicture.asset(Assets.icons.globalSearch.path), 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");
} }
} }

View File

@ -2,11 +2,9 @@ import 'dart:async';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_background_service/flutter_background_service.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_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:proxibuy/services/mqtt_service.dart'; import 'package:proxibuy/services/mqtt_service.dart';
const notificationChannelId = 'proxibuy_foreground_service'; const notificationChannelId = 'proxibuy_foreground_service';
@ -31,15 +29,30 @@ void onStart(ServiceInstance service) async {
} }
Future<void> sendGpsData() async { Future<void> sendGpsData() async {
var locationStatus = await Permission.location.status;
if (!locationStatus.isGranted) {
debugPrint("Background Service: Location permission not granted.");
return;
}
try { 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( final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high); locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
));
final token = await storage.read(key: 'accessToken'); final token = await storage.read(key: 'accessToken');
final userID = await storage.read(key: 'userID'); final userID = await storage.read(key: 'userID');

View File

@ -2,11 +2,9 @@ import 'dart:async';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_background_service/flutter_background_service.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_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:proxibuy/services/mqtt_service.dart'; import 'package:proxibuy/services/mqtt_service.dart';
const notificationChannelId = 'proxibuy_foreground_service'; const notificationChannelId = 'proxibuy_foreground_service';
@ -41,27 +39,42 @@ void onStart(ServiceInstance service) async {
Timer.periodic(const Duration(seconds: 30), (timer) async { Timer.periodic(const Duration(seconds: 30), (timer) async {
debugPrint("✅ Background Service is running and sending location..."); debugPrint("✅ Background Service is running and sending location...");
var locationStatus = await Permission.location.status; try {
var locationAlwaysStatus = await Permission.locationAlways.status; // Check location service status
if (!locationStatus.isGranted || !locationAlwaysStatus.isGranted) { bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
debugPrint("Background Service: Permissions not granted. Task skipped."); if (!serviceEnabled) {
return; debugPrint("Background Service: Location service is disabled.");
} 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);
} }
if (mqttService.isConnected) { // Check location permission using Geolocator
final payload = {"userID": userID, "lat": position.latitude, "lng": position.longitude}; LocationPermission permission = await Geolocator.checkPermission();
mqttService.publish("proxybuy/sendGps", payload); if (permission == LocationPermission.denied ||
debugPrint("Background Service: GPS sent successfully."); 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");
} }
}); });
} }