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 '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<String?> 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);

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_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<OffersPage> {
List<String> _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<OffersPage> {
_initializePage();
_initConnectivityListener();
_fetchInitialReservations();
_startPermissionMonitoring();
}
@override
void dispose() {
_locationServiceSubscription?.cancel();
_connectivitySubscription?.cancel();
_permissionCheckTimer?.cancel();
super.dispose();
}
@ -170,25 +175,63 @@ class _OffersPageState extends State<OffersPage> {
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<OffersBloc>().add(ClearOffers());
// Show GPS dialog if GPS becomes disabled
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
showGPSDialog(context);
}
});
}
}
});
}
Future<void> _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<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() {
final completer = Completer<void>();
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,

View File

@ -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<SplashScreen> {
}
Future<void> _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");
}
}
}

View File

@ -5,110 +5,124 @@ import 'package:proxibuy/core/config/app_colors.dart';
import 'package:proxibuy/core/gen/assets.gen.dart';
Future<void> 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");
}
}

View File

@ -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<void> 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');

View File

@ -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");
}
});
}