background-activity #1
|
|
@ -11,7 +11,7 @@ plugins {
|
||||||
android {
|
android {
|
||||||
namespace = "com.example.proxibuy"
|
namespace = "com.example.proxibuy"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = "27.0.12077973"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
isCoreLibraryDesugaringEnabled = true
|
isCoreLibraryDesugaringEnabled = true
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<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.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
<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" />
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
late final Dio _dio;
|
late final Dio _dio;
|
||||||
final _storage = const FlutterSecureStorage();
|
final _storage = const FlutterSecureStorage();
|
||||||
|
|
||||||
AuthBloc() : super(AuthInitial()) {
|
AuthBloc() : super(AuthUnknown()) {
|
||||||
_dio = Dio();
|
_dio = Dio();
|
||||||
_dio.interceptors.add(
|
_dio.interceptors.add(
|
||||||
LogInterceptor(
|
LogInterceptor(
|
||||||
|
|
@ -44,6 +44,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Future<void> _onSendOTP(SendOTPEvent event, Emitter<AuthState> emit) async {
|
Future<void> _onSendOTP(SendOTPEvent event, Emitter<AuthState> emit) async {
|
||||||
emit(AuthLoading());
|
emit(AuthLoading());
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ abstract class AuthState extends Equatable {
|
||||||
List<Object?> get props => [];
|
List<Object?> get props => [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AuthUnknown extends AuthState {}
|
||||||
|
|
||||||
class AuthInitial extends AuthState {}
|
class AuthInitial extends AuthState {}
|
||||||
|
|
||||||
class AuthLoading extends AuthState {}
|
class AuthLoading extends AuthState {}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
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';
|
||||||
|
|
@ -130,7 +131,7 @@ class _NotificationPreferencesPageState
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: BlocListener<NotificationPreferencesBloc,
|
body: BlocConsumer<NotificationPreferencesBloc,
|
||||||
NotificationPreferencesState>(
|
NotificationPreferencesState>(
|
||||||
listener: (context, state) async {
|
listener: (context, state) async {
|
||||||
if (state.submissionSuccess) {
|
if (state.submissionSuccess) {
|
||||||
|
|
@ -176,151 +177,151 @@ class _NotificationPreferencesPageState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Padding(
|
builder: (context, state) {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
|
return Stack(
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
Padding(
|
||||||
'دریافت اعلان',
|
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
|
||||||
style: TextStyle(
|
child: Column(
|
||||||
fontFamily: 'Dana',
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
fontSize: 20,
|
children: [
|
||||||
fontWeight: FontWeight.bold,
|
const Text(
|
||||||
),
|
'دریافت اعلان',
|
||||||
),
|
style: TextStyle(
|
||||||
const SizedBox(height: 4),
|
fontFamily: 'Dana',
|
||||||
const Divider(),
|
fontSize: 20,
|
||||||
RichText(
|
fontWeight: FontWeight.bold,
|
||||||
text: TextSpan(
|
),
|
||||||
style: TextStyle(
|
|
||||||
fontFamily: 'Dana',
|
|
||||||
fontSize: 14,
|
|
||||||
color: AppColors.hint,
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
children: const <TextSpan>[
|
|
||||||
TextSpan(
|
|
||||||
text:
|
|
||||||
'ترجیح میدی از کدام دستهبندیها اعلان تخفیف دریافت کنی؟ ',
|
|
||||||
style:
|
|
||||||
TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
|
||||||
),
|
),
|
||||||
TextSpan(text: '(حداقل یک مورد رو انتخاب کن).'),
|
const SizedBox(height: 4),
|
||||||
|
const Divider(),
|
||||||
|
RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Dana',
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppColors.hint,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
children: const <TextSpan>[
|
||||||
|
TextSpan(
|
||||||
|
text:
|
||||||
|
'ترجیح میدی از کدام دستهبندیها اعلان تخفیف دریافت کنی؟ ',
|
||||||
|
style:
|
||||||
|
TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||||
|
),
|
||||||
|
TextSpan(text: '(حداقل یک مورد رو انتخاب کن).'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Expanded(
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
if (state.categories.isEmpty && state.isLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
final double horizontalPadding = 24.0;
|
||||||
|
final double crossAxisSpacing = 16.0;
|
||||||
|
final int crossAxisCount = 3;
|
||||||
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
final itemWidth = (screenWidth -
|
||||||
|
(horizontalPadding * 2) -
|
||||||
|
(crossAxisSpacing * (crossAxisCount - 1))) /
|
||||||
|
crossAxisCount;
|
||||||
|
final itemHeight = itemWidth / 0.9;
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Wrap(
|
||||||
|
spacing: crossAxisSpacing,
|
||||||
|
runSpacing: 24.0,
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
children: state.categories.map((category) {
|
||||||
|
final isSelected =
|
||||||
|
state.selectedCategoryIds.contains(category.id);
|
||||||
|
return SizedBox(
|
||||||
|
width: itemWidth,
|
||||||
|
height: itemHeight,
|
||||||
|
child: CategorySelectionCard(
|
||||||
|
name: category.name,
|
||||||
|
icon: category.icon,
|
||||||
|
isSelected: isSelected,
|
||||||
|
showSelectableIndicator:
|
||||||
|
state.selectedCategoryIds.isNotEmpty,
|
||||||
|
onTap: () {
|
||||||
|
context.read<NotificationPreferencesBloc>().add(
|
||||||
|
ToggleCategorySelection(category.id));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (state.selectedCategoryIds.isNotEmpty)
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: state.isLoading
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
final bloc =
|
||||||
|
context.read<NotificationPreferencesBloc>();
|
||||||
|
|
||||||
|
final selectedCategoryNames = bloc
|
||||||
|
.state.categories
|
||||||
|
.where((cat) => bloc.state
|
||||||
|
.selectedCategoryIds
|
||||||
|
.contains(cat.id))
|
||||||
|
.map((cat) => cat.name)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final prefs =
|
||||||
|
await SharedPreferences.getInstance();
|
||||||
|
await prefs.setStringList(
|
||||||
|
'user_selected_categories',
|
||||||
|
selectedCategoryNames);
|
||||||
|
|
||||||
|
bloc.add(SubmitPreferences());
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppColors.confirm,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
disabledBackgroundColor: Colors.grey.withOpacity(0.5),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(50),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'اعمال',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Dana',
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
if (state.isLoading)
|
||||||
Expanded(
|
Container(
|
||||||
child: BlocBuilder<NotificationPreferencesBloc,
|
color: Colors.black.withOpacity(0.4),
|
||||||
NotificationPreferencesState>(
|
child: const Center(
|
||||||
builder: (context, state) {
|
child: CircularProgressIndicator(
|
||||||
if (state.categories.isEmpty && state.isLoading) {
|
color: Colors.white,
|
||||||
return const Center(child: CircularProgressIndicator());
|
),
|
||||||
}
|
),
|
||||||
|
|
||||||
final double horizontalPadding = 24.0;
|
|
||||||
final double crossAxisSpacing = 16.0;
|
|
||||||
final int crossAxisCount = 3;
|
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
|
||||||
final itemWidth = (screenWidth -
|
|
||||||
(horizontalPadding * 2) -
|
|
||||||
(crossAxisSpacing * (crossAxisCount - 1))) /
|
|
||||||
crossAxisCount;
|
|
||||||
final itemHeight = itemWidth / 0.9;
|
|
||||||
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Wrap(
|
|
||||||
spacing: crossAxisSpacing,
|
|
||||||
runSpacing: 24.0,
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
children: state.categories.map((category) {
|
|
||||||
final isSelected =
|
|
||||||
state.selectedCategoryIds.contains(category.id);
|
|
||||||
return SizedBox(
|
|
||||||
width: itemWidth,
|
|
||||||
height: itemHeight,
|
|
||||||
child: CategorySelectionCard(
|
|
||||||
name: category.name,
|
|
||||||
icon: category.icon,
|
|
||||||
isSelected: isSelected,
|
|
||||||
showSelectableIndicator:
|
|
||||||
state.selectedCategoryIds.isNotEmpty,
|
|
||||||
onTap: () {
|
|
||||||
context.read<NotificationPreferencesBloc>().add(
|
|
||||||
ToggleCategorySelection(category.id));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
BlocBuilder<NotificationPreferencesBloc,
|
|
||||||
NotificationPreferencesState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
final areCategoriesSelected =
|
|
||||||
state.selectedCategoryIds.isNotEmpty;
|
|
||||||
|
|
||||||
if (areCategoriesSelected) {
|
|
||||||
return SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: !state.isLoading
|
|
||||||
? () async {
|
|
||||||
final bloc =
|
|
||||||
context.read<NotificationPreferencesBloc>();
|
|
||||||
|
|
||||||
final selectedCategoryNames = bloc
|
|
||||||
.state.categories
|
|
||||||
.where((cat) => bloc.state
|
|
||||||
.selectedCategoryIds
|
|
||||||
.contains(cat.id))
|
|
||||||
.map((cat) => cat.name)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
final prefs =
|
|
||||||
await SharedPreferences.getInstance();
|
|
||||||
await prefs.setStringList(
|
|
||||||
'user_selected_categories',
|
|
||||||
selectedCategoryNames);
|
|
||||||
|
|
||||||
bloc.add(SubmitPreferences());
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: AppColors.confirm,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
disabledBackgroundColor: Colors.grey,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(50),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: state.isLoading
|
|
||||||
? const CircularProgressIndicator(
|
|
||||||
color: Colors.white)
|
|
||||||
: const Text(
|
|
||||||
'اعمال',
|
|
||||||
style: TextStyle(
|
|
||||||
fontFamily: 'Dana',
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// lib/presentation/pages/offers_page.dart
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
@ -205,6 +204,39 @@ class _OffersPageState extends State<OffersPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _handleRefresh() {
|
||||||
|
final completer = Completer<void>();
|
||||||
|
final service = FlutterBackgroundService();
|
||||||
|
|
||||||
|
final timeout = Timer(const Duration(seconds: 20), () {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.completeError('Request timed out.');
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Request timed out. Please try again.')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final StreamSubscription<Map<String, dynamic>?> subscription =
|
||||||
|
service.on('update').listen((event) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
completer.future.whenComplete(() {
|
||||||
|
subscription.cancel();
|
||||||
|
timeout.cancel();
|
||||||
|
});
|
||||||
|
|
||||||
|
service.invoke('force_refresh');
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildFavoriteCategoriesSection() {
|
Widget _buildFavoriteCategoriesSection() {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0),
|
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0),
|
||||||
|
|
@ -220,7 +252,7 @@ class _OffersPageState extends State<OffersPage> {
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await Navigator.of(context).push<bool>(
|
final result = await Navigator.of(context).push<bool>(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => const NotificationPreferencesPage(
|
builder: (context) => const NotificationPreferencesPage(
|
||||||
loadFavoritesOnStart: true,
|
loadFavoritesOnStart: true,
|
||||||
|
|
@ -234,7 +266,11 @@ class _OffersPageState extends State<OffersPage> {
|
||||||
.read<NotificationPreferencesBloc>()
|
.read<NotificationPreferencesBloc>()
|
||||||
.add(ResetSubmissionStatus());
|
.add(ResetSubmissionStatus());
|
||||||
|
|
||||||
_loadPreferences();
|
await _loadPreferences();
|
||||||
|
|
||||||
|
if (result == true) {
|
||||||
|
_handleRefresh();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -362,16 +398,21 @@ class _OffersPageState extends State<OffersPage> {
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: RefreshIndicator(
|
||||||
child: Column(
|
onRefresh: _handleRefresh,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: SingleChildScrollView(
|
||||||
children: [
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
_buildFavoriteCategoriesSection(),
|
child: Column(
|
||||||
OffersView(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
isGpsEnabled: _isGpsEnabled,
|
children: [
|
||||||
isConnectedToInternet: _isConnectedToInternet,
|
_buildFavoriteCategoriesSection(),
|
||||||
),
|
OffersView(
|
||||||
],
|
isGpsEnabled: _isGpsEnabled,
|
||||||
|
isConnectedToInternet: _isConnectedToInternet,
|
||||||
|
selectedCategories: _selectedCategories,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -382,11 +423,13 @@ class _OffersPageState extends State<OffersPage> {
|
||||||
class OffersView extends StatelessWidget {
|
class OffersView extends StatelessWidget {
|
||||||
final bool isGpsEnabled;
|
final bool isGpsEnabled;
|
||||||
final bool isConnectedToInternet;
|
final bool isConnectedToInternet;
|
||||||
|
final List<String> selectedCategories;
|
||||||
|
|
||||||
const OffersView({
|
const OffersView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.isGpsEnabled,
|
required this.isGpsEnabled,
|
||||||
required this.isConnectedToInternet,
|
required this.isConnectedToInternet,
|
||||||
|
required this.selectedCategories,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -418,7 +461,13 @@ class OffersView extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is OffersLoadSuccess) {
|
if (state is OffersLoadSuccess) {
|
||||||
if (state.offers.isEmpty) {
|
final filteredOffers = selectedCategories.isEmpty
|
||||||
|
? state.offers
|
||||||
|
: state.offers
|
||||||
|
.where((offer) => selectedCategories.contains(offer.category))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (filteredOffers.isEmpty) {
|
||||||
return const SizedBox(
|
return const SizedBox(
|
||||||
height: 300,
|
height: 300,
|
||||||
child: Center(
|
child: Center(
|
||||||
|
|
@ -434,7 +483,7 @@ class OffersView extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
final groupedOffers = groupBy(
|
final groupedOffers = groupBy(
|
||||||
state.offers,
|
filteredOffers,
|
||||||
(OfferModel offer) => offer.category,
|
(OfferModel offer) => offer.category,
|
||||||
);
|
);
|
||||||
final categories = groupedOffers.keys.toList();
|
final categories = groupedOffers.keys.toList();
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,7 @@ void onStart(ServiceInstance service) async {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
mqttService.messages.listen((data) {
|
Future<void> sendGpsData() async {
|
||||||
service.invoke('update', {'offers': data});
|
|
||||||
});
|
|
||||||
|
|
||||||
Timer.periodic(const Duration(seconds: 30), (timer) async {
|
|
||||||
debugPrint("✅ Background Service: Sending location...");
|
|
||||||
|
|
||||||
var locationStatus = await Permission.location.status;
|
var locationStatus = await Permission.location.status;
|
||||||
if (!locationStatus.isGranted) {
|
if (!locationStatus.isGranted) {
|
||||||
debugPrint("Background Service: Location permission not granted.");
|
debugPrint("Background Service: Location permission not granted.");
|
||||||
|
|
@ -69,6 +63,20 @@ void onStart(ServiceInstance service) async {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("❌ Background Service Error: $e");
|
debugPrint("❌ Background Service Error: $e");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mqttService.messages.listen((data) {
|
||||||
|
service.invoke('update', {'offers': data});
|
||||||
|
});
|
||||||
|
|
||||||
|
service.on('force_refresh').listen((event) async {
|
||||||
|
debugPrint("✅ Background Service: Received force_refresh event.");
|
||||||
|
await sendGpsData();
|
||||||
|
});
|
||||||
|
|
||||||
|
Timer.periodic(const Duration(seconds: 30), (timer) async {
|
||||||
|
debugPrint("✅ Background Service: Sending location via periodic timer...");
|
||||||
|
await sendGpsData();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue