Compare commits

..

4 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
18 changed files with 1250 additions and 11 deletions

View File

@@ -9,5 +9,9 @@
Printer not added yet
//update it when it is completed
## added secure server
## aded molbio server
## advanced with ping network check

View File

@@ -7,6 +7,7 @@ plugins {
id 'androidx.navigation.safeargs.kotlin'
id 'kotlin-kapt'
id 'com.google.firebase.crashlytics'
id 'kotlin-parcelize'
}
//apply plugin: 'kotlin-android'
@@ -21,7 +22,7 @@ android {
minSdk 30
targetSdk 34
versionCode 138
versionName "2.1.133.3"
versionName "2.1.132"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
@@ -182,4 +183,7 @@ dependencies {
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.17'
implementation 'androidx.security:security-crypto:1.1.0-alpha03'
}

View File

@@ -42,6 +42,9 @@
android:supportsRtl="true"
android:theme="@style/Theme.HPOSTesting"
tools:targetApi="31">
<activity
android:name="com.example.hpostesting.presentation.reportgen.ReportActivity"
android:exported="false" />
<activity
android:name="com.example.hpostesting.presentation.main_base.UpdateValuesActivity"
android:exported="false"
@@ -218,4 +221,5 @@
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

View File

@@ -7,7 +7,7 @@ std::vector<std::pair<int, std::pair<std::string, std::unordered_map<std::string
{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"}}}},
// {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"}}}}
};

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

@@ -25,6 +25,7 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
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.buffercheck.HemocubeBufferCheckActivity
import com.example.hpostesting.presentation.calibration.CalibrationActivity
@@ -89,7 +90,7 @@ class PanelFragment : Fragment() {
binding.btnDeviceInfo.visibility = View.VISIBLE
binding.btnResetPassword.visibility = View.VISIBLE
binding.btnUpdateValues.visibility = View.VISIBLE
binding.btnRnd.visibility = View.GONE
binding.btnPrintReport.visibility = View.GONE
}else{
binding.btnResetPassword.visibility = View.GONE
binding.btnDeviceProvision.visibility = View.GONE
@@ -103,7 +104,7 @@ class PanelFragment : Fragment() {
binding.btnSubmit.visibility = View.GONE
binding.btnDeviceInfo.visibility = View.VISIBLE
binding.btnUpdateValues.visibility = View.GONE
binding.btnRnd.visibility = View.GONE
binding.btnPrintReport.visibility = View.GONE
}
binding.btnResetPassword.setOnClickListener{
@@ -138,6 +139,10 @@ class PanelFragment : Fragment() {
startActivity(Intent(requireContext(), DeviceActivity::class.java))
}
binding.btnPrintReport.setOnClickListener{
startActivity(Intent(requireContext(), ReportActivity::class.java))
}
binding.btnFirefox.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW)
intent.component = ComponentName("org.mozilla.firefox", "org.mozilla.gecko.BrowserApp")

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

@@ -56,6 +56,8 @@ import kotlin.random.Random
//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
@Suppress("MemberVisibilityCanBePrivate")
val autoSumbt = false
class TrueHemeTestFragment : Fragment() {
private var positiveBoderLine10mm1=Constants.positiveBoderLineMetricCheck10mmMin
private var positiveBoderLine10mm2=Constants.positiveBoderLineMetricCheck10mmMax
@@ -1011,11 +1013,14 @@ class TrueHemeTestFragment : Fragment() {
activity?.runOnUiThread {
//changing to invisible
binding.btnSubmit.visibility = View.GONE
binding.btnSubmit.visibility = View.VISIBLE
// binding.btnSubmit.isEnabled = true
// binding.btnSubmit.isClickable = true
// binding.clParent.setBackgroundColor(Color.parseColor("#edfffd"))
autoSmbt(true)
if (autoSumbt){
binding.btnSubmit.visibility = View.GONE
autoSmbt(autoSumbt)
}
binding.ivCheck.visibility = View.VISIBLE
}

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

@@ -56,13 +56,13 @@
<Button
android:id="@+id/btn_rnd"
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="R and D"
android:text="Print Report"
android:textColor="@color/white"
app:cornerRadius="16dp"
app:layout_constraintEnd_toEndOf="parent"
@@ -81,7 +81,7 @@
app:cornerRadius="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_rnd" />
app:layout_constraintTop_toBottomOf="@id/btn_print_report" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_auto_dac"

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

@@ -33,4 +33,13 @@
<item>Other</item>
</string-array>
<string-array name="filter_options">
<item>Today</item>
<item>Yesterday</item>
<item>All</item>
<item>Custom Date</item>
</string-array>
</resources>