Compare commits

...

19 Commits

Author SHA1 Message Date
Sathwik C S
964f2c4178 Edit README.md 2025-07-24 17:26:27 +05:30
Sathwik
e64fb09144 created 132 for gitub 2025-07-24 11:22:24 +05:30
sathwik
05ce1d8411 added print option in panels, created , activity fragment , and viewmodel , added the xml views and cards 2025-07-16 18:21:12 +05:30
sathwik
b6ae754f1b updated the uto manifest 2025-07-16 18:17:21 +05:30
sathwik
c3acb41590 updated the uto sumbit 2025-07-15 14:57:19 +05:30
sathwik
2ee6b65ca5 implementing the secure storage using ndk 2025-07-08 11:25:04 +05:30
sathwik
28b8409207 implementing the secure storage using ndk 2025-07-01 16:27:55 +05:30
sathwik
5c56ef7630 implementing the secure storage using ndk 2025-07-01 16:27:30 +05:30
sathwik
2fbc280b4a implementing the secure storage 2025-06-19 11:58:47 +05:30
sathwikcs
e9c8a9a887 R&D app 2025-05-15 19:04:23 +05:30
sathwikcs
e7ecf7eedf R&D app 2025-05-15 12:57:53 +05:30
sathwikcs
e3435f2182 131-STABLE-super-server 2025-03-13 15:50:04 +05:30
sathwikcs
f6cdd64ef6 131-STABLE-super-server 2025-03-13 15:49:25 +05:30
sathwikcs
7ad21ea5a9 131-STABLE-super-server 2025-03-13 15:23:15 +05:30
sathwikcs
6c89efeb5e 131-STABLE-smi... 2025-03-13 14:08:04 +05:30
sathwikcs
63eaf1cadc Merge remote-tracking branch 'origin/dev-server-2.1.131-stable-smi' into dev-server-2.1.131-stable-smi 2025-03-13 12:45:23 +05:30
sathwikcs
c180f7be31 131-STABLE-smi... 2025-03-13 12:45:04 +05:30
sathwikcs
c243ec6c8f 131-STABLE-smi... 2025-03-13 12:37:23 +05:30
sathwikcs
7c9238591b 131-STABLE-molbio 2025-03-04 13:36:33 +05:30
33 changed files with 1630 additions and 140 deletions

View File

@@ -2,6 +2,16 @@
## current version : 2.1.132 ## current version : 2.1.132
### Release key ### Release key
key0: prime24 key0: prime24
### Version 133.3
Printer not added yet
## added secure server
## aded molbio server
## advanced with ping network check

View File

@@ -7,6 +7,7 @@ plugins {
id 'androidx.navigation.safeargs.kotlin' id 'androidx.navigation.safeargs.kotlin'
id 'kotlin-kapt' id 'kotlin-kapt'
id 'com.google.firebase.crashlytics' id 'com.google.firebase.crashlytics'
id 'kotlin-parcelize'
} }
//apply plugin: 'kotlin-android' //apply plugin: 'kotlin-android'
@@ -20,9 +21,14 @@ android {
applicationId "in.sminnovations.hpostesting.server" applicationId "in.sminnovations.hpostesting.server"
minSdk 30 minSdk 30
targetSdk 34 targetSdk 34
versionCode 134 versionCode 138
versionName "2.1.131" versionName "2.1.132"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ''
}
}
} }
@@ -54,6 +60,12 @@ android {
packagingOptions { packagingOptions {
exclude 'mockito-extensions/org.mockito.plugins.MockMaker' exclude 'mockito-extensions/org.mockito.plugins.MockMaker'
} }
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.22.1'
}
}
// testOptions { // testOptions {
// unitTests { // unitTests {
// includeAndroidResources = true // includeAndroidResources = true
@@ -171,4 +183,7 @@ dependencies {
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.17' implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.17'
implementation 'androidx.security:security-crypto:1.1.0-alpha03' implementation 'androidx.security:security-crypto:1.1.0-alpha03'
} }

View File

@@ -1,37 +0,0 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "in.sminnovations.hpostesting.dev",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 127,
"versionName": "2.1.127",
"outputFile": "app-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/app-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/app-release.dm"
]
}
],
"minSdkVersionForDexing": 21
}

View File

@@ -24,13 +24,12 @@
<uses-feature android:name="android.hardware.usb.host" /> <uses-feature android:name="android.hardware.usb.host" />
<uses-permission android:name="android.permission.USB_PERMISSION" /> <uses-permission android:name="android.permission.USB_PERMISSION" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.ACCOUNT_MANAGER" <uses-permission
android:name="android.permission.ACCOUNT_MANAGER"
tools:ignore="ProtectedPermissions" /> tools:ignore="ProtectedPermissions" />
<application <application
android:name="com.example.hpostesting.HPOSTestingApplication" android:name="com.example.hpostesting.HPOSTestingApplication"
android:allowBackup="true" android:allowBackup="true"
@@ -43,10 +42,13 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.HPOSTesting" android:theme="@style/Theme.HPOSTesting"
tools:targetApi="31"> tools:targetApi="31">
<activity
android:name="com.example.hpostesting.presentation.reportgen.ReportActivity"
android:exported="false" />
<activity <activity
android:name="com.example.hpostesting.presentation.main_base.UpdateValuesActivity" android:name="com.example.hpostesting.presentation.main_base.UpdateValuesActivity"
android:exported="false" android:exported="false"
android:screenOrientation="portrait"/> android:screenOrientation="portrait" />
<service <service
android:name="com.example.hpostesting.util.MyAuthenticatorService" android:name="com.example.hpostesting.util.MyAuthenticatorService"

View File

@@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.18.1)
project("native-server")
# Create the shared library from server.cpp
add_library(
native-server
SHARED
server.cpp
)
# Find Android logging library
find_library(
log-lib
log
)
# Link logging library to our native lib
target_link_libraries(
native-server
${log-lib}
)

View File

@@ -0,0 +1,53 @@
#include <jni.h>
#include <string>
#include <unordered_map>
std::vector<std::pair<int, std::pair<std::string, std::unordered_map<std::string, std::string>>>> firebaseDataOrdered = {
{0, {"dev", {{"apiKey", "AIzaSyAVdNGCev_AFX0qmuZJF6kxzRzXUTeAW5I"}, {"appId", "1:650071678820:android:a53292637abb7c0d6c6471"}, {"projectId", "hpos-af3cc"}, {"storageBucket", "hpos-af3cc.appspot.com"}}}},
{1, {"qc-qa", {{"apiKey", "AIzaSyC4d4GhWHr_NMJAeBvnztQ_yQ3Qe9MAnLs"}, {"appId", "1:1004619739289:android:3dbcefb10ea99654e5c808"}, {"projectId", "hpos-qa"}, {"storageBucket", "hpos-qa.appspot.com"}}}},
{2, {"prod", {{"apiKey", "AIzaSyA_O0oZGzy3Kiw9fiLQ3OFPME-xQJP88vs"}, {"appId", "1:1012719870714:android:d8d7c962d96f4097de5f43"}, {"projectId", "hpos-prod"}, {"storageBucket", "hpos-prod.appspot.com"}}}},
{3, {"pre prod", {{"apiKey", "AIzaSyDYySi27LioZGNisP1NfnNU5inJX_0FT38"}, {"appId", "1:121176529204:android:e6841fdac57bdc95bbed61"}, {"projectId", "hpos-preprod"}, {"storageBucket", "hpos-preprod.appspot.com"}}}},
// {4, {"SMI R&D", {{"apiKey", "AIzaSyAVxpilbB804iRCCpMBDjjmdZhQguctGHM"}, {"appId", "1:327771387914:android:a3b4ed2e42ea6e38dc87f3"}, {"projectId", "hposrandd"}, {"storageBucket", "hposrandd.firebasestorage.app"}}}},
{5, {"molbio", {{"apiKey", "molbio"}, {"appId", "molbio"}, {"projectId", "molbio"}, {"storageBucket", "molbio"}}}}
};
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_hpostesting_firebase_FirebaseManager_getFirebaseDataMap(JNIEnv *env,jobject thiz) {
jclass mapClass = env->FindClass("java/util/LinkedHashMap");
jmethodID init = env->GetMethodID(mapClass, "<init>", "()V");
jmethodID put = env->GetMethodID(mapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
jobject outerMap = env->NewObject(mapClass, init);
for (const auto &entry : firebaseDataOrdered) {
int order = entry.first;
const std::string &outerKeyStr = entry.second.first;
const auto &innerMapData = entry.second.second;
jobject innerMap = env->NewObject(mapClass, init);
// "int" field for order
jstring intKey = env->NewStringUTF("int");
jstring intValue = env->NewStringUTF(std::to_string(order).c_str());
env->CallObjectMethod(innerMap, put, intKey, intValue);
env->DeleteLocalRef(intKey);
env->DeleteLocalRef(intValue);
// actual firebase config keys
for (const auto &innerPair : innerMapData) {
jstring key = env->NewStringUTF(innerPair.first.c_str());
jstring value = env->NewStringUTF(innerPair.second.c_str());
env->CallObjectMethod(innerMap, put, key, value);
env->DeleteLocalRef(key);
env->DeleteLocalRef(value);
}
jstring outerKey = env->NewStringUTF(outerKeyStr.c_str());
env->CallObjectMethod(outerMap, put, outerKey, innerMap);
env->DeleteLocalRef(outerKey);
env->DeleteLocalRef(innerMap);
}
return outerMap;
}

View File

@@ -0,0 +1,254 @@
package com.example.hpostesting.FHIRFormater
import com.example.hpostesting.data.model.patient.HemoCubeTestData
import com.example.hpostesting.data.model.patient.UserData
import org.json.JSONArray
import org.json.JSONObject
class RecommendedFHIRConverter {
private fun createPatient(data: HemoCubeTestData, useStandardCodes: Boolean): JSONObject {
val patient = JSONObject()
patient.put("resourceType", "Patient")
patient.put("id", "patient-${data.sampleid}")
// Structured name
val nameArray = JSONArray()
val nameObj = JSONObject()
nameObj.put("use", "official")
nameObj.put("text", data.name)
nameArray.put(nameObj)
patient.put("name", nameArray)
// Gender (FHIR uses "male", "female", "other", "unknown")
patient.put("gender", data.gender.lowercase())
// Birth date estimation from age (optional: validate or remove if unknown)
val birthYear = try {
2025 - data.age.toInt()
} catch (e: Exception) {
null
}
if (birthYear != null) patient.put("birthDate", "$birthYear-01-01")
// ABHA ID as identifier
if (data.abhaId.isNotBlank()) {
val identifiers = JSONArray()
val identifier = JSONObject()
identifier.put("system", "https://healthid.ndhm.gov.in")
identifier.put("value", data.abhaId)
identifiers.put(identifier)
patient.put("identifier", identifiers)
}
return patient
}
private fun createDevice(data: HemoCubeTestData): JSONObject {
val device = JSONObject()
device.put("resourceType", "Device")
device.put("id", "device-${data.deviceSerialNumber}")
device.put("serialNumber", data.deviceSerialNumber)
// Device type as codeable concept
val type = JSONObject()
type.put("text", data.deviceType)
device.put("type", type)
// Device version
if (!data.appVersion.isNullOrBlank()) {
val versions = JSONArray()
val version = JSONObject()
version.put("type", JSONObject().put("text", "appVersion"))
version.put("value", data.appVersion)
versions.put(version)
device.put("version", versions)
}
return device
}
private fun createHemoglobinObservation(data: HemoCubeTestData, useStandardCodes: Boolean): JSONObject {
val observation = JSONObject()
observation.put("resourceType", "Observation")
observation.put("id", "hemoglobin-${data.sampleid}")
observation.put("status", if (data.testStatus == true) "final" else "preliminary")
// Standard LOINC code or fallback
val code = JSONObject()
val codingArray = JSONArray()
if (useStandardCodes) {
val coding = JSONObject()
coding.put("system", "http://loinc.org")
coding.put("code", "718-7") // LOINC for Hemoglobin [Mass/volume] in Blood
coding.put("display", "Hemoglobin [Mass/volume] in Blood")
codingArray.put(coding)
}
code.put("coding", codingArray)
code.put("text", "Hemoglobin Analysis")
observation.put("code", code)
// Subject reference to patient
observation.put("subject", JSONObject().put("reference", "Patient/patient-${data.sampleid}"))
// Device reference
observation.put("device", JSONObject().put("reference", "Device/device-${data.deviceSerialNumber}"))
// Time of observation
if (!data.testTime.isNullOrBlank()) {
observation.put("effectiveDateTime", data.testTime)
}
// Main result: if hb3 or hb4 exists, use as value
val hbValue = data.hb3 ?: data.hb4
if (hbValue != null) {
val valueQuantity = JSONObject()
valueQuantity.put("value", hbValue)
valueQuantity.put("unit", "g/dL")
valueQuantity.put("system", "http://unitsofmeasure.org")
valueQuantity.put("code", "g/dL")
observation.put("valueQuantity", valueQuantity)
}
// Optional extensions
val components = JSONArray()
fun addComponent(codeText: String, value: Double?) {
if (value != null) {
val comp = JSONObject()
comp.put("code", JSONObject().put("text", codeText))
comp.put("valueQuantity", JSONObject().put("value", value))
components.put(comp)
}
}
addComponent("HB3", data.hb3)
addComponent("HB4", data.hb4)
addComponent("Device Ratio", data.deviceRatio)
addComponent("Blood Group", null) // can't use non-numeric here unless coded properly
if (components.length() > 0) {
observation.put("component", components)
}
return observation
}
private fun createBundleEntry(resource: JSONObject): JSONObject {
val entry = JSONObject()
entry.put("fullUrl", "urn:uuid:${resource.getString("id")}")
entry.put("resource", resource)
entry.put("request", JSONObject().apply {
put("method", "POST")
put("url", resource.getString("resourceType"))
})
return entry
}
fun convertToFHIR(data: HemoCubeTestData, useStandardCodes: Boolean = false): String {
val bundle = JSONObject()
bundle.put("resourceType", "Bundle")
bundle.put("id", "hemocube-test-${data.sampleid}")
bundle.put("type", "collection")
val entries = JSONArray()
entries.put(createBundleEntry(createPatient(data, useStandardCodes)))
entries.put(createBundleEntry(createDevice(data)))
entries.put(createBundleEntry(createHemoglobinObservation(data, useStandardCodes)))
bundle.put("entry", entries)
return bundle.toString(2)
}
}
fun sus() {
val sampleData = HemoCubeTestData(
sampleid = 1234,
_id = "local-001",
name = "John Doe",
incubationTime = "15",
bloodGroup = "B+",
age = "30",
state = "Karnataka",
abhaId = "29-1234-4567",
userImageURL = "https://example.com/user.jpg",
location = UserData.Location(latitude = 12.9716, longitude = 77.5946),
reportUploadTime = "2025-07-15T09:30:00+05:30",
testType = "HEMOCUBE",
testTime = "2025-07-15T09:15:00+05:30",
testStatus = true,
gender = "male",
localFlag = true,
deviceId = "device-5678",
appVersion = "1.4.2",
deviceSerialNumber = "SN-001-XY",
deviceType = "HEMOCUBE",
kitSerial = "KIT-20250715",
resultData = "Raw data goes here",
led1Buffer = 0.123,
led2Buffer = 0.456,
led3Buffer = 0.789,
led4Buffer = 0.321,
led1Sample = 0.654,
led2Sample = 0.987,
led3Sample = 0.432,
led4Sample = 0.210,
led1Average = 0.300,
led2Average = 0.500,
led3Average = 0.400,
led4Average = 0.450,
abs1 = 0.12,
abs2 = 0.23,
abs3 = 0.34,
abs4 = 0.45,
hb3 = 13.5,
hb4 = 13.7,
led1Gain1 = 1.1,
led2Gain1 = 1.2,
led3Gain1 = 1.3,
led4Gain1 = 1.4,
deviceRatio = 0.87,
calculatedRatio = 0.89,
predictedDenovixRatio = 0.91,
slopeRatio = 0.93,
coefficients = "a=1.0,b=2.0,c=3.0",
classificationResult = "Normal",
prdClassification = "PRD1",
deviceRatioClass = "A",
slopeRatioClass = "B",
borderlineMethod2Class = "C",
errorMessages = "",
batteryLevel = "85%",
batteryCapacity = "2800mAh",
batteryMaxCapacity = "3000mAh",
batteryTemperature = "35C",
batteryVoltage = "3.7V",
molbioFlag = false,
quickCapture = false,
solution = "Blood Sample",
concentration = "13.5g/dL",
filter = "None",
volume = "20uL",
isCSVCreated = true,
labName = "ABC Labs",
cuvetteSize = "Standard",
district = "Bangalore Urban",
centerName = "Health Center 1",
ipAddress = "192.168.1.101",
configUpdatedRecent = "true"
)
val converter = RecommendedFHIRConverter()
val standardFhir = converter.convertToFHIR(sampleData, useStandardCodes = true)
println("FHIR Bundle Output:\n$standardFhir")
}

View File

@@ -15,7 +15,6 @@ package com.example.hpostesting
import android.app.Application import android.app.Application
import com.example.hpostesting.firebase.FirebaseManager import com.example.hpostesting.firebase.FirebaseManager
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.FirebaseFirestoreSettings import com.google.firebase.firestore.FirebaseFirestoreSettings
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -23,8 +22,16 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import javax.inject.Inject import javax.inject.Inject
@HiltAndroidApp @HiltAndroidApp
class HPOSTestingApplication : Application() { class HPOSTestingApplication : Application() {
companion object {
init {
System.loadLibrary("native-server")
}
}
@Inject @Inject
lateinit var firebaseManager: FirebaseManager lateinit var firebaseManager: FirebaseManager
override fun onCreate() { override fun onCreate() {
@@ -39,4 +46,5 @@ class HPOSTestingApplication : Application() {
val firestore = firebaseManager.getCurrentFirestore() val firestore = firebaseManager.getCurrentFirestore()
firestore.firestoreSettings = firestoreSettings firestore.firestoreSettings = firestoreSettings
} }
} }

View File

@@ -32,7 +32,6 @@ import retrofit2.http.PUT
import retrofit2.http.Part import retrofit2.http.Part
interface MolbioResultApi { interface MolbioResultApi {
@PUT("deviceService/results/HPOS") @PUT("deviceService/results/HPOS")
suspend fun uploadResults( suspend fun uploadResults(
@Body molbioV2ResultRequest: MolbioV2ResultRequest, @Body molbioV2ResultRequest: MolbioV2ResultRequest,
@@ -47,6 +46,7 @@ interface MolbioResultApi {
// suspend fun deviceUpdate( // suspend fun deviceUpdate(
// @Body deviceUpdateRequest: DeviceUpdateRequest, // @Body deviceUpdateRequest: DeviceUpdateRequest,
// ): ResponseBody // ): ResponseBody
@POST("deviceService/device/getUpdate") @POST("deviceService/device/getUpdate")
suspend fun deviceUpdate( suspend fun deviceUpdate(
@Body deviceUpdateRequest: DeviceUpdateRequest, @Body deviceUpdateRequest: DeviceUpdateRequest,
@@ -62,7 +62,6 @@ interface MolbioResultApi {
@Part logFile: MultipartBody.Part, @Part logFile: MultipartBody.Part,
): UploadLogsResponse ): UploadLogsResponse
@PUT("deviceService/device/uploadDeviceDiagnostics") @PUT("deviceService/device/uploadDeviceDiagnostics")
suspend fun deviceDiagnostics( suspend fun deviceDiagnostics(
@Body deviceDiagnosticsRequest: DeviceDiagnosticsRequest @Body deviceDiagnosticsRequest: DeviceDiagnosticsRequest

View File

@@ -28,8 +28,8 @@ object Constants {
const val DOCUMENT_ID_FOR_UPDATE ="2o71vBKLqdgEKYtFG8Lb" const val DOCUMENT_ID_FOR_UPDATE ="2o71vBKLqdgEKYtFG8Lb"
const val ABHA_APP_PACKAGE = "in.ndhm.phr" const val ABHA_APP_PACKAGE = "in.ndhm.phr"
const val MOLBIO_INTEGRATION = false var MOLBIO_INTEGRATION = true
const val FIREBASE_INTEGRATION = true var FIREBASE_INTEGRATION = false
const val deviceProvisionEmail = "HPOS_provisioner@bigtec.co.in" const val deviceProvisionEmail = "HPOS_provisioner@bigtec.co.in"
const val deviceProvisionPassword = "f2ab0e7f9d69" const val deviceProvisionPassword = "f2ab0e7f9d69"
const val DEVICE_ID_API = "deviceIDAPI" const val DEVICE_ID_API = "deviceIDAPI"
@@ -44,9 +44,9 @@ object Constants {
const val DELAY_BETWEEN_COMMANDS: Long = 1000 const val DELAY_BETWEEN_COMMANDS: Long = 1000
const val TEST_RIGHT_TOTAL_PIXEL = 3694 const val TEST_RIGHT_TOTAL_PIXEL = 3694
const val MAXIMUM_TEST_ALLOWED_BEFORE_REFERENCE = 34 const val MAXIMUM_TEST_ALLOWED_BEFORE_REFERENCE = 34
const val MAXIMUM_TEST_ALLOWED_BEFORE_REFERENCE10MM = 9 const val MAXIMUM_TEST_ALLOWED_BEFORE_REFERENCE10MM = 9
const val MAXIMUM_TEST_ALLOWED_BEFORE_REFERENCE10MM_RANDD= 129
const val RANGE_IN_RESULT_CALCULATIONS = 10 const val RANGE_IN_RESULT_CALCULATIONS = 10

View File

@@ -243,9 +243,10 @@ object AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideNetworkMonitor(@ApplicationContext context: Context): NetworkMonitor { fun provideNetworkMonitor(
return NetworkMonitor(context) @ApplicationContext context: Context
): NetworkMonitor {
return NetworkMonitor(context).apply { startMonitoring() }
} }
} }

View File

@@ -14,7 +14,9 @@ package com.example.hpostesting.firebase
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Process import android.os.Process
import android.util.Log
import androidx.core.content.edit import androidx.core.content.edit
import com.example.hpostesting.data.constant.Constants
import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions import com.google.firebase.FirebaseOptions
import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.FirebaseFirestore
@@ -27,20 +29,52 @@ import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class FirebaseManager @Inject constructor(private val context: Context) { class FirebaseManager @Inject constructor(private val context: Context) {
private var defaultServers = mutableListOf<FirebaseConfig>()
external fun getFirebaseDataMap(): Map<String, Map<String, String>>
private val map: Map<String, Map<String, String>> = getFirebaseDataMap()
companion object {
init {
System.loadLibrary("native-server") //use NDK ro secure somethings
}
}
private val sharedPreferencesServerConfig = context.getSharedPreferences("FirebaseConfigPrefs", Context.MODE_PRIVATE) private val sharedPreferencesServerConfig = context.getSharedPreferences("FirebaseConfigPrefs", Context.MODE_PRIVATE)
private val sharedPreferencesDev = context.getSharedPreferences("DevMode",Context.MODE_PRIVATE) private val sharedPreferencesDev = context.getSharedPreferences("DevMode",Context.MODE_PRIVATE)
private var currentFirestore: FirebaseFirestore = FirebaseFirestore.getInstance() private var currentFirestore: FirebaseFirestore = FirebaseFirestore.getInstance()
private var currentStorage: FirebaseStorage = FirebaseStorage.getInstance() private var currentStorage: FirebaseStorage = FirebaseStorage.getInstance()
var updateStatus = false private var updateStatus = false
private fun Map<String, String>.toFirebaseConfig(server: String): FirebaseConfig? {
val apiKey = this["apiKey"] ?: return null
val appId = this["appId"] ?: return null
val projectId = this["projectId"] ?: return null
val storageBucket = this["storageBucket"] ?: return null
return FirebaseConfig(
serverName = server,
projectId = projectId,
appId = appId,
apiKey = apiKey,
storageBucket = storageBucket
)
}
fun populate() {
defaultServers.clear()
map.forEach { (server, config) ->
config.toFirebaseConfig(server)?.let {
defaultServers.add(it)
} ?: Log.w("Firebase", "Skipping $server due to missing keys")
}
}
private val defaultServers = listOf(
// i have it in different place look down
firebaseConfigProd, firebaseConfigDev , firebaseConfigQaQc, firebaseConfigPreProd
)
init { init {
populate()
val lastSelectedServer = getLastSelectedServer() val lastSelectedServer = getLastSelectedServer()
switchServer(lastSelectedServer) switchServer(lastSelectedServer)
} }
@@ -61,11 +95,17 @@ class FirebaseManager @Inject constructor(private val context: Context) {
fun getCurrentStorage(): FirebaseStorage = currentStorage fun getCurrentStorage(): FirebaseStorage = currentStorage
fun switchServer(firebaseConfig: FirebaseConfig) { fun switchServer(firebaseConfig: FirebaseConfig) {
saveSelectedServer(firebaseConfig) if (firebaseConfig.serverName =="molbio" ||firebaseConfig.apiKey == "molbio"){
val firebaseApp = getOrInitializeFirebaseApp(firebaseConfig) saveSelectedServer(firebaseConfig)
currentFirestore = FirebaseFirestore.getInstance(firebaseApp) makeMolbioAsBackEnd()
currentStorage = FirebaseStorage.getInstance(firebaseApp) }else {
updateStatus = true makeSMIBackEnd()
saveSelectedServer(firebaseConfig)
val firebaseApp = getOrInitializeFirebaseApp(firebaseConfig)
currentFirestore = FirebaseFirestore.getInstance(firebaseApp)
currentStorage = FirebaseStorage.getInstance(firebaseApp)
updateStatus = true
}
} }
@@ -73,7 +113,6 @@ class FirebaseManager @Inject constructor(private val context: Context) {
val existingApp = FirebaseApp.getApps(context).find { it.name == firebaseConfig.serverName } val existingApp = FirebaseApp.getApps(context).find { it.name == firebaseConfig.serverName }
return if (existingApp != null) { return if (existingApp != null) {
existingApp existingApp
} else { } else {
@@ -125,6 +164,16 @@ class FirebaseManager @Inject constructor(private val context: Context) {
fun getAvailableServers(): List<FirebaseConfig> { fun getAvailableServers(): List<FirebaseConfig> {
return defaultServers return defaultServers
} }
private fun makeMolbioAsBackEnd(){
Constants.FIREBASE_INTEGRATION = false
Constants.MOLBIO_INTEGRATION = true
restartApp()
}
private fun makeSMIBackEnd(){
Constants.FIREBASE_INTEGRATION = true
Constants.MOLBIO_INTEGRATION = false
}
} }
fun restartApp(context: Context) { fun restartApp(context: Context) {
@@ -141,34 +190,51 @@ fun delayedRestart(context: Context) {
} }
} }
private val firebaseConfigDev = FirebaseConfig( //
serverName = "dev", //
apiKey = "AIzaSyAVdNGCev_AFX0qmuZJF6kxzRzXUTeAW5I", //private val firebaseConfigDev = FirebaseConfig(
appId = "1:650071678820:android:a53292637abb7c0d6c6471", // serverName = "dev",
projectId = "hpos-af3cc", // apiKey = "AIzaSyAVdNGCev_AFX0qmuZJF6kxzRzXUTeAW5I",
storageBucket = "hpos-af3cc.appspot.com" // appId = "1:650071678820:android:a53292637abb7c0d6c6471",
) // projectId = "hpos-af3cc",
// storageBucket = "hpos-af3cc.appspot.com"
private val firebaseConfigQaQc = FirebaseConfig( //)
serverName = "qc-qa", //private val firebaseConfigQaQc = FirebaseConfig(
apiKey = "AIzaSyC4d4GhWHr_NMJAeBvnztQ_yQ3Qe9MAnLs", // serverName = "qc-qa",
appId = "1:1004619739289:android:3dbcefb10ea99654e5c808", // apiKey = "AIzaSyC4d4GhWHr_NMJAeBvnztQ_yQ3Qe9MAnLs",
projectId = "hpos-qa", // appId = "1:1004619739289:android:3dbcefb10ea99654e5c808",
storageBucket = "hpos-qa.appspot.com" // projectId = "hpos-qa",
) // storageBucket = "hpos-qa.appspot.com"
//)
private val firebaseConfigProd = FirebaseConfig( //
serverName = "prod", //private val firebaseConfigProd = FirebaseConfig(
apiKey = "AIzaSyA_O0oZGzy3Kiw9fiLQ3OFPME-xQJP88vs", // serverName = "prod",
appId = "1:1012719870714:android:d8d7c962d96f4097de5f43", // apiKey = "AIzaSyA_O0oZGzy3Kiw9fiLQ3OFPME-xQJP88vs",
projectId = "hpos-prod", // appId = "1:1012719870714:android:d8d7c962d96f4097de5f43",
storageBucket = "hpos-prod.appspot.com" // projectId = "hpos-prod",
) // storageBucket = "hpos-prod.appspot.com"
//)
private val firebaseConfigPreProd = FirebaseConfig( //
serverName = "pre prod", //private val firebaseConfigPreProd = FirebaseConfig(
apiKey = "AIzaSyDYySi27LioZGNisP1NfnNU5inJX_0FT38", // serverName = "pre prod",
appId = "1:121176529204:android:e6841fdac57bdc95bbed61", // apiKey = "AIzaSyDYySi27LioZGNisP1NfnNU5inJX_0FT38",
projectId = "hpos-preprod", // appId = "1:121176529204:android:e6841fdac57bdc95bbed61",
storageBucket = "hpos-preprod.appspot.com" // projectId = "hpos-preprod",
) // storageBucket = "hpos-preprod.appspot.com"
//)
//
//private val firebaseConfigSmiRandD= FirebaseConfig(
// serverName = "SMI R&D",
// apiKey = "AIzaSyAVxpilbB804iRCCpMBDjjmdZhQguctGHM",
// appId = "1:327771387914:android:a3b4ed2e42ea6e38dc87f3",
// projectId = "hposrandd",
// storageBucket = "hposrandd.firebasestorage.app"
//)
//
//private val configMolbio = FirebaseConfig(
// serverName = "molbio",
// apiKey = "molbio",
// appId = "molbio",
// projectId = "molbio",
// storageBucket = "molbio"
//)

View File

@@ -22,10 +22,12 @@ import android.util.Log
import android.widget.EditText import android.widget.EditText
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContentProviderCompat.requireContext
import com.example.hpostesting.data.constant.DataHolder import com.example.hpostesting.data.constant.DataHolder
import com.example.hpostesting.data.constant.Constants import com.example.hpostesting.data.constant.Constants
import com.example.hpostesting.data.constant.LanguageManager import com.example.hpostesting.data.constant.LanguageManager
import com.example.hpostesting.data.model.test.TestType import com.example.hpostesting.data.model.test.TestType
import com.example.hpostesting.firebase.FirebaseManager
import com.example.hpostesting.presentation.main_base.DashboardActivity import com.example.hpostesting.presentation.main_base.DashboardActivity
import com.example.hpostesting.presentation.hb_test.HBTestActivity import com.example.hpostesting.presentation.hb_test.HBTestActivity
import com.example.hpostesting.presentation.trueheme_test.TrueHemeTestActivity import com.example.hpostesting.presentation.trueheme_test.TrueHemeTestActivity
@@ -52,7 +54,7 @@ class KitScanActivity : AppCompatActivity(), IDcsSdkApiDelegate {
private var fromWhere = "Home" private var fromWhere = "Home"
private val TAG = "KitScanActivity" private val TAG = "KitScanActivity"
private lateinit var binding: ActivityKitScanBinding private lateinit var binding: ActivityKitScanBinding
private lateinit var firebaseManager: FirebaseManager
private lateinit var sharedPreference: SharedPreferences private lateinit var sharedPreference: SharedPreferences
var sdkHandler: SDKHandler? = null var sdkHandler: SDKHandler? = null
@@ -71,21 +73,21 @@ class KitScanActivity : AppCompatActivity(), IDcsSdkApiDelegate {
} }
private fun processScannedData(contents: String) {//edited auto selection of cuvette size private fun processScannedData(contents: String) {//edited auto selection of cuvette size
if(contents.contains("SMI/SC/")){ if (contents.contains("SMI/SC/")) {
with(sharedPreference.edit()) { with(sharedPreference.edit()) {
putString(Constants.CUVETTE_SIZE, "2mm") putString(Constants.CUVETTE_SIZE, "2mm")
apply() apply()
} }
Toast.makeText(this, "Selected cuvette size: 2mm", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Selected cuvette size: 2mm", Toast.LENGTH_SHORT).show()
binding.nameEditText.setText(contents) binding.nameEditText.setText(contents)
}else if(contents.contains("SMI/SC-2-D10/")){ } else if (contents.contains("SMI/SC-2-D10/")) {
with(sharedPreference.edit()) { with(sharedPreference.edit()) {
putString(Constants.CUVETTE_SIZE, "10mm") putString(Constants.CUVETTE_SIZE, "10mm")
apply() apply()
} }
Toast.makeText(this, "Selected cuvette size: 10mm", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Selected cuvette size: 10mm", Toast.LENGTH_SHORT).show()
binding.nameEditText.setText(contents) binding.nameEditText.setText(contents)
}else{ } else {
Toast.makeText(this, R.string.invalid_kit, Toast.LENGTH_LONG).show() Toast.makeText(this, R.string.invalid_kit, Toast.LENGTH_LONG).show()
} }
} }
@@ -127,30 +129,42 @@ class KitScanActivity : AppCompatActivity(), IDcsSdkApiDelegate {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
firebaseManager = FirebaseManager(this)
val currentServer = firebaseManager.getLastSelectedServer().serverName
binding = ActivityKitScanBinding.inflate(layoutInflater) binding = ActivityKitScanBinding.inflate(layoutInflater)
sharedPreference = this.getSharedPreferences("HEMOCUBE", Context.MODE_PRIVATE) sharedPreference = this.getSharedPreferences("HEMOCUBE", Context.MODE_PRIVATE)
setContentView(binding.root) setContentView(binding.root)
binding.toolbar.title = "Kit Serial Number" binding.toolbar.title = "Kit Serial Number"
fromWhere = intent.getStringExtra("fromWhere").toString() fromWhere = intent.getStringExtra("fromWhere").toString()
val maxTest = if(sharedPreference.getString(Constants.CUVETTE_SIZE, "10mm").toString() == "2mm"){
Constants.MAXIMUM_TEST_ALLOWED_BEFORE_REFERENCE val maxTest = if (currentServer == "SMI R&D"){
}else{ Constants.MAXIMUM_TEST_ALLOWED_BEFORE_REFERENCE10MM_RANDD
Constants.MAXIMUM_TEST_ALLOWED_BEFORE_REFERENCE10MM
Log.d(TAG, "onCreate RandD mode")
}else {
if (sharedPreference.getString(Constants.CUVETTE_SIZE, "10mm").toString() == "2mm") {
Constants.MAXIMUM_TEST_ALLOWED_BEFORE_REFERENCE
} else {
Constants.MAXIMUM_TEST_ALLOWED_BEFORE_REFERENCE10MM
}
} }
val time = timeDifference(sharedPreference.getString(Constants.KIT_TIME, "").toString()) val time = timeDifference(sharedPreference.getString(Constants.KIT_TIME, "").toString())
val kitNum = sharedPreference.getString(Constants.KIT_NUMBER, "").toString() val kitNum = sharedPreference.getString(Constants.KIT_NUMBER, "").toString()
// Toast.makeText(this@KitScanActivity,"Test MAx time"+ time+"-Test count-"+kitNum,Toast.LENGTH_SHORT).show() // Toast.makeText(this@KitScanActivity,"Test MAx time"+ time+"-Test count-"+kitNum,Toast.LENGTH_SHORT).show()
// if (sharedPreference.getString(Constants.USER_ID, "").toString() == "ADMIN") { // if (sharedPreference.getString(Constants.USER_ID, "").toString() == "ADMIN") {
// moveToNext() // moveToNext()
// }else{ // }else{
if (sharedPreference.getString(Constants.USER_ID, "").toString() == "ADMIN") { if (sharedPreference.getString(Constants.USER_ID, "").toString() == "ADMIN") {
moveToNext() moveToNext()
}else{ } else {
if (DataHolder.sampleReadCounter <= maxTest && kitNum != "" && time < Constants.MAX_KIT_TIME) { if (DataHolder.sampleReadCounter <= maxTest && kitNum != "" && time < Constants.MAX_KIT_TIME) {
moveToNext() moveToNext()
} else { } else {
Toast.makeText(this@KitScanActivity, "Limit Reached, Use New KIT for testing", Toast.LENGTH_SHORT).show() Toast.makeText(
this@KitScanActivity,
"Limit Reached, Use New KIT for testing",
Toast.LENGTH_SHORT
).show()
DataHolder.sampleReadCounter = 0 DataHolder.sampleReadCounter = 0
DataHolder.kitSerial = "" DataHolder.kitSerial = ""
@@ -212,13 +226,13 @@ class KitScanActivity : AppCompatActivity(), IDcsSdkApiDelegate {
binding.btnGo.setOnClickListener { binding.btnGo.setOnClickListener {
val serialNumber = binding.nameEditText.text.toString().trim() val serialNumber = binding.nameEditText.text.toString().trim()
if (serialNumber.isNotEmpty() && isSerialValid(serialNumber)) { if (serialNumber.isNotEmpty() && isSerialValid(serialNumber)) {
if(serialNumber.contains("SMI/SC/")){ if (serialNumber.contains("SMI/SC/")) {
with(sharedPreference.edit()) { with(sharedPreference.edit()) {
putString(Constants.CUVETTE_SIZE, "2mm") putString(Constants.CUVETTE_SIZE, "2mm")
apply() apply()
} }
Toast.makeText(this, "Selected cuvette size: 2mm", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Selected cuvette size: 2mm", Toast.LENGTH_SHORT).show()
}else if(serialNumber.contains("SMI/SC-2-D10/")){ } else if (serialNumber.contains("SMI/SC-2-D10/")) {
with(sharedPreference.edit()) { with(sharedPreference.edit()) {
putString(Constants.CUVETTE_SIZE, "10mm") putString(Constants.CUVETTE_SIZE, "10mm")
apply() apply()
@@ -301,7 +315,8 @@ class KitScanActivity : AppCompatActivity(), IDcsSdkApiDelegate {
if (mScannerInfoList.isNotEmpty()) { if (mScannerInfoList.isNotEmpty()) {
sdkHandler!!.dcssdkEstablishCommunicationSession(mScannerInfoList[0].scannerID) sdkHandler!!.dcssdkEstablishCommunicationSession(mScannerInfoList[0].scannerID)
} else { } else {
Toast.makeText(this,"Scanner Is not available In this device", Toast.LENGTH_LONG).show() Toast.makeText(this, "Scanner Is not available In this device", Toast.LENGTH_LONG)
.show()
} }
} }
@@ -339,7 +354,7 @@ class KitScanActivity : AppCompatActivity(), IDcsSdkApiDelegate {
runOnUiThread { runOnUiThread {
val editableResult: Editable = Editable.Factory.getInstance().newEditable(result) val editableResult: Editable = Editable.Factory.getInstance().newEditable(result)
processScannedData(result) processScannedData(result)
// binding.nameEditText.text = editableResult // binding.nameEditText.text = editableResult
} }
} }
@@ -352,27 +367,29 @@ class KitScanActivity : AppCompatActivity(), IDcsSdkApiDelegate {
private fun isSerialValid(s: String): Boolean { private fun isSerialValid(s: String): Boolean {
if (s.contains("SMI/SC/")) { if (s.contains("SMI/SC/")) {
if(s.length != 17){ if (s.length != 17) {
binding.nameEditText.error = "Invalid Kit Serial Number, correct example SMI/SC/000/00/000" binding.nameEditText.error =
"Invalid Kit Serial Number, correct example SMI/SC/000/00/000"
return false return false
} }
}else if(s.contains("SMI/SC-2-D10/")){ } else if (s.contains("SMI/SC-2-D10/")) {
if(s.length != 27){ if (s.length != 27) {
binding.nameEditText.error = "Invalid Kit Serial Number, correct example SMI/SC-2-D10/000000/000/000" binding.nameEditText.error =
"Invalid Kit Serial Number, correct example SMI/SC-2-D10/000000/000/000"
return false return false
} }
}else{ } else {
return false return false
} }
return true return true
} }
private fun moveToNext() { private fun moveToNext() {
if(fromWhere == "Main"){ if (fromWhere == "Main") {
if(DataHolder.selectedTestType == TestType.HB_EST){ if (DataHolder.selectedTestType == TestType.HB_EST) {
val i = Intent(applicationContext, HBTestActivity::class.java) val i = Intent(applicationContext, HBTestActivity::class.java)
startActivity(i) startActivity(i)
}else{ } else {
DataHolder.deviceType.observe(this) { deviceType -> DataHolder.deviceType.observe(this) { deviceType ->
when (deviceType) { when (deviceType) {
Constants.DEVICE_TYPE_HEMOCUBE -> { Constants.DEVICE_TYPE_HEMOCUBE -> {
@@ -425,6 +442,7 @@ class KitScanActivity : AppCompatActivity(), IDcsSdkApiDelegate {
true true
} }
} }
private fun timeDifference(createdAt: String): Long { private fun timeDifference(createdAt: String): Long {
val currentTime = Calendar.getInstance().time val currentTime = Calendar.getInstance().time
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())

View File

@@ -14,6 +14,7 @@
package com.example.hpostesting.presentation package com.example.hpostesting.presentation
import android.Manifest import android.Manifest
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
@@ -33,6 +34,7 @@ import com.example.hpostesting.util.MyUtils
import `in`.sminnovations.hpostesting.R import `in`.sminnovations.hpostesting.R
import `in`.sminnovations.hpostesting.databinding.ActivitySplashBinding import `in`.sminnovations.hpostesting.databinding.ActivitySplashBinding
@SuppressLint("CustomSplashScreen")
class SplashActivity : AppCompatActivity() { class SplashActivity : AppCompatActivity() {
private lateinit var binding: ActivitySplashBinding private lateinit var binding: ActivitySplashBinding

View File

@@ -37,6 +37,7 @@ import com.example.hpostesting.presentation.main_base.DashboardActivity
import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase import com.google.firebase.ktx.Firebase
import `in`.sminnovations.hpostesting.databinding.FragmentDeviceProvisionBinding import `in`.sminnovations.hpostesting.databinding.FragmentDeviceProvisionBinding
import okio.ByteString.Companion.decodeBase64
import java.io.File import java.io.File
class DeviceProvisionFragment : Fragment() { class DeviceProvisionFragment : Fragment() {
@@ -147,17 +148,18 @@ class DeviceProvisionFragment : Fragment() {
DashboardActivity::class.java DashboardActivity::class.java
) )
) )
} else { } else {
Toast.makeText( Toast.makeText(
activity, activity,
"An error in device provision: ${response.data.message}", "An error in device provision: ${response.data.message}",
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
Log.d("Error in hpos network", "observeViewModel:${response.data.message} ")
binding.btnSubmit.visibility = View.VISIBLE binding.btnSubmit.visibility = View.VISIBLE
} }
} }
is Result.Error -> { is Result.Error -> {
response.exception.let { message -> response.exception.let { message ->
Toast.makeText( Toast.makeText(
@@ -165,7 +167,9 @@ class DeviceProvisionFragment : Fragment() {
"An error occurred in device provision: $message", "An error occurred in device provision: $message",
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
Log.d("Error in hpos network 2", "observeViewModel:$message} ")
} }
Log.d("Error in hpos network 3", "observeViewModel: $response} ")
} }
is Result.Loading -> { is Result.Loading -> {

View File

@@ -1,16 +1,21 @@
package com.example.hpostesting.presentation.main_base package com.example.hpostesting.presentation.main_base
import com.example.hpostesting.data.constant.Constants.FIREBASE_INTEGRATION
import com.example.hpostesting.data.constant.Constants.MOLBIO_INTEGRATION
class AppVersionTapManager { class AppVersionTapManager {
private var tapCount = 0 private var tapCount = 0
private val maxTapCount = 5 private val maxTapCount = 5
val shouldEnable = FIREBASE_INTEGRATION && !MOLBIO_INTEGRATION
val notEnable = MOLBIO_INTEGRATION && !FIREBASE_INTEGRATION
fun registerTap(onMaxTapsReached: () -> Unit) { fun registerTap(onMaxTapsReached: () -> Unit) {
tapCount++ tapCount++
if (tapCount >= maxTapCount) { if (tapCount >= maxTapCount && shouldEnable) {
onMaxTapsReached() onMaxTapsReached()
reset() reset()
} }
} }
private fun reset() { private fun reset() {
tapCount = 0 tapCount = 0

View File

@@ -25,6 +25,7 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import com.example.hpostesting.data.constant.Constants import com.example.hpostesting.data.constant.Constants
import com.example.hpostesting.presentation.reportgen.ReportActivity
import com.example.hpostesting.presentation.autodac.AutoDacActivity import com.example.hpostesting.presentation.autodac.AutoDacActivity
import com.example.hpostesting.presentation.buffercheck.HemocubeBufferCheckActivity import com.example.hpostesting.presentation.buffercheck.HemocubeBufferCheckActivity
import com.example.hpostesting.presentation.calibration.CalibrationActivity import com.example.hpostesting.presentation.calibration.CalibrationActivity
@@ -89,6 +90,7 @@ class PanelFragment : Fragment() {
binding.btnDeviceInfo.visibility = View.VISIBLE binding.btnDeviceInfo.visibility = View.VISIBLE
binding.btnResetPassword.visibility = View.VISIBLE binding.btnResetPassword.visibility = View.VISIBLE
binding.btnUpdateValues.visibility = View.VISIBLE binding.btnUpdateValues.visibility = View.VISIBLE
binding.btnPrintReport.visibility = View.GONE
}else{ }else{
binding.btnResetPassword.visibility = View.GONE binding.btnResetPassword.visibility = View.GONE
binding.btnDeviceProvision.visibility = View.GONE binding.btnDeviceProvision.visibility = View.GONE
@@ -102,6 +104,7 @@ class PanelFragment : Fragment() {
binding.btnSubmit.visibility = View.GONE binding.btnSubmit.visibility = View.GONE
binding.btnDeviceInfo.visibility = View.VISIBLE binding.btnDeviceInfo.visibility = View.VISIBLE
binding.btnUpdateValues.visibility = View.GONE binding.btnUpdateValues.visibility = View.GONE
binding.btnPrintReport.visibility = View.GONE
} }
binding.btnResetPassword.setOnClickListener{ binding.btnResetPassword.setOnClickListener{
@@ -136,12 +139,17 @@ class PanelFragment : Fragment() {
startActivity(Intent(requireContext(), DeviceActivity::class.java)) startActivity(Intent(requireContext(), DeviceActivity::class.java))
} }
binding.btnPrintReport.setOnClickListener{
startActivity(Intent(requireContext(), ReportActivity::class.java))
}
binding.btnFirefox.setOnClickListener { binding.btnFirefox.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW) val intent = Intent(Intent.ACTION_VIEW)
intent.component = ComponentName("org.mozilla.firefox", "org.mozilla.gecko.BrowserApp") intent.component = ComponentName("org.mozilla.firefox", "org.mozilla.gecko.BrowserApp")
startActivity(intent) startActivity(intent)
} }
binding.btnFiles.setOnClickListener { binding.btnFiles.setOnClickListener {
val intent = Intent(Intent.ACTION_GET_CONTENT) val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "file/*" intent.type = "file/*"

View File

@@ -28,7 +28,6 @@ import androidx.fragment.app.Fragment
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
import com.example.hpostesting.data.constant.Constants import com.example.hpostesting.data.constant.Constants
import com.example.hpostesting.data.constant.DataHolder import com.example.hpostesting.data.constant.DataHolder
@@ -201,7 +200,6 @@ class PrefsFragment : PreferenceFragmentCompat() {
} }
} }
val appVersionPreference = Preference(requireContext()).apply { val appVersionPreference = Preference(requireContext()).apply {
title = "App Version" title = "App Version"
summary = summary =
@@ -212,11 +210,10 @@ class PrefsFragment : PreferenceFragmentCompat() {
tapManager.registerTap { tapManager.registerTap {
firebaseManager.setDevModeOn(true) firebaseManager.setDevModeOn(true)
val devConfig = servers.find { it.serverName == "dev" }
if (firebaseManager.getDevMode() && currentServer != "dev") { if (firebaseManager.getDevMode() && currentServer != "dev") {
Toast.makeText(requireContext(), "Now you are a Developer", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Now you are a Developer", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "Setting Up developer Environmnet", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Setting Up developer Environmnet", Toast.LENGTH_SHORT).show()
switchFirebaseServer(devConfig!!)
} }
if (preferenceScreen.findPreference<SwitchPreferenceCompat>("developer_options") == null) { if (preferenceScreen.findPreference<SwitchPreferenceCompat>("developer_options") == null) {

View File

@@ -0,0 +1,21 @@
package com.example.hpostesting.presentation.reportgen
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.hpostesting.presentation.reportgen.ui.report.ReportFragment
import dagger.hilt.android.AndroidEntryPoint
import `in`.sminnovations.hpostesting.R
@AndroidEntryPoint
class ReportActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.report_gen)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, ReportFragment()) //Direct constructor
.commitNow()
}
}
}

View File

@@ -0,0 +1,70 @@
package com.example.hpostesting.presentation.reportgen.ui.report
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView
import com.example.hpostesting.data.dao.HemoCubeBufferDao
import com.example.hpostesting.data.model.patient.BufferCheckData
import com.example.hpostesting.data.model.patient.HemoCubeTestData
import `in`.sminnovations.hpostesting.R
class BufferAdapter(
private var dataList: List<HemoCubeTestData>,
private val onItemClick: (HemoCubeTestData) -> Unit
) : RecyclerView.Adapter<BufferAdapter.ViewHolder>() {
private var selectedItems: List<HemoCubeTestData> = emptyList()
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val cardView: CardView = view.findViewById(R.id.cardView)
val sampleId: TextView = view.findViewById(R.id.sampleId)
val testTime: TextView = view.findViewById(R.id.testTime)
val result: TextView = view.findViewById(R.id.result)
val selectionIndicator: View = view.findViewById(R.id.selectionIndicator)
init {
cardView.setOnClickListener {
if (adapterPosition != RecyclerView.NO_POSITION) {
onItemClick(dataList[adapterPosition])
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_buffer_card, parent, false)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = dataList[position]
val isSelected = selectedItems.contains(item)
holder.sampleId.text = "Sample ID: ${item._id}"
holder.testTime.text = "Time: ${item.testTime}"
holder.result.text = "Result: ${item.classificationResult}"
// Update selection appearance
if (isSelected) {
holder.cardView.setCardBackgroundColor(Color.parseColor("#E3F2FD"))
holder.selectionIndicator.visibility = View.VISIBLE
} else {
holder.cardView.setCardBackgroundColor(Color.WHITE)
holder.selectionIndicator.visibility = View.GONE
}
}
override fun getItemCount() = dataList.size
fun updateList(newList: List<HemoCubeTestData>) {
dataList = newList
notifyDataSetChanged()
}
fun updateSelectedItems(newSelectedItems: List<HemoCubeTestData>) {
selectedItems = newSelectedItems
notifyDataSetChanged()
}
}

View File

@@ -0,0 +1,329 @@
package com.example.hpostesting.presentation.reportgen.ui.report
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Typeface
import android.graphics.pdf.PdfDocument
import android.util.Log
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import com.example.hpostesting.data.model.patient.HemoCubeTestData
import `in`.sminnovations.hpostesting.R
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object ReportGen {
private const val PAGE_WIDTH = 595 // A4 width in points
private const val PAGE_HEIGHT = 842 // A4 height in points
private const val MARGIN_LEFT = 50f
private const val MARGIN_RIGHT = 50f
private const val MARGIN_TOP = 80f
private const val MARGIN_BOTTOM = 80f
fun generate(context: Context, dataList: List<HemoCubeTestData>) {
if (dataList.isEmpty()) {
Toast.makeText(context, "No items selected for report generation", Toast.LENGTH_SHORT).show()
return
}
Log.d("ReportGen", "Generating report for ${dataList.size} items")
val message = if (dataList.size == 1) {
"Report for kit ${dataList.first()._id} started."
} else {
"Report for ${dataList.size} kits started."
}
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
val pdfFile = createProfessionalPdfReport(context, dataList)
previewPdf(context, pdfFile)
}
private fun createProfessionalPdfReport(context: Context, dataList: List<HemoCubeTestData>): File {
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val file = File(context.getExternalFilesDir(null), "HemoCube_Report_$timestamp.pdf")
val pdfDocument = PdfDocument()
// Paint styles
val headerPaint = Paint().apply {
typeface = Typeface.create(Typeface.DEFAULT_BOLD, Typeface.BOLD)
textSize = 20f
color = Color.rgb(0, 102, 153) // Professional blue
}
val titlePaint = Paint().apply {
typeface = Typeface.create(Typeface.DEFAULT_BOLD, Typeface.BOLD)
textSize = 18f
color = Color.BLACK
}
val sectionHeaderPaint = Paint().apply {
typeface = Typeface.create(Typeface.DEFAULT_BOLD, Typeface.BOLD)
textSize = 14f
color = Color.rgb(0, 102, 153)
}
val contentPaint = Paint().apply {
textSize = 11f
color = Color.DKGRAY
}
val boldContentPaint = Paint().apply {
typeface = Typeface.create(Typeface.DEFAULT_BOLD, Typeface.BOLD)
textSize = 11f
color = Color.BLACK
}
val disclaimerPaint = Paint().apply {
textSize = 9f
color = Color.GRAY
typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC)
}
// Create one page per test data
dataList.forEachIndexed { index, testData ->
val pageInfo = PdfDocument.PageInfo.Builder(PAGE_WIDTH, PAGE_HEIGHT, index + 1).create()
val page = pdfDocument.startPage(pageInfo)
val canvas = page.canvas
drawPage(canvas, testData, index + 1, dataList.size,
headerPaint, titlePaint, sectionHeaderPaint,
contentPaint, boldContentPaint, disclaimerPaint,context)
pdfDocument.finishPage(page)
}
pdfDocument.writeTo(FileOutputStream(file))
pdfDocument.close()
return file
}
private fun drawPage(
canvas: Canvas,
testData: HemoCubeTestData,
pageNumber: Int,
totalPages: Int,
headerPaint: Paint,
titlePaint: Paint,
sectionHeaderPaint: Paint,
contentPaint: Paint,
boldContentPaint: Paint,
disclaimerPaint: Paint,
context: Context
) {
var currentY = MARGIN_TOP
// Header with logo placeholder and title
drawHeader(canvas, headerPaint, titlePaint, currentY, context)
currentY += 100f
// Sample Information Section
currentY = drawSection(canvas, "SAMPLE INFORMATION", sectionHeaderPaint, currentY)
currentY = drawKeyValuePair(canvas, "Sample ID:", testData.sampleid.toString(), boldContentPaint, contentPaint, currentY)
currentY = drawKeyValuePair(canvas, "Blood Group:", testData.bloodGroup, boldContentPaint, contentPaint, currentY)
currentY += 15f
// Test Information Section
currentY = drawSection(canvas, "TEST INFORMATION", sectionHeaderPaint, currentY)
currentY = drawKeyValuePair(canvas, "Test ID:", testData._id, boldContentPaint, contentPaint, currentY)
currentY = drawKeyValuePair(canvas, "Test Type:", testData.testType ?: "TrueHeme", boldContentPaint, contentPaint, currentY)
currentY = drawKeyValuePair(canvas, "Test Time:", testData.testTime ?: "N/A", boldContentPaint, contentPaint, currentY)
currentY = drawKeyValuePair(canvas, "Incubation Time:", testData.incubationTime, boldContentPaint, contentPaint, currentY)
currentY = drawKeyValuePair(canvas, "Kit Serial:", testData.kitSerial, boldContentPaint, contentPaint, currentY)
currentY = drawKeyValuePair(canvas, "Device Serial:", testData.deviceSerialNumber, boldContentPaint, contentPaint, currentY)
currentY += 15f
// Test Results Section
currentY = drawSection(canvas, "TEST RESULTS", sectionHeaderPaint, currentY)
currentY = drawKeyValuePair(canvas, "Classification Result:", testData.classificationResult, boldContentPaint, contentPaint, currentY)
currentY = drawKeyValuePair(canvas, "Device Ratio:", testData.deviceRatio?.toString() ?: "N/A", boldContentPaint, contentPaint, currentY)
currentY = drawKeyValuePair(canvas, "Calculated Ratio:", testData.calculatedRatio?.toString() ?: "N/A", boldContentPaint, contentPaint, currentY)
currentY = drawKeyValuePair(canvas, "Slope Ratio:", testData.slopeRatio?.toString() ?: "N/A", boldContentPaint, contentPaint, currentY)
currentY += 15f
// LED Measurements Section
currentY = drawSection(canvas, "LED MEASUREMENTS", sectionHeaderPaint, currentY)
currentY = drawLEDData(canvas, testData, boldContentPaint, contentPaint, currentY)
currentY += 15f
// Laboratory Information Section
currentY = drawSection(canvas, "LABORATORY INFORMATION", sectionHeaderPaint, currentY)
currentY = drawKeyValuePair(canvas, "Lab Name:", testData.labName ?: "N/A", boldContentPaint, contentPaint, currentY)
currentY = drawKeyValuePair(canvas, "Center Name:", testData.centerName ?: "N/A", boldContentPaint, contentPaint, currentY)
currentY = drawKeyValuePair(canvas, "District:", testData.district ?: "N/A", boldContentPaint, contentPaint, currentY)
currentY += 15f
// Footer with page number and disclaimer
drawFooter(canvas, pageNumber, totalPages, contentPaint, disclaimerPaint)
}
private fun drawHeader(canvas: Canvas, headerPaint: Paint, titlePaint: Paint, startY: Float, context: Context) {
// Load and draw actual logo
try {
// Method 1: Load from drawable resources
val logoDrawable = ContextCompat.getDrawable(context, R.mipmap.hpos_icon) // Replace with your logo resource
logoDrawable?.let { drawable ->
val logoWidth = 70
val logoHeight = 60
drawable.setBounds(
MARGIN_LEFT.toInt(),
(startY - 20f).toInt(),
(MARGIN_LEFT + logoWidth).toInt(),
(startY + logoHeight - 20f).toInt()
)
drawable.draw(canvas)
}
} catch (e: Exception) {
// Fallback to placeholder if logo loading fails
val logoPaint = Paint().apply {
color = Color.rgb(0, 102, 153)
style = Paint.Style.FILL
}
canvas.drawRect(MARGIN_LEFT, startY - 20f, MARGIN_LEFT + 60f, startY + 20f, logoPaint)
// Draw "LOGO" text as placeholder
val placeholderPaint = Paint().apply {
color = Color.WHITE
textSize = 12f
typeface = Typeface.create(Typeface.DEFAULT_BOLD, Typeface.BOLD)
textAlign = Paint.Align.CENTER
}
canvas.drawText("LOGO", MARGIN_LEFT + 30f, startY, placeholderPaint)
}
// Company name and title
canvas.drawText("HemoCube", MARGIN_LEFT + 80f, startY + 10f, headerPaint)
canvas.drawText("Medical Test Report", MARGIN_LEFT, startY + 40f, titlePaint)
// Report generation date
val currentDate = SimpleDateFormat("dd MMM yyyy, HH:mm", Locale.getDefault()).format(Date())
canvas.drawText("Generated: $currentDate", PAGE_WIDTH - MARGIN_RIGHT - 150f, startY + 10f, Paint().apply {
textSize = 10f
color = Color.GRAY
})
// Line under header
val linePaint = Paint().apply {
color = Color.rgb(0, 102, 153)
strokeWidth = 2f
}
canvas.drawLine(MARGIN_LEFT, startY + 60f, PAGE_WIDTH - MARGIN_RIGHT, startY + 60f, linePaint)
}
private fun drawSection(canvas: Canvas, title: String, paint: Paint, y: Float): Float {
canvas.drawText(title, MARGIN_LEFT, y, paint)
// Underline for section
val linePaint = Paint().apply {
color = Color.rgb(0, 102, 153)
strokeWidth = 1f
}
canvas.drawLine(MARGIN_LEFT, y + 5f, MARGIN_LEFT + paint.measureText(title), y + 5f, linePaint)
return y + 25f
}
private fun drawKeyValuePair(canvas: Canvas, key: String, value: String, keyPaint: Paint, valuePaint: Paint, y: Float): Float {
canvas.drawText(key, MARGIN_LEFT + 10f, y, keyPaint)
canvas.drawText(value, MARGIN_LEFT + 150f, y, valuePaint)
return y + 18f
}
private fun drawLEDData(canvas: Canvas, testData: HemoCubeTestData, keyPaint: Paint, valuePaint: Paint, startY: Float): Float {
var currentY = startY
currentY = drawKeyValuePair(canvas, "LED1 Sample:", testData.led1Sample?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
currentY = drawKeyValuePair(canvas, "LED2 Sample:", testData.led2Sample?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
currentY = drawKeyValuePair(canvas, "LED3 Sample:", testData.led3Sample?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
currentY = drawKeyValuePair(canvas, "LED4 Sample:", testData.led4Sample?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
currentY = drawKeyValuePair(canvas, "ABS1:", testData.abs1?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
currentY = drawKeyValuePair(canvas, "ABS2:", testData.abs2?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
currentY = drawKeyValuePair(canvas, "ABS3:", testData.abs3?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
currentY = drawKeyValuePair(canvas, "ABS4:", testData.abs4?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
return currentY
}
private fun drawFooter(canvas: Canvas, pageNumber: Int, totalPages: Int, contentPaint: Paint, disclaimerPaint: Paint) {
val footerY = PAGE_HEIGHT - MARGIN_BOTTOM + 20f
// Page number
canvas.drawText("Page $pageNumber of $totalPages", PAGE_WIDTH - MARGIN_RIGHT - 80f, footerY, contentPaint)
// Disclaimer
val disclaimerText = "DISCLAIMER: This report is generated by HemoCube automated system. " +
"Results should be interpreted by qualified medical professionals. " +
"This report is for diagnostic purposes only and should not be used as the sole basis for medical decisions. " +
"Please consult with your healthcare provider for proper interpretation and treatment recommendations."
val disclaimerLines = wrapText(disclaimerText, disclaimerPaint, PAGE_WIDTH - MARGIN_LEFT - MARGIN_RIGHT)
var disclaimerY = footerY + 20f
disclaimerLines.forEach { line ->
canvas.drawText(line, MARGIN_LEFT, disclaimerY, disclaimerPaint)
disclaimerY += 12f
}
// Footer line
val linePaint = Paint().apply {
color = Color.rgb(0, 102, 153)
strokeWidth = 1f
}
canvas.drawLine(MARGIN_LEFT, footerY - 10f, PAGE_WIDTH - MARGIN_RIGHT, footerY - 10f, linePaint)
}
private fun wrapText(text: String, paint: Paint, maxWidth: Float): List<String> {
val words = text.split(" ")
val lines = mutableListOf<String>()
var currentLine = ""
for (word in words) {
val testLine = if (currentLine.isEmpty()) word else "$currentLine $word"
if (paint.measureText(testLine) <= maxWidth) {
currentLine = testLine
} else {
if (currentLine.isNotEmpty()) {
lines.add(currentLine)
}
currentLine = word
}
}
if (currentLine.isNotEmpty()) {
lines.add(currentLine)
}
return lines
}
private fun previewPdf(context: Context, file: File) {
val uri = FileProvider.getUriForFile(
context,
"${context.packageName}.fileprovider",
file
)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "application/pdf")
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NO_HISTORY
}
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(context, "No PDF viewer found", Toast.LENGTH_SHORT).show()
}
}
}

View File

@@ -0,0 +1,218 @@
package com.example.hpostesting.presentation.reportgen.ui.report
import android.app.DatePickerDialog
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Typeface
import android.graphics.pdf.PdfDocument
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.hpostesting.data.model.patient.HemoCubeTestData
import com.example.hpostesting.presentation.reportgen.viewModel.ReportViewModel
import dagger.hilt.android.AndroidEntryPoint
import `in`.sminnovations.hpostesting.R
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
@AndroidEntryPoint
class ReportFragment : Fragment() {
companion object {
fun newInstance() = ReportFragment()
}
private val viewModel: ReportViewModel by viewModels()
private lateinit var recyclerView: RecyclerView
private lateinit var searchBar: EditText
private lateinit var filterSpinner: Spinner
private lateinit var generateBtn: Button
private lateinit var selectedCountText: TextView
private lateinit var calendarButton: Button
private lateinit var adapter: BufferAdapter
private var selectedItems: MutableList<HemoCubeTestData> = mutableListOf()
private var allData: List<HemoCubeTestData> = emptyList()
private var currentFilter = "Today"
private var customDate: String? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View = inflater.inflate(R.layout.fragment_report, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
initViews(view)
setupRecyclerView()
setupFilterSpinner()
setupObservers()
setupListeners()
// default filter to Today
setDefaultFilter()
}
private fun initViews(view: View) {
recyclerView = view.findViewById(R.id.bufferRecyclerView)
searchBar = view.findViewById(R.id.searchBar)
filterSpinner = view.findViewById(R.id.filterSpinner)
generateBtn = view.findViewById(R.id.generateReportButton)
selectedCountText = view.findViewById(R.id.selectedCountText)
calendarButton = view.findViewById(R.id.calendarButton)
}
private fun setupRecyclerView() {
recyclerView.layoutManager = LinearLayoutManager(requireContext())
adapter = BufferAdapter(emptyList()) { item ->
toggleItemSelection(item)
updateUI()
}
recyclerView.adapter = adapter
}
private fun setupFilterSpinner() {
val filterOptions = arrayOf("Today", "Yesterday", "All", "Custom Date")
val spinnerAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, filterOptions)
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
filterSpinner.adapter = spinnerAdapter
}
private fun setDefaultFilter() {
filterSpinner.setSelection(0) // Today
currentFilter = "Today"
calendarButton.visibility = View.GONE
}
private fun setupObservers() {
viewModel.allKitTestData.observe(viewLifecycleOwner) { list ->
allData = list
filterList(searchBar.text.toString(), currentFilter)
}
}
private fun setupListeners() {
searchBar.addTextChangedListener { editable ->
filterList(editable.toString(), currentFilter)
}
filterSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) {
currentFilter = parent.getItemAtPosition(pos).toString()
if (currentFilter == "Custom Date") {
calendarButton.visibility = View.VISIBLE
if (customDate == null) {
showDatePicker()
} else {
filterList(searchBar.text.toString(), currentFilter)
}
} else {
calendarButton.visibility = View.GONE
customDate = null
filterList(searchBar.text.toString(), currentFilter)
}
}
override fun onNothingSelected(p0: AdapterView<*>?) {}
}
calendarButton.setOnClickListener {
showDatePicker()
}
generateBtn.setOnClickListener {
if (selectedItems.isNotEmpty()) {
ReportGen.generate(requireContext(), selectedItems)
}
}
}
private fun showDatePicker() {
val calendar = Calendar.getInstance()
val year = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH)
val day = calendar.get(Calendar.DAY_OF_MONTH)
DatePickerDialog(requireContext(), { _, selectedYear, selectedMonth, selectedDay ->
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val selectedCalendar = Calendar.getInstance()
selectedCalendar.set(selectedYear, selectedMonth, selectedDay)
customDate = sdf.format(selectedCalendar.time)
// Update calendar button text
val displayFormat = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault())
calendarButton.text = displayFormat.format(selectedCalendar.time)
filterList(searchBar.text.toString(), currentFilter)
}, year, month, day).show()
}
private fun toggleItemSelection(item: HemoCubeTestData) {
if (selectedItems.contains(item)) {
selectedItems.remove(item)
} else {
selectedItems.add(item)
}
adapter.updateSelectedItems(selectedItems)
}
private fun updateUI() {
selectedCountText.text = "Selected: ${selectedItems.size}"
generateBtn.visibility = if (selectedItems.isNotEmpty()) View.VISIBLE else View.GONE
}
private fun filterList(query: String, filter: String) {
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val today = sdf.format(Date())
val calendar = Calendar.getInstance()
calendar.add(Calendar.DAY_OF_MONTH, -1)
val yesterday = sdf.format(calendar.time)
val filtered = allData.filter { item ->
val matchesSearch = item._id.contains(query, true) ||
item.classificationResult.contains(query, true)
val matchesDate = when (filter) {
"Today" -> item.testTime?.contains(today)
"Yesterday" -> item.testTime?.contains(yesterday)
"Custom Date" -> customDate?.let { item.testTime?.contains(it) } ?: true
"All" -> true
else -> true
}
matchesSearch && matchesDate ?: true
}
adapter.updateList(filtered)
// Clear selections if items are no longer visible
selectedItems.removeAll { selectedItem ->
!filtered.contains(selectedItem)
}
adapter.updateSelectedItems(selectedItems)
updateUI()
}
}

View File

@@ -0,0 +1,188 @@
package com.example.hpostesting.presentation.reportgen.viewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.hpostesting.data.dao.HemoCubeDao
import com.example.hpostesting.data.model.patient.HemoCubeTestData
import com.example.hpostesting.data.model.patient.UserData
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class ReportViewModel @Inject constructor(
private val hemoCubeDao: HemoCubeDao,
) : ViewModel() {
// val allKitTestData: LiveData<List<HemoCubeTestData>> = hemoCubeDao.getAll()
val allKitTestData = MutableLiveData<List<HemoCubeTestData>>()
init {
loadTestData()
}
private fun loadTestData() {
viewModelScope.launch {
val data = hemoCubeDao.getAll()
if (data.value.isNullOrEmpty()) {
allKitTestData.value = dummyDataList
} else {
allKitTestData.value = data.value
}
}
}
}
val dummyDataList = listOf(
HemoCubeTestData(
sampleid = 1,
_id = "ID001",
name = "John Doe",
incubationTime = "15 mins",
bloodGroup = "A+",
age = "30",
state = "Karnataka",
abhaId = "ABHA123456",
userImageURL = "https://example.com/image1.jpg",
location = UserData.Location(latitude = 12.9716, longitude = 77.5946),
reportUploadTime = "2025-07-20T10:30:00",
testTime = "2025-07-20T10:00:00",
gender = "Male",
classificationResult = "Normal",
prdClassification = "Class A",
deviceSerialNumber = "SN123456",
deviceId = "DEVICE001",
appVersion = "1.2.3",
kitSerial = "KIT001",
resultData = "{\"hb\":13.5}",
led1Buffer = 0.95,
led2Buffer = 1.01,
led3Buffer = 0.98,
led4Buffer = 1.02,
led1Sample = 0.75,
led2Sample = 0.80,
led3Sample = 0.78,
led4Sample = 0.81,
led1Average = 0.85,
led2Average = 0.86,
led3Average = 0.87,
led4Average = 0.88,
abs1 = 0.10,
abs2 = 0.12,
abs3 = 0.11,
abs4 = 0.13,
hb3 = 13.2,
hb4 = 13.6,
led1Gain1 = 1.1,
led2Gain1 = 1.1,
led3Gain1 = 1.1,
led4Gain1 = 1.1,
led1Air1 = 0.5,
led2Air1 = 0.5,
led3Air1 = 0.5,
led4Air1 = 0.5,
deviceRatio = 1.23,
calculatedRatio = 1.21,
predictedDenovixRatio = 1.22,
slopeRatio = 0.98,
coefficients = "{\"a\":0.1,\"b\":0.2}",
deviceRatioClass = "Optimal",
slopeRatioClass = "Stable",
borderlineMethod2Class = "Negative",
errorMessages = "",
batteryLevel = "85%",
batteryCapacity = "4000mAh",
batteryMaxCapacity = "5000mAh",
batteryTemperature = "36C",
batteryVoltage = "3.7V",
molbioFlag = true,
quickCapture = true,
solution = "NaCl",
concentration = "5%",
filter = "HE",
volume = "0.5ml",
isCSVCreated = true,
labName = "LabCorp",
cuvetteSize = "Standard",
district = "Bangalore Urban",
centerName = "Health Center 1",
ipAddress = "192.168.0.100",
configUpdatedRecent = "2025-07-19T08:45:00"
),
HemoCubeTestData(
sampleid = 2,
_id = "ID002",
name = "Jane Smith",
incubationTime = "10 mins",
bloodGroup = "B-",
age = "28",
state = "Maharashtra",
abhaId = "ABHA654321",
userImageURL = "https://example.com/image2.jpg",
location = UserData.Location(latitude = 18.5204, longitude = 73.8567),
reportUploadTime = "2025-07-21T09:15:00",
testTime = "2025-07-21T08:45:00",
gender = "Female",
classificationResult = "Anemia",
prdClassification = "Class B",
deviceSerialNumber = "SN654321",
deviceId = "DEVICE002",
appVersion = "1.2.3",
kitSerial = "KIT002",
resultData = "{\"hb\":9.0}",
led1Buffer = 1.05,
led2Buffer = 1.00,
led3Buffer = 1.02,
led4Buffer = 0.99,
led1Sample = 0.60,
led2Sample = 0.65,
led3Sample = 0.63,
led4Sample = 0.62,
led1Average = 0.70,
led2Average = 0.71,
led3Average = 0.72,
led4Average = 0.69,
abs1 = 0.15,
abs2 = 0.16,
abs3 = 0.17,
abs4 = 0.14,
hb3 = 9.2,
hb4 = 8.8,
led1Gain1 = 1.2,
led2Gain1 = 1.2,
led3Gain1 = 1.2,
led4Gain1 = 1.2,
led1Air1 = 0.6,
led2Air1 = 0.6,
led3Air1 = 0.6,
led4Air1 = 0.6,
deviceRatio = 0.95,
calculatedRatio = 0.93,
predictedDenovixRatio = 0.94,
slopeRatio = 1.02,
coefficients = "{\"a\":0.15,\"b\":0.25}",
deviceRatioClass = "Low",
slopeRatioClass = "Fluctuating",
borderlineMethod2Class = "Positive",
errorMessages = "Low Hb detected",
batteryLevel = "78%",
batteryCapacity = "3800mAh",
batteryMaxCapacity = "5000mAh",
batteryTemperature = "37C",
batteryVoltage = "3.6V",
molbioFlag = false,
quickCapture = false,
solution = "PBS",
concentration = "3%",
filter = "LE",
volume = "0.4ml",
isCSVCreated = false,
labName = "MedLab",
cuvetteSize = "Small",
district = "Pune",
centerName = "Health Center 2",
ipAddress = "192.168.0.101",
configUpdatedRecent = "2025-07-20T11:00:00"
)
)

View File

@@ -593,7 +593,7 @@ class TrueHemeFragment : Fragment() {
binding.btnSubmit.visibility = View.VISIBLE binding.btnSubmit.visibility = View.VISIBLE
binding.btnSubmit.isEnabled = true binding.btnSubmit.isEnabled = true
binding.btnSubmit.isClickable = true binding.btnSubmit.isClickable = true
// binding.clParent.setBackgroundColor(Color.parseColor("#edfffd")) // binding.clParent.setBackgroundColor(Color.parseColor("#edfffd"))
binding.ivCheck.visibility = View.VISIBLE binding.ivCheck.visibility = View.VISIBLE
} }
} else { } else {
@@ -736,7 +736,7 @@ class TrueHemeFragment : Fragment() {
} }
} }
private fun processResult() { private fun processResult(){
try { try {
hemoCubeViewModel.messages.postValue(getString(R.string.processing_result)) hemoCubeViewModel.messages.postValue(getString(R.string.processing_result))
val deviceLog = resultData val deviceLog = resultData

View File

@@ -29,6 +29,8 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope
import androidx.transition.Visibility
import com.example.hpostesting.data.constant.DataHolder import com.example.hpostesting.data.constant.DataHolder
import com.example.hpostesting.util.Result import com.example.hpostesting.util.Result
import com.example.hpostesting.data.constant.Constants import com.example.hpostesting.data.constant.Constants
@@ -45,12 +47,17 @@ import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase import com.google.firebase.ktx.Firebase
import `in`.sminnovations.hpostesting.R import `in`.sminnovations.hpostesting.R
import `in`.sminnovations.hpostesting.databinding.FragmentHemoCubeReferenceBinding import `in`.sminnovations.hpostesting.databinding.FragmentHemoCubeReferenceBinding
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.log10 import kotlin.math.log10
import kotlin.random.Random import kotlin.random.Random
//This is TrueHemeTestFragment, where actual tests are done first buffer check and then sample check //This is TrueHemeTestFragment, where actual tests are done first buffer check and then sample check
//and finally on submit test data to database if online or store in offline database //and finally on submit test data to database if online or store in offline database
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
val autoSumbt = false
class TrueHemeTestFragment : Fragment() { class TrueHemeTestFragment : Fragment() {
private var positiveBoderLine10mm1=Constants.positiveBoderLineMetricCheck10mmMin private var positiveBoderLine10mm1=Constants.positiveBoderLineMetricCheck10mmMin
private var positiveBoderLine10mm2=Constants.positiveBoderLineMetricCheck10mmMax private var positiveBoderLine10mm2=Constants.positiveBoderLineMetricCheck10mmMax
@@ -142,6 +149,7 @@ class TrueHemeTestFragment : Fragment() {
return binding.root return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initViews() initViews()
@@ -150,10 +158,52 @@ class TrueHemeTestFragment : Fragment() {
observeViewModel() observeViewModel()
} }
private fun autoSmbt(ignoreCuvette : Boolean = true){
submitClick = true
checkSubmit = true
lifecycleScope.launch {
delay(100)
}
activity?.runOnUiThread {
if (checkSubmit) {
with(sharedPreferences.edit()) {
putBoolean(Constants.QUICK_CAPTURE, false)
apply()
}
if (DataHolder.hemoCubeTestData!!.classificationResult != "Invalid") {
DataHolder.sampleReadCounter++
}
binding.btnSubmit.isEnabled = false
binding.btnSubmit.isClickable = false
activity?.runOnUiThread {
Log.d("HemoCubeFragment", "Test Process completed, result saved")
binding.progressBar.visibility = View.VISIBLE
binding.btnSubmit.visibility = View.GONE
}
trueHemeTestViewModel.uploadHemoCubeResultToDatabase(
isOnline,
true,
sharedPreferences.getString(Constants.KIT_NUMBER, ""),
quickCapture
)
Toast.makeText(requireContext(), "Auto Sumbitted", Toast.LENGTH_SHORT).show()
} else {
checkCuvettePresence()
binding.btnSubmit.isEnabled = true
binding.btnSubmit.isClickable = true
Toast.makeText(requireContext(), "Auto Sumbit Failed ", Toast.LENGTH_SHORT).show()
binding.btnSubmit.visibility = View.VISIBLE
}
}
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun initViews() { private fun initViews() {
binding.tvSubtitle4.movementMethod = ScrollingMovementMethod() binding.tvSubtitle4.movementMethod = ScrollingMovementMethod()
binding.tvDeviceMessages.movementMethod = ScrollingMovementMethod() binding.tvDeviceMessages.movementMethod = ScrollingMovementMethod()
binding.btnSubmit.setOnClickListener { binding.btnSubmit.setOnClickListener {
// with(sharedPreferences.edit()) { // with(sharedPreferences.edit()) {
// putBoolean(Constants.QUICK_CAPTURE, false) // putBoolean(Constants.QUICK_CAPTURE, false)
@@ -451,7 +501,7 @@ class TrueHemeTestFragment : Fragment() {
} }
} }
private fun showBufferAlertDialog() { private fun showBufferAlertDialog(){
val title = "WARNING" val title = "WARNING"
val message = getString(R.string.do_exist) val message = getString(R.string.do_exist)
val negativeText = getString(R.string.no) val negativeText = getString(R.string.no)
@@ -658,6 +708,8 @@ class TrueHemeTestFragment : Fragment() {
} }
showRetryButtonForCuvette() showRetryButtonForCuvette()
} }
//need to change where check submit
resultData.contains("#CIN") && this.submitClick && this.testStatusCode < TestStatus.CUVETTE_PRESENTT.code -> { resultData.contains("#CIN") && this.submitClick && this.testStatusCode < TestStatus.CUVETTE_PRESENTT.code -> {
trueHemeTestViewModel.messages.postValue(getString(R.string.cuvette_presentt)) trueHemeTestViewModel.messages.postValue(getString(R.string.cuvette_presentt))
this.testStatusCode = TestStatus.CUVETTE_PRESENTT.code this.testStatusCode = TestStatus.CUVETTE_PRESENTT.code
@@ -665,6 +717,8 @@ class TrueHemeTestFragment : Fragment() {
binding.testing.visibility = View.GONE binding.testing.visibility = View.GONE
} }
} }
resultData.contains("#AIN") && this.submitClick && this.testStatusCode <= TestStatus.CUVETTE_ABSENTT.code -> { resultData.contains("#AIN") && this.submitClick && this.testStatusCode <= TestStatus.CUVETTE_ABSENTT.code -> {
trueHemeTestViewModel.messages.postValue(getString(R.string.cuvette_absentt)) trueHemeTestViewModel.messages.postValue(getString(R.string.cuvette_absentt))
this.testStatusCode = TestStatus.CUVETTE_ABSENTT.code this.testStatusCode = TestStatus.CUVETTE_ABSENTT.code
@@ -957,11 +1011,18 @@ class TrueHemeTestFragment : Fragment() {
if (readingsPerSample == 1) { if (readingsPerSample == 1) {
activity?.runOnUiThread { activity?.runOnUiThread {
//changing to invisible
binding.btnSubmit.visibility = View.VISIBLE binding.btnSubmit.visibility = View.VISIBLE
binding.btnSubmit.isEnabled = true // binding.btnSubmit.isEnabled = true
binding.btnSubmit.isClickable = true // binding.btnSubmit.isClickable = true
// binding.clParent.setBackgroundColor(Color.parseColor("#edfffd")) // binding.clParent.setBackgroundColor(Color.parseColor("#edfffd"))
if (autoSumbt){
binding.btnSubmit.visibility = View.GONE
autoSmbt(autoSumbt)
}
binding.ivCheck.visibility = View.VISIBLE binding.ivCheck.visibility = View.VISIBLE
} }
} else { } else {
trueHemeTestViewModel.uploadHemoCubeResultToDatabase( trueHemeTestViewModel.uploadHemoCubeResultToDatabase(
@@ -1769,4 +1830,7 @@ class TrueHemeTestFragment : Fragment() {
private fun generateTwoDigitRandomNumber(): Int { private fun generateTwoDigitRandomNumber(): Int {
return Random.nextInt(10, 100) return Random.nextInt(10, 100)
} }
} }

View File

@@ -1,5 +1,6 @@
package com.example.hpostesting.util package com.example.hpostesting.util
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.Network import android.net.Network
@@ -12,6 +13,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
@@ -23,7 +26,7 @@ import javax.inject.Singleton
class NetworkMonitor @Inject constructor( class NetworkMonitor @Inject constructor(
@ApplicationContext private val context: Context @ApplicationContext private val context: Context
) { ) {
private var isMonitoringStarted = false
private val connectivityManager = private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
@@ -49,7 +52,10 @@ class NetworkMonitor @Inject constructor(
} }
} }
@SuppressLint("NewApi")
fun startMonitoring() { fun startMonitoring() {
if (isMonitoringStarted) return // Already started, do nothing.
isMonitoringStarted = true
// Check initial connectivity status // Check initial connectivity status
val network = connectivityManager.activeNetwork val network = connectivityManager.activeNetwork
val capabilities = connectivityManager.getNetworkCapabilities(network) val capabilities = connectivityManager.getNetworkCapabilities(network)
@@ -66,6 +72,8 @@ class NetworkMonitor @Inject constructor(
.build() .build()
connectivityManager.registerNetworkCallback(networkRequest, networkCallback) connectivityManager.registerNetworkCallback(networkRequest, networkCallback)
// Start periodic checks:
startPeriodicPingCheck()
} }
fun stopMonitoring() { // never needed to call fun stopMonitoring() { // never needed to call
@@ -96,4 +104,14 @@ class NetworkMonitor @Inject constructor(
} }
} }
} }
private fun startPeriodicPingCheck(intervalMillis: Long = 1 * 60_000L) {
networkScope.launch {
while (isActive) {
performPingCheck()
delay(intervalMillis)
}
}
}
} }

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z"/>
</vector>

View File

@@ -55,7 +55,21 @@
app:layout_constraintTop_toBottomOf="@id/tv_subtitle4" /> app:layout_constraintTop_toBottomOf="@id/tv_subtitle4" />
<Button <Button
android:id="@+id/btn_print_report"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="24dp"
android:clickable="false"
android:text="Print Report"
android:textColor="@color/white"
app:cornerRadius="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_submit" />
<Button
android:id="@+id/btn_diagnostics" android:id="@+id/btn_diagnostics"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -67,7 +81,7 @@
app:cornerRadius="16dp" app:cornerRadius="16dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_submit" /> app:layout_constraintTop_toBottomOf="@id/btn_print_report" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn_auto_dac" android:id="@+id/btn_auto_dac"
@@ -153,6 +167,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_deviceProvision" /> app:layout_constraintTop_toBottomOf="@id/btn_deviceProvision" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn_update_values" android:id="@+id/btn_update_values"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -160,12 +175,13 @@
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:clickable="false" android:clickable="false"
android:text="Update values" android:text="Update valueLs"
android:textColor="@color/white" android:textColor="@color/white"
app:cornerRadius="16dp" app:cornerRadius="16dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_reset_password" /> app:layout_constraintTop_toBottomOf="@id/btn_reset_password" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn_firefox" android:id="@+id/btn_firefox"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="12dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/searchBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Search by smaple ID or result"
android:imeOptions="actionSearch"
android:inputType="text" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<Spinner
android:id="@+id/filterSpinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:entries="@array/filter_options" />
<Button
android:id="@+id/calendarButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Select Date"
android:visibility="gone"
android:layout_marginStart="8dp"
style="@style/Widget.Material3.Button.OutlinedButton" />
</LinearLayout>
<TextView
android:id="@+id/selectedCountText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Selected: 0"
android:layout_marginTop="8dp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/bufferRecyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="8dp" />
<Button
android:id="@+id/generateReportButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Generate Report"
android:visibility="gone"/>
</LinearLayout>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/sampleId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kit No: Sample123"
android:textSize="16sp"
android:textStyle="bold" />
<View
android:id="@+id/selectionIndicator"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="@drawable/ic_baseline_check_circle_24"
android:visibility="gone" />
<TextView
android:id="@+id/testTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/sampleId"
android:layout_marginTop="4dp"
android:text="Time: 2024-01-15 10:30"
android:textSize="14sp" />
<TextView
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/testTime"
android:layout_marginTop="4dp"
android:text="Result: Positive"
android:textSize="14sp" />
</RelativeLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -25,6 +25,7 @@
<item>ka</item> <item>ka</item>
<!-- Use language codes (e.g., en, kn) as values --> <!-- Use language codes (e.g., en, kn) as values -->
</string-array> </string-array>
<string-array name="district"> <string-array name="district">
<item>Mysuru</item> <item>Mysuru</item>
<item>Kodagu</item> <item>Kodagu</item>
@@ -32,4 +33,13 @@
<item>Other</item> <item>Other</item>
</string-array> </string-array>
<string-array name="filter_options">
<item>Today</item>
<item>Yesterday</item>
<item>All</item>
<item>Custom Date</item>
</string-array>
</resources> </resources>

View File

@@ -25,4 +25,3 @@ plugins {
task clean(type: Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }