Compare commits

..

3 Commits

Author SHA1 Message Date
Faiz Hashmi aa81f40118 fixed size issue on calling cards 6 days ago
faizatflutter 856395f273 android 14 stable version 1 month ago
faizatflutter d9c0ce5180 android 14 testing started. 1 month ago

@ -45,4 +45,5 @@ flutter {
dependencies { dependencies {
implementation 'androidx.lifecycle:lifecycle-service:2.8.7' implementation 'androidx.lifecycle:lifecycle-service:2.8.7'
implementation 'androidx.core:core-ktx:1.13.1'
} }

@ -5,6 +5,12 @@
<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.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Android 12+ (API 31+) exact alarm permissions -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<!-- Android 14+ (API 34+) - USE_EXACT_ALARM is for apps that need exact alarms as core functionality -->
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<!-- Foreground service types for Android 14+ -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<queries> <queries>
<intent> <intent>
@ -24,14 +30,30 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" /> <action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<!-- Receiver for scheduled restart alarm -->
<receiver
android:name=".RestartAlarmReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.example.hmg_qline.SCHEDULED_RESTART" />
</intent-filter>
</receiver>
<service <service
android:name=".BootForegroundService" android:name=".BootForegroundService"
android:exported="true" /> android:exported="true"
android:foregroundServiceType="specialUse">
<!-- Android 14+ requires property declaration for FOREGROUND_SERVICE_TYPE_SPECIAL_USE -->
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="App auto-start and scheduled restart" />
</service>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
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"

@ -0,0 +1,225 @@
package com.example.hmg_qline.hmg_qline
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.Settings
import android.util.Log
import java.util.Calendar
/**
* Utility class for scheduling app restart alarms.
* Handles Android version-specific alarm scheduling with proper backward compatibility.
*
* Android Version Compatibility:
* - Android 14+ (API 34): Uses USE_EXACT_ALARM or SCHEDULE_EXACT_ALARM with permission check
* - Android 12-13 (API 31-33): Uses SCHEDULE_EXACT_ALARM with permission check
* - Android 6-11 (API 23-30): Uses setExactAndAllowWhileIdle
* - Android < 6 (API < 23): Uses setExact
*/
object AlarmScheduler {
private const val TAG = "AlarmScheduler"
private const val RESTART_ALARM_REQUEST_CODE = 1001
/**
* Schedule a daily restart alarm at the specified time.
*
* @param context Application context
* @param hour Hour of day (0-23), default is 0 (midnight)
* @param minute Minute (0-59), default is 15
*/
fun scheduleRestartAlarm(context: Context, hour: Int = 0, minute: Int = 15) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, RestartAlarmReceiver::class.java).apply {
action = RestartAlarmReceiver.ACTION_SCHEDULED_RESTART
}
val pendingIntent = PendingIntent.getBroadcast(
context,
RESTART_ALARM_REQUEST_CODE,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Calculate next alarm time
val calendar = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, hour)
set(Calendar.MINUTE, minute)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
// If the time has already passed today, schedule for tomorrow
if (timeInMillis <= System.currentTimeMillis()) {
add(Calendar.DAY_OF_YEAR, 1)
}
}
// Cancel any existing alarm first
alarmManager.cancel(pendingIntent)
Log.d(TAG, "Scheduling restart alarm for: ${calendar.time}")
Log.d(TAG, "Android SDK Version: ${Build.VERSION.SDK_INT}")
// Schedule based on Android version
when {
// Android 14+ (API 34+)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> {
scheduleForAndroid14Plus(context, alarmManager, pendingIntent, calendar.timeInMillis)
}
// Android 12-13 (API 31-33)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
scheduleForAndroid12To13(context, alarmManager, pendingIntent, calendar.timeInMillis)
}
// Android 6-11 (API 23-30)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
Log.d(TAG, "Alarm scheduled using setExactAndAllowWhileIdle (API 23-30)")
}
// Android < 6 (API < 23)
else -> {
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
Log.d(TAG, "Alarm scheduled using setExact (API < 23)")
}
}
}
/**
* Schedule alarm for Android 14+ (API 34+)
* Android 14 requires special handling for exact alarms.
*/
private fun scheduleForAndroid14Plus(
context: Context,
alarmManager: AlarmManager,
pendingIntent: PendingIntent,
triggerTime: Long
) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (alarmManager.canScheduleExactAlarms()) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
)
Log.d(TAG, "Alarm scheduled using setExactAndAllowWhileIdle (API 34+)")
} else {
// Fallback to inexact alarm if permission not granted
alarmManager.setAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
)
Log.w(TAG, "Exact alarm permission not granted, using setAndAllowWhileIdle (API 34+)")
}
}
} catch (e: SecurityException) {
Log.e(TAG, "SecurityException scheduling alarm: ${e.message}")
// Fallback to inexact alarm
alarmManager.setAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
)
}
}
/**
* Schedule alarm for Android 12-13 (API 31-33)
*/
private fun scheduleForAndroid12To13(
context: Context,
alarmManager: AlarmManager,
pendingIntent: PendingIntent,
triggerTime: Long
) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (alarmManager.canScheduleExactAlarms()) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
)
Log.d(TAG, "Alarm scheduled using setExactAndAllowWhileIdle (API 31-33)")
} else {
alarmManager.setAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
)
Log.w(TAG, "Exact alarm permission not granted, using setAndAllowWhileIdle (API 31-33)")
}
}
} catch (e: SecurityException) {
Log.e(TAG, "SecurityException scheduling alarm: ${e.message}")
alarmManager.setAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
)
}
}
/**
* Cancel the scheduled restart alarm.
*/
fun cancelRestartAlarm(context: Context) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, RestartAlarmReceiver::class.java).apply {
action = RestartAlarmReceiver.ACTION_SCHEDULED_RESTART
}
val pendingIntent = PendingIntent.getBroadcast(
context,
RESTART_ALARM_REQUEST_CODE,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
alarmManager.cancel(pendingIntent)
Log.d(TAG, "Restart alarm cancelled")
}
/**
* Check if the app can schedule exact alarms.
* Returns true for Android < 12 (always allowed) or if permission is granted on Android 12+.
*/
fun canScheduleExactAlarms(context: Context): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.canScheduleExactAlarms()
} else {
true // Always allowed on older versions
}
}
/**
* Open system settings to request exact alarm permission (Android 12+).
*/
fun requestExactAlarmPermission(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
try {
val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
} catch (e: Exception) {
Log.e(TAG, "Error opening exact alarm settings: ${e.message}")
}
}
}
}

@ -4,46 +4,128 @@ import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Intent import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import androidx.lifecycle.LifecycleService import androidx.lifecycle.LifecycleService
/**
* Foreground service that launches the app after device boot or scheduled restart.
* Compatible with Android 14+ (API 34+) and older versions.
*
* Android 14+ requires:
* - Explicit foreground service type in manifest and code
* - FOREGROUND_SERVICE_SPECIAL_USE permission
*/
class BootForegroundService : LifecycleService() { class BootForegroundService : LifecycleService() {
companion object {
private const val TAG = "BootForegroundService"
private const val CHANNEL_ID = "boot_service_channel"
private const val NOTIFICATION_ID = 1
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
startForegroundService() Log.d(TAG, "Service created - Android SDK: ${Build.VERSION.SDK_INT}")
startForegroundServiceCompat()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
val source = intent?.getStringExtra("source") ?: "unknown"
Log.d(TAG, "Service started from source: $source")
// Launch the main activity
launchMainActivity(source)
// Stop the service after launching the app
stopSelf()
return START_NOT_STICKY
} }
private fun startForegroundService() { /**
val channelId = "boot_service_channel" * Start foreground service with Android version compatibility.
* Android 14+ requires explicit foreground service type.
*/
private fun startForegroundServiceCompat() {
createNotificationChannel() createNotificationChannel()
val notification: Notification = NotificationCompat.Builder(this, channelId) val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("QLine App") .setContentTitle("QLine App")
.setContentText("Monitoring QLine activity...") .setContentText("Starting QLine...")
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setAutoCancel(true)
.build() .build()
startForeground(1, notification) // Use ServiceCompat for Android 14+ compatibility
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// Android 14+ (API 34+) requires explicit foreground service type
ServiceCompat.startForeground(
this,
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
)
Log.d(TAG, "Foreground service started with SPECIAL_USE type (API 34+)")
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10-13 (API 29-33)
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE)
Log.d(TAG, "Foreground service started with NONE type (API 29-33)")
} else {
// Android 8-9 (API 26-28)
startForeground(NOTIFICATION_ID, notification)
Log.d(TAG, "Foreground service started (API 26-28)")
}
}
/**
* Launch the main activity.
*/
private fun launchMainActivity(source: String) {
try {
Log.d(TAG, "Launching MainActivity from source: $source")
val intent = Intent(this, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
putExtra("launched_from_boot", source == "boot_completed")
putExtra("launched_from_scheduled_restart", source == "scheduled_restart")
}
startActivity(intent)
// Only launch MainActivity if this service is started by the system (e.g. on boot) Log.d(TAG, "MainActivity launched successfully")
val intent = Intent(this, MainActivity::class.java).apply { } catch (e: Exception) {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) Log.e(TAG, "Error launching MainActivity: ${e.message}")
} }
startActivity(intent)
stopSelf() // Stop the service after initialization
} }
/**
* Create notification channel for Android 8.0+ (API 26+).
*/
private fun createNotificationChannel() { private fun createNotificationChannel() {
val channelId = "boot_service_channel"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel( val channel = NotificationChannel(
channelId, CHANNEL_ID,
"Boot Service Channel", "Boot Service Channel",
NotificationManager.IMPORTANCE_HIGH NotificationManager.IMPORTANCE_LOW // Use LOW to avoid sound/vibration
) ).apply {
description = "Used to start QLine app after device boot"
setShowBadge(false)
}
val manager = getSystemService(NotificationManager::class.java) val manager = getSystemService(NotificationManager::class.java)
manager?.createNotificationChannel(channel) manager?.createNotificationChannel(channel)
Log.d(TAG, "Notification channel created")
} }
} }
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "Service destroyed")
}
} }

@ -6,17 +6,58 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
/**
* BroadcastReceiver that handles device boot events.
* Starts the app automatically after device boot and schedules daily restart alarm.
* Compatible with Android 14+ (API 34+) and older versions.
*/
class BootBroadcastReceiver : BroadcastReceiver() { class BootBroadcastReceiver : BroadcastReceiver() {
companion object {
private const val TAG = "BootBroadcastReceiver"
}
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
Log.d("BootReceiver", "Received intent: ${intent.action}") Log.d(TAG, "Received intent: ${intent.action}")
if (intent.action == Intent.ACTION_BOOT_COMPLETED || if (intent.action == Intent.ACTION_BOOT_COMPLETED ||
intent.action == "android.intent.action.QUICKBOOT_POWERON" || intent.action == "android.intent.action.QUICKBOOT_POWERON" ||
intent.action == "com.htc.intent.action.QUICKBOOT_POWERON" intent.action == "com.htc.intent.action.QUICKBOOT_POWERON"
) { ) {
Log.d(TAG, "Boot completed detected - Android SDK: ${Build.VERSION.SDK_INT}")
Log.d("BootReceiver", "Starting BootForegroundService.") // Schedule the daily restart alarm first
val serviceIntent = Intent(context, BootForegroundService::class.java) scheduleRestartAlarm(context)
// Then start the foreground service to launch the app
startAppViaForegroundService(context)
}
}
/**
* Schedule daily restart alarm at 00:15.
* This ensures the alarm is set even if the app wasn't running before reboot.
*/
private fun scheduleRestartAlarm(context: Context) {
try {
Log.d(TAG, "Scheduling daily restart alarm after boot")
AlarmScheduler.scheduleRestartAlarm(context, 0, 15) // 00:15 (12:15 AM)
Log.d(TAG, "Daily restart alarm scheduled successfully")
} catch (e: Exception) {
Log.e(TAG, "Error scheduling restart alarm after boot: ${e.message}")
}
}
/**
* Start the app via foreground service.
* Uses different approach based on Android version for compatibility.
*/
private fun startAppViaForegroundService(context: Context) {
try {
Log.d(TAG, "Starting BootForegroundService")
val serviceIntent = Intent(context, BootForegroundService::class.java).apply {
putExtra("source", "boot_completed")
}
// Use foreground service for Android 8.0+ (API 26+) // Use foreground service for Android 8.0+ (API 26+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -24,6 +65,30 @@ class BootBroadcastReceiver : BroadcastReceiver() {
} else { } else {
context.startService(serviceIntent) context.startService(serviceIntent)
} }
Log.d(TAG, "BootForegroundService started successfully")
} catch (e: Exception) {
Log.e(TAG, "Error starting foreground service: ${e.message}")
// Fallback: try direct activity launch
tryDirectActivityLaunch(context)
}
}
/**
* Fallback method to launch activity directly if service fails.
*/
private fun tryDirectActivityLaunch(context: Context) {
try {
Log.d(TAG, "Attempting direct activity launch as fallback")
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
putExtra("launched_from_boot", true)
}
context.startActivity(launchIntent)
Log.d(TAG, "Direct activity launch successful")
} catch (fallbackError: Exception) {
Log.e(TAG, "Direct activity launch also failed: ${fallbackError.message}")
} }
} }
} }

@ -1,5 +1,8 @@
package com.example.hmg_qline.hmg_qline package com.example.hmg_qline.hmg_qline
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
@ -13,85 +16,168 @@ import java.io.File
class MainActivity : FlutterActivity() { class MainActivity : FlutterActivity() {
private val CHANNEL = "com.example.hmg_qline/foreground" private val CHANNEL = "com.example.hmg_qline/foreground"
companion object {
private const val TAG = "MainActivity"
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
Log.d("MainActivity", "MethodChannel call received: ${call.method}") Log.d(TAG, "MethodChannel call received: ${call.method}")
when (call.method) { when (call.method) {
"reopenApp" -> { "reopenApp" -> {
Log.d("MainActivity", "reopenApp called, bringing app to foreground") Log.d(TAG, "reopenApp called, bringing app to foreground")
moveTaskToBack(false) moveTaskToBack(false)
result.success("App brought to foreground") result.success("App brought to foreground")
} }
"restartApp" -> { "restartApp" -> {
Log.d("MainActivity", "Restarting application") Log.d(TAG, "Restarting application")
restartApplication() restartApplication()
result.success("App restart initiated") result.success("App restart initiated")
} }
"restartDevice" -> { "restartDevice" -> {
Log.d("MainActivity", "Attempting device restart") Log.d(TAG, "Attempting device restart")
restartDevice(result) restartDevice(result)
} }
"runShellScript" -> { "runShellScript" -> {
Log.d("MainActivity", "Executing shell restart command") Log.d(TAG, "Executing shell restart command")
executeShellRestart(result) executeShellRestart(result)
} }
"clearAudioCache" -> { "clearAudioCache" -> {
Log.d("MainActivity", "Clearing audio cache") Log.d(TAG, "Clearing audio cache")
clearAudioResources() clearAudioResources()
result.success("Audio cache cleared") result.success("Audio cache cleared")
} }
"clearAllResources" -> { "clearAllResources" -> {
Log.d("MainActivity", "Clearing all native resources") Log.d(TAG, "Clearing all native resources")
clearAllNativeResources() clearAllNativeResources()
result.success("All resources cleared") result.success("All resources cleared")
} }
// === NEW: Alarm Scheduling Methods for Android 14+ compatibility ===
"scheduleRestartAlarm" -> {
val hour = call.argument<Int>("hour") ?: 0
val minute = call.argument<Int>("minute") ?: 15
Log.d(TAG, "Scheduling restart alarm for $hour:$minute")
scheduleRestartAlarm(hour, minute)
result.success("Restart alarm scheduled for $hour:$minute")
}
"cancelRestartAlarm" -> {
Log.d(TAG, "Cancelling restart alarm")
AlarmScheduler.cancelRestartAlarm(this)
result.success("Restart alarm cancelled")
}
"canScheduleExactAlarms" -> {
val canSchedule = AlarmScheduler.canScheduleExactAlarms(this)
Log.d(TAG, "Can schedule exact alarms: $canSchedule")
result.success(canSchedule)
}
"requestExactAlarmPermission" -> {
Log.d(TAG, "Requesting exact alarm permission")
AlarmScheduler.requestExactAlarmPermission(this)
result.success("Permission request initiated")
}
else -> { else -> {
Log.w("MainActivity", "Method not implemented: ${call.method}") Log.w(TAG, "Method not implemented: ${call.method}")
result.notImplemented() result.notImplemented()
} }
} }
} }
} }
private fun restartApplication() { /**
* Schedule daily restart alarm at specified time.
* Compatible with Android 14+ and older versions.
*/
private fun scheduleRestartAlarm(hour: Int, minute: Int) {
try { try {
Log.d("MainActivity", "Initiating app restart") AlarmScheduler.scheduleRestartAlarm(this, hour, minute)
Log.d(TAG, "Restart alarm scheduled successfully for $hour:$minute")
} catch (e: Exception) {
Log.e(TAG, "Error scheduling restart alarm: ${e.message}")
}
}
// Clear resources before restart private fun restartApplication() {
clearAllNativeResources() try {
Log.d(TAG, "Initiating app restart")
// Create restart intent // Get the launch intent
val intent = packageManager.getLaunchIntentForPackage(packageName)?.apply { val intent = packageManager.getLaunchIntentForPackage(packageName)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
putExtra("restarted", true)
}
if (intent != null) { if (intent != null) {
// Use a shorter delay for faster restart // Configure intent for clean restart
intent.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
putExtra("restarted", true)
}
// Use AlarmManager for reliable restart (works better on Android 14+)
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pendingIntent = PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
)
// Schedule restart in 500ms
alarmManager.set(
AlarmManager.RTC,
System.currentTimeMillis() + 500,
pendingIntent
)
Log.d(TAG, "Restart scheduled via AlarmManager")
// Now safely exit the app
Handler(Looper.getMainLooper()).postDelayed({ Handler(Looper.getMainLooper()).postDelayed({
startActivity(intent)
finishAffinity() finishAffinity()
// Remove exitProcess() call if present android.os.Process.killProcess(android.os.Process.myPid())
// android.os.Process.killProcess(android.os.Process.myPid()) }, 100)
}, 100) // Reduced delay
Log.d("MainActivity", "App restart initiated")
} else { } else {
Log.e("MainActivity", "Could not create restart intent") Log.e(TAG, "Could not create restart intent")
}
} catch (e: Exception) {
Log.e(TAG, "Error during restart: ${e.message}")
// Fallback: try simple restart
fallbackRestart()
}
}
/**
* Fallback restart method if AlarmManager approach fails
*/
private fun fallbackRestart() {
try {
Log.d(TAG, "Attempting fallback restart")
val intent = packageManager.getLaunchIntentForPackage(packageName)?.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
putExtra("restarted", true)
} }
if (intent != null) {
startActivity(intent)
finishAffinity()
Runtime.getRuntime().exit(0)
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e("MainActivity", "Error during restart: ${e.message}") Log.e(TAG, "Fallback restart also failed: ${e.message}")
// Fallback - don't exit, just log the error
} }
} }
@ -237,25 +323,43 @@ class MainActivity : FlutterActivity() {
// Log if app was restarted // Log if app was restarted
if (intent.getBooleanExtra("restarted", false)) { if (intent.getBooleanExtra("restarted", false)) {
Log.d("MainActivity", "App restarted successfully") Log.d(TAG, "App restarted successfully")
} }
// Log if launched from boot // Log if launched from boot
if (intent.getBooleanExtra("launched_from_boot", false)) { if (intent.getBooleanExtra("launched_from_boot", false)) {
Log.d("MainActivity", "App launched from boot") Log.d(TAG, "App launched from boot")
// Give system time to settle after boot // Give system time to settle after boot
Thread.sleep(2000) Thread.sleep(2000)
} }
// Schedule daily restart alarm at 00:15 (12:15 AM)
// This ensures the alarm is always set when app starts
initializeRestartAlarm()
}
/**
* Initialize the daily restart alarm.
* Called on app start to ensure alarm is always scheduled.
*/
private fun initializeRestartAlarm() {
try {
Log.d(TAG, "Initializing daily restart alarm")
AlarmScheduler.scheduleRestartAlarm(this, 0, 15) // 00:15 (12:15 AM)
Log.d(TAG, "Daily restart alarm initialized for 00:15")
} catch (e: Exception) {
Log.e(TAG, "Error initializing restart alarm: ${e.message}")
}
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
Log.d("MainActivity", "Activity resumed") Log.d(TAG, "Activity resumed")
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
Log.d("MainActivity", "Activity paused - cleaning up resources") Log.d(TAG, "Activity paused - cleaning up resources")
// Light cleanup when app goes to background // Light cleanup when app goes to background
System.gc() System.gc()
@ -263,7 +367,7 @@ class MainActivity : FlutterActivity() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
Log.d("MainActivity", "Activity destroyed") Log.d(TAG, "Activity destroyed")
// Final cleanup // Final cleanup
clearAllNativeResources() clearAllNativeResources()

@ -0,0 +1,82 @@
package com.example.hmg_qline.hmg_qline
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
/**
* BroadcastReceiver for handling scheduled app restart alarms.
* This is triggered by AlarmManager at the scheduled time (e.g., 12:15 AM).
* Compatible with Android 14+ (API 34+) and older versions.
*/
class RestartAlarmReceiver : BroadcastReceiver() {
companion object {
private const val TAG = "RestartAlarmReceiver"
const val ACTION_SCHEDULED_RESTART = "com.example.hmg_qline.SCHEDULED_RESTART"
}
override fun onReceive(context: Context, intent: Intent) {
Log.d(TAG, "Received intent: ${intent.action}")
when (intent.action) {
ACTION_SCHEDULED_RESTART -> {
Log.d(TAG, "Scheduled restart triggered")
launchApp(context)
// Re-schedule the alarm for the next day
rescheduleAlarm(context)
}
Intent.ACTION_BOOT_COMPLETED,
"android.intent.action.QUICKBOOT_POWERON",
"com.htc.intent.action.QUICKBOOT_POWERON" -> {
Log.d(TAG, "Boot completed - re-scheduling daily restart alarm")
rescheduleAlarm(context)
}
}
}
private fun launchApp(context: Context) {
try {
Log.d(TAG, "Launching app via foreground service")
val serviceIntent = Intent(context, BootForegroundService::class.java).apply {
putExtra("source", "scheduled_restart")
}
// Use foreground service for Android 8.0+ (API 26+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent)
} else {
context.startService(serviceIntent)
}
Log.d(TAG, "App launch initiated successfully")
} catch (e: Exception) {
Log.e(TAG, "Error launching app: ${e.message}")
// Fallback: try direct activity launch
try {
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
context.startActivity(launchIntent)
} catch (fallbackError: Exception) {
Log.e(TAG, "Fallback launch also failed: ${fallbackError.message}")
}
}
}
private fun rescheduleAlarm(context: Context) {
try {
// Re-schedule for the next day at 00:15
AlarmScheduler.scheduleRestartAlarm(context, 0, 15)
Log.d(TAG, "Alarm rescheduled for next day at 00:15")
} catch (e: Exception) {
Log.e(TAG, "Error rescheduling alarm: ${e.message}")
}
}
}

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.9.10' ext.kotlin_version = '2.1.0'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()

@ -19,7 +19,7 @@ pluginManagement {
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.7.0" apply false id "com.android.application" version "8.7.0" apply false
id "org.jetbrains.kotlin.android" version "1.8.22" apply false id "org.jetbrains.kotlin.android" version "2.1.0" apply false
} }

@ -1,4 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.8333 3.99984C15.8333 3.26346 16.4303 2.6665 17.1667 2.6665H29.1667C29.903 2.6665 30.5 3.26346 30.5 3.99984C30.5 4.73622 29.903 5.33317 29.1667 5.33317L29.1667 23.3332C29.1667 26.6469 26.4804 29.3332 23.1667 29.3332C19.853 29.3332 17.1667 26.6469 17.1667 23.3332L17.1667 9.33405C17.1667 9.33376 17.1667 9.33434 17.1667 9.33405C17.1667 9.33376 17.1667 9.33259 17.1667 9.33229V5.33317C16.4303 5.33317 15.8333 4.73622 15.8333 3.99984ZM19.8333 7.99984V5.33317H26.5L26.5 13.0676C26.1378 13.317 25.8055 13.5125 25.4733 13.6324C25.0059 13.8012 24.5708 13.8095 24.0425 13.4925C22.6945 12.6837 21.4241 12.879 20.3715 13.4052C20.1897 13.4961 20.0098 13.5996 19.8333 13.7111V10.6665H22.5C23.2364 10.6665 23.8333 10.0695 23.8333 9.33317C23.8333 8.59679 23.2364 7.99984 22.5 7.99984H19.8333Z" fill="#2E3039"/>
<path d="M7.14104 12.6116C7.52794 12.2404 8.13873 12.2404 8.52562 12.6116L8.53329 12.6193C8.69353 12.7817 9.1478 13.2422 9.40228 13.5185C9.91854 14.0791 10.6087 14.8721 11.3012 15.7994C11.9917 16.7239 12.6985 17.8007 13.2361 18.9287C13.7693 20.0478 14.1667 21.2805 14.1667 22.4998C14.1667 24.7601 13.4124 26.4319 12.1558 27.5235C10.9246 28.5931 9.33325 28.9998 7.83333 28.9998C6.33342 28.9998 4.74203 28.5931 3.51084 27.5235C2.25425 26.4319 1.5 24.7601 1.5 22.4998C1.5 21.2805 1.89732 20.0478 2.4306 18.9287C2.96813 17.8007 3.67498 16.7239 4.36544 15.7994C5.05795 14.8721 5.74813 14.0791 6.26439 13.5185C6.51888 13.2422 6.97318 12.7817 7.1334 12.6193L7.14104 12.6116Z" fill="#2E3039"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

