location background activity
This commit is contained in:
parent
5d00779bbf
commit
f4cd446cde
|
|
@ -14,29 +14,26 @@ android {
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
isCoreLibraryDesugaringEnabled = true
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
jvmTarget = "1.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId = "com.example.proxibuy"
|
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
|
minSdk = 23
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
|
multiDexEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
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")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,3 +42,8 @@ android {
|
||||||
flutter {
|
flutter {
|
||||||
source = "../.."
|
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">
|
<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.POST_NOTIFICATIONS"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<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_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_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
|
<application
|
||||||
android:label="Proxibuy"
|
android:label="Proxibuy"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<!-- android:networkSecurityConfig="@xml/network_security_config"> -->
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|
@ -22,10 +23,7 @@
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
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
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"
|
||||||
|
|
@ -35,17 +33,16 @@
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</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
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
</application>
|
</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>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ class ApiConfig {
|
||||||
static const String getFavoriteCategories = "/user/getfavoriteCategory";
|
static const String getFavoriteCategories = "/user/getfavoriteCategory";
|
||||||
static const String addReservation = "/reservation/add";
|
static const String addReservation = "/reservation/add";
|
||||||
static const String getReservations = "/reservation/get";
|
static const String getReservations = "/reservation/get";
|
||||||
|
static const String updateFcmToken = "/user/firebaseUpdate";
|
||||||
}
|
}
|
||||||
|
|
@ -149,7 +149,7 @@ class OfferModel extends Equatable {
|
||||||
rating: 0.0,
|
rating: 0.0,
|
||||||
ratingCount: 0,
|
ratingCount: 0,
|
||||||
comments: [],
|
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';
|
imageUrls.isNotEmpty ? imageUrls.first : 'https://via.placeholder.com/400x200.png?text=No+Image';
|
||||||
|
|
||||||
@override
|
@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 {
|
String get distanceAsString {
|
||||||
if (distanceInMeters < 1000) {
|
if (distanceInMeters < 1000) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// lib/main.dart
|
|
||||||
|
|
||||||
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,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/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 {
|
||||||
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);
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||||
Animate.restartOnHotReload = true;
|
Animate.restartOnHotReload = true;
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
|
|
@ -34,22 +53,15 @@ class MyApp extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
RepositoryProvider<MqttService>(
|
RepositoryProvider<MqttService>(create: (context) => MqttService()),
|
||||||
create: (context) => MqttService(),
|
|
||||||
),
|
|
||||||
BlocProvider<AuthBloc>(
|
BlocProvider<AuthBloc>(
|
||||||
create: (context) => AuthBloc()..add(CheckAuthStatusEvent()),
|
create: (context) => AuthBloc()..add(CheckAuthStatusEvent()),
|
||||||
),
|
),
|
||||||
BlocProvider<ReservationCubit>(
|
BlocProvider<ReservationCubit>(create: (context) => ReservationCubit()),
|
||||||
create: (context) => ReservationCubit(),
|
BlocProvider<OffersBloc>(create: (context) => OffersBloc()),
|
||||||
),
|
|
||||||
BlocProvider<OffersBloc>(
|
|
||||||
create: (context) => OffersBloc(),
|
|
||||||
),
|
|
||||||
BlocProvider<NotificationPreferencesBloc>(
|
BlocProvider<NotificationPreferencesBloc>(
|
||||||
create: (context) => NotificationPreferencesBloc(),
|
create: (context) => NotificationPreferencesBloc(),
|
||||||
),
|
),
|
||||||
|
|
||||||
],
|
],
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
title: 'Proxibuy',
|
title: 'Proxibuy',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
// lib/presentation/auth/bloc/auth_bloc.dart
|
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
@ -31,10 +29,13 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
on<VerifyOTPEvent>(_onVerifyOTP);
|
on<VerifyOTPEvent>(_onVerifyOTP);
|
||||||
on<UpdateUserInfoEvent>(_onUpdateUserInfo);
|
on<UpdateUserInfoEvent>(_onUpdateUserInfo);
|
||||||
on<LogoutEvent>(_onLogout);
|
on<LogoutEvent>(_onLogout);
|
||||||
|
on<SendFcmTokenEvent>(_onSendFcmToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onCheckAuthStatus(
|
Future<void> _onCheckAuthStatus(
|
||||||
CheckAuthStatusEvent event, Emitter<AuthState> emit) async {
|
CheckAuthStatusEvent event,
|
||||||
|
Emitter<AuthState> emit,
|
||||||
|
) async {
|
||||||
final token = await _storage.read(key: 'accessToken');
|
final token = await _storage.read(key: 'accessToken');
|
||||||
if (token != null && token.isNotEmpty) {
|
if (token != null && token.isNotEmpty) {
|
||||||
emit(AuthSuccess());
|
emit(AuthSuccess());
|
||||||
|
|
@ -52,10 +53,12 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
);
|
);
|
||||||
if (isClosed) return;
|
if (isClosed) return;
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
emit(AuthCodeSentSuccess(
|
emit(
|
||||||
phone: event.phoneNumber,
|
AuthCodeSentSuccess(
|
||||||
countryCode: event.countryCode,
|
phone: event.phoneNumber,
|
||||||
));
|
countryCode: event.countryCode,
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
emit(AuthFailure(response.data['message'] ?? 'خطایی رخ داد'));
|
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());
|
emit(AuthLoading());
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
|
|
@ -97,8 +103,12 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onUpdateUserInfo(
|
Future<void> _onUpdateUserInfo(
|
||||||
UpdateUserInfoEvent event, Emitter<AuthState> emit) async {
|
UpdateUserInfoEvent event,
|
||||||
debugPrint("AuthBloc: 🔵 ایونت UpdateUserInfoEvent دریافت شد با نام: ${event.name}");
|
Emitter<AuthState> emit,
|
||||||
|
) async {
|
||||||
|
debugPrint(
|
||||||
|
"AuthBloc: 🔵 ایونت UpdateUserInfoEvent دریافت شد با نام: ${event.name}",
|
||||||
|
);
|
||||||
emit(AuthLoading());
|
emit(AuthLoading());
|
||||||
try {
|
try {
|
||||||
final token = await _storage.read(key: 'accessToken');
|
final token = await _storage.read(key: 'accessToken');
|
||||||
|
|
@ -115,30 +125,59 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
options: Options(headers: {'Authorization': 'Bearer $token'}),
|
options: Options(headers: {'Authorization': 'Bearer $token'}),
|
||||||
);
|
);
|
||||||
|
|
||||||
debugPrint("AuthBloc: 🟠 پاسخ سرور دریافت شد. StatusCode: ${response.statusCode}");
|
debugPrint(
|
||||||
|
"AuthBloc: 🟠 پاسخ سرور دریافت شد. StatusCode: ${response.statusCode}",
|
||||||
|
);
|
||||||
|
|
||||||
if (isClosed) {
|
if (isClosed) {
|
||||||
debugPrint("AuthBloc: 🔴 خطا: BLoC قبل از اتمام عملیات بسته شده است.");
|
debugPrint("AuthBloc: 🔴 خطا: BLoC قبل از اتمام عملیات بسته شده است.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
debugPrint("AuthBloc: ✅ درخواست موفق بود. در حال emit کردن AuthSuccess...");
|
debugPrint(
|
||||||
|
"AuthBloc: ✅ درخواست موفق بود. در حال emit کردن AuthSuccess...",
|
||||||
|
);
|
||||||
emit(AuthSuccess());
|
emit(AuthSuccess());
|
||||||
} else {
|
} else {
|
||||||
debugPrint("AuthBloc: 🔴 سرور پاسخ ناموفق داد: ${response.data['message']}");
|
debugPrint(
|
||||||
|
"AuthBloc: 🔴 سرور پاسخ ناموفق داد: ${response.data['message']}",
|
||||||
|
);
|
||||||
emit(AuthFailure(response.data['message'] ?? 'خطا در ثبت اطلاعات'));
|
emit(AuthFailure(response.data['message'] ?? 'خطا در ثبت اطلاعات'));
|
||||||
}
|
}
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
debugPrint("AuthBloc: 🔴 خطای DioException رخ داد: ${e.response?.data['message']}");
|
debugPrint(
|
||||||
|
"AuthBloc: 🔴 خطای DioException رخ داد: ${e.response?.data['message']}",
|
||||||
|
);
|
||||||
if (isClosed) return;
|
if (isClosed) return;
|
||||||
emit(AuthFailure(e.response?.data['message'] ?? 'خطا در ارتباط با سرور'));
|
emit(AuthFailure(e.response?.data['message'] ?? 'خطا در ارتباط با سرور'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Future<void> _onLogout(LogoutEvent event, Emitter<AuthState> emit) async {
|
Future<void> _onLogout(LogoutEvent event, Emitter<AuthState> emit) async {
|
||||||
await _storage.deleteAll();
|
await _storage.deleteAll();
|
||||||
emit(AuthInitial());
|
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});
|
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 'dart:async';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:connectivity_plus/connectivity_plus.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/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';
|
||||||
|
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';
|
||||||
|
|
@ -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/reservation/cubit/reservation_cubit.dart';
|
||||||
import 'package:proxibuy/presentation/widgets/gps_dialog.dart';
|
import 'package:proxibuy/presentation/widgets/gps_dialog.dart';
|
||||||
import 'package:proxibuy/presentation/widgets/notification_permission_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';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
class OffersPage extends StatefulWidget {
|
class OffersPage extends StatefulWidget {
|
||||||
|
|
@ -38,10 +40,7 @@ class OffersPage extends StatefulWidget {
|
||||||
class _OffersPageState extends State<OffersPage> {
|
class _OffersPageState extends State<OffersPage> {
|
||||||
List<String> _selectedCategories = [];
|
List<String> _selectedCategories = [];
|
||||||
StreamSubscription? _locationServiceSubscription;
|
StreamSubscription? _locationServiceSubscription;
|
||||||
StreamSubscription? _mqttMessageSubscription;
|
|
||||||
StreamSubscription? _connectivitySubscription;
|
StreamSubscription? _connectivitySubscription;
|
||||||
Timer? _locationTimer;
|
|
||||||
bool _isSubscribedToOffers = false;
|
|
||||||
bool _isGpsEnabled = false;
|
bool _isGpsEnabled = false;
|
||||||
bool _isConnectedToInternet = true;
|
bool _isConnectedToInternet = true;
|
||||||
|
|
||||||
|
|
@ -57,8 +56,6 @@ class _OffersPageState extends State<OffersPage> {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_locationServiceSubscription?.cancel();
|
_locationServiceSubscription?.cancel();
|
||||||
_mqttMessageSubscription?.cancel();
|
|
||||||
_locationTimer?.cancel();
|
|
||||||
_connectivitySubscription?.cancel();
|
_connectivitySubscription?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +64,8 @@ class _OffersPageState extends State<OffersPage> {
|
||||||
final connectivityResult = await Connectivity().checkConnectivity();
|
final connectivityResult = await Connectivity().checkConnectivity();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_isConnectedToInternet = !connectivityResult.contains(ConnectivityResult.none);
|
_isConnectedToInternet =
|
||||||
|
!connectivityResult.contains(ConnectivityResult.none);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,10 +78,37 @@ class _OffersPageState extends State<OffersPage> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await _loadPreferences();
|
await _loadPreferences();
|
||||||
_initLocationListener();
|
_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() {
|
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() {
|
void _initLocationListener() {
|
||||||
_checkInitialGpsStatus();
|
_checkInitialGpsStatus();
|
||||||
_locationServiceSubscription =
|
_locationServiceSubscription =
|
||||||
|
|
@ -160,11 +177,7 @@ class _OffersPageState extends State<OffersPage> {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isGpsEnabled = isEnabled;
|
_isGpsEnabled = isEnabled;
|
||||||
});
|
});
|
||||||
if (isEnabled) {
|
if (!isEnabled) {
|
||||||
_startSendingLocationUpdates();
|
|
||||||
} else {
|
|
||||||
debugPrint("❌ Location Service Disabled. Stopping updates.");
|
|
||||||
_locationTimer?.cancel();
|
|
||||||
context.read<OffersBloc>().add(ClearOffers());
|
context.read<OffersBloc>().add(ClearOffers());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -177,105 +190,9 @@ class _OffersPageState extends State<OffersPage> {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isGpsEnabled = status;
|
_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 {
|
Future<void> _loadPreferences() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
final savedCategories =
|
final savedCategories =
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ 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: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';
|
||||||
import 'package:proxibuy/presentation/pages/onboarding_page.dart';
|
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:firebase_messaging/firebase_messaging.dart';
|
||||||
|
|
||||||
class SplashScreen extends StatefulWidget {
|
class SplashScreen extends StatefulWidget {
|
||||||
const SplashScreen({super.key});
|
const SplashScreen({super.key});
|
||||||
|
|
@ -28,9 +30,24 @@ class _SplashScreenState extends State<SplashScreen> {
|
||||||
Future.delayed(const Duration(seconds: 2), _startProcess);
|
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 {
|
Future<void> _startProcess() async {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
|
await _requestPermissions();
|
||||||
|
|
||||||
final hasInternet = await _checkInternet();
|
final hasInternet = await _checkInternet();
|
||||||
if (!hasInternet) {
|
if (!hasInternet) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -60,11 +77,18 @@ class _SplashScreenState extends State<SplashScreen> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final String? fcmToken = await getFcmToken();
|
||||||
|
|
||||||
final mqttService = context.read<MqttService>();
|
final mqttService = context.read<MqttService>();
|
||||||
final storage = const FlutterSecureStorage();
|
final storage = const FlutterSecureStorage();
|
||||||
final token = await storage.read(key: 'accessToken');
|
final token = await storage.read(key: 'accessToken');
|
||||||
|
|
||||||
if (token != null && token.isNotEmpty) {
|
if (token != null && token.isNotEmpty) {
|
||||||
|
|
||||||
|
if (fcmToken != null) {
|
||||||
|
context.read<AuthBloc>().add(SendFcmTokenEvent(fcmToken: fcmToken));
|
||||||
|
}
|
||||||
|
|
||||||
if (mqttService.isConnected) {
|
if (mqttService.isConnected) {
|
||||||
_navigateToOffers();
|
_navigateToOffers();
|
||||||
return;
|
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: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 20,
|
horizontal: 15,
|
||||||
vertical: 9,
|
vertical: 12,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.singleOfferType,
|
color: AppColors.singleOfferType,
|
||||||
|
|
@ -351,7 +351,8 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
fontSize: 17,
|
fontSize: 13,
|
||||||
|
overflow: TextOverflow.ellipsis
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -365,7 +366,7 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
|
||||||
Text(
|
Text(
|
||||||
'(${(100 - widget.offer.finalPrice / widget.offer.originalPrice * 100).toInt()}%)',
|
'(${(100 - widget.offer.finalPrice / widget.offer.originalPrice * 100).toInt()}%)',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 12,
|
||||||
color: AppColors.singleOfferType,
|
color: AppColors.singleOfferType,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
),
|
),
|
||||||
|
|
@ -374,7 +375,7 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
|
||||||
Text(
|
Text(
|
||||||
formatCurrency.format(widget.offer.originalPrice),
|
formatCurrency.format(widget.offer.originalPrice),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 13,
|
||||||
color: Colors.grey.shade600,
|
color: Colors.grey.shade600,
|
||||||
decoration: TextDecoration.lineThrough,
|
decoration: TextDecoration.lineThrough,
|
||||||
),
|
),
|
||||||
|
|
@ -386,7 +387,7 @@ class _ReservedListItemCardState extends State<ReservedListItemCard> {
|
||||||
'${formatCurrency.format(widget.offer.finalPrice)} تومان',
|
'${formatCurrency.format(widget.offer.finalPrice)} تومان',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: AppColors.singleOfferType,
|
color: AppColors.singleOfferType,
|
||||||
fontSize: 18,
|
fontSize: 15,
|
||||||
fontWeight: FontWeight.bold,
|
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:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:mqtt_client/mqtt_client.dart';
|
import 'package:mqtt_client/mqtt_client.dart';
|
||||||
|
|
@ -10,18 +9,21 @@ class MqttService {
|
||||||
MqttServerClient? client;
|
MqttServerClient? client;
|
||||||
final String server = '5.75.197.180';
|
final String server = '5.75.197.180';
|
||||||
final int port = 1883;
|
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;
|
Stream<Map<String, dynamic>> get messages => _messageStreamController.stream;
|
||||||
|
|
||||||
bool get isConnected {
|
Completer<Map<String, dynamic>>? _firstMessageCompleter;
|
||||||
return client?.connectionStatus?.state == MqttConnectionState.connected;
|
|
||||||
|
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 {
|
Future<void> connect(String token) async {
|
||||||
final String clientId =
|
final String clientId = 'nest-' + Random().nextInt(0xFFFFFF).toRadixString(16).padLeft(6, '0');
|
||||||
'nest-' + Random().nextInt(0xFFFFFF).toRadixString(16).padLeft(6, '0');
|
|
||||||
final String username = 'ignored';
|
final String username = 'ignored';
|
||||||
final String password = token;
|
final String password = token;
|
||||||
|
|
||||||
|
|
@ -31,10 +33,6 @@ class MqttService {
|
||||||
client!.autoReconnect = true;
|
client!.autoReconnect = true;
|
||||||
client!.setProtocolV311();
|
client!.setProtocolV311();
|
||||||
|
|
||||||
debugPrint('--- [MQTT] Attempting to connect...');
|
|
||||||
debugPrint('--- [MQTT] Server: $server:$port');
|
|
||||||
debugPrint('--- [MQTT] ClientID: $clientId');
|
|
||||||
|
|
||||||
final connMessage = MqttConnectMessage()
|
final connMessage = MqttConnectMessage()
|
||||||
.withClientIdentifier(clientId)
|
.withClientIdentifier(clientId)
|
||||||
.startClean()
|
.startClean()
|
||||||
|
|
@ -46,86 +44,57 @@ class MqttService {
|
||||||
debugPrint('✅ [MQTT] Connected successfully.');
|
debugPrint('✅ [MQTT] Connected successfully.');
|
||||||
client!.updates!.listen((List<MqttReceivedMessage<MqttMessage>> c) {
|
client!.updates!.listen((List<MqttReceivedMessage<MqttMessage>> c) {
|
||||||
final MqttPublishMessage recMess = c[0].payload as MqttPublishMessage;
|
final MqttPublishMessage recMess = c[0].payload as MqttPublishMessage;
|
||||||
|
final String payload = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
|
||||||
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('<<<<< ======================== <<<<<');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final Map<String, dynamic> jsonPayload = json.decode(payload);
|
final Map<String, dynamic> jsonPayload = json.decode(payload);
|
||||||
_messageStreamController.add(jsonPayload);
|
|
||||||
|
if (!_messageStreamController.isClosed) {
|
||||||
|
_messageStreamController.add(jsonPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_firstMessageCompleter != null && !_firstMessageCompleter!.isCompleted) {
|
||||||
|
_firstMessageCompleter!.complete(jsonPayload);
|
||||||
|
}
|
||||||
} catch (e) {
|
} 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 = () {
|
client!.onDisconnected = () => debugPrint('❌ [MQTT] Disconnected.');
|
||||||
debugPrint('❌ [MQTT] Disconnected.');
|
client!.onSubscribed = (String topic) => debugPrint('✅ [MQTT] Subscribed to topic: $topic');
|
||||||
};
|
|
||||||
|
|
||||||
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');
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client!.connect();
|
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) {
|
} catch (e) {
|
||||||
debugPrint('❌ [MQTT] Connection failed - General Exception: $e');
|
debugPrint('❌ [MQTT] Connection failed: $e');
|
||||||
client?.disconnect();
|
client?.disconnect();
|
||||||
|
if (_firstMessageCompleter != null && !_firstMessageCompleter!.isCompleted) {
|
||||||
|
_firstMessageCompleter!.completeError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void subscribe(String topic) {
|
void subscribe(String topic) {
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
client?.subscribe(topic, MqttQos.atLeastOnce);
|
client?.subscribe(topic, MqttQos.atLeastOnce);
|
||||||
} else {
|
|
||||||
debugPrint("⚠️ [MQTT] Cannot subscribe. Client is not connected.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void publish(String topic, Map<String, dynamic> message) {
|
void publish(String topic, Map<String, dynamic> message) {
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
final builder = MqttClientPayloadBuilder();
|
final builder = MqttClientPayloadBuilder();
|
||||||
final payloadString = json.encode(message);
|
builder.addString(json.encode(message));
|
||||||
builder.addString(payloadString);
|
|
||||||
|
|
||||||
debugPrint('>>>>> [MQTT] Publishing Data >>>>>');
|
|
||||||
debugPrint('>>>>> [MQTT] Topic: $topic');
|
|
||||||
debugPrint('>>>>> [MQTT] Payload: $payloadString');
|
|
||||||
debugPrint('>>>>> ======================= >>>>>');
|
|
||||||
|
|
||||||
client?.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!);
|
client?.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!);
|
||||||
} else {
|
|
||||||
debugPrint("⚠️ [MQTT] Cannot publish. Client is not connected.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
debugPrint("--- [MQTT] Disposing MQTT Service.");
|
|
||||||
_messageStreamController.close();
|
|
||||||
client?.disconnect();
|
client?.disconnect();
|
||||||
|
_messageStreamController.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,10 @@ import connectivity_plus
|
||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
import firebase_auth
|
import firebase_auth
|
||||||
import firebase_core
|
import firebase_core
|
||||||
|
import firebase_crashlytics
|
||||||
|
import firebase_messaging
|
||||||
import firebase_storage
|
import firebase_storage
|
||||||
|
import flutter_local_notifications
|
||||||
import flutter_localization
|
import flutter_localization
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
import geolocator_apple
|
import geolocator_apple
|
||||||
|
|
@ -28,7 +31,10 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
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"))
|
FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin"))
|
||||||
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin"))
|
FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||||
|
|
|
||||||
124
pubspec.lock
124
pubspec.lock
|
|
@ -521,6 +521,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.24.1"
|
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:
|
firebase_storage:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -566,6 +606,38 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.5.2"
|
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:
|
flutter_bloc:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -614,6 +686,38 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
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:
|
flutter_localization:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1121,10 +1225,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: permission_handler
|
name: permission_handler
|
||||||
sha256: "2d070d8684b68efb580a5997eb62f675e8a885ef0be6e754fb9ef489c177470f"
|
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "12.0.0+1"
|
version: "12.0.1"
|
||||||
permission_handler_android:
|
permission_handler_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1466,6 +1570,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.1.5"
|
||||||
|
timezone:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: timezone
|
||||||
|
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.1"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1642,6 +1754,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.13.0"
|
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:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ dependencies:
|
||||||
flutter_gen: ^5.10.0
|
flutter_gen: ^5.10.0
|
||||||
country_picker: ^2.0.27
|
country_picker: ^2.0.27
|
||||||
geolocator: ^14.0.1
|
geolocator: ^14.0.1
|
||||||
permission_handler: ^12.0.0+1
|
permission_handler: ^12.0.1
|
||||||
cached_network_image: ^3.4.1
|
cached_network_image: ^3.4.1
|
||||||
collection: ^1.19.1
|
collection: ^1.19.1
|
||||||
shared_preferences: ^2.5.3
|
shared_preferences: ^2.5.3
|
||||||
|
|
@ -62,6 +62,12 @@ dependencies:
|
||||||
dart_jsonwebtoken: ^3.2.0
|
dart_jsonwebtoken: ^3.2.0
|
||||||
audioplayers: ^6.5.0
|
audioplayers: ^6.5.0
|
||||||
intl: ^0.19.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:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
flutter_local_notifications_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue