added foreground service

appointment_merge
Faiz Hashmi 3 months ago
parent 0779955ef0
commit 675441894a

@ -7,7 +7,7 @@ plugins {
android { android {
namespace = "com.example.hmg_qline.hmg_qline" namespace = "com.example.hmg_qline.hmg_qline"
compileSdk = flutter.compileSdkVersion compileSdk = 35
ndkVersion = "27.0.12077973" ndkVersion = "27.0.12077973"
compileOptions { compileOptions {

@ -3,6 +3,7 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<queries> <queries>

@ -1,19 +1,24 @@
package com.example.hmg_qline.hmg_qline package com.example.hmg_qline.hmg_qline
import android.app.AlarmManager
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.lifecycle.LifecycleService import androidx.lifecycle.LifecycleService
import android.app.ActivityManager
class BootForegroundService : LifecycleService() { class BootForegroundService : LifecycleService() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
startForegroundService() startForegroundService()
schedulePeriodicCheck()
} }
private fun startForegroundService() { private fun startForegroundService() {
@ -30,18 +35,73 @@ class BootForegroundService : LifecycleService() {
val notification: Notification = NotificationCompat.Builder(this, channelId) val notification: Notification = NotificationCompat.Builder(this, channelId)
.setContentTitle("QLine App") .setContentTitle("QLine App")
.setContentText("Launching application...") .setContentText("Monitoring QLine activity...")
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.mipmap.ic_launcher)
.build() .build()
startForeground(1, notification) startForeground(1, notification)
// Launch MainActivity val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
Log.d("BootForegroundService", "Starting MainActivity") val runningProcesses = activityManager.runningAppProcesses
val intent = Intent(this, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
stopSelf() // Stop the service after launching the app var isAppInForeground = false
for (process in runningProcesses) {
if (process.processName == packageName &&
process.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
) {
isAppInForeground = true
break
}
}
if (!isAppInForeground) {
Log.d("BootForegroundService", "App is NOT in foreground — launching MainActivity.")
val intent = Intent(this, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
startActivity(intent)
} else {
Log.d("BootForegroundService", "App is already in foreground.")
}
stopSelf() // Stop the service after check
}
private fun schedulePeriodicCheck() {
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, BootForegroundService::class.java)
val pendingIntent = PendingIntent.getService(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Repeat every 1 minute
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + 60 * 1000,
60 * 1000,
pendingIntent
)
}
private fun bringAppToFrontIfNotVisible() {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningProcesses = activityManager.runningAppProcesses
val isAppInForeground = runningProcesses.any {
it.processName == packageName && it.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
}
if (!isAppInForeground) {
Log.d("BootForegroundService", "App is not in foreground, launching MainActivity")
val intent = Intent(this, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
} else {
Log.d("BootForegroundService", "App is already in foreground")
}
} }
} }

@ -1,5 +1,27 @@
package com.example.hmg_qline.hmg_qline package com.example.hmg_qline.hmg_qline
import android.content.Intent
import android.os.Bundle
import android.util.Log
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {} class MainActivity : FlutterActivity() {
private val CHANNEL = "com.example.hmg_qline/foreground"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, _ ->
Log.d("MainActivity", "MethodChannel call received: ${call.method}")
if (call.method == "reopenApp") {
Log.d("MainActivity", "reopenApp called, launching MainActivity")
val intent = Intent(this, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(intent)
}
}
}
}

@ -2,6 +2,7 @@
// import 'package:flutter/material.dart'; // import 'package:flutter/material.dart';
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/services.dart';
import 'package:flutter_tts/flutter_tts.dart'; import 'package:flutter_tts/flutter_tts.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:hmg_qline/api/api_client.dart'; import 'package:hmg_qline/api/api_client.dart';
@ -12,6 +13,7 @@ import 'package:hmg_qline/services/cache_service.dart';
import 'package:hmg_qline/services/connectivity_service.dart'; import 'package:hmg_qline/services/connectivity_service.dart';
import 'package:hmg_qline/services/logger_service.dart'; import 'package:hmg_qline/services/logger_service.dart';
import 'package:hmg_qline/services/text_to_speech_service.dart'; import 'package:hmg_qline/services/text_to_speech_service.dart';
import 'package:hmg_qline/utilities/native_method_handler.dart';
import 'package:hmg_qline/view_models/queuing_view_model.dart'; import 'package:hmg_qline/view_models/queuing_view_model.dart';
import 'package:hmg_qline/view_models/screen_config_view_model.dart'; import 'package:hmg_qline/view_models/screen_config_view_model.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
@ -39,8 +41,10 @@ class AppDependencies {
getIt.registerSingleton<SignalrRepo>(SignalrRepoImp(loggerService: getIt.get<LoggerService>())); getIt.registerSingleton<SignalrRepo>(SignalrRepoImp(loggerService: getIt.get<LoggerService>()));
getIt.registerSingleton<ScreenDetailsRepo>(ScreenDetailsRepoImp(apiClientInstance: getIt.get<ApiClient>(), loggerService: getIt.get<LoggerService>())); getIt.registerSingleton<ScreenDetailsRepo>(ScreenDetailsRepoImp(apiClientInstance: getIt.get<ApiClient>(), loggerService: getIt.get<LoggerService>()));
//ThirdPartyServices //repos
getIt.registerSingleton<NativeMethodChannelService>(NativeMethodChannelServiceImp(loggerService: getIt.get<LoggerService>(), platform: const MethodChannel('com.example.hmg_qline/foreground')));
//ThirdPartyServices
getIt.registerSingleton<ConnectivityService>(ConnectivityServiceImp(connectivityInstance: Connectivity())); getIt.registerSingleton<ConnectivityService>(ConnectivityServiceImp(connectivityInstance: Connectivity()));
getIt.registerSingleton<CacheService>(CacheServiceImp(preferencesInstance: await SharedPreferences.getInstance())); getIt.registerSingleton<CacheService>(CacheServiceImp(preferencesInstance: await SharedPreferences.getInstance()));
getIt.registerSingleton<AudioService>(AudioServiceImp(audioPlayerInstance: AudioPlayer())); getIt.registerSingleton<AudioService>(AudioServiceImp(audioPlayerInstance: AudioPlayer()));
@ -54,6 +58,7 @@ class AppDependencies {
cacheService: getIt.get<CacheService>(), cacheService: getIt.get<CacheService>(),
connectivityService: getIt.get<ConnectivityService>(), connectivityService: getIt.get<ConnectivityService>(),
loggerService: getIt.get<LoggerService>(), loggerService: getIt.get<LoggerService>(),
nativeMethodChannelService: getIt.get<NativeMethodChannelService>(),
), ),
); );

@ -23,6 +23,9 @@ class AppStrings {
static String dataLogsFileName = "data_logs.txt"; static String dataLogsFileName = "data_logs.txt";
static String errorLogsFileName = "error_logs.txt"; static String errorLogsFileName = "error_logs.txt";
static String openAppNativeFunctionName = "reopenApp";
} }
class AppColors { class AppColors {
@ -104,7 +107,7 @@ class AppConstants {
static String testIP = '12.4.5.1'; // projectID.QlineType.ScreenType.AnyNumber (1 to 10) static String testIP = '12.4.5.1'; // projectID.QlineType.ScreenType.AnyNumber (1 to 10)
static int thresholdForListUI = 3; static int thresholdForListUI = 3;
static int currentBuildVersion = 7; static double currentBuildVersion = 8.1;
} }
class ApiConstants { class ApiConstants {

@ -9,11 +9,58 @@ import 'package:hmg_qline/view_models/queuing_view_model.dart';
import 'package:hmg_qline/views/view_helpers/size_config.dart'; import 'package:hmg_qline/views/view_helpers/size_config.dart';
import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:wakelock_plus/wakelock_plus.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await AppDependencies.addDependencies(); await AppDependencies.addDependencies();
WakelockPlus.enable(); // Keep the screen on
await WakelockPlus.enable();
// The following foreground task logic is now handled natively in Android. Commented out for clarity.
/*
void _initializeForegroundTask() {
FlutterForegroundTask.init(
androidNotificationOptions: AndroidNotificationOptions(
channelId: 'foreground_service',
channelName: AppStrings.appName,
channelDescription: '',
onlyAlertOnce: true,
),
iosNotificationOptions: const IOSNotificationOptions(
showNotification: false,
playSound: false,
),
foregroundTaskOptions: ForegroundTaskOptions(
eventAction: ForegroundTaskEventAction.repeat(5000),
autoRunOnBoot: true,
autoRunOnMyPackageReplaced: true,
allowWakeLock: true,
allowWifiLock: true,
),
);
}
Future<void> _startForegroundService() async {
await FlutterForegroundTask.startService(
notificationTitle: AppStrings.appName,
notificationText: 'App is running in foreground',
callback: startCallback,
);
}
// Initialize foreground task first
_initializeForegroundTask();
// Register lifecycle callback (e.g., reopen app on detach)
LifecycleHandler(
onDetached: () {
getIt<NativeMethodChannelService>().reopenApp();
},
).register();
// Start foreground service AFTER initialization
await _startForegroundService();
*/
runApp(const MyApp()); runApp(const MyApp());
} }
@ -26,26 +73,26 @@ class MyApp extends StatelessWidget {
builder: (context, constraints) { builder: (context, constraints) {
return OrientationBuilder(builder: (context, orientation) { return OrientationBuilder(builder: (context, orientation) {
SizeConfig().init(constraints, orientation); SizeConfig().init(constraints, orientation);
// SystemChrome.setPreferredOrientations([DeviceOrientation.portraitDown]);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
return MultiProvider( return MultiProvider(
providers: [ providers: [
ChangeNotifierProvider<ScreenConfigViewModel>(create: (context) => getIt.get<ScreenConfigViewModel>()), ChangeNotifierProvider<ScreenConfigViewModel>(create: (context) => getIt.get<ScreenConfigViewModel>()),
ChangeNotifierProvider<QueuingViewModel>(create: (context) => getIt.get<QueuingViewModel>()), ChangeNotifierProvider<QueuingViewModel>(create: (context) => getIt.get<QueuingViewModel>()),
], ],
child: MaterialApp( child: MaterialApp(
showSemanticsDebugger: false, showSemanticsDebugger: false,
title: AppStrings.appName, title: AppStrings.appName,
theme: ThemeData( theme: ThemeData(
fontFamily: AppStrings.fontNamePoppins, fontFamily: AppStrings.fontNamePoppins,
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.grey).copyWith( colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.grey).copyWith(
surface: const Color.fromRGBO(255, 255, 255, 1), surface: const Color.fromRGBO(255, 255, 255, 1),
),
), ),
initialRoute: AppRoutes.initialRoute, ),
routes: AppRoutes.routes, initialRoute: AppRoutes.initialRoute,
debugShowCheckedModeBanner: false, routes: AppRoutes.routes,
)); debugShowCheckedModeBanner: false,
),
);
}); });
}, },
); );

@ -48,9 +48,27 @@ class SignalrRepoImp implements SignalrRepo {
.build(); .build();
connection!.serverTimeoutInMilliseconds = 120000; connection!.serverTimeoutInMilliseconds = 120000;
connection!.onclose((exception) { int reconnectAttempts = 0;
const int maxReconnectAttempts = 10;
const Duration reconnectDelay = Duration(seconds: 5);
connection!.onclose((exception) async {
log(exception.toString()); log(exception.toString());
onHubDisconnected(exception); onHubDisconnected(exception);
// Attempt to reconnect with limit and delay
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
loggerService.logToFile("SignalR reconnect attempt #$reconnectAttempts", type: LogTypeEnum.data);
await Future.delayed(reconnectDelay);
try {
await connection!.start();
loggerService.logToFile("SignalR reconnected after disconnect", type: LogTypeEnum.data);
reconnectAttempts = 0; // Reset on success
} catch (e) {
loggerService.logError("Reconnect failed: $e");
}
} else {
loggerService.logError("Max SignalR reconnect attempts reached.");
}
}); });
connection!.onreconnecting((exception) => onHubConnecting(exception)); connection!.onreconnecting((exception) => onHubConnecting(exception));

@ -23,7 +23,7 @@ class LoggerServiceImp implements LoggerService {
@override @override
Future<void> logToFile(String message, {LogTypeEnum type = LogTypeEnum.data}) async { Future<void> logToFile(String message, {LogTypeEnum type = LogTypeEnum.data}) async {
try { try {
final timestamp = DateFormat('yyyy-MM-dd HH:mm:ss a').format(DateTime.now()); final timestamp = DateFormat('yyyy-MM-dd hh:mm:ss a').format(DateTime.now());
final formattedMessage = "[$timestamp] ${type.name.toUpperCase()}: $message"; final formattedMessage = "[$timestamp] ${type.name.toUpperCase()}: $message";
final dir = await getApplicationDocumentsDirectory(); final dir = await getApplicationDocumentsDirectory();

@ -0,0 +1,22 @@
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
void startCallback() {
FlutterForegroundTask.setTaskHandler(MyTaskHandler());
}
class MyTaskHandler extends TaskHandler {
@override
Future<void> onStart(DateTime timestamp, TaskStarter? starter) async {
// Initialization code, e.g. open DB or services
}
@override
Future<void> onDestroy(DateTime timestamp, bool isTimeout) async {
// Clean up if needed
}
@override
void onRepeatEvent(DateTime timestamp) {
// Called based on the eventAction set in ForegroundTaskOptions
}
}

@ -0,0 +1,51 @@
import 'package:flutter/widgets.dart';
import 'package:hmg_qline/config/dependency_injection.dart';
import 'package:hmg_qline/services/logger_service.dart';
import 'package:hmg_qline/utilities/enums.dart';
class LifecycleHandler extends WidgetsBindingObserver {
final void Function()? onResumed;
final void Function()? onPaused;
final void Function()? onDetached;
final void Function()? onInactive;
final void Function()? onHidden;
LifecycleHandler({
this.onResumed,
this.onPaused,
this.onDetached,
this.onInactive,
this.onHidden,
});
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
LoggerService loggerService = getIt.get<LoggerService>();
loggerService.logInfo("[didChangeAppLifecycleState] : ${state.toString()}");
switch (state) {
case AppLifecycleState.resumed:
onResumed?.call();
break;
case AppLifecycleState.paused:
onPaused?.call();
break;
case AppLifecycleState.inactive:
onInactive?.call();
break;
case AppLifecycleState.detached:
onDetached?.call();
break;
case AppLifecycleState.hidden:
onHidden?.call();
break;
}
}
void register() {
WidgetsBinding.instance.addObserver(this);
}
void unregister() {
WidgetsBinding.instance.removeObserver(this);
}
}

@ -0,0 +1,38 @@
import 'package:flutter/services.dart';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/services/logger_service.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:restart_app/restart_app.dart';
abstract class NativeMethodChannelService {
void reopenApp();
void restartApp();
}
class NativeMethodChannelServiceImp implements NativeMethodChannelService {
MethodChannel platform;
LoggerService loggerService;
NativeMethodChannelServiceImp({required this.platform, required this.loggerService});
@override
void reopenApp() async {
try {
await platform.invokeMethod(AppStrings.openAppNativeFunctionName);
} catch (e) {
loggerService.logError("Error launching app: $e");
loggerService.logToFile("Error launching app: $e", type: LogTypeEnum.error);
}
}
@override
void restartApp() async {
try {
await Restart.restartApp();
} catch (e) {
loggerService.logError("Error restarting App : $e");
loggerService.logToFile("Error restarting App : $e", type: LogTypeEnum.error);
}
}
}

@ -89,6 +89,8 @@ class QueuingViewModel extends ChangeNotifier {
Future<void> onHubReconnected(var response) async { Future<void> onHubReconnected(var response) async {
log("onHubConnected: $response"); log("onHubConnected: $response");
loggerService.logToFile("onHubConnected", type: LogTypeEnum.data);
ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>(); ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
screenConfigViewModel.updateIsHubConnected(true); screenConfigViewModel.updateIsHubConnected(true);
screenConfigViewModel.notifyListeners(); screenConfigViewModel.notifyListeners();
@ -96,6 +98,8 @@ class QueuingViewModel extends ChangeNotifier {
Future<void> onHubDisconnected(var response) async { Future<void> onHubDisconnected(var response) async {
log("onHubDisconnected: $response"); log("onHubDisconnected: $response");
loggerService.logToFile("onHubDisconnected", type: LogTypeEnum.data);
ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>(); ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
screenConfigViewModel.updateIsHubConnected(false); screenConfigViewModel.updateIsHubConnected(false);
screenConfigViewModel.notifyListeners(); screenConfigViewModel.notifyListeners();

@ -1,8 +1,10 @@
import 'dart:developer'; import 'dart:developer';
import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_qline/config/dependency_injection.dart'; import 'package:hmg_qline/config/dependency_injection.dart';
import 'package:hmg_qline/constants/app_constants.dart'; import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/main.dart';
import 'package:hmg_qline/models/generic_response_model.dart'; import 'package:hmg_qline/models/generic_response_model.dart';
import 'package:hmg_qline/models/global_config_model.dart'; import 'package:hmg_qline/models/global_config_model.dart';
import 'package:hmg_qline/models/kiosk_language_config_model.dart'; import 'package:hmg_qline/models/kiosk_language_config_model.dart';
@ -17,6 +19,7 @@ import 'package:hmg_qline/services/connectivity_service.dart';
import 'package:hmg_qline/services/logger_service.dart'; import 'package:hmg_qline/services/logger_service.dart';
import 'package:hmg_qline/utilities/enums.dart'; import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/utilities/extensions.dart'; import 'package:hmg_qline/utilities/extensions.dart';
import 'package:hmg_qline/utilities/native_method_handler.dart';
import 'package:hmg_qline/view_models/queuing_view_model.dart'; import 'package:hmg_qline/view_models/queuing_view_model.dart';
import 'package:hmg_qline/views/view_helpers/info_components.dart'; import 'package:hmg_qline/views/view_helpers/info_components.dart';
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart';
@ -27,12 +30,14 @@ class ScreenConfigViewModel extends ChangeNotifier {
final CacheService cacheService; final CacheService cacheService;
final ConnectivityService connectivityService; final ConnectivityService connectivityService;
final LoggerService loggerService; final LoggerService loggerService;
final NativeMethodChannelService nativeMethodChannelService;
ScreenConfigViewModel({ ScreenConfigViewModel({
required this.screenDetailsRepo, required this.screenDetailsRepo,
required this.cacheService, required this.cacheService,
required this.connectivityService, required this.connectivityService,
required this.loggerService, required this.loggerService,
required this.nativeMethodChannelService,
}); });
Future<void> initializeScreenConfigVM() async { Future<void> initializeScreenConfigVM() async {
@ -42,6 +47,18 @@ class ScreenConfigViewModel extends ChangeNotifier {
getTheWidgetsConfigurationsEveryMidnight(); getTheWidgetsConfigurationsEveryMidnight();
} }
Future<void> onAppResumed() async {
loggerService.logToFile("[didChangeAppLifecycleState] : [onAppResumed]", type: LogTypeEnum.data);
}
Future<void> onAppPaused() async {
loggerService.logToFile("[didChangeAppLifecycleState] : [onAppPaused]", type: LogTypeEnum.data);
// nativeMethodChannelService.reopenApp();
nativeMethodChannelService.restartApp();
runApp(const MyApp());
}
Future<void> waitForIPAndInitializeConfigVM() async { Future<void> waitForIPAndInitializeConfigVM() async {
while (currentScreenIP == "") { while (currentScreenIP == "") {
await getCurrentScreenIP(); await getCurrentScreenIP();
@ -103,14 +120,17 @@ class ScreenConfigViewModel extends ChangeNotifier {
} }
void listenNetworkConnectivity() { void listenNetworkConnectivity() {
return connectivityService.subscribeToConnectivityChange(onInternetDisConnected: () { return connectivityService.subscribeToConnectivityChange(
updateIsInternetConnected(false); onInternetDisConnected: () {
updateIsHubConnected(false); updateIsInternetConnected(false);
}, onInternetConnected: () { updateIsHubConnected(false);
updateIsInternetConnected(true); },
QueuingViewModel queuingViewModel = getIt.get<QueuingViewModel>(); onInternetConnected: () {
queuingViewModel.startHubConnection(); updateIsInternetConnected(true);
}); QueuingViewModel queuingViewModel = getIt.get<QueuingViewModel>();
queuingViewModel.startHubConnection();
},
);
} }
GlobalConfigurationsModel globalConfigurationsModel = GlobalConfigurationsModel(); GlobalConfigurationsModel globalConfigurationsModel = GlobalConfigurationsModel();
@ -267,48 +287,51 @@ class ScreenConfigViewModel extends ChangeNotifier {
int counter = 0; int counter = 0;
Timer? _midnightTimer;
Future<void> getTheWidgetsConfigurationsEveryMidnight() async { Future<void> getTheWidgetsConfigurationsEveryMidnight() async {
// Cancel any existing timer to avoid multiple timers running
_midnightTimer?.cancel();
if (!(globalConfigurationsModel.isWeatherReq) && !(globalConfigurationsModel.isPrayerTimeReq) && !(globalConfigurationsModel.isRssFeedReq)) { if (!(globalConfigurationsModel.isWeatherReq) && !(globalConfigurationsModel.isPrayerTimeReq) && !(globalConfigurationsModel.isRssFeedReq)) {
return; return;
} }
DateTime current = DateTime.now(); int counter = 0;
Stream timer = Stream.periodic(const Duration(minutes: 1), (i) { DateTime lastChecked = DateTime.now();
current = current.add(const Duration(minutes: 1));
return current;
});
timer.listen((data) async {
DateTime dateTime = DateTime.parse(data.toString());
_midnightTimer = Timer.periodic(const Duration(minutes: 5), (timer) async {
counter++; counter++;
DateTime now = DateTime.now();
log("counterValue: $counter"); log("counterValue: $counter");
if (counter == 60 && globalConfigurationsModel.isRssFeedReq) { // Every hour, update RSS feed if required
if (counter % 12 == 0 && globalConfigurationsModel.isRssFeedReq) {
await getRssFeedDetailsFromServer(); await getRssFeedDetailsFromServer();
} }
if (globalConfigurationsModel.isWeatherReq) { log("lastChecked: [${lastChecked.day}]");
if (dateTime.day > currentLastTimeUpdated.day) { log("now: [${now.day}]");
// At midnight, update weather and prayer details if required
if (now.day != lastChecked.day) {
if (globalConfigurationsModel.isWeatherReq) {
await getWeatherDetailsFromServer(); await getWeatherDetailsFromServer();
} }
} if (globalConfigurationsModel.isPrayerTimeReq) {
if (globalConfigurationsModel.isPrayerTimeReq) {
if (dateTime.day > currentLastTimeUpdated.day) {
await getPrayerDetailsFromServer(); await getPrayerDetailsFromServer();
} }
lastChecked = now;
} }
});
}
if (globalConfigurationsModel.isRssFeedReq) { @override
if (dateTime.day > currentLastTimeUpdated.day) { void dispose() {
await getRssFeedDetailsFromServer(); _midnightTimer?.cancel();
} patientIdController.dispose();
}
getNextPrayerToShow(); super.dispose();
});
} }
DateTime currentLastTimeUpdated = DateTime.now(); DateTime currentLastTimeUpdated = DateTime.now();
@ -432,12 +455,6 @@ class ScreenConfigViewModel extends ChangeNotifier {
final TextEditingController patientIdController = TextEditingController(); final TextEditingController patientIdController = TextEditingController();
@override
void dispose() {
patientIdController.dispose();
super.dispose();
}
Future<void> onPatientIdSubmitted(String text) async { Future<void> onPatientIdSubmitted(String text) async {
int? patientId = int.tryParse(text); int? patientId = int.tryParse(text);
if (patientId != null && patientId > 0) { if (patientId != null && patientId > 0) {

@ -32,10 +32,10 @@ class AppFooter extends StatelessWidget {
AppStrings.poweredBy, AppStrings.poweredBy,
fontSize: SizeConfig.getWidthMultiplier() * 2.5, fontSize: SizeConfig.getWidthMultiplier() * 2.5,
), ),
Text("v${screenConfigVM.currentScreenIP}_${AppConstants.currentBuildVersion}", Text("v${screenConfigVM.currentScreenIP}(${AppConstants.currentBuildVersion})",
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: SizeConfig.getWidthMultiplier() * 1.7, fontSize: SizeConfig.getWidthMultiplier() * 1.5,
)), )),
Row( Row(
children: [ children: [

@ -1,7 +1,12 @@
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_qline/config/dependency_injection.dart';
import 'package:hmg_qline/constants/app_constants.dart'; import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/utilities/enums.dart'; import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/utilities/extensions.dart'; import 'package:hmg_qline/utilities/extensions.dart';
import 'package:hmg_qline/utilities/lifecycle_handler.dart';
import 'package:hmg_qline/utilities/native_method_handler.dart';
import 'package:hmg_qline/view_models/queuing_view_model.dart'; import 'package:hmg_qline/view_models/queuing_view_model.dart';
import 'package:hmg_qline/view_models/screen_config_view_model.dart'; import 'package:hmg_qline/view_models/screen_config_view_model.dart';
import 'package:hmg_qline/views/common_widgets/app_footer.dart'; import 'package:hmg_qline/views/common_widgets/app_footer.dart';
@ -12,9 +17,34 @@ import 'package:hmg_qline/views/main_queue_screen/components/priority_tickets.da
import 'package:hmg_qline/views/main_queue_screen/components/priority_tickets_sidelist.dart'; import 'package:hmg_qline/views/main_queue_screen/components/priority_tickets_sidelist.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class MainQueueScreen extends StatelessWidget { class MainQueueScreen extends StatefulWidget {
const MainQueueScreen({super.key}); const MainQueueScreen({super.key});
@override
State<MainQueueScreen> createState() => _MainQueueScreenState();
}
class _MainQueueScreenState extends State<MainQueueScreen> {
late LifecycleHandler lifecycleHandler;
@override
void initState() {
super.initState();
final ScreenConfigViewModel screenConfigViewModel = context.read<ScreenConfigViewModel>();
lifecycleHandler = LifecycleHandler(
onResumed: () => screenConfigViewModel.onAppResumed(),
onPaused: () => screenConfigViewModel.onAppPaused(),
);
lifecycleHandler.register();
}
@override
void dispose() {
lifecycleHandler.unregister();
super.dispose();
}
Widget dataContent({required BuildContext context}) { Widget dataContent({required BuildContext context}) {
return Consumer2( return Consumer2(
builder: (BuildContext context, ScreenConfigViewModel screenConfigViewModel, QueuingViewModel queuingViewModel, Widget? child) { builder: (BuildContext context, ScreenConfigViewModel screenConfigViewModel, QueuingViewModel queuingViewModel, Widget? child) {
@ -41,7 +71,7 @@ class MainQueueScreen extends StatelessWidget {
fontFamily = AppStrings.fontNameCairo; fontFamily = AppStrings.fontNameCairo;
} }
if (screenConfigViewModel.state == ViewState.error) { if (screenConfigViewModel.state == ViewState.error && queuingViewModel.currentTickets.isEmpty) {
widget = intimationWidget( widget = intimationWidget(
text: AppStrings.configurationIssueContactAdmin, text: AppStrings.configurationIssueContactAdmin,
fontName: AppStrings.fontNamePoppins, fontName: AppStrings.fontNamePoppins,

@ -158,6 +158,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_foreground_task:
dependency: "direct main"
description:
name: flutter_foreground_task
sha256: "9f1b25a81db95d7119d2c5cffc654048cbdd49d4056183e1beadc1a6a38f3e29"
url: "https://pub.dev"
source: hosted
version: "9.1.0"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -472,6 +480,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.10+1" version: "2.0.10+1"
restart_app:
dependency: "direct main"
description:
name: restart_app
sha256: "00d5ec3e9de871cedbe552fc41e615b042b5ec654385e090e0983f6d02f655ed"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
rxdart: rxdart:
dependency: transitive dependency: transitive
description: description:
@ -484,10 +500,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.5" version: "2.5.3"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:

@ -53,6 +53,8 @@ dependencies:
fluttertoast: ^8.2.8 fluttertoast: ^8.2.8
qr_code_scanner_plus: ^2.0.10+1 qr_code_scanner_plus: ^2.0.10+1
path_provider: ^2.1.5 path_provider: ^2.1.5
flutter_foreground_task: ^9.1.0
restart_app: ^1.3.2
# esc_pos_printer: ^4.0.0 # Ensure you are using the latest version # esc_pos_printer: ^4.0.0 # Ensure you are using the latest version
# esc_pos_utils: ^1.0.0 # esc_pos_utils: ^1.0.0

Loading…
Cancel
Save