@ -2,11 +2,12 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hmg_qline/services/logger_service.dart'; import 'package:hmg_qline/services/logger_service.dart';
import 'package:hmg_qline/utilities/api_exception.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/io_client.dart'; import 'package:http/io_client.dart';
import 'package:hmg_qline/utilities/api_exception.dart';
typedef FactoryConstructor<U> = U Function(dynamic); typedef FactoryConstructor<U> = U Function(dynamic);
@ -36,6 +37,9 @@ class ApiClientImp implements ApiClient {
var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: headers0, retryTimes: retryTimes); var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: headers0, retryTimes: retryTimes);
try { try {
if (!kReleaseMode) {
log("responseBody:${response.body}");
}
if (!kReleaseMode) { if (!kReleaseMode) {
log("statusCode:${response.statusCode}"); log("statusCode:${response.statusCode}");
} }
@ -95,9 +99,9 @@ class ApiClientImp implements ApiClient {
loggerService.logInfo("------Payload------"); loggerService.logInfo("------Payload------");
loggerService.logInfo(jsonDecode(requestBody).toString()); loggerService.logInfo(jsonDecode(requestBody).toString());
loggerService.logInfo("------Response------"); loggerService.logInfo("------Response------");
log(jsonDecode(response.body).toString()); loggerService.logInfo(jsonDecode(response.body).toString());
// loggerService.logInfo(jsonDecode(response.body).toString());
} }
if (response.statusCode >= 200 && response.statusCode < 500) { if (response.statusCode >= 200 && response.statusCode < 500) {
var jsonData = jsonDecode(response.body); var jsonData = jsonDecode(response.body);
if (jsonData["StatusMessage"] != null && jsonData["StatusMessage"] == "Unauthorized user attempt to access API") { if (jsonData["StatusMessage"] != null && jsonData["StatusMessage"] == "Unauthorized user attempt to access API") {

@ -8,11 +8,11 @@ bool useTestIP = false;
bool isNeedToBreakVoiceForArabic = true; bool isNeedToBreakVoiceForArabic = true;
bool isSpeechCompleted = true; bool isSpeechCompleted = true;
bool isAndroid14 = true;
class AppStrings { class AppStrings {
static String timeRemainingText = "Time Remaining"; static String timeRemainingText = "Time Remaining";
static String namazTimeText = "Namaz Time"; static String namazTimeText = "Namaz Time";
static String poweredBy = "Powered By"; static String poweredBy = "Powered By";
static String appName = "QLine"; static String appName = "QLine";
static String fontNamePoppins = "Poppins"; static String fontNamePoppins = "Poppins";
@ -157,7 +157,6 @@ class AppAssets {
static String newVitalSignIcon = "assets/images/vitalsign_icon.svg"; static String newVitalSignIcon = "assets/images/vitalsign_icon.svg";
static String newDoctorIcon = "assets/images/doctor_icon.svg"; static String newDoctorIcon = "assets/images/doctor_icon.svg";
static String textBgLeaf = "assets/new_design_icons/text_bg_leaf.svg"; static String textBgLeaf = "assets/new_design_icons/text_bg_leaf.svg";
static String labIcon = "assets/new_design_icons/lab_icon.svg";
} }
class AppConstants { class AppConstants {
@ -168,9 +167,8 @@ class AppConstants {
static String apiKey = 'EE17D21C7943485D9780223CCE55DCE5'; static String apiKey = 'EE17D21C7943485D9780223CCE55DCE5';
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 = 5; static int thresholdForListUI = 5;
static double currentBuildVersion = 9.2; static double currentBuildVersion = 9.3;
static double clearLogsHoursThreshold = 48; static double clearLogsHoursThreshold = 48;
// Maximum log file size in bytes before rotation/clearing. Default 2 MB. // Maximum log file size in bytes before rotation/clearing. Default 2 MB.
static int maxLogFileSizeBytes = 2 * 1024 * 1024; static int maxLogFileSizeBytes = 2 * 1024 * 1024;
} }
@ -180,8 +178,7 @@ class ApiConstants {
static String baseUrlUat = 'https://ms.hmg.com/nscapi'; // UAT static String baseUrlUat = 'https://ms.hmg.com/nscapi'; // UAT
static String baseUrlDev = 'https://ms.hmg.com/nscapi2'; // DEV static String baseUrlDev = 'https://ms.hmg.com/nscapi2'; // DEV
// static String baseUrl = baseUrlLive; static String baseUrl = baseUrlLive;
static String baseUrl = baseUrlDev;
static String baseUrlHub = '$baseUrl/PatientCallingHub'; static String baseUrlHub = '$baseUrl/PatientCallingHub';
static String baseUrlApi = '$baseUrl/api'; static String baseUrlApi = '$baseUrl/api';
static String baseUrlApiGen = '$baseUrl/api/Gen'; static String baseUrlApiGen = '$baseUrl/api/Gen';
@ -238,7 +235,13 @@ class MockJsonRepo {
static TicketData ticket = TicketData( static TicketData ticket = TicketData(
id: 189805, id: 189805,
patientID: 4292695, patientID: 4292695,
queueNo: 'W-T-4', laBQGroupID: null,
queueNo: 'FMC W-T-4',
counterBatchNo: null,
calledBy: null,
calledOn: null,
servedOn: null,
patientName: null,
mobileNo: '0598544522', mobileNo: '0598544522',
patientEmail: 'munira.ali@hotmail.com', patientEmail: 'munira.ali@hotmail.com',
preferredLang: 2, preferredLang: 2,
@ -247,17 +250,22 @@ class MockJsonRepo {
postVoiceText: 'Call for Vital Signs', postVoiceText: 'Call for Vital Signs',
patientGender: 2, patientGender: 2,
roomNo: 'D 12', roomNo: 'D 12',
isActive: null,
createdBy: null,
editedBy: null,
editedOn: DateTime.parse('2025-08-18 15:09:03.633'), editedOn: DateTime.parse('2025-08-18 15:09:03.633'),
createdOn: DateTime.parse('2025-08-18 15:06:07.363'), createdOn: DateTime.parse('2025-08-18 15:06:07.363'),
doctorNameN: null,
callTypeEnum: CallTypeEnum.doctor, callTypeEnum: CallTypeEnum.doctor,
queueNoM: 'W-T-4', queueNoM: 'FMC W-T-4',
callNoStr: 'W_T-4', callNoStr: 'FMC W-T-4',
isQueue: false, isQueue: false,
isToneReq: false, isToneReq: false,
isVoiceReq: false, isVoiceReq: false,
orientationType: 0, orientationType: 0,
isTurnOn: false, isTurnOn: false,
concurrentCallDelaySec: 0, concurrentCallDelaySec: 0,
crTypeAckIP: null,
voiceLanguageText: 'English', voiceLanguageText: 'English',
vitalSignText: 'علامة حيوية', vitalSignText: 'علامة حيوية',
doctorText: 'الطبيب', doctorText: 'الطبيب',
@ -273,6 +281,9 @@ class MockJsonRepo {
queueNoText: 'رقم الانتظار', queueNoText: 'رقم الانتظار',
callForText: 'التوجه الى', callForText: 'التوجه الى',
); );
} }
// RAW DATA: // RAW DATA:

@ -1,17 +1,17 @@
import 'dart:developer';
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:hmg_qline/view_models/screen_config_view_model.dart';
import 'package:provider/provider.dart';
import 'package:hmg_qline/config/dependency_injection.dart'; import 'package:hmg_qline/config/dependency_injection.dart';
import 'package:hmg_qline/config/routes.dart'; import 'package:hmg_qline/config/routes.dart';
import 'package:hmg_qline/constants/app_constants.dart'; import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/services/crash_handler_service.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/views/view_helpers/size_config.dart'; import 'package:hmg_qline/views/view_helpers/size_config.dart';
import 'package:provider/provider.dart';
import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:hmg_qline/services/crash_handler_service.dart';
void main() { void main() {
runZonedGuarded(() async { runZonedGuarded(() async {
@ -43,13 +43,11 @@ class MyApp extends StatelessWidget {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
return OrientationBuilder(builder: (context, orientation) { return OrientationBuilder(builder: (context, orientation) {
log("orientationorientation: ${orientation.toString()}");
SizeConfig().init(constraints, orientation); SizeConfig().init(constraints, orientation);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); if (!isAndroid14) {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
log("orientationorientation: ${orientation.toString()}"); }
return MultiProvider( return MultiProvider(
providers: [ providers: [
ChangeNotifierProvider<ScreenConfigViewModel>( ChangeNotifierProvider<ScreenConfigViewModel>(

@ -1,3 +1,5 @@
import 'dart:developer';
class GenericRespModel { class GenericRespModel {
GenericRespModel({ GenericRespModel({
this.data, this.data,
@ -12,6 +14,7 @@ class GenericRespModel {
String? message; String? message;
factory GenericRespModel.fromJson(Map<String, dynamic> json) { factory GenericRespModel.fromJson(Map<String, dynamic> json) {
log("jsonjsonjosn: $json");
if (json.containsKey('StatusMessage')) { if (json.containsKey('StatusMessage')) {
if ((json['StatusMessage'] as String).contains('Internal server error')) { if ((json['StatusMessage'] as String).contains('Internal server error')) {
// Utils.showToast("${json['StatusMessage']}"); // Utils.showToast("${json['StatusMessage']}");

@ -1,4 +1,5 @@
import 'dart:ui'; import 'dart:ui';
import 'package:hmg_qline/constants/app_constants.dart'; import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/models/kiosk_language_config_model.dart'; import 'package:hmg_qline/models/kiosk_language_config_model.dart';
import 'package:hmg_qline/models/kiosk_queue_model.dart'; import 'package:hmg_qline/models/kiosk_queue_model.dart';
@ -25,8 +26,7 @@ class GlobalConfigurationsModel {
int? priorityWhatsApp; int? priorityWhatsApp;
int? priorityEmail; int? priorityEmail;
String ticketNoText = "Ticket Number"; String ticketNoText = "Ticket Number";
String pleaseVisitCounterTextEn = "Please Visit Counter"; String postVoiceText = "Please Visit Counter";
String pleaseVisitCounterTextAr = "Please Visit Counter";
int? roomNo; int? roomNo;
bool? isRoomNoRequired; bool? isRoomNoRequired;
@ -38,12 +38,12 @@ class GlobalConfigurationsModel {
String? counterTextArb; String? counterTextArb;
String? queueNoTextArb; String? queueNoTextArb;
String? callForTextArb; String? callForTextArb;
String? currentServeTextEng = "Current Serving"; String? currentServeTextEng;
String? currentServeTextArb; String? currentServeTextArb;
String maxText = ""; String maxText = "";
String minText = ""; String minText = "";
String nextPrayerTextEng = "Next Prayer"; String nextPrayerTextEng = "Next Prayer";
String nextPrayerTextArb = "الصلاة القادمة"; String nextPrayerTextArb = "الصلا<EFBFBD><EFBFBD> القادمة";
String weatherText = "Weather"; String weatherText = "Weather";
String? fajarTextEng; String? fajarTextEng;
String? dhuhrTextEng; String? dhuhrTextEng;
@ -69,6 +69,8 @@ class GlobalConfigurationsModel {
bool isWeatherReq = false; bool isWeatherReq = false;
bool isPrayerTimeReq = false; bool isPrayerTimeReq = false;
bool isRssFeedReq = false; bool isRssFeedReq = false;
bool globalClinicPrefixReq = false;
bool clinicPrefixReq = true;
QTypeEnum qTypeEnum = QTypeEnum.appointment; QTypeEnum qTypeEnum = QTypeEnum.appointment;
ScreenTypeEnum screenTypeEnum = ScreenTypeEnum.waitingAreaScreen; ScreenTypeEnum screenTypeEnum = ScreenTypeEnum.waitingAreaScreen;
int? projectID; int? projectID;
@ -78,6 +80,7 @@ class GlobalConfigurationsModel {
List<KioskQueueModel>? kioskQueueList; List<KioskQueueModel>? kioskQueueList;
List<KioskLanguageConfigModel>? kioskLanguageConfigList; List<KioskLanguageConfigModel>? kioskLanguageConfigList;
// Indicates whether the screen belongs to Takhasusi main (based on its IP)
bool isFromTakhasusiMain = false; bool isFromTakhasusiMain = false;
String vitalSignTextEng = "Vital Sign"; String vitalSignTextEng = "Vital Sign";
@ -102,12 +105,6 @@ class GlobalConfigurationsModel {
String callForVaccinationTextArb = "الرجاء التوجّه إلى غرفة التطعيم"; String callForVaccinationTextArb = "الرجاء التوجّه إلى غرفة التطعيم";
String callForNebulizationTextArb = "الرجاء التوجّه إلى غرفة البخاخة"; String callForNebulizationTextArb = "الرجاء التوجّه إلى غرفة البخاخة";
static String defaultIfNullOrEmpty(dynamic value, String def) {
if (value == null) return def;
if (value is String && value.trim().isEmpty) return def;
return value.toString();
}
GlobalConfigurationsModel({ GlobalConfigurationsModel({
this.id, this.id,
this.configType, this.configType,
@ -128,8 +125,7 @@ class GlobalConfigurationsModel {
this.priorityWhatsApp, this.priorityWhatsApp,
this.priorityEmail, this.priorityEmail,
this.ticketNoText = "Ticket Number", this.ticketNoText = "Ticket Number",
this.pleaseVisitCounterTextEn = "Please Visit Counter", this.postVoiceText = "Please Visit Counter",
this.pleaseVisitCounterTextAr = "يرجى زيارة الكاونتر",
this.roomTextEng, this.roomTextEng,
this.roomNo, this.roomNo,
this.isRoomNoRequired = true, this.isRoomNoRequired = true,
@ -139,7 +135,7 @@ class GlobalConfigurationsModel {
this.counterTextArb, this.counterTextArb,
this.queueNoTextArb, this.queueNoTextArb,
this.callForTextArb, this.callForTextArb,
this.currentServeTextEng = "Current Serving", this.currentServeTextEng,
this.currentServeTextArb, this.currentServeTextArb,
this.maxText = "", this.maxText = "",
this.minText = "", this.minText = "",
@ -170,6 +166,8 @@ class GlobalConfigurationsModel {
this.isWeatherReq = false, this.isWeatherReq = false,
this.isPrayerTimeReq = false, this.isPrayerTimeReq = false,
this.isRssFeedReq = false, this.isRssFeedReq = false,
this.globalClinicPrefixReq = false,
this.clinicPrefixReq = true,
this.qTypeEnum = QTypeEnum.appointment, this.qTypeEnum = QTypeEnum.appointment,
this.screenTypeEnum = ScreenTypeEnum.waitingAreaScreen, this.screenTypeEnum = ScreenTypeEnum.waitingAreaScreen,
this.projectID, this.projectID,
@ -202,73 +200,76 @@ class GlobalConfigurationsModel {
}); });
GlobalConfigurationsModel.fromJson({required Map<String, dynamic> json, int qType = 1, int screenType = 1}) { GlobalConfigurationsModel.fromJson({required Map<String, dynamic> json, int qType = 1, int screenType = 1}) {
id = json['id'] ?? 0; id = json['id'];
configType = json['configType'] ?? 0; configType = json['configType'];
description = defaultIfNullOrEmpty(json['description'], ""); description = json['description'];
counterStart = json['counterStart'] ?? 0; counterStart = json['counterStart'];
counterEnd = json['counterEnd'] ?? 0; counterEnd = json['counterEnd'];
concurrentCallDelaySec = json['concurrentCallDelaySec'] ?? 1; concurrentCallDelaySec = json['concurrentCallDelaySec'] ?? 1;
voiceType = json['voiceType'] ?? 0; voiceType = json['voiceType'];
voiceTypeText = defaultIfNullOrEmpty(json['voiceTypeText'], ""); voiceTypeText = json['voiceTypeText'];
screenLanguageEnum = ((json['screenLanguage'] ?? 1) as int).toLanguageEnum(); screenLanguageEnum = (json['screenLanguage'] as int).toLanguageEnum();
screenLanguageText = defaultIfNullOrEmpty(json['screenLanguageText'], "English"); screenLanguageText = json['screenLanguageText'];
textDirection = json['textDirection'] == 2 ? TextDirection.rtl : TextDirection.ltr; // textDirection = json['textDirection'] == 2 ? TextDirection.rtl : TextDirection.ltr;
voiceLanguageEnum = ((json['voiceLanguage'] ?? 1) as int).toLanguageEnum(); textDirection = TextDirection.rtl;
voiceLanguageText = defaultIfNullOrEmpty(json['voiceLanguageText'], "English");
screenMaxDisplayPatients = json['screenMaxDisplayPatients'] ?? 16; screenMaxDisplayPatients = json['screenMaxDisplayPatients'] ?? 16;
isNotiReq = json['isNotiReq'] ?? false; // screenMaxDisplayPatients = 15;
prioritySMS = json['prioritySMS'] ?? 0; voiceLanguageEnum = (json['voiceLanguage'] as int).toLanguageEnum();
priorityWhatsApp = json['priorityWhatsApp'] ?? 0; voiceLanguageText = json['voiceLanguageText'];
priorityEmail = json['priorityEmail'] ?? 0; isNotiReq = json['isNotiReq'];
ticketNoText = defaultIfNullOrEmpty(json['ticketNoText'], "Ticket Number"); prioritySMS = json['prioritySMS'];
pleaseVisitCounterTextEn = defaultIfNullOrEmpty(json['pleaseVisitCounterText'], "Please Visit Counter"); priorityWhatsApp = json['priorityWhatsApp'];
pleaseVisitCounterTextAr = defaultIfNullOrEmpty(json['pleaseVisitCounterTextAr'], "يرجى زيارة الكاونتر"); priorityEmail = json['priorityEmail'];
roomNo = json['roomNo'] ?? 0; ticketNoText = json['ticketNoText'] ?? "Ticket Number";
postVoiceText = json['pleaseVisitCounterText'] ?? "Please Visit Counter";
roomNo = json['roomNo'];
isRoomNoRequired = json['isRoomNoReq'] ?? true; isRoomNoRequired = json['isRoomNoReq'] ?? true;
queueNoTextEng = defaultIfNullOrEmpty(json['queueNoText'], "Queue Number"); queueNoTextEng = json['queueNoText'];
callForTextEng = defaultIfNullOrEmpty(json['callForText'], "Please Proceed"); callForTextEng = json['callForText'];
counterTextEng = defaultIfNullOrEmpty(json['counterText'], "Counter"); counterTextEng = json['counterText'];
roomTextEng = defaultIfNullOrEmpty(json['roomText'], "Room"); roomTextEng = json['roomText'];
queueNoTextArb = defaultIfNullOrEmpty(json['queueNoTextAr'], "الرقم"); queueNoTextArb = json['queueNoTextAr'] ?? "الرقم";
callForTextArb = defaultIfNullOrEmpty(json['callForTextAr'], "التوجه إلى"); callForTextArb = json['callForTextAr'] ?? "التوجه إلى";
counterTextArb = defaultIfNullOrEmpty(json['counterTextAr'], "الكاونتر"); counterTextArb = json['counterTextAr'] ?? "";
roomTextArb = defaultIfNullOrEmpty(json['roomTextAr'], "الغرفة"); roomTextArb = json['roomTextAr'] ?? "الغرفة";
currentServeTextEng = defaultIfNullOrEmpty(json['currentServeText'], "Current Serving"); currentServeTextEng = json['currentServeText'];
currentServeTextArb = defaultIfNullOrEmpty(json['currentServeTextAr'], "يتم خدمة"); currentServeTextArb = json['currentServeTextAr'] ?? "يتم خدمة";
maxText = defaultIfNullOrEmpty(json['maxText'], ""); maxText = json['maxText'] ?? "";
minText = defaultIfNullOrEmpty(json['minText'], ""); minText = json['minText'] ?? "";
nextPrayerTextEng = defaultIfNullOrEmpty(json['nextPrayerText'], "Next Prayer"); nextPrayerTextEng = json['nextPrayerText'] ?? "Next Prayer";
nextPrayerTextArb = defaultIfNullOrEmpty(json['nextPrayerTextArb'], "الصلاة القادمة"); nextPrayerTextArb = json['nextPrayerTextArb'] ?? "الصلاة القادمة";
weatherText = defaultIfNullOrEmpty(json['weatherText'], "Weather"); weatherText = json['weatherText'] ?? "Weather";
fajarTextEng = defaultIfNullOrEmpty(json['fajarText'], "Fajr"); fajarTextEng = json['fajarText'];
dhuhrTextEng = defaultIfNullOrEmpty(json['dhuhrText'], "Dhuhr"); dhuhrTextEng = json['dhuhrText'];
asarTextEng = defaultIfNullOrEmpty(json['asarText'], "Asar"); asarTextEng = json['asarText'];
maghribTextEng = defaultIfNullOrEmpty(json['maghribText'], "Maghrib"); maghribTextEng = json['maghribText'];
ishaTextEng = defaultIfNullOrEmpty(json['ishaText'], "Isha"); ishaTextEng = json['ishaText'];
fajarTextArb = defaultIfNullOrEmpty(json['fajarTextAr'], AppStrings.prayersArray[0]); fajarTextArb = json['fajarTextAr'] ?? AppStrings.prayersArray[0];
dhuhrTextArb = defaultIfNullOrEmpty(json['dhuhrTextAr'], AppStrings.prayersArray[1]); dhuhrTextArb = json['dhuhrTextAr'] ?? AppStrings.prayersArray[1];
asarTextArb = defaultIfNullOrEmpty(json['asarTextAr'], AppStrings.prayersArray[2]); asarTextArb = json['asarTextAr'] ?? AppStrings.prayersArray[2];
maghribTextArb = defaultIfNullOrEmpty(json['maghribTextAr'], AppStrings.prayersArray[3]); maghribTextArb = json['maghribTextAr'] ?? AppStrings.prayersArray[3];
ishaTextArb = defaultIfNullOrEmpty(json['ishaTextAr'], AppStrings.prayersArray[4]); ishaTextArb = json['ishaTextAr'] ?? AppStrings.prayersArray[4];
isActive = json['isActive'] ?? true; isActive = json['isActive'];
createdBy = json['createdBy'] ?? 0; createdBy = json['createdBy'];
createdOn = defaultIfNullOrEmpty(json['createdOn'], ""); createdOn = json['createdOn'];
editedBy = json['editedBy']; editedBy = json['editedBy'];
editedOn = json['editedOn']; editedOn = json['editedOn'];
isToneReq = json['isToneReq'] ?? false; isToneReq = json['isToneReq'] ?? false;
isVoiceReq = json['isVoiceReq'] ?? false; isVoiceReq = json['isVoiceReq'] ?? false;
orientationTypeEnum = ((json['orientationType'] ?? 1) as int).toScreenOrientationEnum(); orientationTypeEnum = ((json['orientationType'] ?? 1) as int).toScreenOrientationEnum();
isTurnOn = json['isTurnOn'] ?? true; isTurnOn = json['isTurnOn'];
waitingAreaType = json['waitingAreaType'] ?? 0; waitingAreaType = json['waitingAreaType'];
gender = json['gender'] ?? 0; gender = json['gender'];
isWeatherReq = json['isWeatherReq'] ?? false; isWeatherReq = json['isWeatherReq'] ?? false;
isPrayerTimeReq = json['isPrayerTimeReq'] ?? false; isPrayerTimeReq = json['isPrayerTimeReq'] ?? false;
isRssFeedReq = json['isRssFeedReq'] ?? false; isRssFeedReq = json['isRssFeedReq'] ?? false;
globalClinicPrefixReq = json['globalClinicPrefixReq'] ?? false;
clinicPrefixReq = json['clinicPrefixReq'] ?? true;
qTypeEnum = ((json['qType'] ?? qType) as int).toQTypeEnum(); qTypeEnum = ((json['qType'] ?? qType) as int).toQTypeEnum();
screenTypeEnum = ((json['screenType'] ?? screenType) as int).toScreenTypeEnum(); screenTypeEnum = ((json['screenType'] ?? screenType) as int).toScreenTypeEnum();
projectID = json['projectID'] ?? 0; projectID = json['projectID'];
projectLatitude = json['projectLatitude']?.toDouble() ?? 0.0; projectLatitude = json['projectLatitude'] == 0 ? 0.0 : json['projectLatitude'];
projectLongitude = json['projectLongitude']?.toDouble() ?? 0.0; projectLongitude = json['projectLongitude'] == 0 ? 0.0 : json['projectLongitude'];
cityKey = json['cityKey'] ?? 0; cityKey = json['cityKey'] ?? 0;
if (json['kioskQueue'] != null) { if (json['kioskQueue'] != null) {
kioskQueueList = List<KioskQueueModel>.from(json['kioskQueue'].map((kioskQueueJson) => KioskQueueModel.fromJson(kioskQueueJson))); kioskQueueList = List<KioskQueueModel>.from(json['kioskQueue'].map((kioskQueueJson) => KioskQueueModel.fromJson(kioskQueueJson)));
@ -276,35 +277,38 @@ class GlobalConfigurationsModel {
kioskQueueList = []; kioskQueueList = [];
} }
if (json['kioskConfig'] != null) { if (json['kioskConfig'] != null) {
kioskLanguageConfigList = List<KioskLanguageConfigModel>.from(json['kioskConfig'].map((kioskQueueJson) => KioskLanguageConfigModel.fromJson(kioskQueueJson))); kioskLanguageConfigList =
List<KioskLanguageConfigModel>.from(json['kioskConfig'].map((kioskQueueJson) => KioskLanguageConfigModel.fromJson(kioskQueueJson)));
} else { } else {
kioskLanguageConfigList = []; kioskLanguageConfigList = [];
} }
// Default to false; actual value (based on device IP) is set in ViewModel after loading config
isFromTakhasusiMain = false; isFromTakhasusiMain = false;
vitalSignTextEng = defaultIfNullOrEmpty(json['vitalSignText'], "Vital Sign"); vitalSignTextEng = json['vitalSignText'] ?? "Vital Sign";
doctorTextEng = defaultIfNullOrEmpty(json['doctorText'], "Doctor"); doctorTextEng = json['doctorText'] ?? "Doctor";
procedureTextEng = defaultIfNullOrEmpty(json['procedureText'], "Procedure"); procedureTextEng = json['procedureText'] ?? "Procedure";
vaccinationTextEng = defaultIfNullOrEmpty(json['vaccinationText'], "Vaccination"); vaccinationTextEng = json['vaccinationText'] ?? "Vaccination";
nebulizationTextEng = defaultIfNullOrEmpty(json['nebulizationText'], "Nebulization"); nebulizationTextEng = json['nebulizationText'] ?? "Nebulization";
callForVitalSignTextEng = defaultIfNullOrEmpty(json['callForVitalSignText'], "Call for Vital Sign"); callForVitalSignTextEng = json['callForVitalSignText'] ?? "Call for Vital Sign";
callForDoctorTextEng = defaultIfNullOrEmpty(json['callForDoctorText'], "Call for Doctor"); callForDoctorTextEng = json['callForDoctorText'] ?? "Call for Doctor";
callForProcedureTextEng = defaultIfNullOrEmpty(json['callForProcedureText'], "Call for Procedure"); callForProcedureTextEng = json['callForProcedureText'] ?? "Call for Procedure";
callForVaccinationTextEng = defaultIfNullOrEmpty(json['callForVaccinationText'], "Call for Vaccination"); callForVaccinationTextEng = json['callForVaccinationText'] ?? "Call for Vaccination";
callForNebulizationTextEng = defaultIfNullOrEmpty(json['callForNebulizationText'], "Call for Nebulization"); callForNebulizationTextEng = json['callForNebulizationText'] ?? "Call for Nebulization";
vitalSignTextArb = defaultIfNullOrEmpty(json['vitalSignTextAr'], "العلامات الحيوية");
doctorTextArb = defaultIfNullOrEmpty(json['doctorTextAr'], "الطبيب"); vitalSignTextArb = json['vitalSignTextAr'] ?? "غرفة العلامات الحيوية";
procedureTextArb = defaultIfNullOrEmpty(json['procedureTextAr'], "الإجراء"); doctorTextArb = json['doctorTextAr'] ?? " غرفة الطبيب";
vaccinationTextArb = defaultIfNullOrEmpty(json['vaccinationTextAr'], "التطعيم"); procedureTextArb = json['procedureTextAr'] ?? "غرفة الإجراء";
nebulizationTextArb = defaultIfNullOrEmpty(json['nebulizationTextAr'], "البخاخة"); vaccinationTextArb = json['vaccinationTextAr'] ?? "غرفة التطعيم";
callForVitalSignTextArb = defaultIfNullOrEmpty(json['callForVitalSignTextAr'], "الرجاء التوجّه إلى غرفة العلامات الحيوية"); nebulizationTextArb = json['nebulizationTextAr'] ?? "غرفة البخاخة";
callForDoctorTextArb = defaultIfNullOrEmpty(json['callForDoctorTextAr'], "الرجاء التوجّه إلى غرفة الطبيب"); callForVitalSignTextArb = json['callForVitalSignTextAr'] ?? " غرفة استدعاء للعلامات الحيوية";
callForProcedureTextArb = defaultIfNullOrEmpty(json['callForProcedureTextAr'], "الرجاء التوجّه إلى غرفة الإجراء"); callForDoctorTextArb = json['callForDoctorTextAr'] ?? "الرجاء التوجّه إلى غرفة الطبيب";
callForVaccinationTextArb = defaultIfNullOrEmpty(json['callForVaccinationTextAr'], "الرجاء التوجّه إلى غرفة التطعيم"); callForProcedureTextArb = json['callForProcedureTextAr'] ?? "الرجاء التوجّه إلى غرفة الإجراء";
callForNebulizationTextArb = defaultIfNullOrEmpty(json['callForNebulizationTextAr'], "الرجاء التوجّه إلى غرفة البخاخة"); callForVaccinationTextArb = json['callForVaccinationTextAr'] ?? "الرجاء لتوجّه إلى غرفة التطعيم";
callForNebulizationTextArb = json['callForNebulizationTextAr'] ?? "الرجاء التوجّه إلى غرفة البخاخة";
} }
@override @override
String toString() { String toString() {
return 'GlobalConfigurationsModel{id: $id, isFromTakhasusiMain: $isFromTakhasusiMain, configType: $configType, description: $description, counterStart: $counterStart, counterEnd: $counterEnd, concurrentCallDelaySec: $concurrentCallDelaySec, voiceType: $voiceType, voiceTypeText: $voiceTypeText, screenLanguageEnum: $screenLanguageEnum, screenLanguageText: $screenLanguageText, textDirection: $textDirection, voiceLanguageEnum: $voiceLanguageEnum, voiceLanguageText: $voiceLanguageText, screenMaxDisplayPatients: $screenMaxDisplayPatients, isNotiReq: $isNotiReq, prioritySMS: $prioritySMS, priorityWhatsApp: $priorityWhatsApp, priorityEmail: $priorityEmail, ticketNoText: $ticketNoText, pleaseVisitCounterTextEn: $pleaseVisitCounterTextEn,pleaseVisitCounterTextAr: $pleaseVisitCounterTextAr, roomText: $roomTextEng, roomNo: $roomNo, isRoomNoRequired: $isRoomNoRequired, counterText: $counterTextEng, queueNoText: $queueNoTextEng, callForText: $callForTextEng, currentServeTextArb: $currentServeTextArb,, currentServeTextEng: $currentServeTextEng, maxText: $maxText, minText: $minText, nextPrayerTextEng: $nextPrayerTextEng, nextPrayerTextArb: $nextPrayerTextArb, weatherText: $weatherText, fajarText: $fajarTextEng, dhuhrText: $dhuhrTextEng, asarText: $asarTextEng, maghribText: $maghribTextEng, ishaText: $ishaTextEng, isActive: $isActive, createdBy: $createdBy, createdOn: $createdOn, editedBy: $editedBy, editedOn: $editedOn, isToneReq: $isToneReq, isVoiceReq: $isVoiceReq, orientationTypeEnum: $orientationTypeEnum, isTurnOn: $isTurnOn, waitingAreaType: $waitingAreaType, gender: $gender, isWeatherReq: $isWeatherReq, isPrayerTimeReq: $isPrayerTimeReq, isRssFeedReq: $isRssFeedReq, qTypeEnum: $qTypeEnum, screenTypeEnum: $screenTypeEnum, projectID: $projectID, projectLatitude: $projectLatitude, projectLongitude: $projectLongitude, cityKey: $cityKey, kioskQueueList: $kioskQueueList, kioskLanguageConfigList: $kioskLanguageConfigList, vitalSignText: $vitalSignTextEng, doctorText: $doctorTextEng, procedureText: $procedureTextEng, vaccinationText: $vaccinationTextEng, nebulizationText: $nebulizationTextEng, callForVitalSignText: $callForVitalSignTextEng, callForDoctorText: $callForDoctorTextEng, callForProcedureText: $callForProcedureTextEng, callForVaccinationText: $callForVaccinationTextEng, callForNebulizationText: $callForNebulizationTextEng, vitalSignTextArb: $vitalSignTextArb, doctorTextArb: $doctorTextArb, procedureTextArb: $procedureTextArb, vaccinationTextArb: $vaccinationTextArb, nebulizationTextArb: $nebulizationTextArb, callForVitalSignTextArb: $callForVitalSignTextArb, callForDoctorTextArb: $callForDoctorTextArb, callForProcedureTextArb: $callForProcedureTextArb, callForVaccinationTextArb: $callForVaccinationTextArb, callForNebulizationTextArb: $callForNebulizationTextArb}'; return 'GlobalConfigurationsModel{id: $id, isFromTakhasusiMain: $isFromTakhasusiMain, configType: $configType, description: $description, counterStart: $counterStart, counterEnd: $counterEnd, concurrentCallDelaySec: $concurrentCallDelaySec, voiceType: $voiceType, voiceTypeText: $voiceTypeText, screenLanguageEnum: $screenLanguageEnum, screenLanguageText: $screenLanguageText, textDirection: $textDirection, voiceLanguageEnum: $voiceLanguageEnum, voiceLanguageText: $voiceLanguageText, screenMaxDisplayPatients: $screenMaxDisplayPatients, isNotiReq: $isNotiReq, prioritySMS: $prioritySMS, priorityWhatsApp: $priorityWhatsApp, priorityEmail: $priorityEmail, ticketNoText: $ticketNoText, postVoiceText: $postVoiceText, roomText: $roomTextEng, roomNo: $roomNo, isRoomNoRequired: $isRoomNoRequired, counterText: $counterTextEng, queueNoText: $queueNoTextEng, callForText: $callForTextEng, currentServeTextArb: $currentServeTextArb,, currentServeTextEng: $currentServeTextEng, maxText: $maxText, minText: $minText, nextPrayerTextEng: $nextPrayerTextEng, nextPrayerTextArb: $nextPrayerTextArb, weatherText: $weatherText, fajarText: $fajarTextEng, dhuhrText: $dhuhrTextEng, asarText: $asarTextEng, maghribText: $maghribTextEng, ishaText: $ishaTextEng, isActive: $isActive, createdBy: $createdBy, createdOn: $createdOn, editedBy: $editedBy, editedOn: $editedOn, isToneReq: $isToneReq, isVoiceReq: $isVoiceReq, orientationTypeEnum: $orientationTypeEnum, isTurnOn: $isTurnOn, waitingAreaType: $waitingAreaType, gender: $gender, isWeatherReq: $isWeatherReq, isPrayerTimeReq: $isPrayerTimeReq, isRssFeedReq: $isRssFeedReq, globalClinicPrefixReq: $globalClinicPrefixReq, clinicPrefixReq: $clinicPrefixReq, qTypeEnum: $qTypeEnum, screenTypeEnum: $screenTypeEnum, projectID: $projectID, projectLatitude: $projectLatitude, projectLongitude: $projectLongitude, cityKey: $cityKey, kioskQueueList: $kioskQueueList, kioskLanguageConfigList: $kioskLanguageConfigList, vitalSignText: $vitalSignTextEng, doctorText: $doctorTextEng, procedureText: $procedureTextEng, vaccinationText: $vaccinationTextEng, nebulizationText: $nebulizationTextEng, callForVitalSignText: $callForVitalSignTextEng, callForDoctorText: $callForDoctorTextEng, callForProcedureText: $callForProcedureTextEng, callForVaccinationText: $callForVaccinationTextEng, callForNebulizationText: $callForNebulizationTextEng, vitalSignTextArb: $vitalSignTextArb, doctorTextArb: $doctorTextArb, procedureTextArb: $procedureTextArb, vaccinationTextArb: $vaccinationTextArb, nebulizationTextArb: $nebulizationTextArb, callForVitalSignTextArb: $callForVitalSignTextArb, callForDoctorTextArb: $callForDoctorTextArb, callForProcedureTextArb: $callForProcedureTextArb, callForVaccinationTextArb: $callForVaccinationTextArb, callForNebulizationTextArb: $callForNebulizationTextArb}';
} }
} }

@ -11,105 +11,104 @@ class TicketDetailsModel {
TicketDetailsModel({this.qTypeEnum, this.screenTypeEnum, this.connectionID, this.ticketModel}); TicketDetailsModel({this.qTypeEnum, this.screenTypeEnum, this.connectionID, this.ticketModel});
TicketDetailsModel.fromJson(Map<String, dynamic> json) { TicketDetailsModel.fromJson(Map<String, dynamic> json) {
qTypeEnum = json['qType'] != null ? (json['qType'] as int).toQTypeEnum() : QTypeEnum.appointment; qTypeEnum = json['qType'] != null ? (json['qType'] as int).toQTypeEnum() : null;
screenTypeEnum = json['screenType'] != null ? (json['screenType'] as int).toScreenTypeEnum() : ScreenTypeEnum.waitingAreaScreen; screenTypeEnum = json['screenType'] != null ? (json['screenType'] as int).toScreenTypeEnum() : null;
connectionID = json['connectionID'] ?? ''; connectionID = json['connectionID'];
ticketModel = json['data'] != null ticketModel = json['data'] != null
? TicketData.fromJson( ? TicketData.fromJson(
json['data'], json['data'],
qTypeEnum: qTypeEnum, qTypeEnum: json['qType'] != null ? (json['qType'] as int).toQTypeEnum() : null,
) )
: TicketData(); // Use default empty TicketData if null : null;
} }
} }
class TicketData { class TicketData {
int id; int? id;
int patientID; int? patientID;
int laBQGroupID; int? laBQGroupID;
String queueNo; String? queueNo;
int counterBatchNo; int? counterBatchNo;
int calledBy; int? calledBy;
String calledOn; String? calledOn;
String servedOn; String? servedOn;
String patientName; String? patientName;
String mobileNo; String? mobileNo;
String patientEmail; String? patientEmail;
int preferredLang; int? preferredLang;
LanguageEnum voiceLanguageEnum; LanguageEnum voiceLanguageEnum = LanguageEnum.english;
String ticketNoText; String ticketNoText = "Ticket Number";
String postVoiceText; String postVoiceText = "Please Visit Counter";
int patientGender; int? patientGender;
String roomNo; String? roomNo;
bool isActive; bool? isActive;
int createdBy; int? createdBy;
int editedBy; int? editedBy;
DateTime? editedOn; DateTime? editedOn;
DateTime? createdOn; DateTime? createdOn;
// New fields // New fields
String doctorNameN; String? doctorNameN;
CallTypeEnum callTypeEnum; CallTypeEnum callTypeEnum = CallTypeEnum.vitalSign;
String queueNoM; String? queueNoM;
String callNoStr; String? callNoStr;
bool isQueue; bool? isQueue;
bool isToneReq; bool? isToneReq;
bool isVoiceReq; bool? isVoiceReq;
int orientationType; int? orientationType;
bool isTurnOn; bool? isTurnOn;
int concurrentCallDelaySec; int? concurrentCallDelaySec;
String crTypeAckIP; String? crTypeAckIP;
int voiceLanguage; int voiceLanguage = 1;
String voiceLanguageText; String voiceLanguageText = "English";
String vitalSignText; String vitalSignText = "Vital Sign";
String doctorText; String doctorText = "Doctor";
String procedureText; String procedureText = "Procedure";
String vaccinationText; String vaccinationText = "Vaccination";
String nebulizationText; String nebulizationText = "Nebulization";
String callForVitalSignText; String callForVitalSignText = "Call for Vital Sign";
String callForDoctorText; String callForDoctorText = "Call for Doctor";
String callForProcedureText; String callForProcedureText = "Call for Procedure";
String callForVaccinationText; String callForVaccinationText = "Call for Vaccination";
String callForNebulizationText; String callForNebulizationText = "Call for Nebulization";
String roomText; String roomText = "Room";
String queueNoText; String queueNoText = "Counter";
String callForText; String callForText = "Call For";
TicketData({ TicketData({
this.id = 0, this.id,
this.patientID = 0, this.patientID,
this.laBQGroupID = 0, this.laBQGroupID,
this.queueNo = "", this.queueNo,
this.counterBatchNo = 0, this.counterBatchNo,
this.calledBy = 0, this.calledBy,
this.calledOn = "", this.calledOn,
this.servedOn = "", this.servedOn,
this.patientName = "", this.patientName,
this.mobileNo = "", this.mobileNo,
this.patientEmail = "", this.patientEmail,
this.preferredLang = 1, this.preferredLang,
this.voiceLanguageEnum = LanguageEnum.english, this.voiceLanguageEnum = LanguageEnum.english,
this.ticketNoText = "Ticket Number", this.ticketNoText = "Ticket Number",
this.postVoiceText = "Please Visit Counter", this.postVoiceText = "Please Visit Counter",
this.patientGender = 1, this.patientGender,
this.roomNo = "", this.roomNo,
this.isActive = true, this.isActive,
this.createdBy = 0, this.createdBy,
this.createdOn, // will fallback in fromJson (see below) this.createdOn,
this.editedBy = 0, this.editedBy,
this.editedOn, // will fallback in fromJson (see below) this.editedOn,
this.doctorNameN = "", this.doctorNameN,
this.callTypeEnum = CallTypeEnum.none, this.callTypeEnum = CallTypeEnum.vitalSign,
this.queueNoM = "", this.queueNoM,
this.callNoStr = "", this.callNoStr,
this.isQueue = false, this.isQueue,
this.isToneReq = false, this.isToneReq,
this.isVoiceReq = false, this.isVoiceReq,
this.orientationType = 1, this.orientationType,
this.isTurnOn = true, this.isTurnOn,
this.concurrentCallDelaySec = 1, this.concurrentCallDelaySec,
this.crTypeAckIP = "", this.crTypeAckIP,
this.voiceLanguage = 1,
this.voiceLanguageText = "English", this.voiceLanguageText = "English",
this.vitalSignText = "Vital Sign", this.vitalSignText = "Vital Sign",
this.doctorText = "Doctor", this.doctorText = "Doctor",
@ -126,61 +125,63 @@ class TicketData {
this.callForText = "Call For", this.callForText = "Call For",
}); });
TicketData.fromJson(Map<String, dynamic> json, {QTypeEnum? qTypeEnum}) TicketData.fromJson(Map<String, dynamic> json, {QTypeEnum? qTypeEnum}) {
: id = json['id'] ?? 0, id = json['id'];
patientID = json['patientID'] ?? 0, patientID = json['patientID'];
laBQGroupID = json['laB_QGroupID'] ?? 0, laBQGroupID = json['laB_QGroupID'];
queueNo = json['queueNo'] ?? "", queueNo = json['queueNoM'];
counterBatchNo = json['counterBatchNo'] ?? 0, counterBatchNo = json['counterBatchNo'];
calledBy = json['calledBy'] ?? 0, calledBy = json['calledBy'];
calledOn = json['calledOn'] ?? "", calledOn = json['calledOn'];
servedOn = json['servedOn'] ?? "", servedOn = json['servedOn'];
patientName = json['patientName'] ?? "", patientName = json['patientName'];
mobileNo = json['mobileNo'] ?? "", mobileNo = json['mobileNo'];
patientEmail = json['patientEmail'] ?? "", patientEmail = json['patientEmail'];
preferredLang = (json['preferredLang'] != null && json['preferredLang'].toString().trim() != "") ? int.parse(json['preferredLang'].toString()) : 1, preferredLang = (json['preferredLang'] != null && json['preferredLang'].toString().trim() != "") ? int.parse(json['preferredLang'].toString()) : 1;
voiceLanguageEnum = (json['preferredLang'] != null && json['preferredLang'].toString().trim() != "") ? (int.parse(json['preferredLang'].toString())).toLanguageEnum() : LanguageEnum.english, voiceLanguageEnum = (json['preferredLang'] != null && json['preferredLang'].toString().trim() != "") ? (int.parse(json['preferredLang'].toString())).toLanguageEnum() : LanguageEnum.english;
ticketNoText = json['ticketNoText'] ?? "Ticket Number", ticketNoText = json['ticketNoText'] ?? "Ticket Number";
postVoiceText = json['pleaseVisitCounterText'] ?? "Please Visit Counter", postVoiceText = json['pleaseVisitCounterText'] ?? "Please Visit Counter";
patientGender = json['patientGender'] ?? 1, patientGender = json['patientGender'] ?? 1;
roomNo = (() { roomNo = json['roomNo']?.toString();
if (qTypeEnum != null && qTypeEnum == QTypeEnum.general) { if (qTypeEnum != null && qTypeEnum == QTypeEnum.general) {
return json['counterNo']?.toString() ?? ""; roomNo = json['counterNo']?.toString();
} }
return json['roomNo']?.toString() ?? ""; isActive = json['isActive'];
})(), createdBy = json['createdBy'];
isActive = json['isActive'] ?? true, editedBy = json['editedBy'];
createdBy = json['createdBy'] ?? 0, editedOn = json['editedOn'] != null ? (json['editedOn'] as String).toDateTime() : DateTime.now();
editedBy = json['editedBy'] ?? 0, createdOn = json['createdOn'] != null ? (json['createdOn'] as String).toDateTime() : DateTime.now();
createdOn = json['createdOn'] != null ? (json['createdOn'] as String).toDateTime() : DateTime.now(),
editedOn = json['editedOn'] != null ? (json['editedOn'] as String).toDateTime() : DateTime.now(), doctorNameN = json['doctorNameN'];
doctorNameN = json['doctorNameN'] ?? "", callTypeEnum = ((json['callType'] ?? 1) as int).toCallTypeEnum();
callTypeEnum = ((json['callType'] ?? 0) as int).toCallTypeEnum(), queueNoM = json['queueNoM'];
queueNoM = json['queueNoM'] ?? "", callNoStr = json['callNoStr'];
callNoStr = json['callNoStr'] ?? "", isQueue = json['isQueue'];
isQueue = json['isQueue'] ?? false, isToneReq = json['isToneReq'];
isToneReq = json['isToneReq'] ?? false, isVoiceReq = json['isVoiceReq'];
isVoiceReq = json['isVoiceReq'] ?? false, orientationType = json['orientationType'];
orientationType = json['orientationType'] ?? 1, isTurnOn = json['isTurnOn'];
isTurnOn = json['isTurnOn'] ?? true, concurrentCallDelaySec = json['concurrentCallDelaySec'];
concurrentCallDelaySec = json['concurrentCallDelaySec'] ?? 1, crTypeAckIP = json['crTypeAckIP'];
crTypeAckIP = json['crTypeAckIP'] ?? "", voiceLanguage = json['voiceLanguage'] ?? 1;
voiceLanguage = json['voiceLanguage'] ?? 1, voiceLanguageText = json['voiceLanguageText'] ?? "English";
voiceLanguageText = json['voiceLanguageText'] ?? "English", vitalSignText = json['vitalSignText'];
vitalSignText = json['vitalSignText'] ?? "Vital Sign", doctorText = json['doctorText'];
doctorText = json['doctorText'] ?? "Doctor", procedureText = json['procedureText'];
procedureText = json['procedureText'] ?? "Procedure", vaccinationText = json['vaccinationText'];
vaccinationText = json['vaccinationText'] ?? "Vaccination", nebulizationText = json['nebulizationText'];
nebulizationText = json['nebulizationText'] ?? "Nebulization", callForVitalSignText = json['callForVitalSignText'];
callForVitalSignText = json['callForVitalSignText'] ?? "Call for Vital Sign", callForDoctorText = json['callForDoctorText'];
callForDoctorText = json['callForDoctorText'] ?? "Call for Doctor", callForProcedureText = json['callForProcedureText'];
callForProcedureText = json['callForProcedureText'] ?? "Call for Procedure", callForVaccinationText = json['callForVaccinationText'];
callForVaccinationText = json['callForVaccinationText'] ?? "Call for Vaccination", callForNebulizationText = json['callForNebulizationText'];
callForNebulizationText = json['callForNebulizationText'] ?? "Call for Nebulization", roomText = json['roomText'];
roomText = json['roomText'] ?? "Room", queueNoText = json['queueNoText'];
queueNoText = json['queueNoText'] ?? "Counter", callForText = json['callForText'];
callForText = json['callForText'] ?? "Call For"; }
@override @override
String toString() => 'TicketData{id: $id, patientID: $patientID, laBQGroupID: $laBQGroupID, queueNo: $queueNo, ...}'; String toString() {
return 'TicketData{id: $id, patientID: $patientID, laBQGroupID: $laBQGroupID, queueNo: $queueNo, counterBatchNo: $counterBatchNo, calledBy: $calledBy, calledOn: $calledOn, servedOn: $servedOn, patientName: $patientName, mobileNo: $mobileNo, patientEmail: $patientEmail, preferredLang: $preferredLang, voiceLanguageEnum: $voiceLanguageEnum, ticketNoText: $ticketNoText, postVoiceText: $postVoiceText, patientGender: $patientGender, roomNo: $roomNo, isActive: $isActive, createdBy: $createdBy, editedBy: $editedBy, editedOn: $editedOn, createdOn: $createdOn, doctorNameN: $doctorNameN, callTypeEnum: $callTypeEnum, queueNoM: $queueNoM, callNoStr: $callNoStr, isQueue: $isQueue, isToneReq: $isToneReq, isVoiceReq: $isVoiceReq, orientationType: $orientationType, isTurnOn: $isTurnOn, concurrentCallDelaySec: $concurrentCallDelaySec, crTypeAckIP: $crTypeAckIP, voiceLanguage: $voiceLanguage, voiceLanguageText: $voiceLanguageText, vitalSignText: $vitalSignText, doctorText: $doctorText, procedureText: $procedureText, vaccinationText: $vaccinationText, nebulizationText: $nebulizationText, callForVitalSignText: $callForVitalSignText, callForDoctorText: $callForDoctorText, callForProcedureText: $callForProcedureText, callForVaccinationText: $callForVaccinationText, callForNebulizationText: $callForNebulizationText, roomText: $roomText, queueNoText: $queueNoText, callForText: $callForText}';
}
} }

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:hmg_qline/api/api_client.dart'; import 'package:hmg_qline/api/api_client.dart';
import 'package:hmg_qline/constants/app_constants.dart'; import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/models/generic_response_model.dart'; import 'package:hmg_qline/models/generic_response_model.dart';
@ -37,7 +39,7 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo {
@override @override
Future<GlobalConfigurationsModel?> getGlobalScreenConfigurations({required String ipAddress}) async { Future<GlobalConfigurationsModel?> getGlobalScreenConfigurations({required String ipAddress}) async {
try { // try {
var params = { var params = {
"ipAddress": ipAddress.toString(), "ipAddress": ipAddress.toString(),
"apiKey": AppConstants.apiKey.toString(), "apiKey": AppConstants.apiKey.toString(),
@ -47,18 +49,24 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo {
ApiConstants.commonConfigGet, ApiConstants.commonConfigGet,
params, params,
); );
List<GlobalConfigurationsModel> globalConfigurationsModel = List.generate(genericModel.data.length, (index) => GlobalConfigurationsModel.fromJson(json: genericModel.data[index])); List<GlobalConfigurationsModel> globalConfigurationsModel =
List.generate(genericModel.data.length, (index) => GlobalConfigurationsModel.fromJson(json: genericModel.data[index]));
if (globalConfigurationsModel.isNotEmpty) { if (globalConfigurationsModel.isNotEmpty) {
loggerService.logToFile(message: globalConfigurationsModel.toString(), type: LogTypeEnum.data, source: "getGlobalScreenConfigurations-> screen_details_repo.dart"); loggerService.logToFile(
message: globalConfigurationsModel.toString(),
type: LogTypeEnum.data,
source: "getGlobalScreenConfigurations-> screen_details_repo.dart");
return globalConfigurationsModel.first; return globalConfigurationsModel.first;
} }
return null; return null;
} catch (e) { // } catch (e) {
loggerService.logError(e.toString()); // log("record:");
loggerService.logToFile(message: e.toString(), source: "getGlobalScreenConfigurations-> screen_details_repo.dart", type: LogTypeEnum.error); // log(e.toString());
InfoComponents.showToast(e.toString()); // loggerService.logError(e.toString());
return null; // loggerService.logToFile(message: e.toString(), source: "getGlobalScreenConfigurations-> screen_details_repo.dart", type: LogTypeEnum.error);
} // InfoComponents.showToast(e.toString());
// return null;
// }
} }
@override @override
@ -105,7 +113,8 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo {
); );
genericRespModel.data = KioskPatientTicket.fromJson(genericRespModel.data); genericRespModel.data = KioskPatientTicket.fromJson(genericRespModel.data);
loggerService.logToFile(message: genericRespModel.toString(), source: "createTicketFromKiosk-> screen_details_repo.dart", type: LogTypeEnum.data); loggerService.logToFile(
message: genericRespModel.toString(), source: "createTicketFromKiosk-> screen_details_repo.dart", type: LogTypeEnum.data);
return genericRespModel; return genericRespModel;
} catch (e) { } catch (e) {
@ -142,9 +151,11 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo {
body, body,
); );
List<WeathersWidgetModel> weathersWidgetModel = List.generate(genericRespModel.data.length, (index) => WeathersWidgetModel.fromJson(genericRespModel.data[index])); List<WeathersWidgetModel> weathersWidgetModel =
List.generate(genericRespModel.data.length, (index) => WeathersWidgetModel.fromJson(genericRespModel.data[index]));
if (weathersWidgetModel.isNotEmpty) { if (weathersWidgetModel.isNotEmpty) {
loggerService.logToFile(message: weathersWidgetModel.toString(), source: "getWeatherDetailsByCity-> screen_details_repo.dart", type: LogTypeEnum.data); loggerService.logToFile(
message: weathersWidgetModel.toString(), source: "getWeatherDetailsByCity-> screen_details_repo.dart", type: LogTypeEnum.data);
return weathersWidgetModel.first; return weathersWidgetModel.first;
} }
return constantWeathersWidgetModel; return constantWeathersWidgetModel;
@ -166,9 +177,11 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo {
body, body,
); );
List<PrayersWidgetModel> prayersWidgetModel = List.generate(genericRespModel.data.length, (index) => PrayersWidgetModel.fromJson(genericRespModel.data[index])); List<PrayersWidgetModel> prayersWidgetModel =
List.generate(genericRespModel.data.length, (index) => PrayersWidgetModel.fromJson(genericRespModel.data[index]));
if (prayersWidgetModel.isNotEmpty) { if (prayersWidgetModel.isNotEmpty) {
loggerService.logToFile(message: prayersWidgetModel.toString(), source: "getPrayerDetailsByLatLong-> screen_details_repo.dart", type: LogTypeEnum.data); loggerService.logToFile(
message: prayersWidgetModel.toString(), source: "getPrayerDetailsByLatLong-> screen_details_repo.dart", type: LogTypeEnum.data);
return prayersWidgetModel.first; return prayersWidgetModel.first;
} }
@ -193,7 +206,8 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo {
List<RssFeedModel> rssFeedModel = List.generate(genericRespModel.data.length, (index) => RssFeedModel.fromJson(genericRespModel.data[index])); List<RssFeedModel> rssFeedModel = List.generate(genericRespModel.data.length, (index) => RssFeedModel.fromJson(genericRespModel.data[index]));
if (rssFeedModel.isNotEmpty) { if (rssFeedModel.isNotEmpty) {
loggerService.logToFile(message: rssFeedModel.toString(), source: "getRssFeedDetailsByLanguageID-> screen_details_repo.dart", type: LogTypeEnum.data); loggerService.logToFile(
message: rssFeedModel.toString(), source: "getRssFeedDetailsByLanguageID-> screen_details_repo.dart", type: LogTypeEnum.data);
return rssFeedModel.first; return rssFeedModel.first;
} }
@ -229,7 +243,8 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo {
} }
@override @override
Future<GenericRespModel?> acknowledgeTicketForAppointment({required int ticketId, required String ipAddress, required CallTypeEnum callTypeEnum}) async { Future<GenericRespModel?> acknowledgeTicketForAppointment(
{required int ticketId, required String ipAddress, required CallTypeEnum callTypeEnum}) async {
try { try {
var params = { var params = {
"id": ticketId.toString(), "id": ticketId.toString(),

@ -1,4 +1,5 @@
import 'dart:developer'; import 'dart:developer';
import 'package:flutter_tts/flutter_tts.dart'; import 'package:flutter_tts/flutter_tts.dart';
import 'package:hmg_qline/constants/app_constants.dart'; import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/models/global_config_model.dart'; import 'package:hmg_qline/models/global_config_model.dart';
@ -6,7 +7,6 @@ import 'package:hmg_qline/models/ticket_model.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:logger/logger.dart';
abstract class TextToSpeechService { abstract class TextToSpeechService {
Future<void> speechText({ Future<void> speechText({
@ -15,8 +15,6 @@ abstract class TextToSpeechService {
bool isMute = false, bool isMute = false,
}); });
// Future<void> speechTextTest(TicketData ticket);
void listenToTextToSpeechEvents({required Function() onVoiceCompleted}); void listenToTextToSpeechEvents({required Function() onVoiceCompleted});
} }
@ -30,122 +28,6 @@ class TextToSpeechServiceImp implements TextToSpeechService {
double pitch = 0.6; double pitch = 0.6;
Map<String, String> arabicVoice = {"name": "ar-xa-x-ard-local", "locale": "ar"}; Map<String, String> arabicVoice = {"name": "ar-xa-x-ard-local", "locale": "ar"};
@override
// Future<void> speechTextTest(TicketData ticket) async {
// const ttsGoogleEngine = 'com.google.android.tts';
// LanguageEnum langEnum = ticket.voiceLanguageEnum;
// List engines = await textToSpeechInstance.getEngines;
// if (engines.contains(ttsGoogleEngine)) {
// await textToSpeechInstance.setEngine(ttsGoogleEngine);
// }
//
// textToSpeechInstance.setVolume(1.0);
//
// // final voices = await textToSpeechInstance.getVoices;
// // log ("voices:: $voices");
//
// await textToSpeechInstance.setVoice(arabicVoice);
//
// if (langEnum == LanguageEnum.arabic) {
// try {
// await textToSpeechInstance.setLanguage(LanguageEnum.arabic.enumToString());
// } catch (e) {
// log("error setting language english: ${e.toString()}");
// }
// } else if (langEnum == LanguageEnum.english) {
// try {
// await textToSpeechInstance.setLanguage(LanguageEnum.english.enumToString());
// } catch (e) {
// log("error setting language english: ${e.toString()}");
// }
// }
// String preVoice = ticket.ticketNoText;
// String postVoice = ticket.postVoiceText;
// if (preVoice.isNotEmpty) {
// preVoice = '$preVoice..';
// }
// String ticketNo = ticket.queueNo!.trim().toString();
//
// log("areLanguagesInstalled: ${await textToSpeechInstance.areLanguagesInstalled(["en", "ar"])}");
//
// log("lang: $langEnum");
// log("preVoice: $preVoice");
// log("postVoice: $postVoice");
// log("ticketNo: $ticketNo");
//
// String patientAlpha = "";
// String patientNumeric = "";
// String clinicName = "";
//
// bool isClinicNameAdded = (ticket.queueNo != ticket.callNoStr);
//
// if (isClinicNameAdded) {
// var queueNo = "";
// var clinic = ticketNo.split(" ");
// if (clinic.length > 1) {
// clinicName = clinic[0];
// queueNo = clinic[1];
// } else {
// queueNo = ticketNo;
// }
//
// var queueNoArray = queueNo.split("-");
// if (queueNoArray.length > 2) {
// patientAlpha = "${queueNoArray[0]} .. ${queueNoArray[1]}";
// patientNumeric = queueNoArray[2];
// } else {
// patientAlpha = queueNoArray[0];
// patientNumeric = queueNoArray[1];
// }
// } else {
// var queueNoArray = ticketNo.split("-");
// if (queueNoArray.length > 2) {
// patientAlpha = "${queueNoArray[0]} .. ${queueNoArray[1]}";
// patientNumeric = queueNoArray[2];
// } else {
// patientAlpha = queueNoArray[0];
// patientNumeric = queueNoArray[1];
// }
// }
//
// patientAlpha = patientAlpha.split('').join(' .. ');
// String roomNo = "";
//
// log("I will now all this:{ $preVoice .. $clinicName .. $patientAlpha .. $patientNumeric .. $postVoice $roomNo } ");
//
// if (langEnum == LanguageEnum.english) {
// await textToSpeechInstance.speak("$preVoice .. $clinicName .. $patientAlpha .. $patientNumeric .. $postVoice $roomNo");
// return;
// }
//
// if (isNeedToBreakVoiceForArabic) {
// await textToSpeechInstance.awaitSpeakCompletion(true);
//
// isSpeechCompleted = false;
// if (preVoice.isNotEmpty) {
// await textToSpeechInstance.speak("$preVoice ");
// }
// try {
// await textToSpeechInstance.setLanguage(LanguageEnum.english.enumToString());
// } catch (e) {
// log("error setting language english: ${e.toString()}");
// }
// await textToSpeechInstance.speak("$patientAlpha .. $patientNumeric");
//
// try {
// await textToSpeechInstance.setLanguage(langEnum.enumToString());
// } catch (e) {
// log("error setting language langEnum: ${e.toString()}");
// }
//
// await textToSpeechInstance.speak("$postVoice $roomNo").whenComplete(() {
// isSpeechCompleted = true;
// });
// } else {
// await textToSpeechInstance.speak("$preVoice .. $clinicName .. $patientAlpha .. $patientNumeric .. $postVoice $roomNo");
// }
// }
@override @override
Future<void> speechText({ Future<void> speechText({
required TicketDetailsModel ticket, required TicketDetailsModel ticket,
@ -169,7 +51,16 @@ class TextToSpeechServiceImp implements TextToSpeechService {
textToSpeechInstance.setVolume(1.0); textToSpeechInstance.setVolume(1.0);
} }
textToSpeechInstance.setSpeechRate(0.4); if (isAndroid14) {
if (langEnum == LanguageEnum.arabic) {
textToSpeechInstance.setSpeechRate(0.5);
} else {
textToSpeechInstance.setSpeechRate(0.4);
}
textToSpeechInstance.setPitch(0.9);
} else {
textToSpeechInstance.setSpeechRate(0.4);
}
if (langEnum == LanguageEnum.arabic) { if (langEnum == LanguageEnum.arabic) {
try { try {
await textToSpeechInstance.setLanguage(LanguageEnum.arabic.enumToString()); await textToSpeechInstance.setLanguage(LanguageEnum.arabic.enumToString());
@ -191,7 +82,9 @@ class TextToSpeechServiceImp implements TextToSpeechService {
postVoice = ticket.ticketModel!.postVoiceText; postVoice = ticket.ticketModel!.postVoiceText;
} }
String roomNo = ''; String roomNo = '';
if (globalConfigurationsModel.qTypeEnum != QTypeEnum.appointment && ticket.ticketModel!.roomNo != null && ticket.ticketModel!.roomNo!.isNotEmpty) { if (globalConfigurationsModel.qTypeEnum != QTypeEnum.appointment &&
ticket.ticketModel!.roomNo != null &&
ticket.ticketModel!.roomNo!.isNotEmpty) {
roomNo = ".. ${ticket.ticketModel!.roomNo.toString()}"; roomNo = ".. ${ticket.ticketModel!.roomNo.toString()}";
} }

@ -166,7 +166,6 @@ extension LanguageEnumToString on LanguageEnum {
extension XCallType on CallTypeEnum { extension XCallType on CallTypeEnum {
Color getColorByCallType() { Color getColorByCallType() {
log("getColorByCallType: $this");
if (this == CallTypeEnum.vitalSign) { if (this == CallTypeEnum.vitalSign) {
return AppColors.newVitalSignColor; return AppColors.newVitalSignColor;
} else if (this == CallTypeEnum.doctor) { } else if (this == CallTypeEnum.doctor) {
@ -177,10 +176,9 @@ extension XCallType on CallTypeEnum {
return AppColors.vaccinationColor; return AppColors.vaccinationColor;
} else if (this == CallTypeEnum.nebulization) { } else if (this == CallTypeEnum.nebulization) {
return AppColors.nebulizationColor; return AppColors.nebulizationColor;
} else if (this == CallTypeEnum.none) { } else {
return AppColors.newDoctorColor; return Colors.black54;
} }
return AppColors.newDoctorColor;
} }
String getMessageByCallTypeForEnglish(GlobalConfigurationsModel globalConfig, {bool isListView = false}) { String getMessageByCallTypeForEnglish(GlobalConfigurationsModel globalConfig, {bool isListView = false}) {
@ -196,7 +194,7 @@ extension XCallType on CallTypeEnum {
case CallTypeEnum.nebulization: case CallTypeEnum.nebulization:
return !isListView ? globalConfig.callForNebulizationTextEng : globalConfig.nebulizationTextEng; return !isListView ? globalConfig.callForNebulizationTextEng : globalConfig.nebulizationTextEng;
case CallTypeEnum.none: case CallTypeEnum.none:
return !isListView ? (globalConfig.pleaseVisitCounterTextEn ?? '') : (globalConfig.counterTextEng ?? ''); return !isListView ? globalConfig.callForVitalSignTextEng : globalConfig.vitalSignTextEng;
} }
} }
@ -213,15 +211,11 @@ extension XCallType on CallTypeEnum {
case CallTypeEnum.nebulization: case CallTypeEnum.nebulization:
return !isListView ? globalConfig.callForNebulizationTextArb : globalConfig.nebulizationTextArb; return !isListView ? globalConfig.callForNebulizationTextArb : globalConfig.nebulizationTextArb;
case CallTypeEnum.none: case CallTypeEnum.none:
return !isListView ? (globalConfig.pleaseVisitCounterTextAr ?? '') : (globalConfig.counterTextArb ?? ''); return !isListView ? globalConfig.callForVitalSignTextArb : globalConfig.vitalSignTextArb;
} }
} }
Widget getIconByCallType(double height, QTypeEnum qType, {double? width, BoxFit fit = BoxFit.contain}) { SvgPicture getIconByCallType(double height, {double? width, BoxFit fit = BoxFit.contain}) {
if (this == CallTypeEnum.vitalSign) {
return const SizedBox.shrink();
}
String iconPath = ""; String iconPath = "";
if (this == CallTypeEnum.vitalSign) { if (this == CallTypeEnum.vitalSign) {
iconPath = AppAssets.newVitalSignIcon; iconPath = AppAssets.newVitalSignIcon;
@ -235,10 +229,6 @@ extension XCallType on CallTypeEnum {
iconPath = AppAssets.nebulizationIcon; iconPath = AppAssets.nebulizationIcon;
} }
if (qType == QTypeEnum.lab) {
iconPath = AppAssets.labIcon;
}
return SvgPicture.asset( return SvgPicture.asset(
iconPath.isEmpty ? "assets/images/wait.svg" : iconPath, iconPath.isEmpty ? "assets/images/wait.svg" : iconPath,
height: height, height: height,
@ -261,7 +251,7 @@ extension XCallType on CallTypeEnum {
case CallTypeEnum.nebulization: case CallTypeEnum.nebulization:
return 5; return 5;
case CallTypeEnum.none: case CallTypeEnum.none:
return 0; return 1;
} }
} }
@ -278,7 +268,7 @@ extension XCallType on CallTypeEnum {
case CallTypeEnum.nebulization: case CallTypeEnum.nebulization:
return AppColors.gradientBorderComboForVitalSigns; return AppColors.gradientBorderComboForVitalSigns;
case CallTypeEnum.none: case CallTypeEnum.none:
return AppColors.gradientBorderComboForDoctor; return AppColors.gradientBorderComboForVitalSigns;
} }
} }
@ -294,8 +284,8 @@ extension XCallType on CallTypeEnum {
return ticket.callForVaccinationText; return ticket.callForVaccinationText;
case CallTypeEnum.nebulization: case CallTypeEnum.nebulization:
return ticket.callForNebulizationText; return ticket.callForNebulizationText;
case CallTypeEnum.none: default:
return ''; return ticket.callForVitalSignText;
} }
} }
} }
@ -307,7 +297,7 @@ extension XCallTypeInt on int {
if (this == 3) return CallTypeEnum.procedure; if (this == 3) return CallTypeEnum.procedure;
if (this == 4) return CallTypeEnum.vaccination; if (this == 4) return CallTypeEnum.vaccination;
if (this == 5) return CallTypeEnum.nebulization; if (this == 5) return CallTypeEnum.nebulization;
return CallTypeEnum.none; return CallTypeEnum.vitalSign;
} }
} }

@ -12,6 +12,19 @@ abstract class NativeMethodChannelService {
Future<void> clearAllResources(); Future<void> clearAllResources();
Future<void> smartRestart({bool forceRestart = false, bool cleanupFirst = true}); Future<void> smartRestart({bool forceRestart = false, bool cleanupFirst = true});
/// Schedule daily restart alarm at specified time.
/// Works on Android 14+ and older versions.
Future<void> scheduleRestartAlarm({int hour = 0, int minute = 15});
/// Cancel the scheduled restart alarm.
Future<void> cancelRestartAlarm();
/// Check if the app can schedule exact alarms (Android 12+).
Future<bool> canScheduleExactAlarms();
/// Request permission to schedule exact alarms (Android 12+).
Future<void> requestExactAlarmPermission();
} }
class NativeMethodChannelServiceImp implements NativeMethodChannelService { class NativeMethodChannelServiceImp implements NativeMethodChannelService {
@ -92,4 +105,85 @@ class NativeMethodChannelServiceImp implements NativeMethodChannelService {
loggerService.logError("Primary restart failed, trying fallback methods: $primaryError"); loggerService.logError("Primary restart failed, trying fallback methods: $primaryError");
} }
} }
// === NEW: Alarm Scheduling Methods for Android 14+ compatibility ===
/// Schedule daily restart alarm at specified time.
/// Default is 00:15 (12:15 AM).
/// Works on Android 14+ and older versions.
@override
Future<void> scheduleRestartAlarm({int hour = 0, int minute = 15}) async {
try {
loggerService.logInfo("Scheduling restart alarm for $hour:$minute");
// First check if we can schedule exact alarms
final canSchedule = await canScheduleExactAlarms();
if (!canSchedule) {
loggerService.logInfo("Exact alarm permission not granted. Requesting permission...");
await requestExactAlarmPermission();
}
await _platform.invokeMethod('scheduleRestartAlarm', {
'hour': hour,
'minute': minute,
});
loggerService.logInfo("Restart alarm scheduled successfully for $hour:$minute");
} catch (e) {
loggerService.logError("Error scheduling restart alarm: $e");
loggerService.logToFile(
message: "Error scheduling restart alarm: $e",
source: "scheduleRestartAlarm -> native_method_handler.dart",
type: LogTypeEnum.error,
);
}
}
/// Cancel the scheduled restart alarm.
@override
Future<void> cancelRestartAlarm() async {
try {
loggerService.logInfo("Cancelling restart alarm");
await _platform.invokeMethod('cancelRestartAlarm');
loggerService.logInfo("Restart alarm cancelled successfully");
} catch (e) {
loggerService.logError("Error cancelling restart alarm: $e");
loggerService.logToFile(
message: "Error cancelling restart alarm: $e",
source: "cancelRestartAlarm -> native_method_handler.dart",
type: LogTypeEnum.error,
);
}
}
/// Check if the app can schedule exact alarms.
/// Returns true on Android < 12 (always allowed) or if permission is granted on Android 12+.
@override
Future<bool> canScheduleExactAlarms() async {
try {
final result = await _platform.invokeMethod('canScheduleExactAlarms');
loggerService.logInfo("Can schedule exact alarms: $result");
return result ?? false;
} catch (e) {
loggerService.logError("Error checking exact alarm permission: $e");
return false;
}
}
/// Request permission to schedule exact alarms (Android 12+).
/// Opens system settings for the user to grant permission.
@override
Future<void> requestExactAlarmPermission() async {
try {
loggerService.logInfo("Requesting exact alarm permission");
await _platform.invokeMethod('requestExactAlarmPermission');
loggerService.logInfo("Exact alarm permission request initiated");
} catch (e) {
loggerService.logError("Error requesting exact alarm permission: $e");
loggerService.logToFile(
message: "Error requesting exact alarm permission: $e",
source: "requestExactAlarmPermission -> native_method_handler.dart",
type: LogTypeEnum.error,
);
}
}
} }

@ -94,8 +94,8 @@ class QueuingViewModel extends ChangeNotifier {
loggerService.logToFile(message: response.toString(), source: "onHubTicketCall -> queueing_view_model.dart ", type: LogTypeEnum.data); loggerService.logToFile(message: response.toString(), source: "onHubTicketCall -> queueing_view_model.dart ", type: LogTypeEnum.data);
log("onHubTicketCall: $response"); log("onHubTicketCall: $response");
log("isCallingInProgress: $isCallingInProgress"); log("isCallingInProgress: $isCallingInProgress");
if (response != null && response.isNotEmpty) { if (response != null && response.isNotEmpty) {
TicketDetailsModel ticketDetailsModel = TicketDetailsModel.fromJson(response.first as Map<String, dynamic>); TicketDetailsModel ticketDetailsModel = TicketDetailsModel.fromJson(response.first as Map<String, dynamic>);
addNewTicket(ticketDetailsModel); addNewTicket(ticketDetailsModel);
@ -215,9 +215,7 @@ class QueuingViewModel extends ChangeNotifier {
callTypeEnum: ticketData.callTypeEnum, callTypeEnum: ticketData.callTypeEnum,
); );
} else { } else {
screenConfigViewModel.acknowledgeTicket(ticketQueueID: ticketData.id ?? 0, screenConfigViewModel.acknowledgeTicket(ticketQueueID: ticketData.id ?? 0);
ipAddress: screenConfigViewModel.currentScreenIP,
);
} }
log("globalConfigurationsModel: ${globalConfigurationsModel.toString()}"); log("globalConfigurationsModel: ${globalConfigurationsModel.toString()}");

@ -1,5 +1,6 @@
import 'dart:developer';
import 'dart:async'; import 'dart:async';
import 'dart:developer';
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';
@ -49,16 +50,19 @@ class ScreenConfigViewModel extends ChangeNotifier {
} }
Future<void> onAppResumed() async { Future<void> onAppResumed() async {
loggerService.logToFile(message: "[didChangeAppLifecycleState] : [onAppResumed]", source: "onAppResumed -> screen_config_view_model.dart", type: LogTypeEnum.data); loggerService.logToFile(
message: "[didChangeAppLifecycleState] : [onAppResumed]", source: "onAppResumed -> screen_config_view_model.dart", type: LogTypeEnum.data);
} }
Future<void> onAppPaused() async { Future<void> onAppPaused() async {
loggerService.logToFile(message: "[didChangeAppLifecycleState] : [onAppPaused]", source: "onAppPaused -> screen_config_view_model.dart", type: LogTypeEnum.data); loggerService.logToFile(
message: "[didChangeAppLifecycleState] : [onAppPaused]", source: "onAppPaused -> screen_config_view_model.dart", type: LogTypeEnum.data);
// nativeMethodChannelService.restartApp(); // nativeMethodChannelService.restartApp();
} }
Future<void> onAppDetached() async { Future<void> onAppDetached() async {
loggerService.logToFile(message: "[didChangeAppLifecycleState] : [onAppDetached]", source: "onAppDetached -> screen_config_view_model.dart", type: LogTypeEnum.data); loggerService.logToFile(
message: "[didChangeAppLifecycleState] : [onAppDetached]", source: "onAppDetached -> screen_config_view_model.dart", type: LogTypeEnum.data);
// nativeMethodChannelService.restartApp(); // nativeMethodChannelService.restartApp();
} }
@ -229,7 +233,9 @@ class ScreenConfigViewModel extends ChangeNotifier {
Future<void> getWeatherDetailsFromServer() async { Future<void> getWeatherDetailsFromServer() async {
int testCityKey = 297030; int testCityKey = 297030;
WeathersWidgetModel? response = await screenDetailsRepo.getWeatherDetailsByCity( WeathersWidgetModel? response = await screenDetailsRepo.getWeatherDetailsByCity(
cityId: ((globalConfigurationsModel.cityKey == null || globalConfigurationsModel.cityKey == 0) ? testCityKey : globalConfigurationsModel.cityKey).toString(), cityId:
((globalConfigurationsModel.cityKey == null || globalConfigurationsModel.cityKey == 0) ? testCityKey : globalConfigurationsModel.cityKey)
.toString(),
); );
if (response == null) { if (response == null) {
@ -336,7 +342,7 @@ class ScreenConfigViewModel extends ChangeNotifier {
DateTime now = DateTime.now(); DateTime now = DateTime.now();
log("counterValue: $counter"); log("counterValue: $counter");
if (globalConfigurationsModel.id == null) { if (globalConfigurationsModel.id == null || state == ViewState.error) {
await getGlobalConfigurationsByIP(); await getGlobalConfigurationsByIP();
} }
@ -393,7 +399,9 @@ class ScreenConfigViewModel extends ChangeNotifier {
Future<void> getLastTimeLogsClearedFromCache() async { Future<void> getLastTimeLogsClearedFromCache() async {
lastTimeLogsCleared = await cacheService.getLastTimeLogsCleared(); lastTimeLogsCleared = await cacheService.getLastTimeLogsCleared();
if (lastTimeLogsCleared == null) { if (lastTimeLogsCleared == null) {
await cacheService.setLastTimeLogsCleared(lastTimeCleared: DateTime.now().millisecondsSinceEpoch).whenComplete(() => lastTimeLogsCleared = DateTime.now()); await cacheService
.setLastTimeLogsCleared(lastTimeCleared: DateTime.now().millisecondsSinceEpoch)
.whenComplete(() => lastTimeLogsCleared = DateTime.now());
} }
} }
@ -477,9 +485,9 @@ class ScreenConfigViewModel extends ChangeNotifier {
} }
} }
Future<void> acknowledgeTicket({required int ticketQueueID, required String ipAddress}) async { Future<void> acknowledgeTicket({required int ticketQueueID}) async {
GenericRespModel? response = await screenDetailsRepo.acknowledgeTicket( GenericRespModel? response = await screenDetailsRepo.acknowledgeTicket(
ipAddress: ipAddress, ipAddress: currentScreenIP,
ticketQueueID: ticketQueueID, ticketQueueID: ticketQueueID,
qTypeEnum: globalConfigurationsModel.qTypeEnum, qTypeEnum: globalConfigurationsModel.qTypeEnum,
); );
@ -491,7 +499,8 @@ class ScreenConfigViewModel extends ChangeNotifier {
} }
} }
Future<void> acknowledgeTicketForAppointmentOnly({required int ticketQueueID, required String ipAddress, required CallTypeEnum callTypeEnum}) async { Future<void> acknowledgeTicketForAppointmentOnly(
{required int ticketQueueID, required String ipAddress, required CallTypeEnum callTypeEnum}) async {
GenericRespModel? response = await screenDetailsRepo.acknowledgeTicketForAppointment( GenericRespModel? response = await screenDetailsRepo.acknowledgeTicketForAppointment(
ticketId: ticketQueueID, ticketId: ticketQueueID,
ipAddress: ipAddress, ipAddress: ipAddress,

@ -1,6 +1,9 @@
import 'dart:async'; // Add this import import 'dart:async'; // Add this import
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:hmg_qline/models/ticket_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_general_widgets.dart'; import 'package:hmg_qline/views/common_widgets/app_general_widgets.dart';
import 'package:hmg_qline/views/common_widgets/date_display_widget.dart'; import 'package:hmg_qline/views/common_widgets/date_display_widget.dart';
@ -309,11 +312,16 @@ class _AppFooterState extends State<AppFooter> {
Padding( Padding(
padding: EdgeInsets.only(top: SizeConfig.getHeightMultiplier()! * 0.1), padding: EdgeInsets.only(top: SizeConfig.getHeightMultiplier()! * 0.1),
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
AppText( InkWell(
AppStrings.poweredBy, onTap: () {
fontSize: SizeConfig.getWidthMultiplier()! * 1.5, // context.read<QueuingViewModel>().addNewTicket(TicketDetailsModel(ticketModel: MockJsonRepo.ticket));
fontWeight: FontWeight.w400, },
color: AppColors.darkGreyTextColor, child: AppText(
AppStrings.poweredBy,
fontSize: SizeConfig.getWidthMultiplier()! * 1.5,
fontWeight: FontWeight.w400,
color: AppColors.darkGreyTextColor,
),
), ),
AppText( AppText(
"v${screenConfigVM.currentScreenIP.replaceAll(".", "-")}(${AppConstants.currentBuildVersion})", "v${screenConfigVM.currentScreenIP.replaceAll(".", "-")}(${AppConstants.currentBuildVersion})",

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:hmg_qline/constants/app_constants.dart'; import 'package:hmg_qline/constants/app_constants.dart';
@ -130,7 +128,7 @@ Widget counterNoText({required int counterNo, required bool isRoomNoRequired, re
fontFamily: AppStrings.fontNamePoppins, fontFamily: AppStrings.fontNamePoppins,
textAlign: TextAlign.center, textAlign: TextAlign.center,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: isRoomNoRequired ? AppColors.greyTextColor : Colors.transparent, color: isRoomNoRequired ? Colors.black : Colors.transparent,
fontSize: SizeConfig.getWidthMultiplier() * 8, fontSize: SizeConfig.getWidthMultiplier() * 8,
); );
} }

@ -1,19 +1,14 @@
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.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/models/global_config_model.dart'; import 'package:hmg_qline/models/global_config_model.dart';
import 'package:hmg_qline/utilities/enums.dart'; import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/utilities/native_method_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/view_models/screen_config_view_model.dart';
import 'package:hmg_qline/views/common_widgets/app_general_widgets.dart'; import 'package:hmg_qline/views/common_widgets/app_general_widgets.dart';
import 'package:provider/provider.dart';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/views/common_widgets/app_texts_widget.dart';
import 'package:hmg_qline/views/view_helpers/size_config.dart'; import 'package:hmg_qline/views/view_helpers/size_config.dart';
import 'package:provider/provider.dart';
class AppHeader extends StatelessWidget implements PreferredSizeWidget { class AppHeader extends StatelessWidget implements PreferredSizeWidget {
const AppHeader({super.key}); const AppHeader({super.key});
@ -34,8 +29,10 @@ class AppHeader extends StatelessWidget implements PreferredSizeWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
InkWell( InkWell(
onTap: () async {
final nativeMethodChannelService = getIt.get<NativeMethodChannelService>();
await nativeMethodChannelService.smartRestart(forceRestart: true, cleanupFirst: true);
onTap: () {
// getIt.get<QueuingViewModel>().triggerOOM(); // getIt.get<QueuingViewModel>().triggerOOM();
}, },
child: engArabicTextWithSeparatorWidget( child: engArabicTextWithSeparatorWidget(

@ -3,12 +3,10 @@ import 'package:flutter/material.dart';
import 'package:hmg_qline/models/global_config_model.dart'; import 'package:hmg_qline/models/global_config_model.dart';
import 'package:hmg_qline/models/ticket_model.dart'; import 'package:hmg_qline/models/ticket_model.dart';
import 'package:hmg_qline/utilities/enums.dart'; import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/view_models/screen_config_view_model.dart';
import 'package:hmg_qline/views/common_widgets/app_general_widgets.dart'; import 'package:hmg_qline/views/common_widgets/app_general_widgets.dart';
import 'package:hmg_qline/views/main_queue_screen/components/ticket_item_calling_card.dart'; import 'package:hmg_qline/views/main_queue_screen/components/ticket_item_calling_card.dart';
import 'package:hmg_qline/views/main_queue_screen/components/ticket_item_normal_card.dart'; import 'package:hmg_qline/views/main_queue_screen/components/ticket_item_normal_card.dart';
import 'package:hmg_qline/views/view_helpers/size_config.dart'; import 'package:hmg_qline/views/view_helpers/size_config.dart';
import 'package:provider/provider.dart';
class PriorityTickets extends StatelessWidget { class PriorityTickets extends StatelessWidget {
final List<TicketDetailsModel> tickets; final List<TicketDetailsModel> tickets;
@ -95,10 +93,7 @@ class PriorityTickets extends StatelessWidget {
} }
Widget _buildPrimaryTicket(BuildContext context, TicketDetailsModel ticket, {bool isFullWidth = false, bool isHalf = false}) { Widget _buildPrimaryTicket(BuildContext context, TicketDetailsModel ticket, {bool isFullWidth = false, bool isHalf = false}) {
final screenConfigViewModel = context.read<ScreenConfigViewModel>();
Widget primaryCallingCard = QueueItemCallingCard( Widget primaryCallingCard = QueueItemCallingCard(
qTypeEnum: screenConfigViewModel.currentQTypeEnum,
isGradientRequired: true, isGradientRequired: true,
isBorderRequired: true, isBorderRequired: true,
isSingleTicket: isFullWidth, isSingleTicket: isFullWidth,
@ -108,8 +103,7 @@ class PriorityTickets extends StatelessWidget {
roomNo: ticket.ticketModel?.roomNo ?? '', roomNo: ticket.ticketModel?.roomNo ?? '',
roomText: _getRoomText(), roomText: _getRoomText(),
roomTextAr: _getRoomTextAr(), roomTextAr: _getRoomTextAr(),
isClinicAdded: false, callTypeEnum: ticket.ticketModel?.callTypeEnum ?? CallTypeEnum.vitalSign,
callTypeEnum: ticket.ticketModel?.callTypeEnum ?? CallTypeEnum.none,
textDirection: globalConfigurationsModel.textDirection, textDirection: globalConfigurationsModel.textDirection,
screenTypeEnum: globalConfigurationsModel.screenTypeEnum, screenTypeEnum: globalConfigurationsModel.screenTypeEnum,
langTypeEnum: globalConfigurationsModel.screenLanguageEnum, langTypeEnum: globalConfigurationsModel.screenLanguageEnum,
@ -136,7 +130,7 @@ class PriorityTickets extends StatelessWidget {
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.6, // 60% of screen width maxWidth: MediaQuery.of(context).size.width * 0.6, // 60% of screen width
minHeight: SizeConfig.getHeightMultiplier() * 2, // Minimum height minHeight: SizeConfig.getHeightMultiplier() * 2.5, // Minimum height
), ),
child: Transform.scale( child: Transform.scale(
scale: _getTicketScale() + 0.2, scale: _getTicketScale() + 0.2,
@ -150,15 +144,12 @@ class PriorityTickets extends StatelessWidget {
} }
Widget _buildSecondaryTicket(BuildContext context, TicketDetailsModel ticket, {EdgeInsets? margin, bool isHalf = false}) { Widget _buildSecondaryTicket(BuildContext context, TicketDetailsModel ticket, {EdgeInsets? margin, bool isHalf = false}) {
final screenConfigViewModel = context.read<ScreenConfigViewModel>();
Widget secondaryCallingCard = QueueItemNormalCard( Widget secondaryCallingCard = QueueItemNormalCard(
qTypeEnum: screenConfigViewModel.currentQTypeEnum,
ticketNo: ticket.ticketModel?.queueNo ?? '', ticketNo: ticket.ticketModel?.queueNo ?? '',
roomNo: ticket.ticketModel?.roomNo ?? '', roomNo: ticket.ticketModel?.roomNo ?? '',
roomText: _getRoomText(), roomText: _getRoomText(),
roomTextAr: _getRoomTextAr(), roomTextAr: _getRoomTextAr(),
isClinicAdded: false, callTypeEnum: ticket.ticketModel?.callTypeEnum ?? CallTypeEnum.vitalSign,
callTypeEnum: ticket.ticketModel?.callTypeEnum ?? CallTypeEnum.none,
textDirection: globalConfigurationsModel.textDirection, textDirection: globalConfigurationsModel.textDirection,
screenTypeEnum: globalConfigurationsModel.screenTypeEnum, screenTypeEnum: globalConfigurationsModel.screenTypeEnum,
langTypeEnum: globalConfigurationsModel.screenLanguageEnum, langTypeEnum: globalConfigurationsModel.screenLanguageEnum,
@ -194,7 +185,7 @@ class PriorityTickets extends StatelessWidget {
// Helper methods to reduce repetition // Helper methods to reduce repetition
double _getTicketScale() { double _getTicketScale() {
return globalConfigurationsModel.screenTypeEnum == ScreenTypeEnum.roomLevelScreen return globalConfigurationsModel.screenTypeEnum == ScreenTypeEnum.roomLevelScreen
? 1.5 ? 2.0
: globalConfigurationsModel.isFromTakhasusiMain : globalConfigurationsModel.isFromTakhasusiMain
? 0.8 ? 0.8
: 1.2; : 1.2;

@ -48,62 +48,62 @@ class PriorityTicketsWithSideSection extends StatelessWidget {
), ),
), ),
), ),
// if (globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment) ...[ if (globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment) ...[
Expanded( Expanded(
flex: 8, flex: 8,
child: SizedBox( child: SizedBox(
height: SizeConfig.getHeightMultiplier() * 0.25, height: SizeConfig.getHeightMultiplier() * 0.25,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Expanded( Expanded(
flex: 2, flex: 2,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ticketModel.callTypeEnum.getIconByCallType(SizeConfig.getHeightMultiplier() * 0.2, globalConfigurationsModel.qTypeEnum), ticketModel.callTypeEnum.getIconByCallType(SizeConfig.getHeightMultiplier() * 0.2),
], ],
),
), ),
), Expanded(
Expanded( flex: 9,
flex: 9, child: Row(
child: Row( mainAxisAlignment: MainAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, children: [
children: [
Expanded(
flex: 3,
child: AppText(
callMessageAr,
color: ticketModel.callTypeEnum.getColorByCallType(),
fontSize: SizeConfig.getWidthMultiplier() * 1.8,
fontWeight: FontWeight.bold,
fontFamily: AppStrings.fontNameGesTwo,
fontHeight: 1,
textOverflow: TextOverflow.clip,
maxLines: 1,
),
),
SizedBox(width: SizeConfig.getWidthMultiplier()),
if (callMessageEng.isNotEmpty) ...[
Expanded( Expanded(
flex: 2, flex: 3,
child: AppText( child: AppText(
"($callMessageEng)", callMessageAr,
color: ticketModel.callTypeEnum.getColorByCallType(), color: ticketModel.callTypeEnum.getColorByCallType(),
fontSize: SizeConfig.getWidthMultiplier() * 1.8, fontSize: SizeConfig.getWidthMultiplier() * 1.8,
fontFamily: AppStrings.fontNamePoppins, fontWeight: FontWeight.bold,
fontFamily: AppStrings.fontNameGesTwo,
fontHeight: 1, fontHeight: 1,
textOverflow: TextOverflow.clip,
maxLines: 1,
), ),
), ),
] SizedBox(width: SizeConfig.getWidthMultiplier()),
], if (callMessageEng.isNotEmpty) ...[
Expanded(
flex: 3,
child: AppText(
"($callMessageEng)",
color: ticketModel.callTypeEnum.getColorByCallType(),
fontSize: SizeConfig.getWidthMultiplier() * 1.8,
fontFamily: AppStrings.fontNamePoppins,
fontHeight: 1,
),
),
]
],
),
), ),
), ],
], ),
), ),
), ),
), ],
// ],
Expanded( Expanded(
flex: 2, flex: 2,
child: Center( child: Center(
@ -124,6 +124,8 @@ class PriorityTicketsWithSideSection extends StatelessWidget {
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<TicketDetailsModel> priorityTickets = []; List<TicketDetailsModel> priorityTickets = [];
@ -168,7 +170,7 @@ class PriorityTicketsWithSideSection extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
flex: 3, flex: 4,
child: engArabicTextWithSeparatorWidget( child: engArabicTextWithSeparatorWidget(
englishText: globalConfigurationsModel.queueNoTextEng ?? "", englishText: globalConfigurationsModel.queueNoTextEng ?? "",
arabicText: globalConfigurationsModel.queueNoTextArb ?? "", arabicText: globalConfigurationsModel.queueNoTextArb ?? "",

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:blinking_text/blinking_text.dart'; import 'package:blinking_text/blinking_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
@ -14,7 +16,6 @@ class QueueItemCallingCard extends StatelessWidget {
final String roomNo; final String roomNo;
final bool blink; final bool blink;
final double scale; final double scale;
final bool isClinicAdded;
final bool isGradientRequired; final bool isGradientRequired;
final bool isBorderRequired; final bool isBorderRequired;
final TextDirection textDirection; final TextDirection textDirection;
@ -24,12 +25,10 @@ class QueueItemCallingCard extends StatelessWidget {
final CallTypeEnum callTypeEnum; final CallTypeEnum callTypeEnum;
final ScreenTypeEnum screenTypeEnum; final ScreenTypeEnum screenTypeEnum;
final LanguageEnum langTypeEnum; final LanguageEnum langTypeEnum;
final QTypeEnum qTypeEnum;
final bool isSingleTicket; final bool isSingleTicket;
const QueueItemCallingCard({ const QueueItemCallingCard({
super.key, super.key,
required this.isClinicAdded,
required this.ticketNo, required this.ticketNo,
required this.roomNo, required this.roomNo,
required this.scale, required this.scale,
@ -40,28 +39,22 @@ class QueueItemCallingCard extends StatelessWidget {
required this.callTypeEnum, required this.callTypeEnum,
required this.screenTypeEnum, required this.screenTypeEnum,
required this.langTypeEnum, required this.langTypeEnum,
required this.qTypeEnum,
this.isGradientRequired = false, this.isGradientRequired = false,
this.isBorderRequired = false, this.isBorderRequired = false,
this.isSingleTicket = false, this.isSingleTicket = false,
this.blink = false, this.blink = false,
}); });
String getFormattedTicket(String ticketNo, bool isClinicAdded) { bool shouldReduceSize(String ticketNo) {
if (isClinicAdded) { // Use regex to check if ticket starts with exactly 3 letters followed by " W-"
var formattedString = ticketNo.split(" "); final hasClinicPrefix = RegExp(r'^[A-Za-z]{3} W-').hasMatch(ticketNo);
if (formattedString.length > 1) {
return "${formattedString[0]} ${formattedString[1]}"; return hasClinicPrefix;
} else {
return ticketNo;
}
}
return ticketNo;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final text = callTypeEnum.getMessageByCallTypeForEnglish(globalConfigurationsModel, isListView: false) + (qTypeEnum == QTypeEnum.appointment ? ("| $roomText $roomNo") : ""); final text = "${callTypeEnum.getMessageByCallTypeForEnglish(globalConfigurationsModel, isListView: false)} | $roomText $roomNo";
return Stack( return Stack(
children: [ children: [
customShadowSmoothContainerWithBackground( customShadowSmoothContainerWithBackground(
@ -84,7 +77,7 @@ class QueueItemCallingCard extends StatelessWidget {
left: textDirection == TextDirection.rtl ? SizeConfig.getWidthMultiplier() * 3.5 : 0, left: textDirection == TextDirection.rtl ? SizeConfig.getWidthMultiplier() * 3.5 : 0,
right: textDirection == TextDirection.ltr ? SizeConfig.getWidthMultiplier() * 3.5 : 0, right: textDirection == TextDirection.ltr ? SizeConfig.getWidthMultiplier() * 3.5 : 0,
), ),
child: callTypeEnum.getIconByCallType(SizeConfig.getHeightMultiplier() * 0.5, qTypeEnum), child: callTypeEnum.getIconByCallType(SizeConfig.getHeightMultiplier() * 0.5),
), ),
SizedBox(height: SizeConfig.getHeightMultiplier()! * 0.15), SizedBox(height: SizeConfig.getHeightMultiplier()! * 0.15),
IntrinsicWidth( IntrinsicWidth(
@ -109,8 +102,8 @@ class QueueItemCallingCard extends StatelessWidget {
top: SizeConfig.getHeightMultiplier() * 0.25, top: SizeConfig.getHeightMultiplier() * 0.25,
), ),
child: AppText( child: AppText(
getFormattedTicket(ticketNo, isClinicAdded), ticketNo,
fontSize: SizeConfig.getWidthMultiplier() * 7.4, fontSize: shouldReduceSize(ticketNo) ? SizeConfig.getWidthMultiplier() * 6 : SizeConfig.getWidthMultiplier() * 7.4,
letterSpacing: -1, letterSpacing: -1,
fontHeight: 0.5, fontHeight: 0.5,
color: AppColors.greyTextColor, color: AppColors.greyTextColor,

@ -11,21 +11,18 @@ import 'package:hmg_qline/views/view_helpers/size_config.dart';
class QueueItemNormalCard extends StatelessWidget { class QueueItemNormalCard extends StatelessWidget {
final String ticketNo; final String ticketNo;
final String roomNo; final String roomNo;
final bool isClinicAdded;
final TextDirection textDirection; final TextDirection textDirection;
final String roomText; final String roomText;
final String roomTextAr; final String roomTextAr;
final GlobalConfigurationsModel globalConfigurationsModel; final GlobalConfigurationsModel globalConfigurationsModel;
final CallTypeEnum callTypeEnum; final CallTypeEnum callTypeEnum;
final ScreenTypeEnum screenTypeEnum; final ScreenTypeEnum screenTypeEnum;
final QTypeEnum qTypeEnum;
final LanguageEnum langTypeEnum; final LanguageEnum langTypeEnum;
final double? height; final double? height;
final double? width; final double? width;
const QueueItemNormalCard({ const QueueItemNormalCard({
super.key, super.key,
required this.isClinicAdded,
required this.ticketNo, required this.ticketNo,
required this.roomNo, required this.roomNo,
required this.textDirection, required this.textDirection,
@ -35,27 +32,20 @@ class QueueItemNormalCard extends StatelessWidget {
required this.callTypeEnum, required this.callTypeEnum,
required this.screenTypeEnum, required this.screenTypeEnum,
required this.langTypeEnum, required this.langTypeEnum,
required this.qTypeEnum,
this.height, this.height,
this.width, this.width,
}); });
String getFormattedTicket(String ticketNo, bool isClinicAdded) { bool shouldReduceSize(String ticketNo) {
if (isClinicAdded) { // Use regex to check if ticket starts with exactly 3 letters followed by " W-"
var formattedString = ticketNo.split(" "); final hasClinicPrefix = RegExp(r'^[A-Za-z]{3} W-').hasMatch(ticketNo);
if (formattedString.length > 1) {
return "${formattedString[0]} ${formattedString[1]}"; return hasClinicPrefix;
} else {
return ticketNo;
}
}
return ticketNo;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final text = callTypeEnum.getMessageByCallTypeForEnglish(globalConfigurationsModel, isListView: false) + (qTypeEnum == QTypeEnum.appointment ? ("| $roomText $roomNo") : ""); final text = "${callTypeEnum.getMessageByCallTypeForEnglish(globalConfigurationsModel, isListView: false)} | $roomText $roomNo";
return Stack( return Stack(
children: [ children: [
customShadowSmoothContainer( customShadowSmoothContainer(
@ -76,8 +66,8 @@ class QueueItemNormalCard extends StatelessWidget {
flex: 3, flex: 3,
child: Center( child: Center(
child: AppText( child: AppText(
getFormattedTicket(ticketNo, isClinicAdded), ticketNo,
fontSize: SizeConfig.getWidthMultiplier() * 5, fontSize: shouldReduceSize(ticketNo) ? SizeConfig.getWidthMultiplier() * 2.5 : SizeConfig.getWidthMultiplier() * 5,
letterSpacing: -1, letterSpacing: -1,
fontHeight: 0.5, fontHeight: 0.5,
color: AppColors.greyTextColor, color: AppColors.greyTextColor,
@ -90,12 +80,12 @@ class QueueItemNormalCard extends StatelessWidget {
flex: 3, flex: 3,
child: Row( child: Row(
children: [ children: [
callTypeEnum.getIconByCallType(SizeConfig.getHeightMultiplier() * 0.35, qTypeEnum), callTypeEnum.getIconByCallType(SizeConfig.getHeightMultiplier() * 0.35),
], ],
), ),
), ),
Flexible( Flexible(
flex: 2, flex: 4,
child: Center( child: Center(
child: AppText( child: AppText(
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -118,7 +108,7 @@ class QueueItemNormalCard extends StatelessWidget {
padding: EdgeInsets.all(SizeConfig.getHeightMultiplier() * 0.05), padding: EdgeInsets.all(SizeConfig.getHeightMultiplier() * 0.05),
color: callTypeEnum.getColorByCallType(), color: callTypeEnum.getColorByCallType(),
child: engArabicTextWithSeparatorWidget( child: engArabicTextWithSeparatorWidget(
fontSize: qTypeEnum != QTypeEnum.appointment ? SizeConfig.getWidthMultiplier()! * 1.9 : SizeConfig.getWidthMultiplier()! * 1.8, fontSize: SizeConfig.getWidthMultiplier()! * 1.8,
englishText: roomNo.extractNumbersIfLong(), englishText: roomNo.extractNumbersIfLong(),
arabicText: roomTextAr, arabicText: roomTextAr,
color: AppColors.whiteColor, color: AppColors.whiteColor,

@ -69,7 +69,7 @@ class _MainQueueScreenState extends State<MainQueueScreen> {
// counterNo: 0, // counterNo: 0,
// roomText: '', // roomText: '',
// ); // );
log("screenConfigViewModel: ${screenConfigViewModel.currentScreenIP}");
if (screenConfigViewModel.currentQTypeEnum == QTypeEnum.general) { if (screenConfigViewModel.currentQTypeEnum == QTypeEnum.general) {
text = AppStrings.awaitingQueueNumberEng; text = AppStrings.awaitingQueueNumberEng;
} }
@ -192,7 +192,9 @@ log("screenConfigViewModel: ${screenConfigViewModel.currentScreenIP}");
// context.read<ScreenConfigViewModel>().createAutoTickets(numOfTicketsToCreate: 20); // context.read<ScreenConfigViewModel>().createAutoTickets(numOfTicketsToCreate: 20);
// context.read<QueuingViewModel>().testSpeech(); // context.read<QueuingViewModel>().testSpeech();
return RotatedBox( return RotatedBox(
quarterTurns: globalConfigurationsModel.isFromTakhasusiMain ? screenOrientationEnum.getTurnsByOrientationForOlderVersions() : screenOrientationEnum.getTurnsByOrientation(), quarterTurns: globalConfigurationsModel.isFromTakhasusiMain
? screenOrientationEnum.getTurnsByOrientationForOlderVersions()
: screenOrientationEnum.getTurnsByOrientation(),
child: AppScaffold( child: AppScaffold(
backgroundColor: AppColors.backgroundColor, backgroundColor: AppColors.backgroundColor,
appBar: const AppHeader(), appBar: const AppHeader(),
@ -204,4 +206,3 @@ log("screenConfigViewModel: ${screenConfigViewModel.currentScreenIP}");
); );
} }
} }

Loading…
Cancel
Save