From 675441894aad3fa078f2a75c09c1921697ea68b3 Mon Sep 17 00:00:00 2001 From: Faiz Hashmi Date: Sun, 3 Aug 2025 17:43:07 +0300 Subject: [PATCH] added foreground service --- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 1 + .../hmg_qline/BootForegroundService.kt | 74 +++++++++++++-- .../hmg_qline/hmg_qline/MainActivity.kt | 24 ++++- lib/config/dependency_injection.dart | 7 +- lib/constants/app_constants.dart | 5 +- lib/main.dart | 85 +++++++++++++---- lib/repositories/signalR_repo.dart | 20 +++- lib/services/logger_service.dart | 2 +- lib/utilities/foreground_task_handler.dart | 22 +++++ lib/utilities/lifecycle_handler.dart | 51 +++++++++++ lib/utilities/native_method_handler.dart | 38 ++++++++ lib/view_models/queuing_view_model.dart | 4 + lib/view_models/screen_config_view_model.dart | 91 +++++++++++-------- lib/views/common_widgets/app_footer.dart | 4 +- .../main_queue_screen/main_queue_screen.dart | 34 ++++++- pubspec.lock | 20 +++- pubspec.yaml | 2 + 18 files changed, 411 insertions(+), 75 deletions(-) create mode 100644 lib/utilities/foreground_task_handler.dart create mode 100644 lib/utilities/lifecycle_handler.dart create mode 100644 lib/utilities/native_method_handler.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index f055702..c31d5ad 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,7 +7,7 @@ plugins { android { namespace = "com.example.hmg_qline.hmg_qline" - compileSdk = flutter.compileSdkVersion + compileSdk = 35 ndkVersion = "27.0.12077973" compileOptions { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a25330d..f4b26f2 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ + diff --git a/android/app/src/main/kotlin/com/example/hmg_qline/hmg_qline/BootForegroundService.kt b/android/app/src/main/kotlin/com/example/hmg_qline/hmg_qline/BootForegroundService.kt index 6b8591e..bafb989 100644 --- a/android/app/src/main/kotlin/com/example/hmg_qline/hmg_qline/BootForegroundService.kt +++ b/android/app/src/main/kotlin/com/example/hmg_qline/hmg_qline/BootForegroundService.kt @@ -1,19 +1,24 @@ package com.example.hmg_qline.hmg_qline +import android.app.AlarmManager import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context import android.content.Intent import android.os.Build import android.util.Log import androidx.core.app.NotificationCompat import androidx.lifecycle.LifecycleService +import android.app.ActivityManager class BootForegroundService : LifecycleService() { override fun onCreate() { super.onCreate() startForegroundService() + schedulePeriodicCheck() } private fun startForegroundService() { @@ -30,18 +35,73 @@ class BootForegroundService : LifecycleService() { val notification: Notification = NotificationCompat.Builder(this, channelId) .setContentTitle("QLine App") - .setContentText("Launching application...") + .setContentText("Monitoring QLine activity...") .setSmallIcon(R.mipmap.ic_launcher) .build() startForeground(1, notification) - // Launch MainActivity - Log.d("BootForegroundService", "Starting MainActivity") - val intent = Intent(this, MainActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) + val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val runningProcesses = activityManager.runningAppProcesses - 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") + } } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/hmg_qline/hmg_qline/MainActivity.kt b/android/app/src/main/kotlin/com/example/hmg_qline/hmg_qline/MainActivity.kt index 79542b6..400406c 100644 --- a/android/app/src/main/kotlin/com/example/hmg_qline/hmg_qline/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/hmg_qline/hmg_qline/MainActivity.kt @@ -1,5 +1,27 @@ 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.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel -class MainActivity : FlutterActivity() {} \ No newline at end of file +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) + } + } + } +} diff --git a/lib/config/dependency_injection.dart b/lib/config/dependency_injection.dart index 140dae5..0572e41 100644 --- a/lib/config/dependency_injection.dart +++ b/lib/config/dependency_injection.dart @@ -2,6 +2,7 @@ // import 'package:flutter/material.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_tts/flutter_tts.dart'; import 'package:get_it/get_it.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/logger_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/screen_config_view_model.dart'; import 'package:just_audio/just_audio.dart'; @@ -39,8 +41,10 @@ class AppDependencies { getIt.registerSingleton(SignalrRepoImp(loggerService: getIt.get())); getIt.registerSingleton(ScreenDetailsRepoImp(apiClientInstance: getIt.get(), loggerService: getIt.get())); - //ThirdPartyServices + //repos + getIt.registerSingleton(NativeMethodChannelServiceImp(loggerService: getIt.get(), platform: const MethodChannel('com.example.hmg_qline/foreground'))); + //ThirdPartyServices getIt.registerSingleton(ConnectivityServiceImp(connectivityInstance: Connectivity())); getIt.registerSingleton(CacheServiceImp(preferencesInstance: await SharedPreferences.getInstance())); getIt.registerSingleton(AudioServiceImp(audioPlayerInstance: AudioPlayer())); @@ -54,6 +58,7 @@ class AppDependencies { cacheService: getIt.get(), connectivityService: getIt.get(), loggerService: getIt.get(), + nativeMethodChannelService: getIt.get(), ), ); diff --git a/lib/constants/app_constants.dart b/lib/constants/app_constants.dart index ccf3c2b..41a286c 100644 --- a/lib/constants/app_constants.dart +++ b/lib/constants/app_constants.dart @@ -23,6 +23,9 @@ class AppStrings { static String dataLogsFileName = "data_logs.txt"; static String errorLogsFileName = "error_logs.txt"; + + + static String openAppNativeFunctionName = "reopenApp"; } class AppColors { @@ -104,7 +107,7 @@ class AppConstants { static String testIP = '12.4.5.1'; // projectID.QlineType.ScreenType.AnyNumber (1 to 10) static int thresholdForListUI = 3; - static int currentBuildVersion = 7; + static double currentBuildVersion = 8.1; } class ApiConstants { diff --git a/lib/main.dart b/lib/main.dart index 58178f6..0b99299 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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:wakelock_plus/wakelock_plus.dart'; - void main() async { WidgetsFlutterBinding.ensureInitialized(); 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 _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().reopenApp(); + }, + ).register(); + + // Start foreground service AFTER initialization + await _startForegroundService(); + */ + runApp(const MyApp()); } @@ -26,26 +73,26 @@ class MyApp extends StatelessWidget { builder: (context, constraints) { return OrientationBuilder(builder: (context, orientation) { SizeConfig().init(constraints, orientation); - // SystemChrome.setPreferredOrientations([DeviceOrientation.portraitDown]); SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); return MultiProvider( - providers: [ - ChangeNotifierProvider(create: (context) => getIt.get()), - ChangeNotifierProvider(create: (context) => getIt.get()), - ], - child: MaterialApp( - showSemanticsDebugger: false, - title: AppStrings.appName, - theme: ThemeData( - fontFamily: AppStrings.fontNamePoppins, - colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.grey).copyWith( - surface: const Color.fromRGBO(255, 255, 255, 1), - ), + providers: [ + ChangeNotifierProvider(create: (context) => getIt.get()), + ChangeNotifierProvider(create: (context) => getIt.get()), + ], + child: MaterialApp( + showSemanticsDebugger: false, + title: AppStrings.appName, + theme: ThemeData( + fontFamily: AppStrings.fontNamePoppins, + colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.grey).copyWith( + surface: const Color.fromRGBO(255, 255, 255, 1), ), - initialRoute: AppRoutes.initialRoute, - routes: AppRoutes.routes, - debugShowCheckedModeBanner: false, - )); + ), + initialRoute: AppRoutes.initialRoute, + routes: AppRoutes.routes, + debugShowCheckedModeBanner: false, + ), + ); }); }, ); diff --git a/lib/repositories/signalR_repo.dart b/lib/repositories/signalR_repo.dart index 6facecf..a03e232 100644 --- a/lib/repositories/signalR_repo.dart +++ b/lib/repositories/signalR_repo.dart @@ -48,9 +48,27 @@ class SignalrRepoImp implements SignalrRepo { .build(); 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()); 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)); diff --git a/lib/services/logger_service.dart b/lib/services/logger_service.dart index 0e71890..ee65a83 100644 --- a/lib/services/logger_service.dart +++ b/lib/services/logger_service.dart @@ -23,7 +23,7 @@ class LoggerServiceImp implements LoggerService { @override Future logToFile(String message, {LogTypeEnum type = LogTypeEnum.data}) async { 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 dir = await getApplicationDocumentsDirectory(); diff --git a/lib/utilities/foreground_task_handler.dart b/lib/utilities/foreground_task_handler.dart new file mode 100644 index 0000000..5e9af0b --- /dev/null +++ b/lib/utilities/foreground_task_handler.dart @@ -0,0 +1,22 @@ +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; + +void startCallback() { + FlutterForegroundTask.setTaskHandler(MyTaskHandler()); +} + +class MyTaskHandler extends TaskHandler { + @override + Future onStart(DateTime timestamp, TaskStarter? starter) async { + // Initialization code, e.g. open DB or services + } + + @override + Future onDestroy(DateTime timestamp, bool isTimeout) async { + // Clean up if needed + } + + @override + void onRepeatEvent(DateTime timestamp) { + // Called based on the eventAction set in ForegroundTaskOptions + } +} diff --git a/lib/utilities/lifecycle_handler.dart b/lib/utilities/lifecycle_handler.dart new file mode 100644 index 0000000..da3142b --- /dev/null +++ b/lib/utilities/lifecycle_handler.dart @@ -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.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); + } +} diff --git a/lib/utilities/native_method_handler.dart b/lib/utilities/native_method_handler.dart new file mode 100644 index 0000000..bec0cfd --- /dev/null +++ b/lib/utilities/native_method_handler.dart @@ -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); + } + } +} diff --git a/lib/view_models/queuing_view_model.dart b/lib/view_models/queuing_view_model.dart index 7e69ec2..5e9b9f8 100644 --- a/lib/view_models/queuing_view_model.dart +++ b/lib/view_models/queuing_view_model.dart @@ -89,6 +89,8 @@ class QueuingViewModel extends ChangeNotifier { Future onHubReconnected(var response) async { log("onHubConnected: $response"); + loggerService.logToFile("onHubConnected", type: LogTypeEnum.data); + ScreenConfigViewModel screenConfigViewModel = getIt.get(); screenConfigViewModel.updateIsHubConnected(true); screenConfigViewModel.notifyListeners(); @@ -96,6 +98,8 @@ class QueuingViewModel extends ChangeNotifier { Future onHubDisconnected(var response) async { log("onHubDisconnected: $response"); + loggerService.logToFile("onHubDisconnected", type: LogTypeEnum.data); + ScreenConfigViewModel screenConfigViewModel = getIt.get(); screenConfigViewModel.updateIsHubConnected(false); screenConfigViewModel.notifyListeners(); diff --git a/lib/view_models/screen_config_view_model.dart b/lib/view_models/screen_config_view_model.dart index b198d02..39a25b0 100644 --- a/lib/view_models/screen_config_view_model.dart +++ b/lib/view_models/screen_config_view_model.dart @@ -1,8 +1,10 @@ import 'dart:developer'; +import 'dart:async'; import 'package:flutter/foundation.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/main.dart'; import 'package:hmg_qline/models/generic_response_model.dart'; import 'package:hmg_qline/models/global_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/utilities/enums.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/views/view_helpers/info_components.dart'; import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; @@ -27,12 +30,14 @@ class ScreenConfigViewModel extends ChangeNotifier { final CacheService cacheService; final ConnectivityService connectivityService; final LoggerService loggerService; + final NativeMethodChannelService nativeMethodChannelService; ScreenConfigViewModel({ required this.screenDetailsRepo, required this.cacheService, required this.connectivityService, required this.loggerService, + required this.nativeMethodChannelService, }); Future initializeScreenConfigVM() async { @@ -42,6 +47,18 @@ class ScreenConfigViewModel extends ChangeNotifier { getTheWidgetsConfigurationsEveryMidnight(); } + Future onAppResumed() async { + loggerService.logToFile("[didChangeAppLifecycleState] : [onAppResumed]", type: LogTypeEnum.data); + } + + Future onAppPaused() async { + loggerService.logToFile("[didChangeAppLifecycleState] : [onAppPaused]", type: LogTypeEnum.data); + + // nativeMethodChannelService.reopenApp(); + nativeMethodChannelService.restartApp(); + runApp(const MyApp()); + } + Future waitForIPAndInitializeConfigVM() async { while (currentScreenIP == "") { await getCurrentScreenIP(); @@ -103,14 +120,17 @@ class ScreenConfigViewModel extends ChangeNotifier { } void listenNetworkConnectivity() { - return connectivityService.subscribeToConnectivityChange(onInternetDisConnected: () { - updateIsInternetConnected(false); - updateIsHubConnected(false); - }, onInternetConnected: () { - updateIsInternetConnected(true); - QueuingViewModel queuingViewModel = getIt.get(); - queuingViewModel.startHubConnection(); - }); + return connectivityService.subscribeToConnectivityChange( + onInternetDisConnected: () { + updateIsInternetConnected(false); + updateIsHubConnected(false); + }, + onInternetConnected: () { + updateIsInternetConnected(true); + QueuingViewModel queuingViewModel = getIt.get(); + queuingViewModel.startHubConnection(); + }, + ); } GlobalConfigurationsModel globalConfigurationsModel = GlobalConfigurationsModel(); @@ -267,48 +287,51 @@ class ScreenConfigViewModel extends ChangeNotifier { int counter = 0; + Timer? _midnightTimer; + Future getTheWidgetsConfigurationsEveryMidnight() async { + // Cancel any existing timer to avoid multiple timers running + _midnightTimer?.cancel(); + if (!(globalConfigurationsModel.isWeatherReq) && !(globalConfigurationsModel.isPrayerTimeReq) && !(globalConfigurationsModel.isRssFeedReq)) { return; } - DateTime current = DateTime.now(); - Stream timer = Stream.periodic(const Duration(minutes: 1), (i) { - current = current.add(const Duration(minutes: 1)); - return current; - }); - - timer.listen((data) async { - DateTime dateTime = DateTime.parse(data.toString()); + int counter = 0; + DateTime lastChecked = DateTime.now(); + _midnightTimer = Timer.periodic(const Duration(minutes: 5), (timer) async { counter++; - + DateTime now = DateTime.now(); log("counterValue: $counter"); - if (counter == 60 && globalConfigurationsModel.isRssFeedReq) { + // Every hour, update RSS feed if required + if (counter % 12 == 0 && globalConfigurationsModel.isRssFeedReq) { await getRssFeedDetailsFromServer(); } - if (globalConfigurationsModel.isWeatherReq) { - if (dateTime.day > currentLastTimeUpdated.day) { + log("lastChecked: [${lastChecked.day}]"); + log("now: [${now.day}]"); + + // At midnight, update weather and prayer details if required + if (now.day != lastChecked.day) { + if (globalConfigurationsModel.isWeatherReq) { await getWeatherDetailsFromServer(); } - } - - if (globalConfigurationsModel.isPrayerTimeReq) { - if (dateTime.day > currentLastTimeUpdated.day) { + if (globalConfigurationsModel.isPrayerTimeReq) { await getPrayerDetailsFromServer(); } + lastChecked = now; } + }); + } - if (globalConfigurationsModel.isRssFeedReq) { - if (dateTime.day > currentLastTimeUpdated.day) { - await getRssFeedDetailsFromServer(); - } - } + @override + void dispose() { + _midnightTimer?.cancel(); + patientIdController.dispose(); - getNextPrayerToShow(); - }); + super.dispose(); } DateTime currentLastTimeUpdated = DateTime.now(); @@ -432,12 +455,6 @@ class ScreenConfigViewModel extends ChangeNotifier { final TextEditingController patientIdController = TextEditingController(); - @override - void dispose() { - patientIdController.dispose(); - super.dispose(); - } - Future onPatientIdSubmitted(String text) async { int? patientId = int.tryParse(text); if (patientId != null && patientId > 0) { diff --git a/lib/views/common_widgets/app_footer.dart b/lib/views/common_widgets/app_footer.dart index 81d6f04..5b436a2 100644 --- a/lib/views/common_widgets/app_footer.dart +++ b/lib/views/common_widgets/app_footer.dart @@ -32,10 +32,10 @@ class AppFooter extends StatelessWidget { AppStrings.poweredBy, fontSize: SizeConfig.getWidthMultiplier() * 2.5, ), - Text("v${screenConfigVM.currentScreenIP}_${AppConstants.currentBuildVersion}", + Text("v${screenConfigVM.currentScreenIP}(${AppConstants.currentBuildVersion})", style: TextStyle( fontWeight: FontWeight.w500, - fontSize: SizeConfig.getWidthMultiplier() * 1.7, + fontSize: SizeConfig.getWidthMultiplier() * 1.5, )), Row( children: [ diff --git a/lib/views/main_queue_screen/main_queue_screen.dart b/lib/views/main_queue_screen/main_queue_screen.dart index 7fcf7d5..cdc50b4 100644 --- a/lib/views/main_queue_screen/main_queue_screen.dart +++ b/lib/views/main_queue_screen/main_queue_screen.dart @@ -1,7 +1,12 @@ +import 'dart:developer'; + 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/utilities/enums.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/screen_config_view_model.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:provider/provider.dart'; -class MainQueueScreen extends StatelessWidget { +class MainQueueScreen extends StatefulWidget { const MainQueueScreen({super.key}); + @override + State createState() => _MainQueueScreenState(); +} + +class _MainQueueScreenState extends State { + late LifecycleHandler lifecycleHandler; + + @override + void initState() { + super.initState(); + + final ScreenConfigViewModel screenConfigViewModel = context.read(); + lifecycleHandler = LifecycleHandler( + onResumed: () => screenConfigViewModel.onAppResumed(), + onPaused: () => screenConfigViewModel.onAppPaused(), + ); + lifecycleHandler.register(); + } + + @override + void dispose() { + lifecycleHandler.unregister(); + super.dispose(); + } + Widget dataContent({required BuildContext context}) { return Consumer2( builder: (BuildContext context, ScreenConfigViewModel screenConfigViewModel, QueuingViewModel queuingViewModel, Widget? child) { @@ -41,7 +71,7 @@ class MainQueueScreen extends StatelessWidget { fontFamily = AppStrings.fontNameCairo; } - if (screenConfigViewModel.state == ViewState.error) { + if (screenConfigViewModel.state == ViewState.error && queuingViewModel.currentTickets.isEmpty) { widget = intimationWidget( text: AppStrings.configurationIssueContactAdmin, fontName: AppStrings.fontNamePoppins, diff --git a/pubspec.lock b/pubspec.lock index d3f7080..dfa3b51 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -158,6 +158,14 @@ packages: description: flutter source: sdk 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: dependency: "direct dev" description: @@ -472,6 +480,14 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -484,10 +500,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.5.3" shared_preferences_android: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a0a31ad..04bd6f3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,8 @@ dependencies: fluttertoast: ^8.2.8 qr_code_scanner_plus: ^2.0.10+1 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_utils: ^1.0.0