Compare commits

...

17 Commits

Author SHA1 Message Date
Faiz Hashmi 16cc783ffd sent 8.8 version to live 2 months ago
Faiz Hashmi 17f79ed09d sent 8.7 version to live 3 months ago
Faiz Hashmi a459fb7cca updated logger_service.dart 3 months ago
Faiz Hashmi 4c54191a34 updated README.md and logger_service.dart 3 months ago
Faiz Hashmi 87880ce1a6 updated README.md 3 months ago
Faiz Hashmi 45b0523865 updated README.md 3 months ago
Faiz Hashmi 1a666a6bbf added README.md 3 months ago
Faiz Hashmi 000ac6f98f shifted logs to external (8.4) 3 months ago
Faiz Hashmi ab535f0d7f Resolved Next Prayer issue (8.3) 3 months ago
Faiz Hashmi 2288192975 improved Logging (8.2) 3 months ago
Faiz Hashmi 675441894a added foreground service 3 months ago
Faiz Hashmi 0779955ef0 pushed after improved logging 3 months ago
Faiz Hashmi 90d1a9329a Fixed Next Prayer Time 3 months ago
Faiz Hashmi adeb555805 added connectivity check 3 months ago
Faiz Hashmi 6660a43aa8 sent updated release to screens 4 months ago
Faiz Hashmi d9813561bc deployed merged with appointment module v6 4 months ago
FaizHashmiCS22 2e215f7a5e updated params 4 months ago

@ -1,53 +1,70 @@
# hmg_qline
# HMG_QLine
A new Flutter project.
HMG_QLine is a Flutter-based application designed for efficient patient management and queue handling in healthcare environments. It features multiple screens for patient display, robust logging, and background service integration to ensure reliability and uptime.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
for ticket generation: LAB_PatientCallNo_Get
{
"projectID": 15,
"patientID": 123456905,
"isVip": false,
"orderNo":"123"
"isVidaPlus": false,
"createdBy": 101,
"apiKey": "EE17D21C7943485D9780223CCE55DCE5"
}
## Features
- Patient queue management across all screens
- Foreground service to monitor app state and auto-reopen if terminated or backgrounded
- SignalR integration for real-time updates
- Persistent logging with automatic log file rotation
- Dynamic app versioning
- Modular architecture with ViewModels and Services
for signalR call: LAB_CallRequest_Update
{
"projectID": 15,
"roomNo": 11,
"isVidaPlus": false,
"editedBy": 101,
"apiKey": "EE17D21C7943485D9780223CCE55DCE5"
}
TicketCall:
{qType: 2, screenType: 1, connectionID: 10.20.10.30, data: {id: 85, patientID: 123456901,
laB_QGroupID: 1, queueNo: LAB-54, counterBatchNo: 1, calledBy: 11, calledOn: 2024-12-19T11:58:
36.573, servedOn: null, patientName: Kholoud Khaled Bawaz, mobileNo: 0596117128, patientEmail:
Test@unknown.com, preferredLang: 1, patientGender: 2, roomNo: null, isActive: true, createdBy: 101,
createdOn: 2024-12-19T11:58:13.74, editedBy: 101, editedOn: 2024-12-19T11:58:36.573}}
ConfigCall:
adb url:
C:\Users\Hashmi.Rahman\AppData\Local\Android\Sdk\platform-tools
## Getting Started
### Prerequisites
- Flutter SDK (>=3.0.0)
- Dart (>=2.17.0)
- Android Studio or Xcode for platform-specific builds
### Installation
1. Clone the repository:
```bash
git clone https://github.com/your-org/HMG_QLine.git
cd HMG_QLine
```
2. Install dependencies:
```bash
flutter pub get
```
3. Run the app:
```bash
flutter run
```
## Project Structure
- `lib/` - Main source code
- `api/` - API integration
- `config/` - Configuration files
- `constants/` - App-wide constants
- `models/` - Data models
- `repositories/` - Data repositories
- `services/` - Business logic and background services
- `utilities/` - Utility functions
- `view_models/` - State management
- `views/` - UI screens
- `assets/` - Fonts, images, icons, and tones
- `android/`, `ios/`, `macos/`, `linux/`, `windows/` - Platform-specific code
## Logging
- Logs are saved in the device's external storage under `/logs/`
- Log files are rotated every 48 hours
- Each log entry includes a timestamp, source, and type
## Foreground Service
- Monitors app lifecycle
- Automatically reopens or restarts the app if terminated or sent to background
- Checks app status every minute
## SignalR Integration
- Reconnects automatically after disconnection
- Ensures real-time updates for patient queues
## Debugging
- Debuggable builds can be enabled in `android/app/build.gradle` for troubleshooting
## Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
## License
This project is licensed under the MIT License.

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

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

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

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

Binary file not shown.

