location background activity
This commit is contained in:
parent
5d00779bbf
commit
f4cd446cde
|
|
@ -14,29 +14,26 @@ android {
|
|||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.example.proxibuy"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = 23
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
multiDexEnabled = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
|
|
@ -45,3 +42,8 @@ android {
|
|||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// این نسخه به آخرین نسخه مورد نیاز آپدیت شد
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
}
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application
|
||||
android:label="Proxibuy"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<!-- android:networkSecurityConfig="@xml/network_security_config"> -->
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
|
@ -22,10 +23,7 @@
|
|||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
|
|
@ -35,17 +33,16 @@
|
|||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
|
||||
<service
|
||||
android:name="id.flutter.flutter_background_service.BackgroundService"
|
||||
android:foregroundServiceType="location" />
|
||||
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
|
|
|
|||
|
|
@ -7,4 +7,5 @@ class ApiConfig {
|
|||
static const String getFavoriteCategories = "/user/getfavoriteCategory";
|
||||
static const String addReservation = "/reservation/add";
|
||||
static const String getReservations = "/reservation/get";
|
||||
static const String updateFcmToken = "/user/firebaseUpdate";
|
||||
}
|
||||
|
|
@ -149,7 +149,7 @@ class OfferModel extends Equatable {
|
|||
rating: 0.0,
|
||||
ratingCount: 0,
|
||||
comments: [],
|
||||
discountInfo: json['Description'],
|
||||
discountInfo: json['Description'] ?? 'توضیحات موجود نیست',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -157,7 +157,29 @@ class OfferModel extends Equatable {
|
|||
imageUrls.isNotEmpty ? imageUrls.first : 'https://via.placeholder.com/400x200.png?text=No+Image';
|
||||
|
||||
@override
|
||||
List<Object?> get props => [id];
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
storeName,
|
||||
title,
|
||||
discount,
|
||||
imageUrls,
|
||||
category,
|
||||
distanceInMeters,
|
||||
expiryTime,
|
||||
address,
|
||||
workingHours,
|
||||
discountType,
|
||||
isOpen,
|
||||
rating,
|
||||
ratingCount,
|
||||
latitude,
|
||||
longitude,
|
||||
originalPrice,
|
||||
finalPrice,
|
||||
features,
|
||||
discountInfo,
|
||||
comments,
|
||||
];
|
||||
|
||||
String get distanceAsString {
|
||||
if (distanceInMeters < 1000) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// lib/main.dart
|
||||
|
||||
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,15 +12,35 @@ import 'package:proxibuy/presentation/auth/bloc/auth_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 {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
HttpOverrides.global = MyHttpOverrides();
|
||||
|
||||
Future<String?> getFcmToken() async {
|
||||
FirebaseMessaging messaging = FirebaseMessaging.instance;
|
||||
String? token = await messaging.getToken();
|
||||
print("🔥 Firebase Messaging Token: $token");
|
||||
return token;
|
||||
}
|
||||
|
||||
// FirebaseMessaging.instance.onTokenRefresh
|
||||
// .listen((fcmToken) {
|
||||
// // TODO: If necessary send token to application server.
|
||||
// // Note: This callback is fired at each app startup and whenever a new
|
||||
// // token is generated.
|
||||
// })
|
||||
// .onError((err) {
|
||||
// // Error getting token.
|
||||
// });
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await initializeService();
|
||||
|
||||
HttpOverrides.global = MyHttpOverrides();
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
Animate.restartOnHotReload = true;
|
||||
runApp(const MyApp());
|
||||
|
|
@ -34,22 +53,15 @@ class MyApp extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
RepositoryProvider<MqttService>(
|
||||
create: (context) => MqttService(),
|
||||
),
|
||||
RepositoryProvider<MqttService>(create: (context) => MqttService()),
|
||||
BlocProvider<AuthBloc>(
|
||||
create: (context) => AuthBloc()..add(CheckAuthStatusEvent()),
|
||||
),
|
||||
BlocProvider<ReservationCubit>(
|
||||
create: (context) => ReservationCubit(),
|
||||
),
|
||||
BlocProvider<OffersBloc>(
|
||||
create: (context) => OffersBloc(),
|
||||
),
|
||||
BlocProvider<ReservationCubit>(create: (context) => ReservationCubit()),
|
||||
BlocProvider<OffersBloc>(create: (context) => OffersBloc()),
|
||||
BlocProvider<NotificationPreferencesBloc>(
|
||||
create: (context) => NotificationPreferencesBloc(),
|
||||
),
|
||||
|
||||
],
|
||||
child: MaterialApp(
|
||||
title: 'Proxibuy',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
// lib/presentation/auth/bloc/auth_bloc.dart
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
|
@ -31,10 +29,13 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||
on<VerifyOTPEvent>(_onVerifyOTP);
|
||||
on<UpdateUserInfoEvent>(_onUpdateUserInfo);
|
||||
on<LogoutEvent>(_onLogout);
|
||||
on<SendFcmTokenEvent>(_onSendFcmToken);
|
||||
}
|
||||
|
||||
Future<void> _onCheckAuthStatus(
|
||||
CheckAuthStatusEvent event, Emitter<AuthState> emit) async {
|
||||
CheckAuthStatusEvent event,
|
||||
Emitter<AuthState> emit,
|
||||
) async {
|
||||
final token = await _storage.read(key: 'accessToken');
|
||||
if (token != null && token.isNotEmpty) {
|
||||
emit(AuthSuccess());
|
||||
|
|
@ -52,10 +53,12 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||
);
|
||||
if (isClosed) return;
|
||||
if (response.statusCode == 200) {
|
||||
emit(AuthCodeSentSuccess(
|
||||
emit(
|
||||
AuthCodeSentSuccess(
|
||||
phone: event.phoneNumber,
|
||||
countryCode: event.countryCode,
|
||||
));
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(AuthFailure(response.data['message'] ?? 'خطایی رخ داد'));
|
||||
}
|
||||
|
|
@ -65,7 +68,10 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _onVerifyOTP(VerifyOTPEvent event, Emitter<AuthState> emit) async {
|
||||
Future<void> _onVerifyOTP(
|
||||
VerifyOTPEvent event,
|
||||
Emitter<AuthState> emit,
|
||||
) async {
|
||||
emit(AuthLoading());
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
|
|
@ -97,8 +103,12 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||
}
|
||||
|
||||
Future<void> _onUpdateUserInfo(
|
||||
UpdateUserInfoEvent event, Emitter<AuthState> emit) async {
|
||||
debugPrint("AuthBloc: 🔵 ایونت UpdateUserInfoEvent دریافت شد با نام: ${event.name}");
|
||||
UpdateUserInfoEvent event,
|
||||
Emitter<AuthState> emit,
|
||||
) async {
|
||||
debugPrint(
|
||||
"AuthBloc: 🔵 ایونت UpdateUserInfoEvent دریافت شد با نام: ${event.name}",
|
||||
);
|
||||
emit(AuthLoading());
|
||||
try {
|
||||
final token = await _storage.read(key: 'accessToken');
|
||||
|
|
@ -115,7 +125,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||
options: Options(headers: {'Authorization': 'Bearer $token'}),
|
||||
);
|
||||
|
||||
debugPrint("AuthBloc: 🟠 پاسخ سرور دریافت شد. StatusCode: ${response.statusCode}");
|
||||
debugPrint(
|
||||
"AuthBloc: 🟠 پاسخ سرور دریافت شد. StatusCode: ${response.statusCode}",
|
||||
);
|
||||
|
||||
if (isClosed) {
|
||||
debugPrint("AuthBloc: 🔴 خطا: BLoC قبل از اتمام عملیات بسته شده است.");
|
||||
|
|
@ -123,22 +135,49 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
debugPrint("AuthBloc: ✅ درخواست موفق بود. در حال emit کردن AuthSuccess...");
|
||||
debugPrint(
|
||||
"AuthBloc: ✅ درخواست موفق بود. در حال emit کردن AuthSuccess...",
|
||||
);
|
||||
emit(AuthSuccess());
|
||||
} else {
|
||||
debugPrint("AuthBloc: 🔴 سرور پاسخ ناموفق داد: ${response.data['message']}");
|
||||
debugPrint(
|
||||
"AuthBloc: 🔴 سرور پاسخ ناموفق داد: ${response.data['message']}",
|
||||
);
|
||||
emit(AuthFailure(response.data['message'] ?? 'خطا در ثبت اطلاعات'));
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
debugPrint("AuthBloc: 🔴 خطای DioException رخ داد: ${e.response?.data['message']}");
|
||||
debugPrint(
|
||||
"AuthBloc: 🔴 خطای DioException رخ داد: ${e.response?.data['message']}",
|
||||
);
|
||||
if (isClosed) return;
|
||||
emit(AuthFailure(e.response?.data['message'] ?? 'خطا در ارتباط با سرور'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _onLogout(LogoutEvent event, Emitter<AuthState> emit) async {
|
||||
await _storage.deleteAll();
|
||||
emit(AuthInitial());
|
||||
}
|
||||
|
||||
Future<void> _onSendFcmToken(
|
||||
SendFcmTokenEvent event,
|
||||
Emitter<AuthState> emit,
|
||||
) async {
|
||||
try {
|
||||
final token = await _storage.read(key: 'accessToken');
|
||||
if (token == null) {
|
||||
emit(const AuthFailure("شما وارد نشدهاید."));
|
||||
return;
|
||||
}
|
||||
|
||||
await _dio.post(
|
||||
ApiConfig.baseUrl + ApiConfig.updateFcmToken,
|
||||
data: {'Token': event.fcmToken},
|
||||
options: Options(headers: {'Authorization': 'Bearer $token'}),
|
||||
);
|
||||
print("Firebase token: ${event.fcmToken}");
|
||||
} on DioException catch (e) {
|
||||
debugPrint("Error sending FCM token: ${e.response?.data['message']}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,3 +26,8 @@ class UpdateUserInfoEvent extends AuthEvent {
|
|||
|
||||
UpdateUserInfoEvent({required this.name, required this.gender});
|
||||
}
|
||||
|
||||
class SendFcmTokenEvent extends AuthEvent {
|
||||
final String fcmToken;
|
||||
SendFcmTokenEvent({required this.fcmToken});
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
// lib/presentation/pages/offers_page.dart
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
|
|
@ -5,6 +7,7 @@ import 'package:dio/dio.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
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';
|
||||
|
|
@ -23,7 +26,6 @@ import 'package:proxibuy/presentation/pages/reserved_list_page.dart';
|
|||
import 'package:proxibuy/presentation/reservation/cubit/reservation_cubit.dart';
|
||||
import 'package:proxibuy/presentation/widgets/gps_dialog.dart';
|
||||
import 'package:proxibuy/presentation/widgets/notification_permission_dialog.dart';
|
||||
import 'package:proxibuy/services/mqtt_service.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class OffersPage extends StatefulWidget {
|
||||
|
|
@ -38,10 +40,7 @@ class OffersPage extends StatefulWidget {
|
|||
class _OffersPageState extends State<OffersPage> {
|
||||
List<String> _selectedCategories = [];
|
||||
StreamSubscription? _locationServiceSubscription;
|
||||
StreamSubscription? _mqttMessageSubscription;
|
||||
StreamSubscription? _connectivitySubscription;
|
||||
Timer? _locationTimer;
|
||||
bool _isSubscribedToOffers = false;
|
||||
bool _isGpsEnabled = false;
|
||||
bool _isConnectedToInternet = true;
|
||||
|
||||
|
|
@ -57,8 +56,6 @@ class _OffersPageState extends State<OffersPage> {
|
|||
@override
|
||||
void dispose() {
|
||||
_locationServiceSubscription?.cancel();
|
||||
_mqttMessageSubscription?.cancel();
|
||||
_locationTimer?.cancel();
|
||||
_connectivitySubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
|
@ -67,7 +64,8 @@ class _OffersPageState extends State<OffersPage> {
|
|||
final connectivityResult = await Connectivity().checkConnectivity();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isConnectedToInternet = !connectivityResult.contains(ConnectivityResult.none);
|
||||
_isConnectedToInternet =
|
||||
!connectivityResult.contains(ConnectivityResult.none);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -80,10 +78,37 @@ class _OffersPageState extends State<OffersPage> {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
await _loadPreferences();
|
||||
_initLocationListener();
|
||||
_subscribeToUserOffersOnLoad();
|
||||
_listenToBackgroundService();
|
||||
}
|
||||
|
||||
void _listenToBackgroundService() {
|
||||
FlutterBackgroundService().on('update').listen((event) {
|
||||
if (event == null || event['offers'] == null) return;
|
||||
|
||||
final data = event['offers']['data'];
|
||||
if (data == null || data is! List) {
|
||||
if (mounted) {
|
||||
context.read<OffersBloc>().add(const OffersReceivedFromMqtt([]));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
List<OfferModel> offers = data
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map((json) => OfferModel.fromJson(json))
|
||||
.toList();
|
||||
|
||||
if (mounted) {
|
||||
context.read<OffersBloc>().add(OffersReceivedFromMqtt(offers));
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint("❌ Error parsing offers from Background Service: $e");
|
||||
debugPrint(stackTrace.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _initConnectivityListener() {
|
||||
|
|
@ -143,14 +168,6 @@ class _OffersPageState extends State<OffersPage> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _subscribeToUserOffersOnLoad() async {
|
||||
final storage = const FlutterSecureStorage();
|
||||
final userID = await storage.read(key: 'userID');
|
||||
if (userID != null && mounted) {
|
||||
_subscribeToUserOffers(userID);
|
||||
}
|
||||
}
|
||||
|
||||
void _initLocationListener() {
|
||||
_checkInitialGpsStatus();
|
||||
_locationServiceSubscription =
|
||||
|
|
@ -160,11 +177,7 @@ class _OffersPageState extends State<OffersPage> {
|
|||
setState(() {
|
||||
_isGpsEnabled = isEnabled;
|
||||
});
|
||||
if (isEnabled) {
|
||||
_startSendingLocationUpdates();
|
||||
} else {
|
||||
debugPrint("❌ Location Service Disabled. Stopping updates.");
|
||||
_locationTimer?.cancel();
|
||||
if (!isEnabled) {
|
||||
context.read<OffersBloc>().add(ClearOffers());
|
||||
}
|
||||
}
|
||||
|
|
@ -177,104 +190,8 @@ class _OffersPageState extends State<OffersPage> {
|
|||
setState(() {
|
||||
_isGpsEnabled = status;
|
||||
});
|
||||
if (_isGpsEnabled) {
|
||||
_startSendingLocationUpdates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _startSendingLocationUpdates() {
|
||||
debugPrint("🚀 Starting periodic location updates.");
|
||||
_locationTimer?.cancel();
|
||||
_locationTimer = Timer.periodic(const Duration(seconds: 30), (timer) {
|
||||
_sendLocationUpdate();
|
||||
});
|
||||
_sendLocationUpdate();
|
||||
}
|
||||
|
||||
Future<void> _sendLocationUpdate() async {
|
||||
if (!_isConnectedToInternet || !_isGpsEnabled) return;
|
||||
|
||||
final mqttService = context.read<MqttService>();
|
||||
if (!mqttService.isConnected) {
|
||||
debugPrint("⚠️ MQTT not connected in OffersPage. Cannot send location.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.denied ||
|
||||
permission == LocationPermission.deniedForever) {
|
||||
debugPrint("🚫 Location permission denied by user.");
|
||||
return;
|
||||
}
|
||||
|
||||
final position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
);
|
||||
|
||||
const storage = FlutterSecureStorage();
|
||||
final userID = await storage.read(key: 'userID');
|
||||
|
||||
if (userID == null) {
|
||||
debugPrint("⚠️ UserID not found. Cannot send location.");
|
||||
return;
|
||||
}
|
||||
|
||||
final payload = {
|
||||
"userID": userID,
|
||||
"lat":32.6685,
|
||||
"lng": 51.6826
|
||||
};
|
||||
|
||||
mqttService.publish("proxybuy/sendGps", payload);
|
||||
} catch (e) {
|
||||
debugPrint("❌ Error sending location update in OffersPage: $e");
|
||||
}
|
||||
}
|
||||
|
||||
void _subscribeToUserOffers(String userID) {
|
||||
if (_isSubscribedToOffers) return;
|
||||
|
||||
final mqttService = context.read<MqttService>();
|
||||
if (!mqttService.isConnected) {
|
||||
debugPrint("⚠️ Cannot subscribe. MQTT client is not connected.");
|
||||
return;
|
||||
}
|
||||
|
||||
final topic = 'user-proxybuy/$userID';
|
||||
mqttService.subscribe(topic);
|
||||
_isSubscribedToOffers = true;
|
||||
|
||||
_mqttMessageSubscription = mqttService.messages.listen((message) {
|
||||
final data = message['data'];
|
||||
|
||||
if (data == null || data is! List) {
|
||||
if (mounted) {
|
||||
context.read<OffersBloc>().add(const OffersReceivedFromMqtt([]));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
List<OfferModel> offers = data
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map((json) => OfferModel.fromJson(json))
|
||||
.toList();
|
||||
|
||||
if (mounted) {
|
||||
context.read<OffersBloc>().add(OffersReceivedFromMqtt(offers));
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint("❌ Error parsing offers from MQTT: $e");
|
||||
debugPrint(stackTrace.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadPreferences() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ 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:permission_handler/permission_handler.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';
|
||||
import 'package:proxibuy/services/mqtt_service.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
|
||||
class SplashScreen extends StatefulWidget {
|
||||
const SplashScreen({super.key});
|
||||
|
|
@ -28,9 +30,24 @@ class _SplashScreenState extends State<SplashScreen> {
|
|||
Future.delayed(const Duration(seconds: 2), _startProcess);
|
||||
}
|
||||
|
||||
Future<String?> getFcmToken() async {
|
||||
FirebaseMessaging messaging = FirebaseMessaging.instance;
|
||||
try {
|
||||
String? token = await messaging.getToken();
|
||||
debugPrint("🔥 Firebase Messaging Token: $token");
|
||||
return token;
|
||||
} catch(e) {
|
||||
debugPrint("Error getting FCM token: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _startProcess() async {
|
||||
if (!mounted) return;
|
||||
|
||||
await _requestPermissions();
|
||||
|
||||
final hasInternet = await _checkInternet();
|
||||
if (!hasInternet) {
|
||||
setState(() {
|
||||
|
|
@ -60,11 +77,18 @@ class _SplashScreenState extends State<SplashScreen> {
|
|||
return;
|
||||
}
|
||||
|
||||
final String? fcmToken = await getFcmToken();
|
||||
|
||||
final mqttService = context.read<MqttService>();
|
||||
final storage = const FlutterSecureStorage();
|
||||
final token = await storage.read(key: 'accessToken');
|
||||
|
||||
if (token != null && token.isNotEmpty) {
|
||||
|
||||
if (fcmToken != null) {
|
||||
context.read<AuthBloc>().add(SendFcmTokenEvent(fcmToken: fcmToken));
|
||||
}
|
||||
|
||||
if (mqttService.isConnected) {
|
||||
_navigateToOffers();
|
||||
return;
|
||||
|
|
@ -186,4 +210,13 @@ class _SplashScreenState extends State<SplashScreen> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _requestPermissions() async {
|
||||
await Permission.notification.request();
|
||||
|
||||
var status = await Permission.location.request();
|
||||
if (status.isGranted) {
|
||||
await Permission.locationAlways.request();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -339,8 +339,8 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
|
|||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 9,
|
||||
horizontal: 15,
|
||||
vertical: 12,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.singleOfferType,
|
||||
|
|
@ -351,7 +351,8 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
|
|||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 17,
|
||||
fontSize: 13,
|
||||
overflow: TextOverflow.ellipsis
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -365,7 +366,7 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
|
|||
Text(
|
||||
'(${(100 - widget.offer.finalPrice / widget.offer.originalPrice * 100).toInt()}%)',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontSize: 12,
|
||||
color: AppColors.singleOfferType,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
|
|
@ -374,7 +375,7 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
|
|||
Text(
|
||||
formatCurrency.format(widget.offer.originalPrice),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontSize: 13,
|
||||
color: Colors.grey.shade600,
|
||||
decoration: TextDecoration.lineThrough,
|
||||
),
|
||||
|
|
@ -386,7 +387,7 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
|
|||
'${formatCurrency.format(widget.offer.finalPrice)} تومان',
|
||||
style: const TextStyle(
|
||||
color: AppColors.singleOfferType,
|
||||
fontSize: 18,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
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';
|
||||
const notificationId = 888;
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void onStart(ServiceInstance service) async {
|
||||
DartPluginRegistrant.ensureInitialized();
|
||||
|
||||
final MqttService mqttService = MqttService();
|
||||
const storage = FlutterSecureStorage();
|
||||
|
||||
if (service is AndroidServiceInstance) {
|
||||
service.setForegroundNotificationInfo(
|
||||
title: "ProxiBuy فعال است",
|
||||
content: "در حال جستجو برای تخفیف های اطراف شما...",
|
||||
);
|
||||
|
||||
service.on('stopService').listen((event) {
|
||||
service.stopSelf();
|
||||
});
|
||||
}
|
||||
|
||||
mqttService.messages.listen((data) {
|
||||
service.invoke('update', {'offers': data});
|
||||
});
|
||||
|
||||
Timer.periodic(const Duration(seconds: 30), (timer) async {
|
||||
debugPrint("✅ Background Service: Sending location...");
|
||||
|
||||
var locationStatus = await Permission.location.status;
|
||||
if (!locationStatus.isGranted) {
|
||||
debugPrint("Background Service: Location permission not granted.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
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 && userID != null) {
|
||||
if (!mqttService.isConnected) {
|
||||
await mqttService.connect(token);
|
||||
final topic = 'user-proxybuy/$userID';
|
||||
mqttService.subscribe(topic);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> initializeService() async {
|
||||
final service = FlutterBackgroundService();
|
||||
|
||||
const AndroidNotificationChannel channel = AndroidNotificationChannel(
|
||||
notificationChannelId,
|
||||
'ProxiBuy Background Service',
|
||||
description: 'This channel is used for location service notifications.',
|
||||
importance: Importance.low,
|
||||
);
|
||||
|
||||
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
|
||||
await flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.createNotificationChannel(channel);
|
||||
|
||||
await service.configure(
|
||||
androidConfiguration: AndroidConfiguration(
|
||||
onStart: onStart,
|
||||
isForegroundMode: true,
|
||||
autoStart: true,
|
||||
notificationChannelId: notificationChannelId,
|
||||
initialNotificationTitle: 'ProxiBuy فعال است',
|
||||
initialNotificationContent: 'در حال جستجو برای تخفیفهای اطراف شما...',
|
||||
foregroundServiceNotificationId: notificationId,
|
||||
),
|
||||
iosConfiguration: IosConfiguration(
|
||||
autoStart: true,
|
||||
onForeground: onStart,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
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';
|
||||
const notificationId = 888;
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void onStart(ServiceInstance service) async {
|
||||
DartPluginRegistrant.ensureInitialized();
|
||||
|
||||
final MqttService mqttService = MqttService();
|
||||
const storage = FlutterSecureStorage();
|
||||
|
||||
if (service is AndroidServiceInstance) {
|
||||
service.setForegroundNotificationInfo(
|
||||
title: "ProxiBuy فعال است",
|
||||
content: "در حال یافتن تخفیف های اطراف شما...",
|
||||
);
|
||||
|
||||
service.on('setAsForeground').listen((event) {
|
||||
service.setAsForegroundService();
|
||||
});
|
||||
|
||||
service.on('setAsBackground').listen((event) {
|
||||
service.setAsBackgroundService();
|
||||
});
|
||||
}
|
||||
|
||||
service.on('stopService').listen((event) {
|
||||
service.stopSelf();
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (mqttService.isConnected) {
|
||||
final payload = {"userID": userID, "lat": position.latitude, "lng": position.longitude};
|
||||
mqttService.publish("proxybuy/sendGps", payload);
|
||||
debugPrint("Background Service: GPS sent successfully.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> initializeService() async {
|
||||
final service = FlutterBackgroundService();
|
||||
|
||||
const AndroidNotificationChannel channel = AndroidNotificationChannel(
|
||||
notificationChannelId,
|
||||
'ProxiBuy Background Service',
|
||||
description: 'This channel is used for location service notifications.',
|
||||
importance: Importance.low,
|
||||
);
|
||||
|
||||
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
|
||||
await flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.createNotificationChannel(channel);
|
||||
|
||||
await service.configure(
|
||||
androidConfiguration: AndroidConfiguration(
|
||||
onStart: onStart,
|
||||
isForegroundMode: true,
|
||||
autoStart: true,
|
||||
notificationChannelId: notificationChannelId,
|
||||
initialNotificationTitle: 'ProxiBuy فعال است',
|
||||
initialNotificationContent: 'در حال جستجو برای تخفیفهای اطراف شما...',
|
||||
foregroundServiceNotificationId: notificationId,
|
||||
),
|
||||
iosConfiguration: IosConfiguration(
|
||||
autoStart: true,
|
||||
onForeground: onStart,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mqtt_client/mqtt_client.dart';
|
||||
|
|
@ -10,18 +9,21 @@ class MqttService {
|
|||
MqttServerClient? client;
|
||||
final String server = '5.75.197.180';
|
||||
final int port = 1883;
|
||||
final StreamController<Map<String, dynamic>> _messageStreamController =
|
||||
StreamController.broadcast();
|
||||
|
||||
final StreamController<Map<String, dynamic>> _messageStreamController = StreamController.broadcast();
|
||||
Stream<Map<String, dynamic>> get messages => _messageStreamController.stream;
|
||||
|
||||
bool get isConnected {
|
||||
return client?.connectionStatus?.state == MqttConnectionState.connected;
|
||||
Completer<Map<String, dynamic>>? _firstMessageCompleter;
|
||||
|
||||
bool get isConnected => client?.connectionStatus?.state == MqttConnectionState.connected;
|
||||
|
||||
Future<Map<String, dynamic>> awaitFirstMessage() {
|
||||
_firstMessageCompleter = Completer<Map<String, dynamic>>();
|
||||
return _firstMessageCompleter!.future;
|
||||
}
|
||||
|
||||
Future<void> connect(String token) async {
|
||||
final String clientId =
|
||||
'nest-' + Random().nextInt(0xFFFFFF).toRadixString(16).padLeft(6, '0');
|
||||
final String clientId = 'nest-' + Random().nextInt(0xFFFFFF).toRadixString(16).padLeft(6, '0');
|
||||
final String username = 'ignored';
|
||||
final String password = token;
|
||||
|
||||
|
|
@ -31,10 +33,6 @@ class MqttService {
|
|||
client!.autoReconnect = true;
|
||||
client!.setProtocolV311();
|
||||
|
||||
debugPrint('--- [MQTT] Attempting to connect...');
|
||||
debugPrint('--- [MQTT] Server: $server:$port');
|
||||
debugPrint('--- [MQTT] ClientID: $clientId');
|
||||
|
||||
final connMessage = MqttConnectMessage()
|
||||
.withClientIdentifier(clientId)
|
||||
.startClean()
|
||||
|
|
@ -46,86 +44,57 @@ class MqttService {
|
|||
debugPrint('✅ [MQTT] Connected successfully.');
|
||||
client!.updates!.listen((List<MqttReceivedMessage<MqttMessage>> c) {
|
||||
final MqttPublishMessage recMess = c[0].payload as MqttPublishMessage;
|
||||
|
||||
final String payload =
|
||||
MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
|
||||
|
||||
debugPrint('<<<<< [MQTT] Received Data <<<<<');
|
||||
debugPrint('<<<<< [MQTT] Topic: ${c[0].topic}');
|
||||
debugPrint('<<<<< [MQTT] Payload as String: $payload');
|
||||
debugPrint('<<<<< ======================== <<<<<');
|
||||
final String payload = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
|
||||
|
||||
try {
|
||||
final Map<String, dynamic> jsonPayload = json.decode(payload);
|
||||
|
||||
if (!_messageStreamController.isClosed) {
|
||||
_messageStreamController.add(jsonPayload);
|
||||
}
|
||||
|
||||
if (_firstMessageCompleter != null && !_firstMessageCompleter!.isCompleted) {
|
||||
_firstMessageCompleter!.complete(jsonPayload);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("❌ [MQTT] Error decoding received JSON: $e");
|
||||
debugPrint("❌ [MQTT] Error decoding JSON: $e");
|
||||
if (_firstMessageCompleter != null && !_firstMessageCompleter!.isCompleted) {
|
||||
_firstMessageCompleter!.completeError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
client!.onDisconnected = () {
|
||||
debugPrint('❌ [MQTT] Disconnected.');
|
||||
};
|
||||
|
||||
client!.onAutoReconnect = () {
|
||||
debugPrint('↪️ [MQTT] Auto-reconnecting...');
|
||||
};
|
||||
|
||||
client!.onAutoReconnected = () {
|
||||
debugPrint('✅ [MQTT] Auto-reconnected successfully.');
|
||||
};
|
||||
|
||||
client!.onSubscribed = (String topic) {
|
||||
debugPrint('✅ [MQTT] Subscribed to topic: $topic');
|
||||
};
|
||||
|
||||
client!.pongCallback = () {
|
||||
debugPrint('🏓 [MQTT] Ping response received');
|
||||
};
|
||||
client!.onDisconnected = () => debugPrint('❌ [MQTT] Disconnected.');
|
||||
client!.onSubscribed = (String topic) => debugPrint('✅ [MQTT] Subscribed to topic: $topic');
|
||||
|
||||
try {
|
||||
await client!.connect();
|
||||
} on NoConnectionException catch (e) {
|
||||
debugPrint('❌ [MQTT] Connection failed - No Connection Exception: $e');
|
||||
client?.disconnect();
|
||||
} on SocketException catch (e) {
|
||||
debugPrint('❌ [MQTT] Connection failed - Socket Exception: $e');
|
||||
client?.disconnect();
|
||||
} catch (e) {
|
||||
debugPrint('❌ [MQTT] Connection failed - General Exception: $e');
|
||||
debugPrint('❌ [MQTT] Connection failed: $e');
|
||||
client?.disconnect();
|
||||
if (_firstMessageCompleter != null && !_firstMessageCompleter!.isCompleted) {
|
||||
_firstMessageCompleter!.completeError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void subscribe(String topic) {
|
||||
if (isConnected) {
|
||||
client?.subscribe(topic, MqttQos.atLeastOnce);
|
||||
} else {
|
||||
debugPrint("⚠️ [MQTT] Cannot subscribe. Client is not connected.");
|
||||
}
|
||||
}
|
||||
|
||||
void publish(String topic, Map<String, dynamic> message) {
|
||||
if (isConnected) {
|
||||
final builder = MqttClientPayloadBuilder();
|
||||
final payloadString = json.encode(message);
|
||||
builder.addString(payloadString);
|
||||
|
||||
debugPrint('>>>>> [MQTT] Publishing Data >>>>>');
|
||||
debugPrint('>>>>> [MQTT] Topic: $topic');
|
||||
debugPrint('>>>>> [MQTT] Payload: $payloadString');
|
||||
debugPrint('>>>>> ======================= >>>>>');
|
||||
|
||||
builder.addString(json.encode(message));
|
||||
client?.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!);
|
||||
} else {
|
||||
debugPrint("⚠️ [MQTT] Cannot publish. Client is not connected.");
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
debugPrint("--- [MQTT] Disposing MQTT Service.");
|
||||
_messageStreamController.close();
|
||||
client?.disconnect();
|
||||
_messageStreamController.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,10 @@ import connectivity_plus
|
|||
import file_selector_macos
|
||||
import firebase_auth
|
||||
import firebase_core
|
||||
import firebase_crashlytics
|
||||
import firebase_messaging
|
||||
import firebase_storage
|
||||
import flutter_local_notifications
|
||||
import flutter_localization
|
||||
import flutter_secure_storage_macos
|
||||
import geolocator_apple
|
||||
|
|
@ -28,7 +31,10 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin"))
|
||||
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||
FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin"))
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||
|
|
|
|||
124
pubspec.lock
124
pubspec.lock
|
|
@ -521,6 +521,46 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.24.1"
|
||||
firebase_crashlytics:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_crashlytics
|
||||
sha256: "662ae6443da91bca1fb0be8aeeac026fa2975e8b7ddfca36e4d90ebafa35dde1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.10"
|
||||
firebase_crashlytics_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_crashlytics_platform_interface
|
||||
sha256: "7222a8a40077c79f6b8b3f3439241c9f2b34e9ddfde8381ffc512f7b2e61f7eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.8.10"
|
||||
firebase_messaging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_messaging
|
||||
sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.10"
|
||||
firebase_messaging_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_platform_interface
|
||||
sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.10"
|
||||
firebase_messaging_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_web
|
||||
sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.10.10"
|
||||
firebase_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -566,6 +606,38 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.2"
|
||||
flutter_background_service:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_background_service
|
||||
sha256: "70a1c185b1fa1a44f8f14ecd6c86f6e50366e3562f00b2fa5a54df39b3324d3d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.0"
|
||||
flutter_background_service_android:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_background_service_android
|
||||
sha256: ca0793d4cd19f1e194a130918401a3d0b1076c81236f7273458ae96987944a87
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.1"
|
||||
flutter_background_service_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_background_service_ios
|
||||
sha256: "6037ffd45c4d019dab0975c7feb1d31012dd697e25edc05505a4a9b0c7dc9fba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.3"
|
||||
flutter_background_service_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_background_service_platform_interface
|
||||
sha256: ca74aa95789a8304f4d3f57f07ba404faa86bed6e415f83e8edea6ad8b904a41
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.2"
|
||||
flutter_bloc:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -614,6 +686,38 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_local_notifications:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
sha256: "20ca0a9c82ce0c855ac62a2e580ab867f3fbea82680a90647f7953832d0850ae"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "19.4.0"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_linux
|
||||
sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_local_notifications_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_platform_interface
|
||||
sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.0"
|
||||
flutter_local_notifications_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_windows
|
||||
sha256: ed46d7ae4ec9d19e4c8fa2badac5fe27ba87a3fe387343ce726f927af074ec98
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
flutter_localization:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1121,10 +1225,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "2d070d8684b68efb580a5997eb62f675e8a885ef0be6e754fb9ef489c177470f"
|
||||
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.0+1"
|
||||
version: "12.0.1"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1466,6 +1570,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timezone
|
||||
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.1"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1642,6 +1754,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.13.0"
|
||||
workmanager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: workmanager
|
||||
sha256: "746a50c535af15b6dc225abbd9b52ab272bcd292c535a104c54b5bc02609c38a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ dependencies:
|
|||
flutter_gen: ^5.10.0
|
||||
country_picker: ^2.0.27
|
||||
geolocator: ^14.0.1
|
||||
permission_handler: ^12.0.0+1
|
||||
permission_handler: ^12.0.1
|
||||
cached_network_image: ^3.4.1
|
||||
collection: ^1.19.1
|
||||
shared_preferences: ^2.5.3
|
||||
|
|
@ -62,6 +62,12 @@ dependencies:
|
|||
dart_jsonwebtoken: ^3.2.0
|
||||
audioplayers: ^6.5.0
|
||||
intl: ^0.19.0
|
||||
flutter_background_service: ^5.1.0
|
||||
flutter_background_service_android: ^6.3.1
|
||||
flutter_local_notifications: ^19.4.0
|
||||
workmanager: ^0.7.0
|
||||
firebase_messaging: ^15.2.10
|
||||
firebase_crashlytics: ^4.3.10
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
flutter_local_notifications_windows
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
|
|
|||
Loading…
Reference in New Issue