From c0cd969558d7c5e37b60f16d17eb17d0a3b23800 Mon Sep 17 00:00:00 2001 From: FaizHashmi Date: Thu, 30 Jul 2026 16:04:02 +0300 Subject: [PATCH] added context file --- PROJECT_CONTEXT.md | 1763 ++++++++++++++++++++++++++++++++++++++++++++ README.md | 8 + 2 files changed, 1771 insertions(+) create mode 100644 PROJECT_CONTEXT.md diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md new file mode 100644 index 0000000..39b23ce --- /dev/null +++ b/PROJECT_CONTEXT.md @@ -0,0 +1,1763 @@ +# HMG_QLine - Project Context Documentation + +> **Last Updated:** July 30, 2026 +> **Project Type:** Flutter Healthcare Queue Management System +> **Target Platform:** Android AC-Powered Kiosk Displays +> **Status:** Production (Running 24/7 in HMG Hospitals, Saudi Arabia) + +--- + +## 📋 Table of Contents + +1. [Project Overview](#project-overview) +2. [System Architecture](#system-architecture) +3. [Technology Stack](#technology-stack) +4. [Directory Structure](#directory-structure) +5. [Core Features](#core-features) +6. [Data Flow](#data-flow) +7. [Key Components](#key-components) +8. [Android Native Layer](#android-native-layer) +9. [API Integration](#api-integration) +10. [Logging System](#logging-system) +11. [Deployment](#deployment) +12. [Recent Critical Fixes](#recent-critical-fixes) +13. [Configuration System](#configuration-system) +14. [Known Issues & Solutions](#known-issues--solutions) +15. [Testing & Monitoring](#testing--monitoring) +16. [Development Guidelines](#development-guidelines) + +--- + +## 📖 Project Overview + +### Purpose +HMG_QLine is an enterprise-grade hospital queue management system designed for continuous 24/7 operation on AC-powered Android LED kiosk displays across multiple HMG (Habib Medical Group) hospital branches in Saudi Arabia. + +### Key Objectives +- **Real-time Patient Queue Management**: Display and manage patient queues across waiting areas +- **Multi-modal Patient Calling**: Audio tones + Text-to-Speech voice announcements +- **Self-Service Kiosks**: Allow patients to generate tickets via QR scan or ID entry +- **24/7 Reliability**: Run continuously without manual intervention +- **Multi-Branch Support**: Centralized configuration for multiple hospital locations +- **Multi-Language**: Full English/Arabic support with RTL + +### Supported Branches +| Branch | Project ID | Location | +|--------|-----------|----------| +| Takhasusi Main | 16 | Riyadh (uses legacy orientation detection) | +| Suwaidi | 17 | Riyadh | +| Khobar | 60 | Eastern Province | +| Takhasusi Women | 140 | Riyadh | + +--- + +## 🏗️ System Architecture + +### Architecture Pattern +**MVVM (Model-View-ViewModel)** with Provider for state management + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Views (UI) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Splash │ │ Main Queue │ │ Kiosk │ │ +│ │ Screen │ │ Screen │ │ Screen │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└────────────────────────────┬─────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ ViewModels (State) │ +│ ┌────────────────────────┐ ┌────────────────────────┐ │ +│ │ ScreenConfigViewModel │ │ QueuingViewModel │ │ +│ │ - Configuration │ │ - Queue Management │ │ +│ │ - Lifecycle │ │ - SignalR Hub │ │ +│ │ - Health Checks │ │ - Audio/TTS │ │ +│ └────────────────────────┘ └────────────────────────┘ │ +└────────────────────────────┬─────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Services (Business Logic) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Logger │ │ Audio │ │ TTS │ │ Network │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +└────────────────────────────┬─────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Repositories (Data Layer) │ +│ ┌────────────────────────┐ ┌────────────────────────┐ │ +│ │ SignalR Repository │ │ Screen Details Repo │ │ +│ │ - Real-time Hub │ │ - REST API Calls │ │ +│ └────────────────────────┘ └────────────────────────┘ │ +└────────────────────────────┬─────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Data Sources │ +│ ┌────────────────────────┐ ┌────────────────────────┐ │ +│ │ SignalR Hub (WSS) │ │ REST API (HTTPS) │ │ +│ │ - Live updates │ │ - Configuration │ │ +│ │ - Patient calls │ │ - Ticket generation │ │ +│ └────────────────────────┘ └────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Dependency Injection +**GetIt** is used for service location and dependency injection: +```dart +// All singletons registered in lib/config/dependency_injection.dart +- LoggerService +- CrashHandlerService +- ApiClient +- SignalrRepo +- ScreenDetailsRepo +- NativeMethodChannelService +- ConnectivityService +- CacheService +- AudioService +- TextToSpeechService +- ScreenConfigViewModel +- QueuingViewModel +``` + +--- + +## 🛠️ Technology Stack + +### Flutter/Dart +- **Flutter SDK**: >=3.5.4 +- **Dart SDK**: ^3.5.4 +- **State Management**: Provider 6.1.2 +- **DI Container**: GetIt 8.0.2 + +### Key Packages +| Package | Version | Purpose | +|---------|---------|---------| +| `signalr_core` | 1.1.1 | Real-time WebSocket communication | +| `provider` | 6.1.2 | State management | +| `get_it` | 8.0.2 | Dependency injection | +| `connectivity_plus` | 6.1.0 | Network status monitoring | +| `wakelock_plus` | 1.2.10 | Keep device awake | +| `flutter_tts` | 4.2.0 | Text-to-speech announcements | +| `just_audio` | 0.9.42 | Audio tone playback | +| `shared_preferences` | 2.3.5 | Local key-value storage | +| `path_provider` | 2.1.5 | Access to file system | +| `qr_code_scanner_plus` | 2.0.10+1 | QR code scanning | +| `logger` | 2.4.0 | Logging framework | +| `intl` | 0.19.0 | Internationalization | +| `flutter_svg` | 2.0.14 | SVG rendering | +| `marquee` | 2.2.3 | Scrolling text animation | + +### Android Native (Kotlin) +- **Kotlin**: JVM Target 1.8 +- **Compile SDK**: 35 (Android 15) +- **Min SDK**: Flutter default (likely 21+) +- **Dependencies**: + - `androidx.lifecycle:lifecycle-service:2.8.7` + - `androidx.core:core-ktx:1.13.1` + +--- + +## 📁 Directory Structure + +``` +HMG_QLine/ +├── lib/ +│ ├── main.dart # Entry point with crash handling +│ ├── api/ +│ │ └── api_client.dart # HTTP client with logging +│ ├── config/ +│ │ ├── dependency_injection.dart # GetIt DI setup +│ │ └── routes.dart # App routes configuration +│ ├── constants/ +│ │ └── app_constants.dart # All constants (API, colors, assets) +│ ├── models/ +│ │ ├── global_config_model.dart # Main configuration model +│ │ ├── ticket_model.dart # Patient ticket data +│ │ ├── kiosk_*.dart # Kiosk-specific models +│ │ ├── prayers_widget_model.dart # Islamic prayer times +│ │ ├── weathers_widget_model.dart # Weather data +│ │ └── rss_feed_model.dart # News feed +│ ├── repositories/ +│ │ ├── signalR_repo.dart # SignalR hub management +│ │ └── screen_details_repo.dart # REST API calls +│ ├── services/ +│ │ ├── logger_service.dart # File logging with rotation +│ │ ├── crash_handler_service.dart # Global error handling +│ │ ├── connectivity_service.dart # Network monitoring +│ │ ├── audio_service.dart # Audio tone playback +│ │ ├── text_to_speech_service.dart # TTS announcements +│ │ └── cache_service.dart # SharedPreferences wrapper +│ ├── utilities/ +│ │ ├── enums.dart # All enums +│ │ ├── extensions.dart # Dart extensions +│ │ ├── native_method_handler.dart # Platform channel +│ │ ├── foreground_task_handler.dart # Background service +│ │ └── lifecycle_handler.dart # App lifecycle +│ ├── view_models/ +│ │ ├── screen_config_view_model.dart # Configuration & lifecycle VM +│ │ └── queuing_view_model.dart # Queue management VM +│ └── views/ +│ ├── splash_screen/ # Loading screen +│ ├── main_queue_screen/ # Main display (waiting area) +│ ├── kiosk_screens/ # Self-service kiosk UI +│ ├── common_widgets/ # Reusable components +│ └── view_helpers/ # UI utilities +├── android/ +│ ├── app/ +│ │ ├── build.gradle # Android build config +│ │ └── src/main/ +│ │ ├── AndroidManifest.xml # Permissions & components +│ │ └── kotlin/com/example/hmg_qline/hmg_qline/ +│ │ ├── MainActivity.kt # Main activity with kiosk flags +│ │ ├── AlarmScheduler.kt # Daily restart scheduler +│ │ ├── BootReceiver.kt # Auto-start on boot +│ │ ├── RestartAlarmReceiver.kt # Scheduled restart handler +│ │ └── BootForegroundService.kt # Boot service +│ ├── build.gradle # Project-level build +│ └── gradle.properties # Gradle properties +├── assets/ +│ ├── fonts/ # Poppins, Cairo, GE_SS_Two +│ ├── images/ # Logos, icons (SVG/PNG) +│ ├── new_design_icons/ # Updated design assets +│ │ └── weather_icons/ # Weather SVG icons +│ └── tones/ +│ └── call_tone.mp3 # Patient call tone +├── qline_scripts/ # Deployment automation +│ ├── deploy_qline.sh # Deploy APK to devices +│ ├── restart_qline.sh # Restart app +│ ├── extract_logs.sh # Pull logs +│ ├── clear_logs.sh # Clear device logs +│ ├── connect_devices.sh # Connect via ADB +│ ├── kill_qline.sh # Force stop app +│ ├── device_ips_*.txt # IP address lists per branch +│ └── apks/ # APK storage (gitignored) +├── pubspec.yaml # Flutter dependencies +├── README.md # Basic project info +├── QUICK_SUMMARY.md # Quick reference +├── CHANGES_EXPLANATION.md # Detailed changelog +├── AC_POWERED_KIOSK_SUMMARY.md # Kiosk-specific docs +├── DEPLOYMENT_READY.md # (empty) +└── PROJECT_CONTEXT.md # This file + +``` + +--- + +## 🎯 Core Features + +### 1. Queue Management System + +#### Patient Call Types (Enums) +```dart +enum CallTypeEnum { + vitalSign, // 1 - Vital signs measurement room + doctor, // 2 - Doctor consultation + procedure, // 3 - Medical procedures + vaccination, // 4 - Vaccination room + nebulization, // 5 - Nebulization treatment + none, // 6 - No specific type +} +``` + +#### Queue Types +```dart +enum QTypeEnum { + appointment, // 1 - Scheduled appointments + lab, // 2 - Laboratory services + rad, // 3 - Radiology services + general, // 4 - General queue +} +``` + +#### Screen Types +```dart +enum ScreenTypeEnum { + waitingAreaScreen, // 1 - Main waiting area display + roomLevelScreen, // 2 - Specific counter/room + receptionScreen, // 3 - Reception desk + dashboardScreen, // 4 - Admin dashboard + kioskScreen, // 5 - Self-service kiosk +} +``` + +### 2. Real-Time Communication (SignalR) + +#### Hub Connection Features +- **Base URL**: `{baseUrl}/PatientCallingHub?IPAddress={deviceIp}` +- **Auto-reconnect**: Custom delays `[0, 2000, 5000, 10000, 30000]` ms +- **Keep-alive**: 15-second heartbeat +- **Timeout**: 2 minutes +- **Manual retry**: 10 attempts with 5-second delay + +#### Hub Events +```dart +// Receive patient call +connection.on("SendQLinePatientCall", (message) => { + // Adds ticket to queue + // Plays tone + TTS announcement + // Updates display +}); + +// Receive configuration update +connection.on("SendQLineConfig", (message) => { + // Updates global configuration + // Refreshes widgets (weather, prayer, RSS) +}); +``` + +### 3. Audio & Voice System + +#### Audio Service (Just Audio) +- Plays call tone: `assets/tones/call_tone.mp3` +- Mute support based on configuration +- Completion callback triggers TTS + +#### Text-to-Speech Service +- Multi-language: English + Arabic +- Voice speed configuration +- Dynamic message construction based on call type +- Completion callback triggers next ticket in queue + +#### Call Flow +``` +1. Ticket received via SignalR +2. Add to queue (currentTickets or queueTickets) +3. Play audio tone (if enabled) +4. On tone complete → Speak TTS message (if enabled) +5. On TTS complete → Wait {concurrentCallDelaySec} seconds +6. Process next ticket in queue +``` + +### 4. Kiosk Self-Service + +#### Kiosk States +```dart +enum KioskScreenStateEnums { + languageState, // Select language + queueSelectionState, // Select service queue + askPrescriptionState,// Ask for prescription (if applicable) + ticketNumState, // Display generated ticket + busyState, // Loading +} +``` + +#### Kiosk Input Methods +1. **QR Code Scanner**: Scan patient ID barcode +2. **Manual Entry**: Type patient ID on virtual keyboard + +#### Ticket Generation Flow +``` +1. Select language (English/Arabic) +2. Select queue (from kioskQueueList) +3. Scan QR / Enter patient ID +4. API call: /api/Gen/GEN_PatientCallNo_Get +5. Display ticket with queue number +6. Auto-return to language selection after timeout +``` + +### 5. Information Widgets + +#### Weather Widget +- **API**: `/api/PatientCall/WeatherForecast_GetBy5Days` +- **Data**: 5-day forecast with min/max temp +- **Icons**: SVG weather icons (sunny, rainy, cloudy, etc.) +- **Update**: Every midnight or manual refresh + +#### Prayer Times Widget +- **API**: `/api/PatientCall/PrayerTime_Today` +- **Data**: Fajr, Dhuhr, Asr, Maghrib, Isha +- **Display**: Next upcoming prayer with countdown +- **Source**: Based on project lat/long coordinates + +#### RSS Feed Widget +- **API**: `/api/PatientCall/RssFeed_Get` +- **Display**: Marquee scrolling text +- **Update**: Every 2 hours +- **Fallback**: Dummy news text if API fails + +### 6. Health Monitoring System + +#### Health Check Timer (Every 5 Minutes) +```dart +_startHealthCheckTimer() { + Timer.periodic(Duration(minutes: 5), (timer) { + // 1. Calculate actual uptime + final uptime = DateTime.now().difference(appStartTime); + + // 2. Log health status + loggerService.logToFile( + "Health check - App uptime: ${uptime.inHours}h ${uptime.inMinutes % 60}m" + ); + + // 3. Check SignalR connection + syncHubConnectionState(); + + // 4. Track consecutive failures + if (failure) { + healthCheckFailures++; + if (healthCheckFailures >= 3) { + // Auto-restart SignalR connection + } + } + + // 5. Ensure wakelock still enabled + WakelockPlus.enable(); + + // 6. Log connection status + logToFile("Hub connected: $isHubConnected, Internet: $isInternetConnected"); + + // 7. Warn if running >60 hours + if (uptime.inHours > 60) { + logToFile("WARNING: App running for ${uptime.inHours} hours"); + } + }); +} +``` + +--- + +## 🔄 Data Flow + +### Ticket Call Flow +```mermaid +graph TD + A[Backend sends ticket via SignalR] --> B[onHubTicketCall in QueuingViewModel] + B --> C{Is calling in progress?} + C -->|No| D[Add to currentTickets, show on screen] + C -->|Yes| E[Add to queueTickets for later] + D --> F[Acknowledge ticket via API] + F --> G[Play audio tone] + G --> H[Speak TTS message] + H --> I{More tickets in queue?} + I -->|Yes| J[Wait concurrentCallDelaySec] + J --> K[Move next ticket from queueTickets to currentTickets] + K --> F + I -->|No| L[Set isCallingInProgress = false] +``` + +### Configuration Update Flow +```mermaid +graph TD + A[App starts] --> B[Get device IP address] + B --> C[Call /api/PatientCall/Common_Config_GetByIP] + C --> D[Parse GlobalConfigurationsModel] + D --> E[Set screen type, queue type, orientation] + E --> F{Widgets required?} + F -->|Weather| G[Fetch weather data] + F -->|Prayers| H[Fetch prayer times] + F -->|RSS| I[Fetch RSS feed] + G --> J[Start periodic update timer] + H --> J + I --> J + J --> K[Update every 10 minutes / midnight] +``` + +### Lifecycle & Recovery Flow +```mermaid +graph TD + A[App initialized] --> B[Record appStartTime] + B --> C[Start health check timer: 5 min] + C --> D[Start midnight check timer: 10 min] + D --> E[Listen to network changes] + E --> F{Network disconnected?} + F -->|Yes| G[Set isInternetConnected = false] + G --> H[Set isHubConnected = false] + F -->|No| I{Network reconnected?} + I -->|Yes| J[Set isInternetConnected = true] + J --> K[Restart SignalR connection] + K --> L[Health check validates connection] + L --> M{3 consecutive failures?} + M -->|Yes| N[Force restart SignalR] + M -->|No| O[Continue monitoring] +``` + +--- + +## 🔑 Key Components + +### 1. ScreenConfigViewModel + +**Responsibilities:** +- Device IP detection +- Global configuration management +- Lifecycle management (onAppPaused, onAppResumed, onAppDetached) +- Health monitoring (5-minute timer) +- Widget data fetching (weather, prayers, RSS) +- Kiosk queue selection +- QR scanner management +- Ticket generation for kiosk + +**Key Properties:** +```dart +String currentScreenIP // Device IP address +GlobalConfigurationsModel globalConfigurationsModel +ScreenTypeEnum currentScreenTypeEnum +QTypeEnum currentQTypeEnum +bool isInternetConnected +bool isHubConnected +DateTime appStartTime // For uptime tracking +int healthCheckFailures // Consecutive failure count +``` + +**Key Methods:** +```dart +initializeScreenConfigVM() // Initialize on app start +waitForIPAndInitializeConfigVM() // Wait for IP then init +getGlobalConfigurationsByIP() // Fetch config from API +listenNetworkConnectivity() // Monitor network changes +_startHealthCheckTimer() // Health monitoring every 5 min +syncHubConnectionState() // Check SignalR status +onAppPaused() // Log when app backgrounded +onAppResumed() // Re-enable wakelock +onAppDetached() // Log when app killed +``` + +### 2. QueuingViewModel + +**Responsibilities:** +- SignalR hub connection management +- Patient ticket queue management +- Audio tone playback coordination +- TTS voice announcement coordination +- Ticket acknowledgment to backend + +**Key Properties:** +```dart +List currentTickets // Currently displayed +List queueTickets // Waiting queue +bool isCallingInProgress // Calling state lock +``` + +**Key Methods:** +```dart +initializeQueueingVM() // Initialize services +startHubConnection() // Connect to SignalR hub +stopHubConnection() // Disconnect from hub +onHubTicketCall() // Handle incoming ticket +onHubConfigCall() // Handle config update +onHubReconnected() // Handle reconnection +onHubDisconnected() // Handle disconnection +addNewTicket() // Add ticket to queue +callTicketOnScreen() // Display & announce ticket +onToneCompleted() // Trigger TTS after tone +onVoiceCompleted() // Process next ticket +waitAndCallNextTicketIfAvailable() // Queue management +``` + +### 3. SignalrRepo + +**Implementation:** +```dart +class SignalrRepoImp implements SignalrRepo { + HubConnection? connection; + + // Check if connected + bool get isConnected => connection?.state == HubConnectionState.connected; + + // Start connection with auto-reconnect + startHubConnection({ + required String deviceIp, + required Function(List?) onHubTicketCall, + required Function(dynamic) onHubConfigCall, + required Function(dynamic) onHubReconnected, + required Function(dynamic) onHubDisconnected, + }) { + connection = HubConnectionBuilder() + .withUrl("$baseUrl?IPAddress=$deviceIp") + .withAutomaticReconnect([0, 2000, 5000, 10000, 30000]) + .build(); + + connection!.serverTimeoutInMilliseconds = 120000; + connection!.keepAliveIntervalInMilliseconds = 15000; + + // Register event handlers + connection!.on("SendQLinePatientCall", onHubTicketCall); + connection!.on("SendQLineConfig", onHubConfigCall); + + // Handle reconnection + connection!.onreconnected(onHubReconnected); + connection!.onclose((exception) { + // Auto-retry up to 10 times + }); + + await connection!.start(); + } +} +``` + +### 4. LoggerService + +**Features:** +- File-based logging to external storage +- Three log types: Data, Error, Connectivity +- Log rotation based on time (48h) or size (2MB) +- IP-based log file naming + +**Log File Structure:** +``` +/storage/emulated/0/Android/data/com.example.hmg_qline.hmg_qline/files/logs/ +├── 12.4.5.1_data_logs.txt +├── 12.4.5.1_error_logs.txt +└── 12.4.5.1_connectivity_logs.txt +``` + +**Log Format:** +``` +[2026-07-30 02:30:15 PM] [SOURCE: _startHealthCheckTimer -> screen_config_view_model.dart] DATA: Health check performed - App uptime: 14h 23m (started: 2026-07-30 00:07:00) +``` + +### 5. CrashHandlerService + +**Global Error Handling:** +```dart +setupGlobalErrorHandlers() { + // Flutter framework errors + FlutterError.onError = (FlutterErrorDetails details) { + handleCrash(error: details.exception, stackTrace: details.stack); + }; + + // Platform dispatcher errors + PlatformDispatcher.instance.onError = (error, stack) { + handleCrash(error: error, stackTrace: stack); + return true; + }; + + // Zone errors handled by runZonedGuarded in main.dart +} +``` + +### 6. GlobalConfigurationsModel + +**Key Configuration Fields:** +```dart +class GlobalConfigurationsModel { + int? id; + int? projectID; // Branch identifier + ScreenTypeEnum screenTypeEnum; // Screen type + QTypeEnum qTypeEnum; // Queue type + ScreenOrientationEnum orientationTypeEnum; + + // Display settings + int screenMaxDisplayPatients = 16; + int concurrentCallDelaySec = 1; + + // Audio/Voice + bool isToneReq = false; + bool isVoiceReq = false; + LanguageEnum voiceLanguageEnum; + LanguageEnum screenLanguageEnum; + + // Widget visibility + bool isWeatherReq = false; + bool isPrayerTimeReq = false; + bool isRssFeedReq = false; + + // Location data + double? projectLatitude; + double? projectLongitude; + int? cityKey; + + // Clinic prefix validation + bool globalClinicPrefixReq = false; + bool clinicPrefixReq = true; + + // Kiosk configuration + List? kioskQueueList; + List? kioskLanguageConfigList; + + // Legacy support flag + bool isFromTakhasusiMain = false; // projectID == 16 + + // Clinic prefix validation method + bool isClinicPrefixAdded(String ticketNo) { + return RegExp(r'^[A-Za-z]{3} W-').hasMatch(ticketNo); + // Returns true for: "ABC W-123" format + } +} +``` + +--- + +## 🤖 Android Native Layer + +### MainActivity.kt + +**Kiosk Display Mode Flags:** +```kotlin +override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Keep screen on at all times (AC-powered kiosk) + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) + window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) + window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD) + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + + // Initialize daily restart alarm at 00:15 + initializeRestartAlarm() +} +``` + +**Method Channel Support:** +```kotlin +configureFlutterEngine(flutterEngine: FlutterEngine) { + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) + .setMethodCallHandler { call, result -> + when (call.method) { + "reopenApp" -> bringAppToForeground() + "restartApp" -> restartApplication() + "restartDevice" -> restartDevice() // Requires root + "clearAudioCache" -> clearAudioResources() + "clearAllResources" -> clearAllNativeResources() + "scheduleRestartAlarm" -> scheduleRestartAlarm(hour, minute) + "cancelRestartAlarm" -> cancelRestartAlarm() + "canScheduleExactAlarms" -> checkAlarmPermission() + "requestExactAlarmPermission" -> requestAlarmPermission() + } + } +} +``` + +### AlarmScheduler.kt + +**Android Version Compatibility:** +```kotlin +fun scheduleRestartAlarm(context: Context, hour: Int = 0, minute: Int = 15) { + when { + // Android 14+ (API 34+) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> { + if (alarmManager.canScheduleExactAlarms()) { + alarmManager.setExactAndAllowWhileIdle(...) + } else { + alarmManager.setAndAllowWhileIdle(...) // Fallback + } + } + // Android 12-13 (API 31-33) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + if (alarmManager.canScheduleExactAlarms()) { + alarmManager.setExactAndAllowWhileIdle(...) + } + } + // Android 6-11 (API 23-30) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> { + alarmManager.setExactAndAllowWhileIdle(...) + } + // Android < 6 + else -> { + alarmManager.setExact(...) + } + } +} +``` + +### BootBroadcastReceiver.kt + +**Auto-Start on Device Boot:** +```kotlin +override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_BOOT_COMPLETED) { + // 1. Schedule daily restart alarm + AlarmScheduler.scheduleRestartAlarm(context, 0, 15) + + // 2. Start foreground service to launch app + val serviceIntent = Intent(context, BootForegroundService::class.java) + context.startForegroundService(serviceIntent) + } +} +``` + +### AndroidManifest.xml + +**Critical Permissions:** +```xml + + + + + + + + +``` + +**Activity Configuration:** +```xml + + +``` + +--- + +## 🌐 API Integration + +### Base URLs + +```dart +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 = baseUrlUat; // Current environment +``` + +### API Endpoints + +#### Configuration APIs +```dart +// Get device configuration by IP +GET /api/PatientCall/Common_Config_GetByIP +Parameters: { ipAddress: string, apiKey: string } +Returns: GlobalConfigurationsModel + +// Get waiting area screen config +GET /api/PatientCall/WaitingAreaScreen_Config_Get +Parameters: { ipAddress: string, apiKey: string } +Returns: Screen configuration + +// Get RSS feed +GET /api/PatientCall/RssFeed_Get +Parameters: { languageId: int } +Returns: RssFeedModel + +// Get weather forecast +GET /api/PatientCall/WeatherForecast_GetBy5Days +Parameters: { cityKey: string } +Returns: WeathersWidgetModel (5-day forecast) + +// Get prayer times +GET /api/PatientCall/PrayerTime_Today +Parameters: { latitude: double, longitude: double } +Returns: PrayersWidgetModel +``` + +#### Ticket APIs +```dart +// Create ticket for kiosk +POST /api/Gen/GEN_PatientCallNo_Get +Body: { + projectID: int, + queueID: int, + patientID: int, + apiKey: string +} +Returns: KioskPatientTicket + +// Acknowledge ticket viewed +POST /api/Common/TicketQueueAck_Insert +Body: { + ticketQueueID: int, + ipAddress: string, + qType: int, + apiKey: string +} + +// Update call request (appointment only) +POST /api/PatientCall/CallRequest_QueueUpdate +Body: { + ticketId: int, + ipAddress: string, + callType: int, + apiKey: string +} +``` + +#### SignalR Hub +```dart +// Connect to hub +WSS /PatientCallingHub?IPAddress={deviceIp} + +// Subscribe to events +Hub.on("SendQLinePatientCall", callback) // Patient call notification +Hub.on("SendQLineConfig", callback) // Configuration update +``` + +### API Constants + +```dart +class ApiConstants { + static String apiKey = 'EE17D21C7943485D9780223CCE55DCE5'; + + // Hub events + static String sendQLinePatientCall = "SendQLinePatientCall"; + static String sendQLineConfig = "SendQLineConfig"; +} +``` + +--- + +## 📝 Logging System + +### Log Types + +```dart +enum LogTypeEnum { + data, // Health checks, events, lifecycle + error, // Errors and crashes + connectivity, // Network status, SignalR events +} +``` + +### Log File Locations + +```bash +# External storage path +/storage/emulated/0/Android/data/com.example.hmg_qline.hmg_qline/files/logs/ + +# Files (named by device IP) +{IP}_data_logs.txt # General data and health checks +{IP}_error_logs.txt # Errors and crashes +{IP}_connectivity_logs.txt # Network and SignalR events +``` + +### Log Rotation Rules + +1. **Time-based**: Clear logs after 48 hours +2. **Size-based**: Clear when file exceeds 2 MB +3. **Timestamp**: Last cleared time stored in SharedPreferences + +### Log Format + +``` +[YYYY-MM-DD HH:MM:SS AM/PM] [SOURCE: method -> file.dart] TYPE: message +``` + +**Examples:** +``` +[2026-07-30 02:30:15 PM] [SOURCE: _startHealthCheckTimer -> screen_config_view_model.dart] DATA: Health check performed - App uptime: 14h 23m (started: 2026-07-30 00:07:00) + +[2026-07-30 02:30:16 PM] [SOURCE: _startHealthCheckTimer -> screen_config_view_model.dart] CONNECTIVITY: Health check - Hub connected: true, Internet: true + +[2026-07-30 02:35:20 PM] [SOURCE: onHubDisconnected -> queueing_view_model.dart] CONNECTIVITY: Hub disconnected: Connection lost + +==================== CRASH ==================== +[2026-07-30 02:40:00 PM] +CONTEXT: QueuingViewModel.triggerAsyncCrash +ERROR: Exception: Test async crash +STACKTRACE: +#0 QueuingViewModel.triggerAsyncCrash +#1 ... +================================================== +``` + +### Logging Methods + +```dart +// Data logging +loggerService.logToFile( + message: "Health check performed", + source: "method -> file.dart", + type: LogTypeEnum.data, +); + +// Error logging +loggerService.logError("Error message"); + +// Crash logging +loggerService.logCrash( + error: exception, + stackTrace: stack, + context: "method context", +); +``` + +--- + +## 🚀 Deployment + +### Build Process + +```bash +# Clean build +cd /Users/faiz-hashmi/StudioProjects/HMG_QLine +flutter clean +flutter pub get +flutter build apk --release + +# Output location +build/app/outputs/flutter-apk/app-release.apk +``` + +### Shell Scripts (qline_scripts/) + +#### 1. Connect Devices +```bash +sh connect_devices.sh -ipfile device_ips_all.txt + +# Connects to all devices via ADB over TCP/IP +# Uses format: adb connect {ip}:5555 +``` + +#### 2. Deploy APK +```bash +sh deploy_qline.sh -ipfile device_ips_all.txt -version 9.4 + +# Steps: +# 1. Connect to all devices +# 2. Install APK: adb -s {ip}:5555 install -r apks/app-release-9.4.apk +# 3. Launch app: adb -s {ip}:5555 shell am start -n {package}/{activity} +# 4. Log installation in qline_installation_records.txt +``` + +#### 3. Restart App +```bash +sh restart_qline.sh -ipfile device_ips_all.txt + +# Steps: +# 1. Force stop: adb shell am force-stop {package} +# 2. Launch: adb shell am start -n {package}/{activity} +``` + +#### 4. Extract Logs +```bash +# Single device +sh extract_logs.sh -ip 10.70.6.143 + +# All devices +sh extract_all_logs.sh -ipfile device_ips_all.txt + +# Pulls files from: +# /storage/emulated/0/Android/data/{package}/files/logs/ +# To: logs/{ip}_{timestamp}/ +``` + +#### 5. Clear Logs +```bash +# Single device +sh clear_logs.sh -ip 10.70.6.143 + +# All devices +sh clear_logs_all.sh -ipfile device_ips_all.txt + +# Removes all log files from device +``` + +#### 6. Kill App +```bash +sh kill_qline.sh -ipfile device_ips_all.txt + +# Force stop on all devices +# adb shell am force-stop {package} +``` + +### Device IP Lists + +``` +device_ips_all.txt # All hospital devices +device_ips_tak.txt # Takhasusi Main (projectID: 16) +device_ips_tak_women.txt # Takhasusi Women (projectID: 140) +device_ips_swd.txt # Suwaidi (projectID: 17) +device_ips_khobar.txt # Khobar (projectID: 60) +device_ips_cs_test.txt # Cloud Solutions test devices +one_device_ip.txt # Single device for testing +``` + +**Format (one IP per line):** +``` +10.70.6.143 +10.70.6.144 +10.70.6.145 +``` + +--- + +## 🔥 Recent Critical Fixes (March 2026) + +### Problem: App Dying After 14 Hours + +**Symptoms:** +- App ran successfully for ~14 hours +- Complete log silence after 8:36 AM +- No crash logs, no lifecycle events +- Required manual restart by user + +**Root Causes:** +1. Android OS killed app due to "idle" detection (even on AC power) +2. Incorrect uptime tracking (calculated time since midnight, not app start) +3. No foreground service protection +4. No automatic failure recovery +5. Missing kiosk display mode flags + +### Solutions Implemented + +#### 1. ✅ Fixed Uptime Tracking Bug + +**Before (WRONG):** +```dart +// Calculated time since last midnight check +DateTime.now().difference(lastChecked).inHours +// At 6:31 AM, showed "6 hours" but app ran since midnight (6.5 hours) +``` + +**After (CORRECT):** +```dart +DateTime appStartTime = DateTime.now(); // Track at initialization + +// Calculate actual uptime +final uptimeHours = DateTime.now().difference(appStartTime).inHours; +final uptimeMinutes = DateTime.now().difference(appStartTime).inMinutes % 60; + +logToFile("Health check - App uptime: ${uptimeHours}h ${uptimeMinutes}m (started: $appStartTime)"); +``` + +#### 2. ✅ Android Kiosk Display Mode + +**File:** `android/app/src/main/kotlin/.../MainActivity.kt` + +**Added:** +```kotlin +override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Prevent Android from treating app as "idle" + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) + window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) + window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD) + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + + Log.d(TAG, "MainActivity created - Kiosk display mode active") +} +``` + +**File:** `android/app/src/main/AndroidManifest.xml` + +**Added:** +```xml + + +``` + +#### 3. ✅ Health Check System + +**File:** `lib/view_models/screen_config_view_model.dart` + +**Features:** +```dart +Timer? _healthCheckTimer; +int healthCheckFailures = 0; + +void _startHealthCheckTimer() { + _healthCheckTimer = Timer.periodic(Duration(minutes: 5), (timer) { + try { + // 1. Calculate actual uptime + final uptime = DateTime.now().difference(appStartTime); + + // 2. Log health check + logToFile("Health check - App uptime: ${uptime.inHours}h ${uptime.inMinutes % 60}m"); + + // 3. Check SignalR connection + try { + syncHubConnectionState(); + healthCheckFailures = 0; // Reset on success + } catch (e) { + healthCheckFailures++; + + // Auto-recover after 3 failures + if (healthCheckFailures >= 3) { + logToFile("CRITICAL: 3 consecutive failures - attempting recovery"); + await queuingViewModel.stopHubConnection(); + await Future.delayed(Duration(seconds: 2)); + await queuingViewModel.startHubConnection(); + } + } + + // 4. Ensure wakelock enabled + await WakelockPlus.enable(); + + // 5. Log connection status + logToFile("Hub connected: $isHubConnected, Internet: $isInternetConnected"); + + // 6. Warn if running >60 hours + if (uptime.inHours > 60) { + logToFile("WARNING: App running for ${uptime.inHours} hours"); + } + + // 7. Keep UI thread active + notifyListeners(); + + } catch (e) { + healthCheckFailures++; + logError("Health check failed: $e"); + } + }); +} +``` + +#### 4. ✅ Enhanced Lifecycle Tracking + +**File:** `lib/view_models/screen_config_view_model.dart` + +**Added:** +```dart +Future onAppPaused() async { + final uptimeHours = DateTime.now().difference(appStartTime).inHours; + logToFile("[onAppPaused] - App uptime: ${uptimeHours}h - WARNING: App going to background!"); +} + +Future onAppResumed() async { + final uptimeHours = DateTime.now().difference(appStartTime).inHours; + logToFile("[onAppResumed] - App uptime: ${uptimeHours}h"); + + // Re-enable wakelock + await WakelockPlus.enable(); + + // Verify connections + syncHubConnectionState(); +} + +Future onAppDetached() async { + final uptimeHours = DateTime.now().difference(appStartTime).inHours; + logToFile("[onAppDetached] - App uptime: ${uptimeHours}h - CRITICAL: App being killed!"); +} +``` + +**Impact:** +- Now see EXACTLY when app goes to background or gets killed +- If app stays alive correctly, should NEVER see these logs + +#### 5. ✅ SignalR Connection Improvements + +**File:** `lib/repositories/signalR_repo.dart` + +**Enhanced:** +```dart +connection = HubConnectionBuilder() + .withUrl("$hubBaseURL?IPAddress=$deviceIp") + .withAutomaticReconnect([0, 2000, 5000, 10000, 30000]) // Custom retry delays + .build(); + +connection!.serverTimeoutInMilliseconds = 120000; // 2 minutes +connection!.keepAliveIntervalInMilliseconds = 15000; // 15 seconds + +// Manual reconnection on close +connection!.onclose((exception) async { + reconnectAttempts++; + if (reconnectAttempts < maxReconnectAttempts) { + logToFile("SignalR reconnect attempt #$reconnectAttempts"); + await Future.delayed(Duration(seconds: 5)); + try { + await connection!.start(); + logToFile("SignalR reconnected after disconnect"); + reconnectAttempts = 0; + } catch (e) { + logError("Reconnect failed: $e"); + } + } +}); +``` + +#### 6. ✅ Clinic Prefix Feature + +**File:** `lib/models/global_config_model.dart` + +**Added:** +```dart +bool globalClinicPrefixReq = false; +bool clinicPrefixReq = true; + +bool isClinicPrefixAdded(String ticketNo) { + // Check for format: "XXX W-XX" (3 letters + space + W-) + final hasClinicPrefix = RegExp(r'^[A-Za-z]{3} W-').hasMatch(ticketNo); + return hasClinicPrefix; +} + +// Returns true for: "ABC W-123", "FMC W-T-4" +// Returns false for: "W-123", "A W-123", "ABCD W-123" +``` + +### Files Modified + +1. `lib/view_models/screen_config_view_model.dart` - Uptime, health checks, lifecycle +2. `lib/models/global_config_model.dart` - Clinic prefix feature +3. `lib/repositories/signalR_repo.dart` - Connection improvements +4. `android/app/src/main/kotlin/.../MainActivity.kt` - Kiosk display mode +5. `android/app/src/main/AndroidManifest.xml` - Activity flags + +--- + +## ⚙️ Configuration System + +### IP-Based Configuration + +Each device is identified by its IP address and receives specific configuration: + +```dart +// Device IP detection (automatic or test) +if (useTestIP) { + currentScreenIP = AppConstants.testIP; // "12.4.5.1" +} else { + currentScreenIP = await connectivityService.getCurrentScreenIP(); +} + +// Fetch configuration +GlobalConfigurationsModel config = await screenDetailsRepo.getGlobalScreenConfigurations( + ipAddress: currentScreenIP +); +``` + +### Project ID Detection + +```dart +// Takhasusi Main uses legacy orientation detection +value.isFromTakhasusiMain = value.projectID == AppConstants.projectIdTakhasusiMain; // 16 + +if (value.isFromTakhasusiMain) { + log("✅ This device will use getTurnsByOrientationForOlderVersions()"); +} else { + log("✅ This device will use getTurnsByOrientationForNewVersions()"); +} +``` + +### Configuration Hierarchy + +``` +1. Device IP → API call → GlobalConfigurationsModel +2. Screen Type (waiting/room/kiosk/dashboard) +3. Queue Type (appointment/lab/rad/general) +4. Orientation (portrait/landscape) +5. Display Settings (max patients, delay) +6. Audio/Voice Settings (tone, voice, language) +7. Widget Visibility (weather, prayers, RSS) +8. Kiosk Queues & Languages +``` + +### Dynamic Configuration Updates + +Configuration can be updated via SignalR: + +```dart +// Backend sends new config +Hub → SendQLineConfig → onHubConfigCall() + +// Updates applied +globalConfigurationsModel = GlobalConfigurationsModel.fromJson(data); +updateGlobalConfigurationsModel(value: globalConfigurationsModel, needNotify: true); +getInfoWidgetsDetailsFromServer(); // Refresh widgets +``` + +--- + +## ⚠️ Known Issues & Solutions + +### Issue 1: App Dies After 14 Hours +**Status:** ✅ FIXED (March 2026) + +**Solution:** +- Added kiosk display mode flags +- Implemented health check system +- Fixed uptime tracking +- Added lifecycle logging + +### Issue 2: Android Doze Mode +**Status:** ⚠️ REQUIRES DEVICE CONFIGURATION + +**Solution:** +Some Android devices (Xiaomi, Oppo, Vivo) have aggressive task killers: + +**Manual Steps:** +1. Settings > Apps > QLine +2. Enable "Auto-start" +3. Set Battery to "No restrictions" +4. Disable "Battery optimization" + +**ADB Command:** +```bash +adb shell dumpsys deviceidle whitelist +com.example.hmg_qline.hmg_qline +``` + +### Issue 3: Memory Leaks (48+ Hour Uptime) +**Status:** ✅ MITIGATED + +**Solution:** +- Daily restart scheduled at 00:15 AM +- Resource cleanup on lifecycle events +- System.gc() calls at strategic points + +### Issue 4: TTS Stopping on Some Devices +**Status:** ⚠️ DEVICE-SPECIFIC + +**Solution:** +- Clear audio cache via `clearAudioCache()` method channel +- Force stop TTS service: `am force-stop com.google.android.tts` +- Restart app if TTS completely fails + +### Issue 5: SignalR Connection Drops +**Status:** ✅ MITIGATED + +**Solution:** +- Auto-reconnect with exponential backoff +- Health check monitors connection every 5 min +- Auto-restart after 3 consecutive failures + +### Issue 6: Exact Alarm Permission (Android 14+) +**Status:** ⚠️ REQUIRES USER PERMISSION + +**Solution:** +```kotlin +// Check permission +if (!alarmManager.canScheduleExactAlarms()) { + // Open settings + Intent intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM) + startActivity(intent) +} +``` + +--- + +## 📊 Testing & Monitoring + +### Testing Checklist + +#### Deployment Testing +- [ ] Build APK successfully +- [ ] Deploy to test device via ADB +- [ ] App starts automatically +- [ ] Check log: "MainActivity created - Kiosk display mode active" +- [ ] Verify health checks appear every 5 minutes + +#### Short-Term Testing (1 Hour) +- [ ] Uptime increments correctly (0h 5m → 0h 10m → 0h 15m → 1h 0m) +- [ ] SignalR connection established +- [ ] Hub connected: true in logs +- [ ] Internet connected: true in logs +- [ ] No `[onAppPaused]` or `[onAppDetached]` logs + +#### Medium-Term Testing (24 Hours) +- [ ] Health check logs continuous (no gaps) +- [ ] App uptime reaches 24h +- [ ] Hub connection stable +- [ ] No lifecycle events (paused/detached) +- [ ] Midnight refresh occurs (if widgets enabled) + +#### Long-Term Testing (48+ Hours) +- [ ] App runs past 14-hour checkpoint (critical) +- [ ] App reaches 48h uptime +- [ ] Check for >60h warning log +- [ ] Scheduled restart at 00:15 works correctly +- [ ] Auto-start on boot works (if device rebooted) + +#### Network Resilience Testing +- [ ] Disconnect WiFi for 10 minutes +- [ ] Check log: "Hub disconnected" +- [ ] Reconnect WiFi +- [ ] Check log: "SignalR reconnect attempt #X" +- [ ] Check log: "SignalR reconnected after disconnect" +- [ ] Verify auto-recovery after 3 failures + +#### Audio/Voice Testing +- [ ] Audio tone plays when ticket called +- [ ] TTS voice announcement speaks (if enabled) +- [ ] Language switching works (English/Arabic) +- [ ] Mute settings respected +- [ ] Sequential calls work with delay + +#### Kiosk Testing +- [ ] Language selection screen appears +- [ ] Queue selection shows all options +- [ ] QR scanner works +- [ ] Manual ID entry works +- [ ] Ticket generation successful +- [ ] Thank you screen displays +- [ ] Auto-return to language selection after timeout + +### Monitoring Commands + +#### Check if App Running +```bash +adb shell ps | grep hmg_qline +``` + +#### View Logcat (MainActivity) +```bash +adb logcat | grep MainActivity +``` + +#### View Logcat (SignalR) +```bash +adb logcat | grep SignalR +``` + +#### Check Device Idle State +```bash +adb shell dumpsys deviceidle +``` + +#### Check Battery Optimization Status +```bash +adb shell dumpsys deviceidle whitelist | grep hmg_qline +``` + +#### View Last 100 App Logs +```bash +adb logcat -t 100 | grep hmg_qline +``` + +#### Pull All Logs from Device +```bash +cd qline_scripts +sh extract_logs.sh -ip 10.70.6.143 +``` + +--- + +## 🔧 Development Guidelines + +### Code Style + +#### Naming Conventions +- **Classes:** PascalCase (`ScreenConfigViewModel`) +- **Methods:** camelCase (`startHubConnection()`) +- **Variables:** camelCase (`currentScreenIP`) +- **Constants:** camelCase (`static String baseUrl`) +- **Enums:** PascalCase (`ScreenTypeEnum`) +- **Files:** snake_case (`screen_config_view_model.dart`) + +#### File Organization +```dart +// 1. Imports +import 'dart:async'; +import 'package:flutter/material.dart'; + +// 2. Class declaration +class MyClass extends ChangeNotifier { + + // 3. Private constants + static const String _CONSTANT = "value"; + + // 4. Dependencies + final MyService myService; + + // 5. Constructor + MyClass({required this.myService}); + + // 6. Public properties + String publicProperty = ""; + + // 7. Private properties + String _privateProperty = ""; + + // 8. Public methods + void publicMethod() {} + + // 9. Private methods + void _privateMethod() {} + + // 10. Overrides + @override + void dispose() { + super.dispose(); + } +} +``` + +### Logging Best Practices + +```dart +// Always include source file in logs +loggerService.logToFile( + message: "Meaningful message with data: $value", + source: "methodName -> file_name.dart", + type: LogTypeEnum.data, +); + +// Use appropriate log types +LogTypeEnum.data // Health checks, state changes, events +LogTypeEnum.error // Errors, exceptions +LogTypeEnum.connectivity // Network, SignalR events + +// Include context in error logs +try { + // code +} catch (e, stackTrace) { + loggerService.logCrash( + error: e, + stackTrace: stackTrace, + context: "methodName -> file_name.dart", + ); +} +``` + +### Error Handling + +```dart +// Always use try-catch for async operations +Future myAsyncMethod() async { + try { + final result = await somethingRisky(); + // handle success + } catch (e, stackTrace) { + loggerService.logCrash( + error: e, + stackTrace: stackTrace, + context: "myAsyncMethod -> my_file.dart", + ); + // handle error gracefully + } +} + +// Provide fallbacks +final value = config.someValue ?? defaultValue; + +// Validate before use +if (data != null && data.isNotEmpty) { + processData(data); +} +``` + +### State Management + +```dart +// Always notifyListeners after state change +void updateValue(String newValue) { + myValue = newValue; + notifyListeners(); // Trigger UI rebuild +} + +// Use getIt for singleton access +final viewModel = getIt.get(); + +// Consumer for reactive UI +Consumer( + builder: (context, viewModel, child) { + return Text(viewModel.currentScreenIP); + }, +) +``` + +### API Calls + +```dart +// Always check response for null +final response = await screenDetailsRepo.getConfig(ip: currentScreenIP); + +if (response == null) { + loggerService.logError("Config API returned null"); + updateViewState(ViewState.error); + return; +} + +// Use response +updateConfig(response); +``` + +### Testing Additions + +When adding new features: + +1. **Add health check logs** for monitoring +2. **Add error handling** with proper logging +3. **Add null checks** for all external data +4. **Test with network disconnection** scenarios +5. **Test with long uptime** (24+ hours) +6. **Update this documentation** with new features + +--- + +## 📚 Additional Documentation Files + +- **README.md** - Basic project information +- **QUICK_SUMMARY.md** - Quick reference for March 2026 changes +- **CHANGES_EXPLANATION.md** - Detailed explanation of fixes +- **AC_POWERED_KIOSK_SUMMARY.md** - Kiosk-specific documentation +- **PROJECT_CONTEXT.md** - This file (comprehensive context) + +--- + +## 🔄 Version History + +| Version | Date | Changes | +|---------|------|---------| +| 9.4 | July 2026 | Current production version | +| 9.2 | March 2026 | Critical fixes: uptime tracking, health checks, kiosk flags | +| 9.0 | February 2026 | SignalR improvements, lifecycle tracking | +| 8.x | 2025 | Previous stable versions | + +--- + +## 📞 Emergency Contacts & Procedures + +### If App Dies Again + +**Collect Information:** +1. Last 200 lines of logs before app stopped +2. Android version and device model +3. Screenshot of Apps > QLine > Battery settings +4. Output of: `adb shell dumpsys deviceidle whitelist` + +**Immediate Actions:** +1. Check device battery optimization settings +2. Verify app is whitelisted from doze mode +3. Check if exact alarm permission granted (Android 12+) +4. Review logs for `[onAppPaused]` or `[onAppDetached]` +5. Check for lifecycle events before silence + +**Advanced Troubleshooting:** +```bash +# Force whitelist from doze +adb shell dumpsys deviceidle whitelist +com.example.hmg_qline.hmg_qline + +# Check app status +adb shell ps | grep hmg_qline + +# Check if app is optimized +adb shell dumpsys battery | grep hmg_qline + +# Force prevent background kill +adb shell cmd appops set com.example.hmg_qline.hmg_qline RUN_IN_BACKGROUND allow +``` + +--- + +## 🎯 Future Improvements + +### Planned Enhancements +1. **Remote Monitoring**: Send health status to backend server +2. **Watchdog Service**: External process that restarts app if killed +3. **Scheduled Restart**: Auto-restart at 3 AM daily (already implemented at 00:15) +4. **Memory Profiling**: Track and log memory usage trends +5. **Firebase Crashlytics**: Cloud-based crash analytics +6. **Network Quality Metrics**: Log ping times and bandwidth + +### Technical Debt +1. Clean up commented code in MainActivity.kt +2. Add unit tests for ViewModels +3. Add integration tests for SignalR connection +4. Implement proper i18n instead of hardcoded strings +5. Refactor large ViewModels into smaller components + +--- + +## 📖 Glossary + +- **AC-Powered Kiosk**: Android device permanently plugged into power, not battery-powered +- **App Standby Buckets**: Android 9+ feature that restricts background apps +- **Doze Mode**: Android 6+ battery optimization that restricts background processing +- **Health Check**: Periodic verification that app is functioning correctly +- **Hub**: SignalR WebSocket server for real-time communication +- **Kiosk Mode**: Display mode where app runs continuously without user intervention +- **QType**: Queue type (appointment, lab, rad, general) +- **SignalR**: Real-time communication library using WebSockets +- **TTS**: Text-to-Speech for voice announcements +- **Uptime**: Duration the app has been running since last start +- **Wakelock**: Android API to prevent device from sleeping + +--- + +**END OF CONTEXT DOCUMENTATION** + +> This document should be updated whenever significant changes are made to the project. +> Last updated by: AI Assistant +> Last update date: July 30, 2026 + diff --git a/README.md b/README.md index eff007c..c11f7a2 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,14 @@ 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. +## 📚 Documentation + +**For complete project context, architecture, and implementation details, see:** +- **[PROJECT_CONTEXT.md](PROJECT_CONTEXT.md)** - Comprehensive documentation for developers and AI models +- **[QUICK_SUMMARY.md](QUICK_SUMMARY.md)** - Quick reference for recent changes +- **[CHANGES_EXPLANATION.md](CHANGES_EXPLANATION.md)** - Detailed changelog +- **[AC_POWERED_KIOSK_SUMMARY.md](AC_POWERED_KIOSK_SUMMARY.md)** - Kiosk-specific documentation + ## Features - Patient queue management across all screens