@ -3,9 +3,9 @@ import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:hmg_qline/services/logger_service.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/utilities/api_exception.dart';
typedef FactoryConstructor<U> = U Function(dynamic);
@ -14,14 +14,18 @@ abstract class ApiClient {
Future<U> postJsonForObject<T, U>(FactoryConstructor<U> factoryConstructor, String url, T jsonObject,
{String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0});
Future<Response> postJsonForResponse<T>(String url, T jsonObject, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0});
Future<http.Response> postJsonForResponse<T>(String url, T jsonObject, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0});
Future<U> getJsonForObject<T, U>(FactoryConstructor<U> factoryConstructor, String url, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0});
Future<Response> getJsonForResponse<T>(String url, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0});
Future<http.Response> getJsonForResponse<T>(String url, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0});
}
class ApiClientImp implements ApiClient {
LoggerService loggerService;
ApiClientImp({required this.loggerService});
@override
Future<U> postJsonForObject<T, U>(FactoryConstructor<U> factoryConstructor, String url, T jsonObject,
{String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
@ -45,7 +49,7 @@ class ApiClientImp implements ApiClient {
}
@override
Future<Response> postJsonForResponse<T>(String url, T jsonObject, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
Future<http.Response> postJsonForResponse<T>(String url, T jsonObject, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
String? requestBody;
if (jsonObject != null) {
requestBody = jsonEncode(jsonObject);
@ -59,7 +63,7 @@ class ApiClientImp implements ApiClient {
return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes);
}
Future<Response> _postForResponse(
Future<http.Response> _postForResponse(
String url,
requestBody, {
String? token,
@ -81,17 +85,17 @@ class ApiClientImp implements ApiClient {
var queryString = Uri(queryParameters: queryParameters).query;
url = '$url?$queryString';
}
Response response;
http.Response response;
response = await _post(Uri.parse(url), body: requestBody, headers: headers0).timeout(const Duration(seconds: 100));
if (!kReleaseMode) {
logger.d("------URL------");
logger.i(url);
logger.d("------Payload------");
logger.i(jsonDecode(requestBody));
logger.d("------Response------");
logger.i(jsonDecode(response.body));
loggerService.logInfo("------URL------");
loggerService.logInfo(url);
loggerService.logInfo("------Payload------");
loggerService.logInfo(jsonDecode(requestBody).toString());
loggerService.logInfo("------Response------");
loggerService.logInfo(jsonDecode(response.body).toString());
}
if (response.statusCode >= 200 && response.statusCode < 500) {
var jsonData = jsonDecode(response.body);
@ -120,7 +124,7 @@ class ApiClientImp implements ApiClient {
}
} on TimeoutException catch (e) {
throw APIException(APIException.timeout, arguments: e);
} on ClientException catch (e) {
} on http.ClientException catch (e) {
if (retryTimes > 0) {
log('will retry after 3 seconds...');
await Future.delayed(const Duration(seconds: 3));
@ -136,7 +140,7 @@ class ApiClientImp implements ApiClient {
bool _certificateCheck(X509Certificate cert, String host, int port) => true;
Future<T> _withClient<T>(Future<T> Function(Client) fn) async {
Future<T> _withClient<T>(Future<T> Function(http.Client) fn) async {
var httpClient = HttpClient()..badCertificateCallback = _certificateCheck;
var client = IOClient(httpClient);
try {
@ -146,7 +150,7 @@ class ApiClientImp implements ApiClient {
}
}
Future<Response> _post(url, {Map<String, String>? headers, body, Encoding? encoding}) => _withClient((client) => client.post(url, headers: headers, body: body, encoding: encoding));
Future<http.Response> _post(url, {Map<String, String>? headers, body, Encoding? encoding}) => _withClient((client) => client.post(url, headers: headers, body: body, encoding: encoding));
@override
Future<U> getJsonForObject<T, U>(FactoryConstructor<U> factoryConstructor, String url,
@ -166,7 +170,7 @@ class ApiClientImp implements ApiClient {
}
@override
Future<Response> getJsonForResponse<T>(String url, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
Future<http.Response> getJsonForResponse<T>(String url, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
if (headers == null) {
headers = {'Content-Type': 'application/json'};
} else {
@ -177,7 +181,7 @@ class ApiClientImp implements ApiClient {
bool isFirstCall = true;
Future<Response> _getForResponse(String url, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
Future<http.Response> _getForResponse(String url, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
try {
var headers0 = <String, String>{};
if (token != null) {
@ -193,13 +197,13 @@ class ApiClientImp implements ApiClient {
}
if (!kReleaseMode) {
logger.i(url);
logger.i("$queryParameters");
loggerService.logInfo(url);
loggerService.logInfo("$queryParameters");
}
var response = await _get(Uri.parse(url), headers: headers0).timeout(const Duration(seconds: 60));
if (!kReleaseMode) {
logger.i(jsonDecode(response.body));
loggerService.logInfo(jsonDecode(response.body));
}
if (response.statusCode >= 200 && response.statusCode < 500) {
var jsonData = jsonDecode(response.body);
@ -229,7 +233,7 @@ class ApiClientImp implements ApiClient {
}
} on TimeoutException catch (e) {
throw APIException(APIException.timeout, arguments: e);
} on ClientException catch (e) {
} on http.ClientException catch (e) {
if (retryTimes > 0) {
await Future.delayed(const Duration(seconds: 3));
return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1);
@ -239,5 +243,5 @@ class ApiClientImp implements ApiClient {
}
}
Future<Response> _get(url, {Map<String, String>? headers}) => _withClient((client) => client.get(url, headers: headers));
Future<http.Response> _get(url, {Map<String, String>? headers}) => _withClient((client) => client.get(url, headers: headers));
}

@ -2,6 +2,7 @@
// import 'package:flutter/material.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/services.dart';
import 'package:flutter_tts/flutter_tts.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_qline/api/api_client.dart';
@ -10,28 +11,44 @@ import 'package:hmg_qline/repositories/signalR_repo.dart';
import 'package:hmg_qline/services/audio_service.dart';
import 'package:hmg_qline/services/cache_service.dart';
import 'package:hmg_qline/services/connectivity_service.dart';
import 'package:hmg_qline/services/logger_service.dart';
import 'package:hmg_qline/services/text_to_speech_service.dart';
import 'package:hmg_qline/utilities/native_method_handler.dart';
import 'package:hmg_qline/view_models/queuing_view_model.dart';
import 'package:hmg_qline/view_models/screen_config_view_model.dart';
import 'package:just_audio/just_audio.dart';
import 'package:logger/logger.dart';
import 'package:shared_preferences/shared_preferences.dart';
final getIt = GetIt.instance;
class AppDependencies {
static Future<void> addDependencies() async {
final logger = Logger(
printer: PrettyPrinter(
printEmojis: false,
colors: true,
dateTimeFormat: DateTimeFormat.none,
),
);
//api client
getIt.registerSingleton<ApiClient>(ApiClientImp());
getIt.registerSingleton<LoggerService>(LoggerServiceImp(logger: logger));
getIt.registerSingleton<ApiClient>(ApiClientImp(loggerService: getIt.get<LoggerService>()));
//repos
getIt.registerSingleton<SignalrRepo>(SignalrRepoImp(loggerService: getIt.get<LoggerService>()));
getIt.registerSingleton<ScreenDetailsRepo>(ScreenDetailsRepoImp(apiClientInstance: getIt.get<ApiClient>(), loggerService: getIt.get<LoggerService>()));
//repos
getIt.registerSingleton<SignalrRepo>(SignalrRepoImp());
getIt.registerSingleton<ScreenDetailsRepo>(ScreenDetailsRepoImp(apiClientInstance: getIt.get<ApiClient>()));
getIt.registerSingleton<NativeMethodChannelService>(NativeMethodChannelServiceImp(loggerService: getIt.get<LoggerService>(), platform: const MethodChannel('com.example.hmg_qline/foreground')));
//ThirdPartyServices
getIt.registerSingleton<ConnectivityService>(ConnectivityServiceImp(connectivityInstance: Connectivity()));
getIt.registerSingleton<CacheService>(CacheServiceImp(preferencesInstance: await SharedPreferences.getInstance()));
getIt.registerSingleton<AudioService>(AudioServiceImp(audioPlayerInstance: AudioPlayer()));
getIt.registerSingleton<TextToSpeechService>(TextToSpeechServiceImp(textToSpeechInstance: FlutterTts()));
getIt.registerSingleton<TextToSpeechService>(TextToSpeechServiceImp(textToSpeechInstance: FlutterTts(), loggerService: getIt.get<LoggerService>()));
//ViewModels
@ -40,11 +57,14 @@ class AppDependencies {
screenDetailsRepo: getIt.get<ScreenDetailsRepo>(),
cacheService: getIt.get<CacheService>(),
connectivityService: getIt.get<ConnectivityService>(),
loggerService: getIt.get<LoggerService>(),
nativeMethodChannelService: getIt.get<NativeMethodChannelService>(),
),
);
getIt.registerSingleton<QueuingViewModel>(
QueuingViewModel(
loggerService: getIt.get<LoggerService>(),
screenDetailsRepo: getIt.get<ScreenDetailsRepo>(),
cacheService: getIt.get<CacheService>(),
textToSpeechService: getIt.get<TextToSpeechService>(),

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
import 'package:hmg_qline/models/ticket_model.dart';
import 'package:hmg_qline/utilities/enums.dart';
bool useTestIP = false;
Logger logger = Logger(printer: PrettyPrinter(printEmojis: false, colors: true, dateTimeFormat: DateTimeFormat.none));
// app globals
@ -20,7 +20,12 @@ class AppStrings {
static String awaitingQueueNumberAr = "في انتظار رقم قائمة الانتظار";
static String counterNo = "Counter Number: ";
static String awaitingArrivalAr = "في انتظار وصول المرضى";
static String configurationIssueContactAdmin = "There is something wrong with configuration. Please contact Admin.";
static String configurationIssueContactAdmin = "There is something wrong with configuration. Please contact IT Support.";
static String dataLogsFileName = "data_logs.txt";
static String errorLogsFileName = "error_logs.txt";
static String openAppNativeFunctionName = "reopenApp";
}
class AppColors {
@ -33,7 +38,13 @@ class AppColors {
static Color whiteColor = Colors.white;
static Color yellowColor = const Color(0xFFC99609);
static Color brownColor = const Color(0xFF460707);
static Color blackColor = Colors.black54;
static Color blackColor = Colors.black;
static Color nebulizationColor = const Color(0xFF3C86D0);
static Color vitalSignColor = const Color(0xFFD02127);
static Color doctorColor = const Color(0xFF52964F);
static Color vaccinationColor = const Color(0xFFC99609);
static Color procedureColor = const Color(0xFF460707);
//Decoration
@ -95,10 +106,17 @@ class AppConstants {
static String apiKey = 'EE17D21C7943485D9780223CCE55DCE5';
static String testIP = '12.4.5.1'; // projectID.QlineType.ScreenType.AnyNumber (1 to 10)
static int thresholdForListUI = 3;
static double currentBuildVersion = 8.8;
static double clearLogsHoursThreshold = 48;
}
class ApiConstants {
static String baseUrl = 'https://ms.hmg.com/nscapi2';
static String baseUrlLive = 'https://qline.hmg.com'; // LIVE
static String baseUrlUat = 'https://ms.hmg.com/nscapi'; // UAT
static String baseUrlDev = 'https://ms.hmg.com/nscapi2'; // DEV
static String baseUrl = baseUrlLive;
static String baseUrlHub = '$baseUrl/PatientCallingHub';
static String baseUrlApi = '$baseUrl/api';
static String baseUrlApiGen = '$baseUrl/api/Gen';
@ -110,12 +128,15 @@ class ApiConstants {
static String rssFeedGet = '$baseUrlApiPatientCall/RssFeed_Get';
static String getWeatherForecastBy5Days = "$baseUrlApiPatientCall/WeatherForecast_GetBy5Days";
static String prayerTimeToday = "$baseUrlApiPatientCall/PrayerTime_Today";
static String ticketCallRequestUpdate = "$baseUrlApiPatientCall/CallRequest_QueueUpdate";
//Generic
static String createTicketForKiosk = '$baseUrlApiGen/GEN_PatientCallNo_Get';
static String ticketAcknowledgementInsert = '$baseUrlApi/Common/TicketQueueAck_Insert';
// AppointmentSpecific
static String ticketCallRequestUpdate = "$baseUrlApiPatientCall/CallRequest_QueueUpdate";
// Signal R Constants
static String sendQLinePatientCall = "SendQLinePatientCall";
@ -124,6 +145,7 @@ class ApiConstants {
class CacheConstants {
static String lastTimeUpdated = "lastTimeUpdated";
static String logLastCleared = "log_last_cleared";
}
// calling for pharmacy
@ -148,3 +170,57 @@ class CacheConstants {
// "createdBy": 101,
// "apiKey": "EE17D21C7943485D9780223CCE55DCE5"
// }
class MockJsonRepo {
static TicketData ticket = TicketData(
id: 189805,
patientID: 4292695,
laBQGroupID: null,
queueNo: 'W-T-4',
counterBatchNo: null,
calledBy: null,
calledOn: null,
servedOn: null,
patientName: null,
mobileNo: '0598544522',
patientEmail: 'munira.ali@hotmail.com',
preferredLang: 2,
voiceLanguageEnum: LanguageEnum.english,
ticketNoText: 'Ticket Number',
postVoiceText: 'Call for Vital Signs',
patientGender: 2,
roomNo: 'D 12',
isActive: null,
createdBy: null,
editedBy: null,
editedOn: DateTime.parse('2025-08-18 15:09:03.633'),
createdOn: DateTime.parse('2025-08-18 15:06:07.363'),
doctorNameN: null,
callTypeEnum: CallTypeEnum.doctor,
queueNoM: 'W-T-4',
callNoStr: 'W_T-4',
isQueue: false,
isToneReq: false,
isVoiceReq: false,
orientationType: 0,
isTurnOn: false,
concurrentCallDelaySec: 0,
crTypeAckIP: null,
voiceLanguageText: 'English',
vitalSignText: 'علامة حيوية',
doctorText: 'الطبيب',
procedureText: 'الاجراءات',
vaccinationText: 'المطاعيم',
nebulizationText: 'التنفس',
callForVitalSignText: 'التوجه الى غرفة قياس العلامات الحيوية',
callForDoctorText: 'التوجه الى الطبيب',
callForProcedureText: 'التوجه الى غرفة الإجراءات',
callForVaccinationText: 'التوجه الى غرفة المطاعيم',
callForNebulizationText: 'التوجه الى غرفة التنفس',
roomText: 'غرفة',
queueNoText: 'رقم الانتظار',
callForText: 'التوجه الى',
);
}
// RAW DATA:

@ -12,7 +12,7 @@ import 'package:wakelock_plus/wakelock_plus.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await AppDependencies.addDependencies();
WakelockPlus.enable();
await WakelockPlus.enable();
runApp(const MyApp());
}
@ -25,26 +25,29 @@ class MyApp extends StatelessWidget {
builder: (context, constraints) {
return OrientationBuilder(builder: (context, orientation) {
SizeConfig().init(constraints, orientation);
// SystemChrome.setPreferredOrientations([DeviceOrientation.portraitDown]);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
return MultiProvider(
providers: [
ChangeNotifierProvider<ScreenConfigViewModel>(create: (context) => getIt.get<ScreenConfigViewModel>()),
ChangeNotifierProvider<QueuingViewModel>(create: (context) => getIt.get<QueuingViewModel>()),
],
child: MaterialApp(
showSemanticsDebugger: false,
title: AppStrings.appName,
theme: ThemeData(
fontFamily: AppStrings.fontNamePoppins,
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.grey).copyWith(
surface: const Color.fromRGBO(255, 255, 255, 1),
),
providers: [
ChangeNotifierProvider<ScreenConfigViewModel>(create: (context) => getIt.get<ScreenConfigViewModel>()),
ChangeNotifierProvider<QueuingViewModel>(create: (context) => getIt.get<QueuingViewModel>()),
],
child: MaterialApp(
showSemanticsDebugger: false,
title: AppStrings.appName,
theme: ThemeData(
fontFamily: AppStrings.fontNamePoppins,
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.grey).copyWith(
surface: const Color.fromRGBO(255, 255, 255, 1),
),
initialRoute: AppRoutes.initialRoute,
routes: AppRoutes.routes,
debugShowCheckedModeBanner: false,
));
),
initialRoute: AppRoutes.initialRoute,
routes: AppRoutes.routes,
debugShowCheckedModeBanner: false,
),
);
});
},
);

@ -24,4 +24,9 @@ class GenericRespModel {
message: json["message"],
);
}
@override
String toString() {
return 'GenericRespModel{data: $data, messageStatus: $messageStatus, totalItemsCount: $totalItemsCount, message: $message}';
}
}

@ -55,7 +55,7 @@ class GlobalConfigurationsModel {
bool isWeatherReq = false;
bool isPrayerTimeReq = false;
bool isRssFeedReq = false;
QTypeEnum qTypeEnum = QTypeEnum.lab;
QTypeEnum qTypeEnum = QTypeEnum.appointment;
ScreenTypeEnum screenTypeEnum = ScreenTypeEnum.waitingAreaScreen;
int? projectID;
double? projectLatitude;
@ -64,6 +64,17 @@ class GlobalConfigurationsModel {
List<KioskQueueModel>? kioskQueueList;
List<KioskLanguageConfigModel>? kioskLanguageConfigList;
String vitalSignText = "Vital Sign";
String doctorText = "Doctor";
String procedureText = "Procedure";
String vaccinationText = "Vaccination";
String nebulizationText = "Nebulization";
String callForVitalSignText = "Call for Vital Sign";
String callForDoctorText = "Call for Doctor";
String callForProcedureText = "Call for Procedure";
String callForVaccinationText = "Call for Vaccination";
String callForNebulizationText = "Call for Nebulization";
GlobalConfigurationsModel({
this.id,
this.configType,
@ -115,7 +126,7 @@ class GlobalConfigurationsModel {
this.isWeatherReq = false,
this.isPrayerTimeReq = false,
this.isRssFeedReq = false,
this.qTypeEnum = QTypeEnum.lab,
this.qTypeEnum = QTypeEnum.appointment,
this.screenTypeEnum = ScreenTypeEnum.waitingAreaScreen,
this.projectID,
this.projectLatitude,
@ -123,9 +134,19 @@ class GlobalConfigurationsModel {
this.cityKey,
this.kioskQueueList,
this.kioskLanguageConfigList,
this.vitalSignText = "Vital Sign",
this.doctorText = "Doctor",
this.procedureText = "Procedure",
this.vaccinationText = "Vaccination",
this.nebulizationText = "Nebulization",
this.callForVitalSignText = "Call for Vital Sign",
this.callForDoctorText = "Call for Doctor",
this.callForProcedureText = "Call for Procedure",
this.callForVaccinationText = "Call for Vaccination",
this.callForNebulizationText = "Call for Nebulization",
});
GlobalConfigurationsModel.fromJson(Map<String, dynamic> json) {
GlobalConfigurationsModel.fromJson({required Map<String, dynamic> json, int qType = 1, int screenType = 1}) {
id = json['id'];
configType = json['configType'];
description = json['description'];
@ -149,7 +170,7 @@ class GlobalConfigurationsModel {
counterText = json['counterText'];
roomText = json['roomText'];
roomNo = json['roomNo'];
isRoomNoRequired = json['isRoomNoRequired'] ?? true;
isRoomNoRequired = json['isRoomNoReq'] ?? true;
queueNoText = json['queueNoText'];
callForText = json['callForText'];
currentServeText = json['currentServeText'];
@ -169,8 +190,6 @@ class GlobalConfigurationsModel {
editedOn = json['editedOn'];
isToneReq = json['isToneReq'] ?? false;
isVoiceReq = json['isVoiceReq'] ?? false;
// isToneReq = true;
// isVoiceReq = true;
orientationTypeEnum = ((json['orientationType'] ?? 1) as int).toScreenOrientationEnum();
isTurnOn = json['isTurnOn'];
waitingAreaType = json['waitingAreaType'];
@ -178,12 +197,12 @@ class GlobalConfigurationsModel {
isWeatherReq = json['isWeatherReq'] ?? false;
isPrayerTimeReq = json['isPrayerTimeReq'] ?? false;
isRssFeedReq = json['isRssFeedReq'] ?? false;
qTypeEnum = (json['qType'] as int).toQTypeEnum();
screenTypeEnum = (json['screenType'] as int).toScreenTypeEnum();
qTypeEnum = ((json['qType'] ?? qType) as int).toQTypeEnum();
screenTypeEnum = ((json['screenType'] ?? screenType) as int).toScreenTypeEnum();
projectID = json['projectID'];
projectLatitude = json['projectLatitude'] == 0 ? 0.0 : json['projectLatitude'];
projectLongitude = json['projectLongitude'] == 0 ? 0.0 : json['projectLongitude'];
cityKey = json['cityKey'];
cityKey = json['cityKey'] ?? 0;
if (json['kioskQueue'] != null) {
kioskQueueList = List<KioskQueueModel>.from(json['kioskQueue'].map((kioskQueueJson) => KioskQueueModel.fromJson(kioskQueueJson)));
} else {
@ -194,6 +213,21 @@ class GlobalConfigurationsModel {
} else {
kioskLanguageConfigList = [];
}
vitalSignText = json['vitalSignText'];
doctorText = json['doctorText'];
procedureText = json['procedureText'];
vaccinationText = json['vaccinationText'];
nebulizationText = json['nebulizationText'];
callForVitalSignText = json['callForVitalSignText'];
callForDoctorText = json['callForDoctorText'];
callForProcedureText = json['callForProcedureText'];
callForVaccinationText = json['callForVaccinationText'];
callForNebulizationText = json['callForNebulizationText'];
}
@override
String toString() {
return 'GlobalConfigurationsModel{id: $id, 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: $roomText, roomNo: $roomNo, isRoomNoRequired: $isRoomNoRequired, counterText: $counterText, queueNoText: $queueNoText, callForText: $callForText, currentServeText: $currentServeText, maxText: $maxText, minText: $minText, nextPrayerText: $nextPrayerText, weatherText: $weatherText, fajarText: $fajarText, dhuhrText: $dhuhrText, asarText: $asarText, maghribText: $maghribText, ishaText: $ishaText, 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: $vitalSignText, doctorText: $doctorText, procedureText: $procedureText, vaccinationText: $vaccinationText, nebulizationText: $nebulizationText, callForVitalSignText: $callForVitalSignText, callForDoctorText: $callForDoctorText, callForProcedureText: $callForProcedureText, callForVaccinationText: $callForVaccinationText, callForNebulizationText: $callForNebulizationText}';
}
}

@ -8,7 +8,6 @@ class KioskQueueModel {
String? queueNameN;
int? kioskID;
bool? isPharmacyQueue;
String? kioskName;
String? kioskNameN;
String? ipAddress;

@ -19,7 +19,7 @@ class PrayersWidgetModel {
PrayersWidgetModel.fromJson(Map<String, dynamic> json) {
fajr = dateTimeConversion(json['dateFor'], json['fajr']);
sunrise = json['sunrise'];
dhuhr = dateTimeConversion(json['dateFor'], getDhuhrData(json), isForDhuhr: true);
dhuhr = dateTimeConversion(json['dateFor'], json['dhuhr']);
asr = dateTimeConversion(json['dateFor'], json['asr']);
sunset = json['sunset'];
maghrib = dateTimeConversion(json['dateFor'], json['maghrib']);
@ -31,15 +31,12 @@ class PrayersWidgetModel {
dateFor = json['dateFor'];
}
static String getDhuhrData(dynamic json) {
return (json['dhuhr'] as String).contains('PM') ? json['dhuhr'] : json['dhuhr'] + ' PM';
}
static dateTimeConversion(String date, String time, {bool isForDhuhr = false}) {
if (isForDhuhr) {
return DateFormat('DD MMM yyyy hh:mm a').parse("$date $time").millisecondsSinceEpoch;
static dateTimeConversion(String date, String time) {
try {
return DateFormat('dd MMM yyyy HH:mm').parse("$date $time").millisecondsSinceEpoch;
} catch (e) {
return null;
}
return DateFormat('DD MMM yyyy hh:mm').parse("$date $time").millisecondsSinceEpoch;
}
@override

@ -1,50 +1,50 @@
class RoomTicketModel {
int? rowID;
int? id;
int? floorID;
int? buildingID;
int? projectID;
String? roomName;
String? roomNameN;
int? roomNo;
String? roomQScreenIP;
bool? isActive;
int? createdBy;
String? createdOn;
String? editedBy;
String? editedOn;
RoomTicketModel({
this.rowID,
this.id,
this.floorID,
this.buildingID,
this.projectID,
this.roomName,
this.roomNameN,
this.roomNo,
this.roomQScreenIP,
this.isActive,
this.createdBy,
this.createdOn,
this.editedBy,
this.editedOn,
});
RoomTicketModel.fromJson(Map<String, dynamic> json) {
rowID = json['rowID'];
id = json['id'];
floorID = json['floorID'];
buildingID = json['buildingID'];
projectID = json['projectID'];
roomName = json['roomName'];
roomNameN = json['roomNameN'];
roomNo = json['roomNo'];
roomQScreenIP = json['roomQScreenIP'];
isActive = json['isActive'];
createdBy = json['createdBy'];
createdOn = json['createdOn'];
editedBy = json['editedBy'];
editedOn = json['editedOn'];
}
}
// class RoomTicketModel {
// int? rowID;
// int? id;
// int? floorID;
// int? buildingID;
// int? projectID;
// String? roomName;
// String? roomNameN;
// int? roomNo;
// String? roomQScreenIP;
// bool? isActive;
// int? createdBy;
// String? createdOn;
// String? editedBy;
// String? editedOn;
//
// RoomTicketModel({
// this.rowID,
// this.id,
// this.floorID,
// this.buildingID,
// this.projectID,
// this.roomName,
// this.roomNameN,
// this.roomNo,
// this.roomQScreenIP,
// this.isActive,
// this.createdBy,
// this.createdOn,
// this.editedBy,
// this.editedOn,
// });
//
// RoomTicketModel.fromJson(Map<String, dynamic> json) {
// rowID = json['rowID'];
// id = json['id'];
// floorID = json['floorID'];
// buildingID = json['buildingID'];
// projectID = json['projectID'];
// roomName = json['roomName'];
// roomNameN = json['roomNameN'];
// roomNo = json['roomNo'];
// roomQScreenIP = json['roomQScreenIP'];
// isActive = json['isActive'];
// createdBy = json['createdBy'];
// createdOn = json['createdOn'];
// editedBy = json['editedBy'];
// editedOn = json['editedOn'];
// }
// }

@ -12,4 +12,9 @@ class RssFeedModel {
data['rssFeed'] = rssFeed;
return data;
}
@override
String toString() {
return 'RssFeedModel{rssFeed: $rssFeed}';
}
}

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:hmg_qline/utilities/date_utils.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/utilities/extensions.dart';
@ -49,6 +47,34 @@ class TicketData {
DateTime? editedOn;
DateTime? createdOn;
// New fields
String? doctorNameN;
CallTypeEnum callTypeEnum = CallTypeEnum.vitalSign;
String? queueNoM;
String? callNoStr;
bool? isQueue;
bool? isToneReq;
bool? isVoiceReq;
int? orientationType;
bool? isTurnOn;
int? concurrentCallDelaySec;
String? crTypeAckIP;
int voiceLanguage = 1;
String voiceLanguageText = "English";
String vitalSignText = "Vital Sign";
String doctorText = "Doctor";
String procedureText = "Procedure";
String vaccinationText = "Vaccination";
String nebulizationText = "Nebulization";
String callForVitalSignText = "Call for Vital Sign";
String callForDoctorText = "Call for Doctor";
String callForProcedureText = "Call for Procedure";
String callForVaccinationText = "Call for Vaccination";
String callForNebulizationText = "Call for Nebulization";
String roomText = "Room";
String queueNoText = "Counter";
String callForText = "Call For";
TicketData({
this.id,
this.patientID,
@ -72,13 +98,38 @@ class TicketData {
this.createdOn,
this.editedBy,
this.editedOn,
this.doctorNameN,
this.callTypeEnum = CallTypeEnum.vitalSign,
this.queueNoM,
this.callNoStr,
this.isQueue,
this.isToneReq,
this.isVoiceReq,
this.orientationType,
this.isTurnOn,
this.concurrentCallDelaySec,
this.crTypeAckIP,
this.voiceLanguageText = "English",
this.vitalSignText = "Vital Sign",
this.doctorText = "Doctor",
this.procedureText = "Procedure",
this.vaccinationText = "Vaccination",
this.nebulizationText = "Nebulization",
this.callForVitalSignText = "Call for Vital Sign",
this.callForDoctorText = "Call for Doctor",
this.callForProcedureText = "Call for Procedure",
this.callForVaccinationText = "Call for Vaccination",
this.callForNebulizationText = "Call for Nebulization",
this.roomText = "Room",
this.queueNoText = "Counter",
this.callForText = "Call For",
});
TicketData.fromJson(Map<String, dynamic> json, {QTypeEnum? qTypeEnum}) {
id = json['id'];
patientID = json['patientID'];
laBQGroupID = json['laB_QGroupID'];
queueNo = json['queueNo'];
queueNo = json['queueNoM'];
counterBatchNo = json['counterBatchNo'];
calledBy = json['calledBy'];
calledOn = json['calledOn'];
@ -86,19 +137,51 @@ class TicketData {
patientName = json['patientName'];
mobileNo = json['mobileNo'];
patientEmail = json['patientEmail'];
preferredLang = (json['preferredLang'] != null && json['preferredLang'].trim() != "") ? int.parse(json['preferredLang']) : 1;
voiceLanguageEnum = (json['preferredLang'] != null && json['preferredLang'].trim() != "") ? (int.parse(json['preferredLang'])).toLanguageEnum() : LanguageEnum.english;
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;
ticketNoText = json['ticketNoText'] ?? "Ticket Number";
postVoiceText = json['pleaseVisitCounterText'] ?? "Please Visit Counter";
patientGender = json['patientGender'] ?? 1;
roomNo = json['roomNo'].toString();
roomNo = json['roomNo']?.toString();
if (qTypeEnum != null && qTypeEnum == QTypeEnum.general) {
roomNo = json['counterNo'].toString();
roomNo = json['counterNo']?.toString();
}
isActive = json['isActive'];
createdBy = json['createdBy'];
editedBy = json['editedBy'];
editedOn = json['editedOn'] != null ? (json['editedOn'] as String).toDateTime() : DateTime.now();
createdOn = json['createdOn'] != null ? (json['createdOn'] as String).toDateTime() : DateTime.now();
doctorNameN = json['doctorNameN'];
callTypeEnum = ((json['callType'] ?? 1) as int).toCallTypeEnum();
queueNoM = json['queueNoM'];
callNoStr = json['callNoStr'];
isQueue = json['isQueue'];
isToneReq = json['isToneReq'];
isVoiceReq = json['isVoiceReq'];
orientationType = json['orientationType'];
isTurnOn = json['isTurnOn'];
concurrentCallDelaySec = json['concurrentCallDelaySec'];
crTypeAckIP = json['crTypeAckIP'];
voiceLanguage = json['voiceLanguage'] ?? 1;
voiceLanguageText = json['voiceLanguageText'] ?? "English";
vitalSignText = json['vitalSignText'];
doctorText = json['doctorText'];
procedureText = json['procedureText'];
vaccinationText = json['vaccinationText'];
nebulizationText = json['nebulizationText'];
callForVitalSignText = json['callForVitalSignText'];
callForDoctorText = json['callForDoctorText'];
callForProcedureText = json['callForProcedureText'];
callForVaccinationText = json['callForVaccinationText'];
callForNebulizationText = json['callForNebulizationText'];
roomText = json['roomText'];
queueNoText = json['queueNoText'];
callForText = json['callForText'];
}
@override
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}';
}
}

@ -62,4 +62,9 @@ class WeathersWidgetModel {
return AppAssets.weatherIcon;
}
}
@override
String toString() {
return 'WeathersWidgetModel{id: $id, headline: $headline, maxTemp: $maxTemp, minTemp: $minTemp, iconPhrase: $iconPhrase, forecastDate: $forecastDate, cityID: $cityID, forecastDay: $forecastDay, createDateTime: $createDateTime, windSpeed: $windSpeed, windDirection: $windDirection, windDegrees: $windDegrees, weatherIconPath: $weatherIconPath}';
}
}

@ -1,53 +1,53 @@
class WidgetsConfigModel {
int? waitingAreaID;
String? waitingAreaName;
int? projectID;
double? projectLatitude;
double? projectLongitude;
int? cityKey;
bool isWeatherReq = false;
bool isPrayerTimeReq = false;
bool isRssFeedReq = false;
WidgetsConfigModel({
this.waitingAreaID,
this.waitingAreaName,
this.isWeatherReq = false,
this.isPrayerTimeReq = false,
this.isRssFeedReq = false,
this.projectID,
this.projectLatitude,
this.projectLongitude,
this.cityKey,
});
WidgetsConfigModel.fromJson(Map<String, dynamic> json) {
waitingAreaID = json['waitingAreaID'];
waitingAreaName = json['waitingAreaName'];
isRssFeedReq = json['isRssFeedReq'] ?? false;
isWeatherReq = json['isWeatherReq'] ?? false;
isPrayerTimeReq = json['isPrayerTimeReq'] ?? false;
projectID = json['projectID'];
projectLatitude = json['projectLatitude'];
projectLongitude = json['projectLongitude'];
cityKey = json['cityKey'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['waitingAreaID'] = waitingAreaID;
data['waitingAreaName'] = waitingAreaName;
data['isWeatherReq'] = isWeatherReq;
data['isPrayerTimeReq'] = isPrayerTimeReq;
data['projectID'] = projectID;
data['projectLatitude'] = projectLatitude;
data['projectLongitude'] = projectLongitude;
data['cityKey'] = cityKey;
return data;
}
@override
String toString() {
return 'WidgetsConfigModel{waitingAreaID: $waitingAreaID, waitingAreaName: $waitingAreaName, isWeatherReq: $isWeatherReq, isPrayerTimeReq: $isPrayerTimeReq, projectLatitude: $projectLatitude,projectLongitude: $projectLongitude, cityKey: $cityKey}';
}
}
// class WidgetsConfigModel {
// int? waitingAreaID;
// String? waitingAreaName;
// int? projectID;
// double? projectLatitude;
// double? projectLongitude;
// int? cityKey;
// bool isWeatherReq = false;
// bool isPrayerTimeReq = false;
// bool isRssFeedReq = false;
//
// WidgetsConfigModel({
// this.waitingAreaID,
// this.waitingAreaName,
// this.isWeatherReq = false,
// this.isPrayerTimeReq = false,
// this.isRssFeedReq = false,
// this.projectID,
// this.projectLatitude,
// this.projectLongitude,
// this.cityKey,
// });
//
// WidgetsConfigModel.fromJson(Map<String, dynamic> json) {
// waitingAreaID = json['waitingAreaID'];
// waitingAreaName = json['waitingAreaName'];
// isRssFeedReq = json['isRssFeedReq'] ?? false;
// isWeatherReq = json['isWeatherReq'] ?? false;
// isPrayerTimeReq = json['isPrayerTimeReq'] ?? false;
// projectID = json['projectID'];
// projectLatitude = json['projectLatitude'];
// projectLongitude = json['projectLongitude'];
// cityKey = json['cityKey'];
// }
//
// Map<String, dynamic> toJson() {
// final Map<String, dynamic> data = <String, dynamic>{};
// data['waitingAreaID'] = waitingAreaID;
// data['waitingAreaName'] = waitingAreaName;
// data['isWeatherReq'] = isWeatherReq;
// data['isPrayerTimeReq'] = isPrayerTimeReq;
// data['projectID'] = projectID;
// data['projectLatitude'] = projectLatitude;
// data['projectLongitude'] = projectLongitude;
// data['cityKey'] = cityKey;
// return data;
// }
//
// @override
// String toString() {
// return 'WidgetsConfigModel{waitingAreaID: $waitingAreaID, waitingAreaName: $waitingAreaName, isWeatherReq: $isWeatherReq, isPrayerTimeReq: $isPrayerTimeReq, projectLatitude: $projectLatitude,projectLongitude: $projectLongitude, cityKey: $cityKey}';
// }
// }

@ -6,7 +6,7 @@ import 'package:hmg_qline/models/kiosk_ticket_model.dart';
import 'package:hmg_qline/models/prayers_widget_model.dart';
import 'package:hmg_qline/models/rss_feed_model.dart';
import 'package:hmg_qline/models/weathers_widget_model.dart';
import 'package:hmg_qline/models/widgets_config_model.dart';
import 'package:hmg_qline/services/logger_service.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/utilities/extensions.dart';
import 'package:hmg_qline/views/view_helpers/info_components.dart';
@ -18,21 +18,22 @@ abstract class ScreenDetailsRepo {
Future<GenericRespModel?> createTicketFromKiosk({required int projectId, required int queueId, int patientId = 0});
Future<WidgetsConfigModel?> getScreenConfigurationsByIP({required String ipAddress});
Future<WeathersWidgetModel?> getWeatherDetailsByCity({required String cityId});
Future<PrayersWidgetModel?> getPrayerDetailsByLatLong({required double latitude, required double longitude});
Future<RssFeedModel?> getRssFeedDetailsByLanguageID({required int languageId});
Future<GenericRespModel?> acknowledgeTicket({required String ipAddress, required String ticketQueueID, required QTypeEnum qTypeEnum});
Future<GenericRespModel?> acknowledgeTicket({required String ipAddress, required int ticketQueueID, required QTypeEnum qTypeEnum});
Future<GenericRespModel?> acknowledgeTicketForAppointment({required int ticketId, required String ipAddress, required CallTypeEnum callTypeEnum});
}
class ScreenDetailsRepoImp implements ScreenDetailsRepo {
ApiClient apiClientInstance;
LoggerService loggerService;
ScreenDetailsRepoImp({required this.apiClientInstance});
ScreenDetailsRepoImp({required this.apiClientInstance, required this.loggerService});
@override
Future<GlobalConfigurationsModel?> getGlobalScreenConfigurations({required String ipAddress}) async {
@ -41,19 +42,21 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo {
"ipAddress": ipAddress.toString(),
"apiKey": AppConstants.apiKey.toString(),
};
GenericRespModel adsGenericModel = await apiClientInstance.postJsonForObject(
GenericRespModel genericModel = await apiClientInstance.postJsonForObject(
(json) => GenericRespModel.fromJson(json),
ApiConstants.commonConfigGet,
params,
);
List<GlobalConfigurationsModel> globalConfigurationsModel = List.generate(adsGenericModel.data.length, (index) => GlobalConfigurationsModel.fromJson(adsGenericModel.data[index]));
List<GlobalConfigurationsModel> globalConfigurationsModel = List.generate(genericModel.data.length, (index) => GlobalConfigurationsModel.fromJson(json: genericModel.data[index]));
if (globalConfigurationsModel.isNotEmpty) {
loggerService.logToFile(message: globalConfigurationsModel.toString(), type: LogTypeEnum.data, source: "getGlobalScreenConfigurations-> screen_details_repo.dart");
return globalConfigurationsModel.first;
}
return null;
} catch (e) {
logger.e(e.toString());
loggerService.logError(e.toString());
loggerService.logToFile(message: e.toString(), source: "getGlobalScreenConfigurations-> screen_details_repo.dart", type: LogTypeEnum.error);
InfoComponents.showToast(e.toString());
return null;
}
@ -71,14 +74,15 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo {
"createdBy": "101",
"apiKey": AppConstants.apiKey,
};
GenericRespModel adsGenericModel = await apiClientInstance.postJsonForObject(
GenericRespModel genericModel = await apiClientInstance.postJsonForObject(
(json) => GenericRespModel.fromJson(json),
ApiConstants.createTicket,
params,
);
return adsGenericModel;
return genericModel;
} catch (e) {
logger.e(e.toString());
loggerService.logError(e.toString());
loggerService.logToFile(message: e.toString(), source: "createTestTickets-> screen_details_repo.dart", type: LogTypeEnum.error);
InfoComponents.showToast(e.toString());
return null;
}
@ -102,30 +106,12 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo {
);
genericRespModel.data = KioskPatientTicket.fromJson(genericRespModel.data);
return genericRespModel;
} catch (e) {
logger.e(e.toString());
InfoComponents.showToast(e.toString());
return null;
}
}
loggerService.logToFile(message: genericRespModel.toString(), source: "createTicketFromKiosk-> screen_details_repo.dart", type: LogTypeEnum.data);
@override
Future<WidgetsConfigModel?> getScreenConfigurationsByIP({required String ipAddress}) async {
try {
final body = {
"ipAddress": ipAddress.toString(),
};
GenericRespModel genericRespModel = await apiClientInstance.postJsonForObject(
(json) => GenericRespModel.fromJson(json),
ApiConstants.waitingAreaScreenConfigGet,
body,
);
WidgetsConfigModel widgetsConfigModel = WidgetsConfigModel.fromJson(genericRespModel.data);
return widgetsConfigModel;
return genericRespModel;
} catch (e) {
logger.e(e.toString());
loggerService.logError(e.toString());
loggerService.logToFile(message: e.toString(), source: "createTicketFromKiosk-> screen_details_repo.dart", type: LogTypeEnum.error);
InfoComponents.showToast(e.toString());
return null;
}
@ -143,11 +129,14 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo {
List<WeathersWidgetModel> weathersWidgetModel = List.generate(genericRespModel.data.length, (index) => WeathersWidgetModel.fromJson(genericRespModel.data[index]));
if (weathersWidgetModel.isNotEmpty) {
loggerService.logToFile(message: weathersWidgetModel.toString(), source: "getWeatherDetailsByCity-> screen_details_repo.dart", type: LogTypeEnum.data);
return weathersWidgetModel.first;
}
return null;
} catch (e) {
logger.e(e.toString());
loggerService.logError(e.toString());
loggerService.logToFile(message: e.toString(), source: "getWeatherDetailsByCity-> screen_details_repo.dart", type: LogTypeEnum.error);
InfoComponents.showToast(e.toString());
return null;
}
@ -165,12 +154,14 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo {
List<PrayersWidgetModel> prayersWidgetModel = List.generate(genericRespModel.data.length, (index) => PrayersWidgetModel.fromJson(genericRespModel.data[index]));
if (prayersWidgetModel.isNotEmpty) {
loggerService.logToFile(message: prayersWidgetModel.toString(), source: "getPrayerDetailsByLatLong-> screen_details_repo.dart", type: LogTypeEnum.data);
return prayersWidgetModel.first;
}
return null;
} catch (e) {
logger.e(e.toString());
loggerService.logError(e.toString());
loggerService.logToFile(message: e.toString(), source: "getPrayerDetailsByLatLong-> screen_details_repo.dart", type: LogTypeEnum.error);
InfoComponents.showToast(e.toString());
return null;
}
@ -188,32 +179,60 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo {
List<RssFeedModel> rssFeedModel = List.generate(genericRespModel.data.length, (index) => RssFeedModel.fromJson(genericRespModel.data[index]));
if (rssFeedModel.isNotEmpty) {
loggerService.logToFile(message: rssFeedModel.toString(), source: "getRssFeedDetailsByLanguageID-> screen_details_repo.dart", type: LogTypeEnum.data);
return rssFeedModel.first;
}
return null;
} catch (e) {
logger.e(e.toString());
loggerService.logError(e.toString());
loggerService.logToFile(message: e.toString(), source: "getRssFeedDetailsByLanguageID-> screen_details_repo.dart", type: LogTypeEnum.error);
InfoComponents.showToast(e.toString());
return null;
}
}
@override
Future<GenericRespModel?> acknowledgeTicket({required String ipAddress, required String ticketQueueID, required QTypeEnum qTypeEnum}) async {
Future<GenericRespModel?> acknowledgeTicket({required String ipAddress, required int ticketQueueID, required QTypeEnum qTypeEnum}) async {
try {
var params = {
"ipAddress": ipAddress.toString(),
"ticketQueueID": ticketQueueID.toString(),
"qType": qTypeEnum.getQTypeIDFromEnum().toString(),
};
GenericRespModel adsGenericModel = await apiClientInstance.postJsonForObject(
GenericRespModel genericModel = await apiClientInstance.postJsonForObject(
(json) => GenericRespModel.fromJson(json),
ApiConstants.ticketAcknowledgementInsert,
params,
);
return adsGenericModel;
return genericModel;
} catch (e) {
loggerService.logError(e.toString());
loggerService.logToFile(message: e.toString(), source: "acknowledgeTicket-> screen_details_repo.dart", type: LogTypeEnum.error);
InfoComponents.showToast(e.toString());
return null;
}
}
@override
Future<GenericRespModel?> acknowledgeTicketForAppointment({required int ticketId, required String ipAddress, required CallTypeEnum callTypeEnum}) async {
try {
var params = {
"id": ticketId.toString(),
"apiKey": AppConstants.apiKey,
"ipAddress": ipAddress,
"callType": callTypeEnum.getIdFromCallTypeEnum().toString(),
};
GenericRespModel genericModel = await apiClientInstance.postJsonForObject(
(json) => GenericRespModel.fromJson(json),
ApiConstants.ticketCallRequestUpdate,
params,
);
return genericModel;
} catch (e) {
logger.e(e.toString());
loggerService.logError(e.toString());
loggerService.logToFile(message: e.toString(), source: "acknowledgeTicketForAppointment-> screen_details_repo.dart", type: LogTypeEnum.error);
InfoComponents.showToast(e.toString());
return null;
}

@ -1,12 +1,13 @@
import 'dart:developer';
import 'dart:io';
import 'package:hmg_qline/services/logger_service.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:http/io_client.dart';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:signalr_core/signalr_core.dart';
abstract class SignalrRepo {
Future<void> startHubConnection({
Future<bool?> startHubConnection({
required String deviceIp,
required Function(List<Object?>?) onHubTicketCall,
required Function(dynamic) onHubConfigCall,
@ -18,9 +19,14 @@ abstract class SignalrRepo {
class SignalrRepoImp implements SignalrRepo {
HubConnection? connection;
LoggerService loggerService;
bool get isConnected => connection?.state == HubConnectionState.connected;
SignalrRepoImp({this.connection, required this.loggerService});
@override
Future<void> startHubConnection({
Future<bool?> startHubConnection({
required String deviceIp,
required Function(List<Object?>?) onHubTicketCall,
required Function(dynamic) onHubConfigCall,
@ -28,42 +34,65 @@ class SignalrRepoImp implements SignalrRepo {
required Function(dynamic) onHubReconnected,
required Function(dynamic) onHubDisconnected,
}) async {
String hubBaseURL = ApiConstants.baseUrlHub;
try {
String hubBaseURL = ApiConstants.baseUrlHub;
if ((connection != null) && (connection!.state == HubConnectionState.connected || connection!.state == HubConnectionState.connecting)) {
return true;
}
connection = HubConnectionBuilder()
.withUrl(
"$hubBaseURL?IPAddress=$deviceIp",
HttpConnectionOptions(
client: IOClient(HttpClient()..badCertificateCallback = (x, y, z) => true),
logging: (level, message) => log(message),
))
.withAutomaticReconnect()
.build();
if ((connection != null) && (connection!.state == HubConnectionState.connected || connection!.state == HubConnectionState.connecting)) {
return;
}
connection = HubConnectionBuilder()
.withUrl(
"$hubBaseURL?IPAddress=$deviceIp",
HttpConnectionOptions(
client: IOClient(HttpClient()..badCertificateCallback = (x, y, z) => true),
logging: (level, message) => log(message),
))
.withAutomaticReconnect()
.build();
connection!.serverTimeoutInMilliseconds = 120000;
connection!.onclose((exception) {
log(exception.toString());
onHubDisconnected(exception);
});
connection!.serverTimeoutInMilliseconds = 120000;
int reconnectAttempts = 0;
const int maxReconnectAttempts = 10;
const Duration reconnectDelay = Duration(seconds: 5);
connection!.onclose((exception) async {
log(exception.toString());
onHubDisconnected(exception);
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
loggerService.logToFile(message: "SignalR reconnect attempt #$reconnectAttempts", source: "startHubConnection -> signalR_repo.dart", type: LogTypeEnum.data);
await Future.delayed(reconnectDelay);
try {
await connection!.start();
loggerService.logToFile(message: "SignalR reconnected after disconnect", source: "startHubConnection -> signalR_repo.dart", type: LogTypeEnum.data);
reconnectAttempts = 0; // Reset on success
} catch (e) {
loggerService.logError("Reconnect failed: $e");
}
} else {
loggerService.logError("Max SignalR reconnect attempts reached.");
}
});
connection!.onreconnecting((exception) => onHubConnecting(exception));
connection!.onreconnected((connectionId) {
log(connectionId.toString());
onHubReconnected(connectionId);
});
connection!.onreconnecting((exception) => onHubConnecting(exception));
connection!.onreconnected((connectionId) {
log(connectionId.toString());
onHubReconnected(connectionId);
});
connection!.on(ApiConstants.sendQLinePatientCall, (List<Object?>? message) {
onHubTicketCall(message);
});
connection!.on(ApiConstants.sendQLinePatientCall, (List<Object?>? message) {
onHubTicketCall(message);
});
connection!.on(ApiConstants.sendQLineConfig, (List<Object?>? message) {
onHubConfigCall(message);
});
connection!.on(ApiConstants.sendQLineConfig, (List<Object?>? message) {
onHubConfigCall(message);
});
await connection!.start();
await connection!.start();
return true;
} catch (e) {
loggerService.logError(e.toString());
loggerService.logToFile(message: e.toString(), source: "startHubConnection -> signalR_repo.dart", type: LogTypeEnum.error);
return false;
}
}
bool getHubConnectionState() {

@ -1,9 +1,9 @@
import 'package:just_audio/just_audio.dart';
abstract class AudioService {
Future<void> playTone({required String path});
Future<void> playTone({required String path, bool isMute = false});
Future<void> listenAudioPlayerEvents({required Function() onAudioCompleted});
Future<void> listenAudioPlayerEvents({required Function() onToneCompleted});
Future<void> disposeAudioPlayer();
}
@ -14,17 +14,22 @@ class AudioServiceImp implements AudioService {
AudioServiceImp({required this.audioPlayerInstance});
@override
Future<void> listenAudioPlayerEvents({required Function() onAudioCompleted}) async {
Future<void> listenAudioPlayerEvents({required Function() onToneCompleted}) async {
audioPlayerInstance.playerStateStream.listen((playerState) async {
if (playerState.processingState == ProcessingState.completed) {
onAudioCompleted();
onToneCompleted();
}
});
}
@override
Future<void> playTone({required String path}) async {
Future<void> playTone({required String path, bool isMute = false}) async {
audioPlayerInstance.setAsset(path);
if (isMute) {
audioPlayerInstance.setVolume(0.0);
} else {
audioPlayerInstance.setVolume(1.0);
}
await audioPlayerInstance.play();
}

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -5,6 +7,10 @@ abstract class CacheService {
Future<DateTime?> getLastTimeUpdatedFromCache();
Future<void> setLastTimeUpdatedInCache({required String lasTimeUpdated});
Future<void> setLastTimeLogsCleared({required int lastTimeCleared});
Future<DateTime?> getLastTimeLogsCleared();
}
class CacheServiceImp implements CacheService {
@ -14,10 +20,8 @@ class CacheServiceImp implements CacheService {
@override
Future<DateTime?> getLastTimeUpdatedFromCache() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.containsKey(CacheConstants.lastTimeUpdated)) {
String? lastTimeUpdated = prefs.getString(CacheConstants.lastTimeUpdated);
if (preferencesInstance.containsKey(CacheConstants.lastTimeUpdated)) {
String? lastTimeUpdated = preferencesInstance.getString(CacheConstants.lastTimeUpdated);
DateTime currentLastTimeUpdated = DateTime.fromMillisecondsSinceEpoch(int.parse(lastTimeUpdated!));
return currentLastTimeUpdated;
} else {
@ -29,4 +33,20 @@ class CacheServiceImp implements CacheService {
Future<void> setLastTimeUpdatedInCache({required String lasTimeUpdated}) async {
await preferencesInstance.setString(CacheConstants.lastTimeUpdated, lasTimeUpdated);
}
@override
Future<DateTime?> getLastTimeLogsCleared() async {
if (preferencesInstance.containsKey(CacheConstants.lastTimeUpdated)) {
String? lastTimeUpdated = preferencesInstance.getString(CacheConstants.lastTimeUpdated);
DateTime currentLastTimeUpdated = DateTime.fromMillisecondsSinceEpoch(int.parse(lastTimeUpdated!));
return currentLastTimeUpdated;
} else {
return null;
}
}
@override
Future<void> setLastTimeLogsCleared({required int lastTimeCleared}) async {
await preferencesInstance.setString(CacheConstants.lastTimeUpdated, lastTimeCleared.toString());
}
}

@ -0,0 +1,72 @@
import 'dart:io';
import 'package:hmg_qline/config/dependency_injection.dart';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/services/cache_service.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/view_models/screen_config_view_model.dart';
import 'package:intl/intl.dart';
import 'package:logger/logger.dart';
import 'package:path_provider/path_provider.dart';
abstract class LoggerService {
Future<void> logToFile({required String message, required LogTypeEnum type, required String source});
void logError(String message);
void logInfo(String message);
}
class LoggerServiceImp implements LoggerService {
Logger logger;
LoggerServiceImp({required this.logger});
@override
Future<void> logToFile({required String message, required LogTypeEnum type, required String source}) async {
try {
ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
final timestamp = DateFormat('yyyy-MM-dd hh:mm:ss a').format(DateTime.now());
final formattedMessage = "[$timestamp] [SOURCE: $source] ${type.name.toUpperCase()}: $message";
final baseDir = await getExternalStorageDirectory();
final logDir = Directory('${baseDir!.path}/logs');
if (!await logDir.exists()) {
await logDir.create(recursive: true);
}
final logFileName = type == LogTypeEnum.error
? AppStrings.errorLogsFileName
: AppStrings.dataLogsFileName;
final file = File('${logDir.path}/${screenConfigViewModel.currentScreenIP}_$logFileName');
CacheService cacheService = getIt.get<CacheService>();
final lastLogsClearedAt = screenConfigViewModel.lastTimeLogsCleared;
final now = DateTime.now();
if (lastLogsClearedAt != null &&
now.difference(lastLogsClearedAt).inHours >= AppConstants.clearLogsHoursThreshold) {
final clearingMessage = "[$timestamp] [SOURCE: $source] ${type.name.toUpperCase()}: LOGS HAVE BEEN CLEARED";
await file.writeAsString(clearingMessage);
screenConfigViewModel.lastTimeLogsCleared = now;
cacheService.setLastTimeLogsCleared(lastTimeCleared: now.millisecondsSinceEpoch);
}
await file.writeAsString("$formattedMessage\n", mode: FileMode.append);
} catch (e) {
logger.e('Logging failed: $e');
}
}
@override
void logError(String message) {
logger.e(message);
}
@override
void logInfo(String message) {
logger.i(message);
}
}

@ -3,89 +3,201 @@ import 'package:flutter_tts/flutter_tts.dart';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/models/global_config_model.dart';
import 'package:hmg_qline/models/ticket_model.dart';
import 'package:hmg_qline/services/logger_service.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/utilities/extensions.dart';
import 'package:logger/logger.dart';
abstract class TextToSpeechService {
Future<void> speechText({
required TicketDetailsModel ticket,
required GlobalConfigurationsModel globalConfigurationsModel,
bool isMute = false,
});
Future<void> speechTextTest(String test);
Future<void> speechTextTest(TicketData ticket);
void listenToTextToSpeechEvents({required Function() onVoiceCompleted});
}
class TextToSpeechServiceImp implements TextToSpeechService {
FlutterTts textToSpeechInstance;
LoggerService loggerService;
TextToSpeechServiceImp({required this.textToSpeechInstance});
TextToSpeechServiceImp({required this.textToSpeechInstance, required this.loggerService});
double volume = 1.0;
double pitch = 0.6;
double rate = 0.2;
Map<String, String> arabicVoice = {"name": "ar-xa-x-ard-local", "locale": "ar"};
@override
Future<void> speechTextTest(String test) async {
log("lang: ${await textToSpeechInstance.areLanguagesInstalled(["en", "ar"])}");
log("getDefaultEngine: ${await textToSpeechInstance.getDefaultEngine}");
log("getEngines: ${await textToSpeechInstance.getEngines}");
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;
}
// await textToSpeechInstance.setLanguage(LanguageEnum.arabic.enumToString());
// textToSpeechInstance.setSpeechRate(0.45);
// textToSpeechInstance.setPitch(0.9);
if (isNeedToBreakVoiceForArabic) {
await textToSpeechInstance.awaitSpeakCompletion(true);
await textToSpeechInstance.setLanguage(LanguageEnum.english.enumToString());
textToSpeechInstance.setSpeechRate(0.37);
textToSpeechInstance.setPitch(0.85);
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");
await textToSpeechInstance.speak(test);
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
Future<void> speechText({
required TicketDetailsModel ticket,
required GlobalConfigurationsModel globalConfigurationsModel,
bool isMute = false,
}) async {
log("ticketdata i got: ${ticket.ticketModel.toString()}");
const ttsGoogleEngine = 'com.google.android.tts';
// const ttsFlyTecEngine = 'com.iflytek.speechcloud';
LanguageEnum langEnum = ticket.ticketModel!.voiceLanguageEnum;
List engines = await textToSpeechInstance.getEngines;
if (engines.contains(ttsGoogleEngine)) {
await textToSpeechInstance.setEngine(ttsGoogleEngine);
}
textToSpeechInstance.setVolume(1.0);
await textToSpeechInstance.setVoice(arabicVoice);
if (isMute) {
textToSpeechInstance.setVolume(0.0);
} else {
textToSpeechInstance.setVolume(1.0);
}
if (langEnum == LanguageEnum.arabic) {
await textToSpeechInstance.setLanguage(LanguageEnum.arabic.enumToString());
textToSpeechInstance.setSpeechRate(0.45);
textToSpeechInstance.setPitch(0.9);
try {
await textToSpeechInstance.setLanguage(LanguageEnum.arabic.enumToString());
} catch (e) {
log("error setting language english: ${e.toString()}");
}
} else if (langEnum == LanguageEnum.english) {
await textToSpeechInstance.setLanguage(LanguageEnum.english.enumToString());
textToSpeechInstance.setSpeechRate(0.37);
textToSpeechInstance.setPitch(0.85);
try {
await textToSpeechInstance.setLanguage(LanguageEnum.english.enumToString());
} catch (e) {
log("error setting language english: ${e.toString()}");
}
}
String preVoice = ticket.ticketModel!.ticketNoText;
String postVoice = '';
if (globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment) {
postVoice = ticket.ticketModel!.callTypeEnum.getCallTextFromCallType(ticket.ticketModel!);
} else {
postVoice = ticket.ticketModel!.postVoiceText;
}
// String postVoice = globalConfigurationsModel.postVoiceText;
// String preVoice = ticket.ticketModel!.ticketNoText;
String postVoice = ticket.ticketModel!.postVoiceText;
String preVoice = '';
String roomNo = '';
if (ticket.ticketModel!.roomNo != null && ticket.ticketModel!.roomNo!.isNotEmpty) {
roomNo = ticket.ticketModel!.roomNo.toString();
if (globalConfigurationsModel.qTypeEnum != QTypeEnum.appointment && ticket.ticketModel!.roomNo != null && ticket.ticketModel!.roomNo!.isNotEmpty) {
roomNo = ".. ${ticket.ticketModel!.roomNo.toString()}";
}
if (preVoice.isNotEmpty) {
preVoice = '$preVoice..';
}
String ticketNo = ticket.ticketModel!.queueNo!.trim().toString();
log("lang: ${await textToSpeechInstance.areLanguagesInstalled(["en", "ar"])}");
log("getDefaultEngine: ${await textToSpeechInstance.getDefaultEngine}");
log("getEngines: ${await textToSpeechInstance.getEngines}");
log("areLanguagesInstalled: ${await textToSpeechInstance.areLanguagesInstalled(["en-US", "ar-SA"])}");
log("lang: $langEnum");
log("preVoice: $preVoice");
log("postVoice: $postVoice");
@ -93,20 +205,45 @@ class TextToSpeechServiceImp implements TextToSpeechService {
String patientAlpha = "";
String patientNumeric = "";
String clinicName = "";
bool isClinicNameAdded = (ticket.ticketModel?.queueNo != ticket.ticketModel?.callNoStr);
var queueNoArray = ticketNo.split("-");
if (queueNoArray.length > 2) {
patientAlpha = "${queueNoArray[0]} .. ${queueNoArray[1]}";
patientNumeric = queueNoArray[2];
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 {
patientAlpha = queueNoArray[0];
patientNumeric = queueNoArray[1];
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(' .. ');
log("I will now all this:{ $preVoice .. $clinicName .. $patientAlpha .. $patientNumeric .. $postVoice $roomNo } ");
if (langEnum == LanguageEnum.english) {
await textToSpeechInstance.speak("$preVoice $patientAlpha .. $patientNumeric .. $postVoice $roomNo");
await textToSpeechInstance.speak("$preVoice .. $clinicName .. $patientAlpha .. $patientNumeric .. $postVoice $roomNo");
return;
}
@ -117,21 +254,43 @@ class TextToSpeechServiceImp implements TextToSpeechService {
if (preVoice.isNotEmpty) {
await textToSpeechInstance.speak("$preVoice ");
}
textToSpeechInstance.setLanguage(LanguageEnum.english.enumToString());
try {
await textToSpeechInstance.setLanguage(LanguageEnum.english.enumToString());
} catch (e) {
log("error setting language english: ${e.toString()}");
}
await textToSpeechInstance.speak("$patientAlpha .. $patientNumeric");
textToSpeechInstance.setLanguage(langEnum.enumToString());
// await textToSpeechInstance.speak(postVoice);
// textToSpeechInstance.setLanguage(LanguageEnum.english.enumToString());
await textToSpeechInstance.speak("$postVoice .. $roomNo").whenComplete(() {
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("$patientAlpha .. .. $patientNumeric .. .. $postVoice .. $roomNo");
await textToSpeechInstance.speak("$preVoice .. $clinicName .. $patientAlpha .. $patientNumeric .. $postVoice $roomNo");
}
}
@override
void listenToTextToSpeechEvents({required Function() onVoiceCompleted}) {
textToSpeechInstance.setCompletionHandler(onVoiceCompleted);
textToSpeechInstance.setErrorHandler((message) {
loggerService.logInfo("setErrorHandler: $message\nCompleting the voice for now");
isSpeechCompleted = true;
onVoiceCompleted();
});
textToSpeechInstance.setPauseHandler(() {
loggerService.logInfo("setPauseHandler");
});
textToSpeechInstance.setCancelHandler(() {
loggerService.logInfo("setCancelHandler");
});
}
}

@ -4,6 +4,16 @@ enum ViewState {
error,
}
enum CallTypeEnum {
vitalSign, // 1
doctor, // 2
procedure, // 3
vaccination, // 4
nebulization, // 5
none, // 6
}
enum ScreenOrientationEnum {
portraitUp, // 1
portraitDown, // 2
@ -38,3 +48,10 @@ enum KioskScreenStateEnums {
ticketNumState,
busyState,
}
enum LogTypeEnum {
data,
error,
}

@ -1,4 +1,10 @@
import 'package:flutter/cupertino.dart';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/models/global_config_model.dart';
import 'package:hmg_qline/models/ticket_model.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:intl/intl.dart';
@ -32,11 +38,9 @@ extension NavigationExt on BuildContext {
navigateReplaceTo(String route) {
Navigator.pushReplacementNamed(this, route);
}
navigateTo(String route) {
Navigator.pushNamed(this, route);
}
popScreen() => Navigator.of(this).pop();
}
@ -44,13 +48,13 @@ extension ScreenOrientationExt on ScreenOrientationEnum {
int getTurnsByOrientation() {
switch (this) {
case ScreenOrientationEnum.portraitUp:
return 0;
return 1;
case ScreenOrientationEnum.portraitDown:
return 2;
return 3;
case ScreenOrientationEnum.landscapeRight:
return 1;
return 2;
case ScreenOrientationEnum.landscapeLeft:
return 3;
return 4;
default:
return 0;
}
@ -59,6 +63,7 @@ extension ScreenOrientationExt on ScreenOrientationEnum {
extension QTypeEnumExtension on int {
QTypeEnum toQTypeEnum() {
// return QTypeEnum.lab;
switch (this) {
case 1:
return QTypeEnum.appointment;
@ -69,7 +74,7 @@ extension QTypeEnumExtension on int {
case 4:
return QTypeEnum.general;
default:
return QTypeEnum.lab;
return QTypeEnum.appointment;
}
}
}
@ -138,11 +143,118 @@ extension LanguageEnumToString on LanguageEnum {
String enumToString() {
switch (this) {
case LanguageEnum.english:
return "en";
return "en-US";
case LanguageEnum.arabic:
return "ar";
return "ar-SA";
default:
return "en-US";
}
}
}
extension XCallType on CallTypeEnum {
Color getColorByCallType() {
if (this == CallTypeEnum.vitalSign) {
return AppColors.vitalSignColor;
} else if (this == CallTypeEnum.doctor) {
return AppColors.doctorColor;
} else if (this == CallTypeEnum.procedure) {
return AppColors.procedureColor;
} else if (this == CallTypeEnum.vaccination) {
return AppColors.vaccinationColor;
} else if (this == CallTypeEnum.nebulization) {
return AppColors.nebulizationColor;
} else {
return Colors.black54;
}
}
String getMessageByCallType(GlobalConfigurationsModel globalConfig, {bool isListView = false}) {
switch (this) {
case CallTypeEnum.vitalSign:
return !isListView ? globalConfig.callForVitalSignText : globalConfig.vitalSignText;
case CallTypeEnum.doctor:
return !isListView ? globalConfig.callForDoctorText : globalConfig.doctorText;
case CallTypeEnum.procedure:
return !isListView ? globalConfig.callForProcedureText : globalConfig.procedureText;
case CallTypeEnum.vaccination:
return !isListView ? globalConfig.callForVaccinationText : globalConfig.vaccinationText;
case CallTypeEnum.nebulization:
return !isListView ? globalConfig.callForNebulizationText : globalConfig.nebulizationText;
case CallTypeEnum.none:
return !isListView ? globalConfig.callForVitalSignText : globalConfig.vitalSignText;
default:
return globalConfig.callForVitalSignText;
}
}
SvgPicture getIconByCallType(double height, {double? width, BoxFit fit = BoxFit.contain}) {
String iconPath = "";
if (this == CallTypeEnum.vitalSign) {
iconPath = AppAssets.vitalSignIcon;
} else if (this == CallTypeEnum.doctor) {
iconPath = AppAssets.doctorIcon;
} else if (this == CallTypeEnum.procedure) {
iconPath = AppAssets.procedureIcon;
} else if (this == CallTypeEnum.vaccination) {
iconPath = AppAssets.vaccinationIcon;
} else if (this == CallTypeEnum.nebulization) {
iconPath = AppAssets.nebulizationIcon;
}
return SvgPicture.asset(
iconPath.isEmpty ? "assets/images/wait.svg" : iconPath,
height: height,
width: width,
color: getColorByCallType(),
// fit: fit,
);
}
int getIdFromCallTypeEnum() {
switch (this) {
case CallTypeEnum.vitalSign:
return 1;
case CallTypeEnum.doctor:
return 2;
case CallTypeEnum.procedure:
return 3;
case CallTypeEnum.vaccination:
return 4;
case CallTypeEnum.nebulization:
return 5;
case CallTypeEnum.none:
return 1;
}
}
String getCallTextFromCallType(TicketData ticket) {
switch (this) {
case CallTypeEnum.vitalSign:
return ticket.callForVitalSignText;
case CallTypeEnum.doctor:
return ticket.callForDoctorText;
case CallTypeEnum.procedure:
return ticket.callForProcedureText;
case CallTypeEnum.vaccination:
return ticket.callForVaccinationText;
case CallTypeEnum.nebulization:
return ticket.callForNebulizationText;
default:
return "en";
return ticket.callForVitalSignText;
}
}
}
extension XCallTypeInt on int {
CallTypeEnum toCallTypeEnum() {
if (this == 1) return CallTypeEnum.vitalSign;
if (this == 2) return CallTypeEnum.doctor;
if (this == 3) return CallTypeEnum.procedure;
if (this == 4) return CallTypeEnum.vaccination;
if (this == 5) return CallTypeEnum.nebulization;
return CallTypeEnum.vitalSign;
}
}

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

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

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

@ -9,7 +9,9 @@ import 'package:hmg_qline/repositories/screen_details_repo.dart';
import 'package:hmg_qline/repositories/signalR_repo.dart';
import 'package:hmg_qline/services/audio_service.dart';
import 'package:hmg_qline/services/cache_service.dart';
import 'package:hmg_qline/services/logger_service.dart';
import 'package:hmg_qline/services/text_to_speech_service.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/view_models/screen_config_view_model.dart';
class QueuingViewModel extends ChangeNotifier {
@ -18,6 +20,7 @@ class QueuingViewModel extends ChangeNotifier {
final CacheService cacheService;
final AudioService audioService;
final TextToSpeechService textToSpeechService;
final LoggerService loggerService;
QueuingViewModel({
required this.screenDetailsRepo,
@ -25,6 +28,7 @@ class QueuingViewModel extends ChangeNotifier {
required this.cacheService,
required this.audioService,
required this.textToSpeechService,
required this.loggerService,
});
Future<void> initializeQueueingVM() async {
@ -33,9 +37,9 @@ class QueuingViewModel extends ChangeNotifier {
initializeTextToSpeech();
}
Future<void> startHubConnection() async {
Future<bool?> startHubConnection() async {
ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
await signalrRepo.startHubConnection(
return await signalrRepo.startHubConnection(
deviceIp: screenConfigViewModel.currentScreenIP,
onHubTicketCall: onHubTicketCall,
onHubConfigCall: onHubConfigCall,
@ -46,7 +50,7 @@ class QueuingViewModel extends ChangeNotifier {
}
initializeAudioPlayer() {
audioService.listenAudioPlayerEvents(onAudioCompleted: onToneCompleted);
audioService.listenAudioPlayerEvents(onToneCompleted: onToneCompleted);
}
initializeTextToSpeech() {
@ -54,25 +58,48 @@ class QueuingViewModel extends ChangeNotifier {
}
Future<void> onHubConfigCall(var response) async {
log("onHubConfigCall: $response");
loggerService.logToFile(message: response.toString(), source: "onHubConfigCall -> queueing_view_model.dart ", type: LogTypeEnum.data);
if (response != null && response.isNotEmpty) {
var data = response.first['data'];
GlobalConfigurationsModel globalConfigurationsModel = GlobalConfigurationsModel.fromJson(data as Map<String, dynamic>);
GlobalConfigurationsModel globalConfigurationsModel = GlobalConfigurationsModel.fromJson(
json: data,
qType: response.first['qType'] ?? 1,
screenType: response.first['screenType'] ?? 1,
);
log("onHubConfigCall: ${globalConfigurationsModel.toString()}");
ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
screenConfigViewModel.updateGlobalConfigurationsModel(value: globalConfigurationsModel, needNotify: true, shouldUpdateNextPrayer: true);
}
}
Future<void> onHubTicketCall(List<Object?>? response) async {
loggerService.logToFile(message: response.toString(), source: "onHubTicketCall -> queueing_view_model.dart ", type: LogTypeEnum.data);
log("onHubTicketCall: $response");
log("isCallingInProgress: $isCallingInProgress");
if (response != null && response.isNotEmpty) {
TicketDetailsModel ticketDetailsModel = TicketDetailsModel.fromJson(response.first as Map<String, dynamic>);
addNewTicket(ticketDetailsModel);
}
}
Future<void> onHubReconnected(var response) async {
log("onHubConnected: $response");
loggerService.logToFile(message: response.toString(), source: "onHubReconnected -> queueing_view_model.dart ", type: LogTypeEnum.data);
ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
screenConfigViewModel.updateIsHubConnected(true);
log("screenConfigViewModel: ${screenConfigViewModel.isHubConnected}");
screenConfigViewModel.notifyListeners();
}
Future<void> onHubDisconnected(var response) async {
log("onHubDisconnected: $response");
loggerService.logToFile(message: response.toString(), source: "onHubDisconnected -> queueing_view_model.dart ", type: LogTypeEnum.data);
ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
screenConfigViewModel.updateIsHubConnected(false);
screenConfigViewModel.notifyListeners();
@ -82,11 +109,8 @@ class QueuingViewModel extends ChangeNotifier {
Future<void> onToneCompleted() async {
GlobalConfigurationsModel globalConfigurationsModel = getIt.get<ScreenConfigViewModel>().globalConfigurationsModel;
if (globalConfigurationsModel.isVoiceReq) {
textToSpeechService.speechText(
globalConfigurationsModel: globalConfigurationsModel,
ticket: currentTickets.first,
);
if (true) {
await textToSpeechService.speechText(globalConfigurationsModel: globalConfigurationsModel, ticket: currentTickets.first, isMute: !(globalConfigurationsModel.isVoiceReq));
} else {
waitAndCallNextTicketIfAvailable();
}
@ -103,7 +127,12 @@ class QueuingViewModel extends ChangeNotifier {
GlobalConfigurationsModel globalConfigurationsModel = getIt.get<ScreenConfigViewModel>().globalConfigurationsModel;
Timer(Duration(seconds: globalConfigurationsModel.concurrentCallDelaySec), () async {
if (queueTickets.isNotEmpty) {
int index = itemAlreadyAvailableAtIndex(ticketToSearch: queueTickets.first, listToSearchIn: currentTickets);
if (index != -1) {
currentTickets.removeAt(index);
}
currentTickets.insert(0, queueTickets.first);
if (currentTickets.length > globalConfigurationsModel.screenMaxDisplayPatients) {
currentTickets.removeLast();
}
@ -116,11 +145,19 @@ class QueuingViewModel extends ChangeNotifier {
});
}
Future<void> onHubTicketCall(List<Object?>? response) async {
logger.i("onHubTicketCall: $response");
if (response != null && response.isNotEmpty) {
TicketDetailsModel ticketDetailsModel = TicketDetailsModel.fromJson(response.first as Map<String, dynamic>);
addNewTicket(ticketDetailsModel);
int itemAlreadyAvailableAtIndex({required TicketDetailsModel ticketToSearch, required List<TicketDetailsModel> listToSearchIn}) {
int isFoundAt = -1;
try {
isFoundAt = listToSearchIn.indexWhere((ticket) {
log("ticket.ticketModel!.queueNo: ${ticket.ticketModel!.queueNo}");
log("ticketToSearch.ticketModel!.queueNo: ${ticketToSearch.ticketModel!.queueNo}");
return ticket.ticketModel!.queueNo == ticketToSearch.ticketModel!.queueNo;
});
log("itemAlreadyAvailableAtIndex: $isFoundAt");
return isFoundAt;
} catch (e) {
log("Error isItemAlreadyAvailableInList: ${e.toString()}");
return isFoundAt;
}
}
@ -132,13 +169,22 @@ class QueuingViewModel extends ChangeNotifier {
void addNewTicket(TicketDetailsModel ticket) {
if (!isCallingInProgress) {
int index = itemAlreadyAvailableAtIndex(ticketToSearch: ticket, listToSearchIn: currentTickets);
if (index != -1) {
currentTickets.removeAt(index);
}
currentTickets.insert(0, ticket);
GlobalConfigurationsModel globalConfigurationsModel = getIt.get<ScreenConfigViewModel>().globalConfigurationsModel;
if (currentTickets.length > globalConfigurationsModel.screenMaxDisplayPatients) {
currentTickets.removeLast();
}
callTicketOnScreen(ticketData: currentTickets.first.ticketModel);
} else {
int index = itemAlreadyAvailableAtIndex(ticketToSearch: ticket, listToSearchIn: queueTickets);
if (index != -1) {
queueTickets.removeAt(index);
}
queueTickets.add(ticket);
log("inQueue Length: ${queueTickets.length}");
}
@ -146,25 +192,57 @@ class QueuingViewModel extends ChangeNotifier {
}
Future<void> testSpeech() async {
textToSpeechService.speechTextTest("Ticket Number ... ABC One Tow Three.. Please visit Doctor.");
textToSpeechService.speechTextTest(MockJsonRepo.ticket);
}
Future<void> callTicketOnScreen({required TicketData? ticketData}) async {
if (ticketData == null) return;
ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
GlobalConfigurationsModel globalConfigurationsModel = screenConfigViewModel.globalConfigurationsModel;
screenConfigViewModel.acknowledgeTicket(ticketQueueID: ticketData.id!.toString());
if (globalConfigurationsModel.isToneReq) {
if (globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment) {
screenConfigViewModel.acknowledgeTicketForAppointmentOnly(
ticketQueueID: ticketData.id ?? 0,
ipAddress: screenConfigViewModel.currentScreenIP,
callTypeEnum: ticketData.callTypeEnum,
);
} else {
screenConfigViewModel.acknowledgeTicket(ticketQueueID: ticketData.id ?? 0);
}
log("globalConfigurationsModel: ${globalConfigurationsModel.toString()}");
if (true) {
isCallingInProgress = true;
await audioService.playTone(path: AppAssets.callTone);
await audioService.playTone(path: AppAssets.callTone, isMute: !(globalConfigurationsModel.isToneReq));
} else if (globalConfigurationsModel.isVoiceReq) {
isCallingInProgress = true;
await textToSpeechService.speechText(
globalConfigurationsModel: globalConfigurationsModel,
ticket: currentTickets.first,
);
await textToSpeechService.speechText(globalConfigurationsModel: globalConfigurationsModel, ticket: currentTickets.first, isMute: !(globalConfigurationsModel.isVoiceReq));
} else {
waitAndCallNextTicketIfAvailable();
}
}
}
// Future<void> callTicketOnScreen({required TicketData? ticketData}) async {
// if (ticketData == null) return;
// ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
// GlobalConfigurationsModel globalConfigurationsModel = screenConfigViewModel.globalConfigurationsModel;
// if (globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment) {
// screenConfigViewModel.acknowledgeTicketForAppointmentOnly(
// ticketQueueID: ticketData.id ?? 0,
// ipAddress: screenConfigViewModel.currentScreenIP,
// callTypeEnum: ticketData.callTypeEnum,
// );
// } else {
// screenConfigViewModel.acknowledgeTicket(ticketQueueID: ticketData.id ?? 0);
// }
// if (globalConfigurationsModel.isToneReq) {
// isCallingInProgress = true;
// await audioService.playTone(path: AppAssets.callTone, isMute: !(globalConfigurationsModel.isToneReq));
// } else if (globalConfigurationsModel.isVoiceReq) {
// isCallingInProgress = true;
// await textToSpeechService.speechText(globalConfigurationsModel: globalConfigurationsModel, ticket: currentTickets.first, isMute: !(globalConfigurationsModel.isVoiceReq));
// } else {
// waitAndCallNextTicketIfAvailable();
// }
// }

@ -1,8 +1,10 @@
import 'dart:developer';
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:hmg_qline/config/dependency_injection.dart';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/main.dart';
import 'package:hmg_qline/models/generic_response_model.dart';
import 'package:hmg_qline/models/global_config_model.dart';
import 'package:hmg_qline/models/kiosk_language_config_model.dart';
@ -12,33 +14,53 @@ import 'package:hmg_qline/models/prayers_widget_model.dart';
import 'package:hmg_qline/models/rss_feed_model.dart';
import 'package:hmg_qline/models/weathers_widget_model.dart';
import 'package:hmg_qline/repositories/screen_details_repo.dart';
import 'package:hmg_qline/repositories/signalR_repo.dart';
import 'package:hmg_qline/services/cache_service.dart';
import 'package:hmg_qline/services/connectivity_service.dart';
import 'package:hmg_qline/services/logger_service.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/utilities/extensions.dart';
import 'package:hmg_qline/utilities/native_method_handler.dart';
import 'package:hmg_qline/view_models/queuing_view_model.dart';
import 'package:hmg_qline/views/view_helpers/info_components.dart';
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart';
// import 'package:timezone/browser.dart' as tz;
class ScreenConfigViewModel extends ChangeNotifier {
final ScreenDetailsRepo screenDetailsRepo;
final CacheService cacheService;
final ConnectivityService connectivityService;
final LoggerService loggerService;
final NativeMethodChannelService nativeMethodChannelService;
ScreenConfigViewModel({
required this.screenDetailsRepo,
required this.cacheService,
required this.connectivityService,
required this.loggerService,
required this.nativeMethodChannelService,
});
Future<void> initializeScreenConfigVM() async {
await getGlobalConfigurationsByIP();
await getInfoWidgetsDetailsFromServer();
await getLastTimeUpdatedFromCache();
await getLastTimeLogsClearedFromCache();
listenNetworkConnectivity();
getTheWidgetsConfigurationsEveryMidnight();
}
Future<void> onAppResumed() async {
loggerService.logToFile(message: "[didChangeAppLifecycleState] : [onAppResumed]", source: "onAppResumed -> screen_config_view_model.dart", type: LogTypeEnum.data);
}
Future<void> onAppPaused() async {
loggerService.logToFile(message: "[didChangeAppLifecycleState] : [onAppPaused]", source: "onAppPaused -> screen_config_view_model.dart", type: LogTypeEnum.data);
// nativeMethodChannelService.reopenApp();
nativeMethodChannelService.restartApp();
// runApp(const MyApp());
}
Future<void> waitForIPAndInitializeConfigVM() async {
while (currentScreenIP == "") {
await getCurrentScreenIP();
@ -100,22 +122,27 @@ class ScreenConfigViewModel extends ChangeNotifier {
}
void listenNetworkConnectivity() {
return connectivityService.subscribeToConnectivityChange(onInternetDisConnected: () {
updateIsInternetConnected(false);
updateIsHubConnected(false);
}, onInternetConnected: () {
updateIsInternetConnected(true);
QueuingViewModel queuingViewModel = getIt.get<QueuingViewModel>();
queuingViewModel.startHubConnection();
});
return connectivityService.subscribeToConnectivityChange(
onInternetDisConnected: () {
updateIsInternetConnected(false);
updateIsHubConnected(false);
},
onInternetConnected: () {
updateIsInternetConnected(true);
QueuingViewModel queuingViewModel = getIt.get<QueuingViewModel>();
queuingViewModel.startHubConnection();
},
);
}
GlobalConfigurationsModel globalConfigurationsModel = GlobalConfigurationsModel();
Future<void> getGlobalConfigurationsByIP() async {
GlobalConfigurationsModel? response = await screenDetailsRepo.getGlobalScreenConfigurations(ipAddress: currentScreenIP);
if (response == null) {
log("response; $response");
loggerService.logError("response was: $response");
updateViewState(ViewState.error);
return;
}
updateGlobalConfigurationsModel(value: response);
@ -127,6 +154,7 @@ class ScreenConfigViewModel extends ChangeNotifier {
void updateGlobalConfigurationsModel({required var value, bool needNotify = false, bool shouldUpdateNextPrayer = false}) {
globalConfigurationsModel = value;
if (needNotify) {
notifyListeners();
}
@ -188,7 +216,7 @@ class ScreenConfigViewModel extends ChangeNotifier {
Future<void> getWeatherDetailsFromServer() async {
int testCityKey = 297030;
WeathersWidgetModel? response = await screenDetailsRepo.getWeatherDetailsByCity(
cityId: (globalConfigurationsModel.cityKey == 0 ? testCityKey : globalConfigurationsModel.cityKey).toString(),
cityId: ((globalConfigurationsModel.cityKey == null || globalConfigurationsModel.cityKey == 0) ? testCityKey : globalConfigurationsModel.cityKey).toString(),
);
if (response == null) {
@ -202,12 +230,20 @@ class ScreenConfigViewModel extends ChangeNotifier {
void getNextPrayerToShow() async {
final current = DateTime.now();
log("Checking Namaz time Locally at ${current.toString()} and ${current.timeZoneName} ");
log("Checking Namaz time Locally at $current and ${current.timeZoneName}");
// log("Data Before Check : ${prayersWidgetModel.fajr}");
// log("Data Before Check : ${globalConfigurationsModel.isPrayerTimeReq}");
if (globalConfigurationsModel.isPrayerTimeReq && prayersWidgetModel.fajr == null) {
await getPrayerDetailsFromServer();
}
// log("prayersWidgetModel.dhuhr: ${prayersWidgetModel.dhuhr!}");
// log("dhuhr: ${prayersWidgetModel.dhuhr!.toDateTimeFromInt()}");
// log("current: ${DateFormat('yyyy-MM-dd hh:mm a').format(DateTime.now().toLocal())}");
// log("current: ${DateTime.now().timeZoneName}");
if (prayersWidgetModel.fajr != null && prayersWidgetModel.fajr!.toDateTimeFromInt().isAfter(current)) {
final namazTime = prayersWidgetModel.fajr!.toFormattedDateTimeFromInt();
nextPrayerToShowWithTime = "${globalConfigurationsModel.fajarText} at $namazTime";
@ -238,54 +274,69 @@ class ScreenConfigViewModel extends ChangeNotifier {
notifyListeners();
return;
}
final namazTime = prayersWidgetModel.fajr!.toFormattedDateTimeFromInt();
nextPrayerToShowWithTime = "${globalConfigurationsModel.fajarText} at $namazTime";
notifyListeners();
return;
}
int counter = 0;
DateTime lastChecked = DateTime.now();
Timer? _midnightTimer;
Future<void> getTheWidgetsConfigurationsEveryMidnight() async {
// Cancel any existing timer to avoid multiple timers running
_midnightTimer?.cancel();
if (!(globalConfigurationsModel.isWeatherReq) && !(globalConfigurationsModel.isPrayerTimeReq) && !(globalConfigurationsModel.isRssFeedReq)) {
return;
}
DateTime current = DateTime.now();
Stream timer = Stream.periodic(const Duration(minutes: 1), (i) {
current = current.add(const Duration(minutes: 1));
return current;
});
timer.listen((data) async {
DateTime dateTime = DateTime.parse(data.toString());
// Only start timer if not already running
if (_midnightTimer != null) {
return;
}
_midnightTimer = Timer.periodic(const Duration(minutes: 1), (timer) async {
counter++;
DateTime now = DateTime.now();
log("counterValue: $counter");
if (counter == 60 && globalConfigurationsModel.isRssFeedReq) {
// Every hour, update RSS feed if required
if (counter % 60 == 0 && globalConfigurationsModel.isRssFeedReq) {
await getRssFeedDetailsFromServer();
}
if (globalConfigurationsModel.isWeatherReq) {
if (dateTime.day > currentLastTimeUpdated.day) {
await getWeatherDetailsFromServer();
}
}
if (globalConfigurationsModel.isPrayerTimeReq) {
if (dateTime.day > currentLastTimeUpdated.day) {
await getPrayerDetailsFromServer();
}
}
if (globalConfigurationsModel.isRssFeedReq) {
if (dateTime.day > currentLastTimeUpdated.day) {
await getRssFeedDetailsFromServer();
log("lastChecked: [${lastChecked.day}]");
log("now: [${now.day}]");
// At midnight, update weather and prayer details if required
if (now.day != lastChecked.day) {
if (now.difference(now.copyWith(hour: 0, minute: 0, second: 0, millisecond: 0, microsecond: 0)).inMinutes >= 5) {
if (globalConfigurationsModel.isWeatherReq) {
await getWeatherDetailsFromServer();
}
if (globalConfigurationsModel.isPrayerTimeReq) {
await getPrayerDetailsFromServer();
}
lastChecked = now;
}
}
getNextPrayerToShow();
syncHubConnectionState();
});
}
@override
void dispose() {
_midnightTimer?.cancel();
patientIdController.dispose();
super.dispose();
}
DateTime currentLastTimeUpdated = DateTime.now();
Future<void> getLastTimeUpdatedFromCache() async {
@ -295,6 +346,15 @@ class ScreenConfigViewModel extends ChangeNotifier {
}
}
DateTime? lastTimeLogsCleared = DateTime.now();
Future<void> getLastTimeLogsClearedFromCache() async {
lastTimeLogsCleared = await cacheService.getLastTimeLogsCleared();
if (lastTimeLogsCleared == null) {
await cacheService.setLastTimeLogsCleared(lastTimeCleared: DateTime.now().millisecondsSinceEpoch).whenComplete(() => lastTimeLogsCleared = DateTime.now());
}
}
// *************************** KIOSK FUNCTIONS *************************
KioskScreenStateEnums kioskScreenStateEnum = KioskScreenStateEnums.languageState;
@ -364,28 +424,42 @@ class ScreenConfigViewModel extends ChangeNotifier {
patientId: patientId,
);
if (response == null || response.messageStatus != 1) {
logger.e("response null from createTicketFromKiosk");
loggerService.logError("response null from createTicketFromKiosk");
return null;
}
return response.data;
} catch (e) {
InfoComponents.showToast(e.toString());
logger.i(e.toString());
loggerService.logError(e.toString());
return null;
}
}
Future<void> acknowledgeTicket({required String ticketQueueID}) async {
Future<void> acknowledgeTicket({required int ticketQueueID}) async {
GenericRespModel? response = await screenDetailsRepo.acknowledgeTicket(
ipAddress: currentScreenIP,
ticketQueueID: ticketQueueID,
qTypeEnum: globalConfigurationsModel.qTypeEnum,
);
if (response == null || response.messageStatus != 1) {
logger.e("response null from acknowledgeTicket");
loggerService.logError("response null from acknowledgeTicket");
return;
} else {
logger.i("response from acknowledgeTicket ${response.data}");
loggerService.logInfo("response from acknowledgeTicket ${response.data}");
}
}
Future<void> acknowledgeTicketForAppointmentOnly({required int ticketQueueID, required String ipAddress, required CallTypeEnum callTypeEnum}) async {
GenericRespModel? response = await screenDetailsRepo.acknowledgeTicketForAppointment(
ticketId: ticketQueueID,
ipAddress: ipAddress,
callTypeEnum: callTypeEnum,
);
if (response == null || response.messageStatus != 1) {
loggerService.logError("response null from acknowledgeTicketForAppointmentOnly");
return;
} else {
loggerService.logInfo("response from acknowledgeTicketForAppointmentOnly ${response.data}");
}
}
@ -393,12 +467,6 @@ class ScreenConfigViewModel extends ChangeNotifier {
final TextEditingController patientIdController = TextEditingController();
@override
void dispose() {
patientIdController.dispose();
super.dispose();
}
Future<void> onPatientIdSubmitted(String text) async {
int? patientId = int.tryParse(text);
if (patientId != null && patientId > 0) {
@ -427,7 +495,7 @@ class ScreenConfigViewModel extends ChangeNotifier {
qrViewController = controller;
controller.scannedDataStream.listen((scanData) async {
logger.i("Found: ${scanData.code} and isGeneratingTicket: $isGeneratingTicket");
loggerService.logInfo("Found: ${scanData.code} and isGeneratingTicket: $isGeneratingTicket");
if (isGeneratingTicket) return;
await validateAndGenerateTicketFromQR(data: scanData, onFailure: onFailure, onSuccess: onSuccess);
});
@ -466,4 +534,20 @@ class ScreenConfigViewModel extends ChangeNotifier {
await qrViewController!.flipCamera();
}
}
void syncHubConnectionState() async {
QueuingViewModel queuingViewModel = getIt.get<QueuingViewModel>();
bool newState = (queuingViewModel.signalrRepo as SignalrRepoImp).isConnected;
if (isHubConnected != newState) {
updateIsHubConnected(newState);
}
if (!isHubConnected) {
log("Hub is Not Connected!, I will try to reconnect now.");
QueuingViewModel queuingViewModel = getIt.get<QueuingViewModel>();
bool? status = await queuingViewModel.startHubConnection();
// syncHubConnectionState will update isHubConnected
} else {
log("Hub is Connected!");
}
}
}

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:hmg_qline/view_models/queuing_view_model.dart';
import 'package:hmg_qline/view_models/screen_config_view_model.dart';
import 'package:marquee/marquee.dart';
import 'package:provider/provider.dart';
@ -29,50 +30,50 @@ class AppFooter extends StatelessWidget {
children: [
AppText(
AppStrings.poweredBy,
fontSize: SizeConfig.getWidthMultiplier() * 2,
fontSize: SizeConfig.getWidthMultiplier() * 2.5,
),
Text(screenConfigVM.currentScreenIP,
Text("v${screenConfigVM.currentScreenIP}(${AppConstants.currentBuildVersion})",
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: SizeConfig.getWidthMultiplier() * 2.2,
fontSize: SizeConfig.getWidthMultiplier() * 1.5,
)),
Row(
children: [
InkWell(
onTap: () {
screenConfigVM.updateCurrentScreenRotation(ScreenOrientationEnum.portraitUp);
},
child: Icon(
Icons.arrow_upward,
size: SizeConfig.getWidthMultiplier() * 2,
),
),
InkWell(
onTap: () {
screenConfigVM.updateCurrentScreenRotation(ScreenOrientationEnum.landscapeRight);
},
child: Icon(Icons.arrow_forward, size: SizeConfig.getWidthMultiplier() * 2),
),
InkWell(
onTap: () {
screenConfigVM.updateCurrentScreenRotation(ScreenOrientationEnum.portraitDown);
},
child: Icon(
Icons.arrow_downward,
size: SizeConfig.getWidthMultiplier() * 2,
),
),
InkWell(
onTap: () {
screenConfigVM.updateCurrentScreenRotation(ScreenOrientationEnum.landscapeLeft);
},
child: Icon(
Icons.arrow_back,
size: SizeConfig.getWidthMultiplier() * 2,
),
),
],
),
// Row(
// children: [
// InkWell(
// onTap: () {
// screenConfigVM.updateCurrentScreenRotation(ScreenOrientationEnum.portraitUp);
// },
// child: Icon(
// Icons.arrow_upward,
// size: SizeConfig.getWidthMultiplier() * 2,
// ),
// ),
// InkWell(
// onTap: () {
// screenConfigVM.updateCurrentScreenRotation(ScreenOrientationEnum.landscapeRight);
// },
// child: Icon(Icons.arrow_forward, size: SizeConfig.getWidthMultiplier() * 2),
// ),
// InkWell(
// onTap: () {
// screenConfigVM.updateCurrentScreenRotation(ScreenOrientationEnum.portraitDown);
// },
// child: Icon(
// Icons.arrow_downward,
// size: SizeConfig.getWidthMultiplier() * 2,
// ),
// ),
// InkWell(
// onTap: () {
// screenConfigVM.updateCurrentScreenRotation(ScreenOrientationEnum.landscapeLeft);
// },
// child: Icon(
// Icons.arrow_back,
// size: SizeConfig.getWidthMultiplier() * 2,
// ),
// ),
// ],
// ),
],
),
const SizedBox(width: 10),
@ -82,36 +83,36 @@ class AppFooter extends StatelessWidget {
},
child: Image.asset(
AppAssets.cloudLogo,
height: SizeConfig.getHeightMultiplier() * 0.5,
height: SizeConfig.getHeightMultiplier() * 0.6,
),
),
],
),
Expanded(
child: (!screenConfigVM.globalConfigurationsModel.isRssFeedReq || screenConfigVM.rssFeedModel.rssFeed == null || screenConfigVM.rssFeedModel.rssFeed!.isEmpty)
? const SizedBox()
: Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Marquee(
text: screenConfigVM.rssFeedModel.rssFeed ?? "",
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: SizeConfig.getWidthMultiplier() * 4,
fontFamily: AppStrings.fontNamePoppins,
),
scrollAxis: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.center,
blankSpace: 20.0,
velocity: 100.0,
pauseAfterRound: const Duration(seconds: 1),
startPadding: 10.0,
accelerationDuration: const Duration(seconds: 1),
accelerationCurve: Curves.linear,
decelerationDuration: const Duration(milliseconds: 500),
decelerationCurve: Curves.easeOut,
),
if (screenConfigVM.globalConfigurationsModel.isRssFeedReq && screenConfigVM.rssFeedModel.rssFeed != null && screenConfigVM.rssFeedModel.rssFeed!.isNotEmpty) ...[
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Marquee(
text: screenConfigVM.rssFeedModel.rssFeed ?? "",
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: SizeConfig.getWidthMultiplier() * 4,
fontFamily: AppStrings.fontNamePoppins,
),
)
scrollAxis: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.center,
blankSpace: 20.0,
velocity: 100.0,
pauseAfterRound: const Duration(seconds: 1),
startPadding: 10.0,
accelerationDuration: const Duration(seconds: 1),
accelerationCurve: Curves.linear,
decelerationDuration: const Duration(milliseconds: 500),
decelerationCurve: Curves.easeOut,
),
),
)
]
],
));
});

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:hmg_qline/constants/app_constants.dart';
@ -62,7 +64,7 @@ Widget getPrayerWidget(ScreenConfigViewModel screenConfigViewModel) {
screenConfigViewModel.globalConfigurationsModel.orientationTypeEnum == ScreenOrientationEnum.portraitDown)
? SizeConfig.getHeightMultiplier() * 2
: SizeConfig.getHeightMultiplier() * 0.9,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
decoration: AppColors.configWidgetDecoration,
child: Directionality(
@ -82,7 +84,7 @@ Widget getPrayerWidget(ScreenConfigViewModel screenConfigViewModel) {
),
AppText(
screenConfigViewModel.nextPrayerToShowWithTime,
fontSize: SizeConfig.getWidthMultiplier() * 3,
fontSize: SizeConfig.getWidthMultiplier() * 2.7,
fontHeight: 1,
fontFamily: screenConfigViewModel.globalConfigurationsModel.screenLanguageEnum == LanguageEnum.arabic ? AppStrings.fontNameCairo : AppStrings.fontNamePoppins,
),
@ -131,13 +133,21 @@ Widget counterNoText({required int counterNo, required bool isRoomNoRequired, re
);
}
Widget noPatientInQueue({required String text, required String fontName, required String roomText, required bool isRoomNoRequired, required bool isForRoomLevel, required int counterNo}) {
Widget intimationWidget({
required String text,
required String fontName,
required String roomText,
required bool isRoomNoRequired,
required bool isForRoomLevel,
required int counterNo,
bool isForError = false,
}) {
Widget noPatientText = Center(
child: AppText(
text,
fontFamily: fontName,
textAlign: TextAlign.center,
fontSize: SizeConfig.getWidthMultiplier() * 9,
fontSize: isForError ? SizeConfig.getWidthMultiplier() * 7 : SizeConfig.getWidthMultiplier() * 9,
),
);
if (isForRoomLevel) {

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:hmg_qline/models/global_config_model.dart';
@ -67,7 +69,7 @@ class AppHeader extends StatelessWidget implements PreferredSizeWidget {
alignment: Alignment.center,
height: SizeConfig.getHeightMultiplier() * 0.6,
padding: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(color: AppColors.greenColor),
decoration: BoxDecoration(color: globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment ? AppColors.redColor : AppColors.greenColor),
child: Directionality(
textDirection: globalConfigurationsModel.textDirection,
child: Row(
@ -77,11 +79,11 @@ class AppHeader extends StatelessWidget implements PreferredSizeWidget {
AppText(
globalConfigurationsModel.currentServeText ?? "",
color: Colors.white,
fontSize: SizeConfig.getHeightMultiplier() * 0.1,
fontSize: SizeConfig.getHeightMultiplier() * 0.4,
fontFamily: globalConfigurationsModel.screenLanguageEnum == LanguageEnum.arabic ? AppStrings.fontNameCairo : AppStrings.fontNamePoppins,
),
SvgPicture.asset(
AppAssets.hmgLogoPharmacy,
globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment ? AppAssets.hmgLogo : AppAssets.hmgLogoPharmacy,
height: SizeConfig.getHeightMultiplier() * 0.5,
),
],

@ -59,11 +59,12 @@ class KioskMainScreen extends StatelessWidget {
bool isLanguageConfigAvailable = screenConfigViewModel.globalConfigurationsModel.kioskLanguageConfigList != null;
if (!isLanguageConfigAvailable) {
return noPatientInQueue(
return intimationWidget(
text: AppStrings.configurationIssueContactAdmin,
fontName: AppStrings.fontNamePoppins,
isForRoomLevel: false,
isRoomNoRequired: false,
isForError: true,
counterNo: 0,
roomText: '',
);
@ -105,11 +106,12 @@ class KioskMainScreen extends StatelessWidget {
Widget configurationWidgetIssue() {
return Center(
child: noPatientInQueue(
child: intimationWidget(
text: AppStrings.configurationIssueContactAdmin,
fontName: AppStrings.fontNamePoppins,
isForRoomLevel: false,
isRoomNoRequired: false,
isForError: true,
counterNo: 0,
roomText: '',
),

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:hmg_qline/models/global_config_model.dart';
import 'package:hmg_qline/models/ticket_model.dart';
@ -13,46 +15,47 @@ class PriorityTickets extends StatelessWidget {
@override
Widget build(BuildContext context) {
final TicketDetailsModel firstTicket = tickets[0];
final TicketData firstTicket = tickets[0].ticketModel!;
final List<TicketDetailsModel> otherTickets = tickets.sublist(1, tickets.length);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TicketItem(
ticketNo: firstTicket.ticketModel!.queueNo ?? '',
ticketNo: firstTicket.queueNo ?? '',
scale: globalConfigurationsModel.screenTypeEnum == ScreenTypeEnum.roomLevelScreen ? 2 : 1.2,
blink: true,
roomNo: firstTicket.ticketModel!.roomNo ?? '',
roomText: globalConfigurationsModel.counterText ?? "",
roomNo: firstTicket.roomNo ?? '',
roomText: (globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment ? globalConfigurationsModel.roomText : globalConfigurationsModel.counterText) ?? "",
isClinicAdded: false,
callTypeEnum: firstTicket.callTypeEnum,
textDirection: globalConfigurationsModel.textDirection,
message: globalConfigurationsModel.ticketNoText,
screenTypeEnum: globalConfigurationsModel.screenTypeEnum,
langTypeEnum: globalConfigurationsModel.screenLanguageEnum,
globalConfigurationsModel: globalConfigurationsModel,
),
if (tickets.length > 1) ...[
// SizedBox(height: SizeConfig.getHeightMultiplier() * 0.8),
Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: otherTickets
.map(
(ticket) => Padding(
padding: EdgeInsets.only(top: SizeConfig.getHeightMultiplier() * 0.5),
child: TicketItem(
ticketNo: ticket.ticketModel!.queueNo ?? '',
scale: 0.8,
roomNo: firstTicket.ticketModel!.roomNo ?? '',
roomText: globalConfigurationsModel.counterText ?? "",
isClinicAdded: false,
textDirection: globalConfigurationsModel.textDirection,
message: globalConfigurationsModel.ticketNoText,
screenTypeEnum: globalConfigurationsModel.screenTypeEnum,
langTypeEnum: globalConfigurationsModel.screenLanguageEnum,
),
),
)
.toList(),
children: otherTickets.map((ticketDetailsModel) {
TicketData ticketData = ticketDetailsModel.ticketModel!;
return Padding(
padding: EdgeInsets.only(top: SizeConfig.getHeightMultiplier() * 0.5),
child: TicketItem(
ticketNo: ticketData.queueNo ?? '',
scale: 0.8,
roomNo: ticketData.roomNo ?? '',
roomText: (globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment ? globalConfigurationsModel.roomText : globalConfigurationsModel.counterText) ?? "",
isClinicAdded: false,
callTypeEnum: ticketData.callTypeEnum,
textDirection: globalConfigurationsModel.textDirection,
screenTypeEnum: globalConfigurationsModel.screenTypeEnum,
langTypeEnum: globalConfigurationsModel.screenLanguageEnum,
globalConfigurationsModel: globalConfigurationsModel,
),
);
}).toList(),
)
]
],

@ -3,16 +3,17 @@ import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/models/global_config_model.dart';
import 'package:hmg_qline/models/ticket_model.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/utilities/extensions.dart';
import 'package:hmg_qline/views/common_widgets/app_texts_widget.dart';
import 'package:hmg_qline/views/main_queue_screen/components/priority_tickets.dart';
import 'package:hmg_qline/views/view_helpers/size_config.dart';
class PriorityTicketsSidelist extends StatelessWidget {
class PriorityTicketsWithSidelist extends StatelessWidget {
final List<TicketDetailsModel> tickets;
final GlobalConfigurationsModel globalConfigurationsModel;
final ScreenOrientationEnum screenOrientationEnum;
const PriorityTicketsSidelist({
const PriorityTicketsWithSidelist({
super.key,
required this.tickets,
required this.globalConfigurationsModel,
@ -21,8 +22,8 @@ class PriorityTicketsSidelist extends StatelessWidget {
@override
Widget build(BuildContext context) {
final priorityTickets = tickets.sublist(0, 3);
final otherTickets = tickets.sublist(3, tickets.length);
final priorityTickets = tickets.sublist(0, AppConstants.thresholdForListUI);
final otherTickets = tickets.sublist(AppConstants.thresholdForListUI, tickets.length);
final List<Widget> children = [
Expanded(flex: 7, child: PriorityTickets(globalConfigurationsModel: globalConfigurationsModel, tickets: priorityTickets)),
@ -62,35 +63,37 @@ class PriorityTicketsSidelist extends StatelessWidget {
height: SizeConfig.getHeightMultiplier() * 0.3,
margin: const EdgeInsets.symmetric(horizontal: 15),
),
// Expanded(
// flex: 5,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// AppText(
// globalConfigurationsModel.callForText ?? "",
// letterSpacing: -2,
// fontHeight: 0.5,
// fontWeight: FontWeight.bold,
// fontSize: SizeConfig.getWidthMultiplier() * 3.8,
// textAlign: TextAlign.center,
// ),
// ],
// ),
// ),
// Container(
// color: Colors.grey.withOpacity(0.5),
// width: 5,
// height: SizeConfig.getHeightMultiplier() * 0.3,
// margin: const EdgeInsets.symmetric(horizontal: 15),
// ),
if (globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment) ...[
Expanded(
flex: 5,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AppText(
globalConfigurationsModel.callForText ?? "",
letterSpacing: -2,
fontHeight: 0.5,
fontWeight: FontWeight.bold,
fontSize: SizeConfig.getWidthMultiplier() * 3.8,
textAlign: TextAlign.center,
),
],
),
),
Container(
color: Colors.grey.withOpacity(0.5),
width: 5,
height: SizeConfig.getHeightMultiplier() * 0.3,
margin: const EdgeInsets.symmetric(horizontal: 15),
),
],
Expanded(
flex: 3,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AppText(
globalConfigurationsModel.counterText ?? "",
(globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment ? globalConfigurationsModel.roomText : globalConfigurationsModel.counterText) ?? "",
letterSpacing: -2,
fontHeight: 0.5,
fontWeight: FontWeight.bold,
@ -109,7 +112,7 @@ class PriorityTicketsSidelist extends StatelessWidget {
shrinkWrap: true,
itemCount: otherTickets.length,
itemBuilder: (ctx, index) {
final ticket = otherTickets[index];
final ticketModel = otherTickets[index].ticketModel;
return Padding(
padding: const EdgeInsets.all(8),
child: Directionality(
@ -123,12 +126,12 @@ class PriorityTicketsSidelist extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
AppText(
ticket.ticketModel!.queueNo ?? "",
ticketModel!.queueNo ?? "",
letterSpacing: -1,
fontHeight: 0.5,
fontWeight: FontWeight.bold,
fontSize: SizeConfig.getWidthMultiplier() * 3.5,
textAlign: TextAlign.center,
fontHeight: 0.5,
fontFamily: globalConfigurationsModel.screenLanguageEnum == LanguageEnum.arabic ? AppStrings.fontNameCairo : AppStrings.fontNamePoppins,
),
],
@ -140,41 +143,50 @@ class PriorityTicketsSidelist extends StatelessWidget {
height: SizeConfig.getHeightMultiplier() * 0.3,
margin: const EdgeInsets.symmetric(horizontal: 15),
),
// Expanded(
// flex: 5,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// AppText(
// "ZahoorWillAddThis",
// color: AppColors.blackColor,
// letterSpacing: -1,
// fontSize: SizeConfig.getWidthMultiplier() * 3,
// fontWeight: FontWeight.w600,
// fontHeight: 0.5,
// ),
// ],
// ),
// ),
// Container(
// color: Colors.grey.withOpacity(0.5),
// width: 5,
// height: SizeConfig.getHeightMultiplier() * 0.3,
// margin: const EdgeInsets.symmetric(horizontal: 15),
// ),
if (globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment) ...[
Expanded(
flex: 5,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: SizedBox(
height: SizeConfig.getWidthMultiplier() * 3,
child: ticketModel.callTypeEnum.getIconByCallType(SizeConfig.getHeightMultiplier() * 0.3),
),
),
const SizedBox(width: 15),
AppText(
ticketModel.callTypeEnum.getMessageByCallType(globalConfigurationsModel, isListView: true),
color: ticketModel.callTypeEnum.getColorByCallType(),
fontSize: SizeConfig.getWidthMultiplier() * 3,
fontWeight: FontWeight.w600,
fontHeight: 0.5,
),
],
),
),
Container(
color: Colors.grey.withOpacity(0.5),
width: 5,
height: SizeConfig.getHeightMultiplier() * 0.3,
margin: const EdgeInsets.symmetric(horizontal: 15),
),
],
Expanded(
flex: 3,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AppText(
ticket.ticketModel!.roomNo ?? "",
ticketModel.roomNo ?? "",
color: AppColors.blackColor,
letterSpacing: -1.5,
fontHeight: 0.5,
fontSize: SizeConfig.getWidthMultiplier() * 3.3,
fontWeight: FontWeight.w600,
fontHeight: 0.5,
fontFamily: globalConfigurationsModel.screenLanguageEnum == LanguageEnum.arabic ? AppStrings.fontNameCairo : AppStrings.fontNamePoppins,
),
],
@ -190,12 +202,6 @@ class PriorityTicketsSidelist extends StatelessWidget {
),
)
];
return (screenOrientationEnum == ScreenOrientationEnum.portraitUp || screenOrientationEnum == ScreenOrientationEnum.portraitDown)
? Row(
children: children,
)
: Column(
children: children,
);
return (screenOrientationEnum == ScreenOrientationEnum.portraitUp || screenOrientationEnum == ScreenOrientationEnum.portraitDown) ? Row(children: children) : Column(children: children);
}
}

@ -1,7 +1,9 @@
import 'package:blinking_text/blinking_text.dart';
import 'package:flutter/material.dart';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/models/global_config_model.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/utilities/extensions.dart';
import 'package:hmg_qline/views/common_widgets/app_texts_widget.dart';
import 'package:hmg_qline/views/view_helpers/size_config.dart';
@ -12,8 +14,9 @@ class TicketItem extends StatelessWidget {
final double scale;
final bool isClinicAdded;
final TextDirection textDirection;
final String message;
final String roomText;
final GlobalConfigurationsModel globalConfigurationsModel;
final CallTypeEnum callTypeEnum;
final ScreenTypeEnum screenTypeEnum;
final LanguageEnum langTypeEnum;
@ -24,8 +27,9 @@ class TicketItem extends StatelessWidget {
required this.roomNo,
required this.scale,
required this.textDirection,
required this.message,
required this.roomText,
required this.globalConfigurationsModel,
required this.callTypeEnum,
required this.screenTypeEnum,
required this.langTypeEnum,
this.blink = false,
@ -34,7 +38,11 @@ class TicketItem extends StatelessWidget {
String getFormattedTicket(String ticketNo, bool isClinicAdded) {
if (isClinicAdded) {
var formattedString = ticketNo.split(" ");
return "${formattedString[0]} ${formattedString[1]}";
if (formattedString.length > 1) {
return "${formattedString[0]} ${formattedString[1]}";
} else {
return ticketNo;
}
}
return ticketNo;
}
@ -56,10 +64,9 @@ class TicketItem extends StatelessWidget {
),
beginColor: Colors.black,
endColor: blink ? Colors.black.withOpacity(0.1) : Colors.black,
// endColor: blink ? AppGlobal.appRedColor : Colors.black,
times: 0,
duration: const Duration(seconds: 1)),
const SizedBox(height: 20),
const SizedBox(height: 30),
screenTypeEnum == ScreenTypeEnum.roomLevelScreen
? const SizedBox()
: Directionality(
@ -68,10 +75,17 @@ class TicketItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: SizedBox(
height: SizeConfig.getWidthMultiplier() * 3,
child: callTypeEnum.getIconByCallType(SizeConfig.getHeightMultiplier() * 0.3),
),
),
const SizedBox(width: 13),
AppText(
message,
color: AppColors.blackColor,
letterSpacing: -1,
callTypeEnum.getMessageByCallType(globalConfigurationsModel, isListView: false),
color: callTypeEnum.getColorByCallType(),
fontSize: SizeConfig.getWidthMultiplier() * 3.8,
fontWeight: FontWeight.w600,
fontHeight: 1,
@ -85,7 +99,7 @@ class TicketItem extends StatelessWidget {
),
AppText(
textDirection == TextDirection.ltr ? "$roomText: $roomNo" : " $roomNo : $roomText",
color: AppColors.blackColor,
color: callTypeEnum.getColorByCallType(),
letterSpacing: -1,
fontSize: SizeConfig.getWidthMultiplier() * 3.8,
fontWeight: FontWeight.w600,

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/utilities/extensions.dart';
import 'package:hmg_qline/utilities/lifecycle_handler.dart';
import 'package:hmg_qline/view_models/queuing_view_model.dart';
import 'package:hmg_qline/view_models/screen_config_view_model.dart';
import 'package:hmg_qline/views/common_widgets/app_footer.dart';
@ -12,12 +14,45 @@ import 'package:hmg_qline/views/main_queue_screen/components/priority_tickets.da
import 'package:hmg_qline/views/main_queue_screen/components/priority_tickets_sidelist.dart';
import 'package:provider/provider.dart';
class MainQueueScreen extends StatelessWidget {
class MainQueueScreen extends StatefulWidget {
const MainQueueScreen({super.key});
@override
State<MainQueueScreen> createState() => _MainQueueScreenState();
}
class _MainQueueScreenState extends State<MainQueueScreen> {
late LifecycleHandler lifecycleHandler;
@override
void initState() {
super.initState();
final ScreenConfigViewModel screenConfigViewModel = context.read<ScreenConfigViewModel>();
lifecycleHandler = LifecycleHandler(
onResumed: () => screenConfigViewModel.onAppResumed(),
onPaused: () => screenConfigViewModel.onAppPaused(),
);
lifecycleHandler.register();
}
@override
void dispose() {
lifecycleHandler.unregister();
super.dispose();
}
Widget dataContent({required BuildContext context}) {
return Consumer2(
builder: (BuildContext context, ScreenConfigViewModel screenConfigViewModel, QueuingViewModel queuingViewModel, Widget? child) {
//
// // TODO: FOR TEST
// queuingViewModel.textToSpeechService.speechText(
// globalConfigurationsModel: screenConfigViewModel.globalConfigurationsModel,
// ticket: queuingViewModel.currentTickets.first,
// );
// screenConfigViewModel.getNextPrayerToShow();
Widget widget = const SizedBox();
String text = AppStrings.awaitingArrivalEng;
String fontFamily = AppStrings.fontNamePoppins;
@ -32,9 +67,20 @@ class MainQueueScreen extends StatelessWidget {
text = AppStrings.awaitingArrivalAr;
fontFamily = AppStrings.fontNameCairo;
}
if (queuingViewModel.currentTickets.isEmpty) {
if (screenConfigViewModel.state == ViewState.error && queuingViewModel.currentTickets.isEmpty) {
widget = intimationWidget(
text: AppStrings.configurationIssueContactAdmin,
fontName: AppStrings.fontNamePoppins,
isForRoomLevel: false,
isRoomNoRequired: false,
isForError: true,
counterNo: 0,
roomText: '',
);
} else if (queuingViewModel.currentTickets.isEmpty) {
bool isForRoomLevel = screenConfigViewModel.globalConfigurationsModel.screenTypeEnum == ScreenTypeEnum.roomLevelScreen;
widget = noPatientInQueue(
widget = intimationWidget(
text: text,
fontName: fontFamily,
isForRoomLevel: isForRoomLevel,
@ -65,7 +111,7 @@ class MainQueueScreen extends StatelessWidget {
],
);
} else if (queuingViewModel.currentTickets.length > AppConstants.thresholdForListUI) {
widget = PriorityTicketsSidelist(
widget = PriorityTicketsWithSidelist(
tickets: queuingViewModel.currentTickets,
globalConfigurationsModel: screenConfigViewModel.globalConfigurationsModel,
screenOrientationEnum: screenConfigViewModel.globalConfigurationsModel.orientationTypeEnum,
@ -125,7 +171,6 @@ class MainQueueScreen extends StatelessWidget {
//TODO: For Testing Only
// context.read<ScreenConfigViewModel>().createAutoTickets(numOfTicketsToCreate: 20);
// context.read<QueuingViewModel>().testSpeech();
// context.read<QueuingViewModel>().voiceCallTicket(ticketData: context.read<QueuingViewModel>().currentTickets.first.ticketModel);
return RotatedBox(
quarterTurns: screenOrientationEnum.getTurnsByOrientation(),
child: AppScaffold(

@ -28,7 +28,7 @@ class SplashScreen extends StatelessWidget {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(), // Loading indicator
child: CircularProgressIndicator(),
),
);
}
@ -47,7 +47,6 @@ class SplashScreen extends StatelessWidget {
// If the data is loaded successfully
if (snapshot.connectionState == ConnectionState.done) {
Future.delayed(const Duration(seconds: 1)).whenComplete(() {
log("context.read<ScreenConfigViewModel>().currentScreenTypeEnum: ${context.read<ScreenConfigViewModel>().currentScreenTypeEnum}");
if (context.read<ScreenConfigViewModel>().currentScreenTypeEnum == ScreenTypeEnum.kioskScreen) {
context.navigateReplaceTo(AppRoutes.kioskMainScreen);
} else {

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: args
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.6.0"
version: "2.7.0"
async:
dependency: transitive
description:
@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: audio_session
sha256: b2a26ba8b7efa1790d6460e82971fde3e398cfbe2295df9dea22f3499d2c12a7
sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac"
url: "https://pub.dev"
source: hosted
version: "0.1.23"
version: "0.1.25"
blinking_text:
dependency: "direct main"
description:
@ -61,18 +61,18 @@ packages:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.19.0"
connectivity_plus:
dependency: "direct main"
description:
name: connectivity_plus
sha256: e0817759ec6d2d8e57eb234e6e57d2173931367a865850c7acea40d4b4f9c27d
sha256: "051849e2bd7c7b3bc5844ea0d096609ddc3a859890ec3a9ac4a65a2620cc1f99"
url: "https://pub.dev"
source: hosted
version: "6.1.1"
version: "6.1.4"
connectivity_plus_platform_interface:
dependency: transitive
description:
@ -101,10 +101,10 @@ packages:
dependency: transitive
description:
name: dbus
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
version: "0.7.11"
equatable:
dependency: transitive
description:
@ -158,6 +158,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_foreground_task:
dependency: "direct main"
description:
name: flutter_foreground_task
sha256: "9f1b25a81db95d7119d2c5cffc654048cbdd49d4056183e1beadc1a6a38f3e29"
url: "https://pub.dev"
source: hosted
version: "9.1.0"
flutter_lints:
dependency: "direct dev"
description:
@ -170,10 +178,10 @@ packages:
dependency: "direct main"
description:
name: flutter_svg
sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123"
sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1
url: "https://pub.dev"
source: hosted
version: "2.0.16"
version: "2.1.0"
flutter_test:
dependency: "direct dev"
description: flutter
@ -183,10 +191,10 @@ packages:
dependency: "direct main"
description:
name: flutter_tts
sha256: cbec5f0447223e1b4c47f893c7f8ef663ac582120c147e4a1e2cade7f2e8b0c8
sha256: bdf2fc4483e74450dc9fc6fe6a9b6a5663e108d4d0dad3324a22c8e26bf48af4
url: "https://pub.dev"
source: hosted
version: "4.2.0"
version: "4.2.3"
flutter_web_plugins:
dependency: transitive
description: flutter
@ -196,26 +204,26 @@ packages:
dependency: "direct main"
description:
name: fluttertoast
sha256: "95f349437aeebe524ef7d6c9bde3e6b4772717cf46a0eb6a3ceaddc740b297cc"
sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1"
url: "https://pub.dev"
source: hosted
version: "8.2.8"
version: "8.2.12"
get_it:
dependency: "direct main"
description:
name: get_it
sha256: f126a3e286b7f5b578bf436d5592968706c4c1de28a228b870ce375d9f743103
sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b
url: "https://pub.dev"
source: hosted
version: "8.0.3"
version: "8.2.0"
http:
dependency: "direct overridden"
description:
name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
url: "https://pub.dev"
source: hosted
version: "1.2.2"
version: "1.5.0"
http_parser:
dependency: transitive
description:
@ -236,42 +244,42 @@ packages:
dependency: "direct main"
description:
name: just_audio
sha256: a49e7120b95600bd357f37a2bb04cd1e88252f7cdea8f3368803779b925b1049
sha256: f978d5b4ccea08f267dae0232ec5405c1b05d3f3cd63f82097ea46c015d5c09e
url: "https://pub.dev"
source: hosted
version: "0.9.42"
version: "0.9.46"
just_audio_platform_interface:
dependency: transitive
description:
name: just_audio_platform_interface
sha256: "0243828cce503c8366cc2090cefb2b3c871aa8ed2f520670d76fd47aa1ab2790"
sha256: "4cd94536af0219fa306205a58e78d67e02b0555283c1c094ee41e402a14a5c4a"
url: "https://pub.dev"
source: hosted
version: "4.3.0"
version: "4.5.0"
just_audio_web:
dependency: transitive
description:
name: just_audio_web
sha256: "9a98035b8b24b40749507687520ec5ab404e291d2b0937823ff45d92cb18d448"
sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663"
url: "https://pub.dev"
source: hosted
version: "0.4.13"
version: "0.4.16"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
url: "https://pub.dev"
source: hosted
version: "10.0.5"
version: "10.0.7"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.0.8"
leak_tracker_testing:
dependency: transitive
description:
@ -292,10 +300,10 @@ packages:
dependency: "direct main"
description:
name: logger
sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1
sha256: "2621da01aabaf223f8f961e751f2c943dbb374dc3559b982f200ccedadaa6999"
url: "https://pub.dev"
source: hosted
version: "2.5.0"
version: "2.6.0"
logging:
dependency: transitive
description:
@ -356,18 +364,18 @@ packages:
dependency: transitive
description:
name: package_info_plus
sha256: "70c421fe9d9cc1a9a7f3b05ae56befd469fe4f8daa3b484823141a55442d858d"
sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
url: "https://pub.dev"
source: hosted
version: "8.1.2"
version: "8.3.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.2.0"
path:
dependency: transitive
description:
@ -385,7 +393,7 @@ packages:
source: hosted
version: "1.1.0"
path_provider:
dependency: transitive
dependency: "direct main"
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
@ -460,10 +468,10 @@ packages:
dependency: "direct main"
description:
name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
url: "https://pub.dev"
source: hosted
version: "6.1.2"
version: "6.1.5"
qr_code_scanner_plus:
dependency: "direct main"
description:
@ -472,6 +480,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.10+1"
restart_app:
dependency: "direct main"
description:
name: restart_app
sha256: "00d5ec3e9de871cedbe552fc41e615b042b5ec654385e090e0983f6d02f655ed"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
rxdart:
dependency: transitive
description:
@ -484,18 +500,18 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev"
source: hosted
version: "2.3.5"
version: "2.5.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d"
sha256: "9f9f3d372d4304723e6136663bb291c0b93f5e4c8a4a6314347f481a33bda2b1"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.7"
shared_preferences_foundation:
dependency: transitive
description:
@ -524,10 +540,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_web
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
@ -548,7 +564,7 @@ packages:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
version: "0.0.0"
source_span:
dependency: transitive
description:
@ -577,10 +593,10 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
version: "1.12.0"
stream_channel:
dependency: transitive
description:
@ -593,10 +609,18 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "3.3.0+3"
term_glyph:
dependency: transitive
description:
@ -609,10 +633,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
version: "0.7.3"
tuple:
dependency: transitive
description:
@ -641,18 +665,18 @@ packages:
dependency: transitive
description:
name: vector_graphics
sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7"
sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de"
url: "https://pub.dev"
source: hosted
version: "1.1.15"
version: "1.1.18"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb"
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
url: "https://pub.dev"
source: hosted
version: "1.1.12"
version: "1.1.13"
vector_graphics_compiler:
dependency: transitive
description:
@ -673,26 +697,26 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
url: "https://pub.dev"
source: hosted
version: "14.2.5"
version: "14.3.0"
wakelock_plus:
dependency: "direct main"
description:
name: wakelock_plus
sha256: "36c88af0b930121941345306d259ec4cc4ecca3b151c02e3a9e71aede83c615e"
sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678
url: "https://pub.dev"
source: hosted
version: "1.2.10"
version: "1.3.2"
wakelock_plus_platform_interface:
dependency: transitive
description:
name: wakelock_plus_platform_interface
sha256: "70e780bc99796e1db82fe764b1e7dcb89a86f1e5b3afb1db354de50f2e41eb7a"
sha256: e10444072e50dbc4999d7316fd303f7ea53d31c824aa5eb05d7ccbdd98985207
url: "https://pub.dev"
source: hosted
version: "1.2.2"
version: "1.2.3"
web:
dependency: transitive
description:
@ -713,10 +737,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69"
sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
url: "https://pub.dev"
source: hosted
version: "5.9.0"
version: "5.10.1"
xdg_directories:
dependency: transitive
description:

@ -40,7 +40,7 @@ dependencies:
get_it: ^8.0.2
connectivity_plus: ^6.1.0
flutter_svg: ^2.0.14
# http: ^1.2.2
# http: ^1.2.2
blinking_text: ^1.0.2
just_audio: ^0.9.42
flutter_tts: ^4.2.0
@ -52,6 +52,9 @@ dependencies:
logger: ^2.4.0
fluttertoast: ^8.2.8
qr_code_scanner_plus: ^2.0.10+1
path_provider: ^2.1.5
flutter_foreground_task: ^9.1.0
restart_app: ^1.3.2
# esc_pos_printer: ^4.0.0 # Ensure you are using the latest version
# esc_pos_utils: ^1.0.0
@ -115,3 +118,5 @@ flutter:
- asset: assets/fonts/Cairo/Cairo-Bold/Cairo-Bold.ttf
weight: 700

Loading…
Cancel
Save