Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ android {
}

buildTypes {
debug {
buildConfigField "boolean", "USE_MOCK_PATIENT_FLOW", "true"
}
release {
buildConfigField "boolean", "USE_MOCK_PATIENT_FLOW", "false"
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
Expand All @@ -45,6 +49,7 @@ android {
targetCompatibility JavaVersion.VERSION_17
}
buildFeatures {
buildConfig true
viewBinding true
// dataBinding true
compose true
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@
<activity
android:name=".view.general.PatientDetailsActivity"
android:exported="false" />
<activity
android:name=".view.general.PatientOverviewActivity"
android:exported="false" />
<activity
android:name=".view.general.AddPatientLogActivity"
android:exported="false" />
Expand Down
23 changes: 15 additions & 8 deletions app/src/main/java/deakin/gopher/guardian/PatientExerciseModules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package deakin.gopher.guardian

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.activity.OnBackPressedCallback

/**
* Host activity for the patient exercise modules
Expand All @@ -11,19 +12,25 @@ class PatientExerciseModules : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_patient_exercise_container)

onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStack()
} else {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
}
}
},
)

if (savedInstanceState == null) {
val fragment = PatientExercisePortalFragment.newInstance()
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit()
}
}

override fun onBackPressed() {
if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStack()
} else {
super.onBackPressed()
}
}
}
111 changes: 111 additions & 0 deletions app/src/main/java/deakin/gopher/guardian/model/MockPatientData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package deakin.gopher.guardian.model

import deakin.gopher.guardian.model.register.User

/** Local samples for DEBUG UI review (doctor list + overview + admin overview card). */
object MockPatientData {
private val stubCaretaker =
User(
id = "mock-caretaker-1",
email = "j.morgan@example.com",
name = "Jamie Morgan",
roleName = "caretaker",
photoUrl = "",
organization = null,
)

private val stubNurses =
listOf(
User(
id = "mock-nurse-1",
email = "a.patel@example.com",
name = "Priya Patel",
roleName = "nurse",
photoUrl = "",
organization = null,
),
User(
id = "mock-nurse-2",
email = "l.chen@example.com",
name = "Liam Chen",
roleName = "nurse",
photoUrl = "",
organization = null,
),
)

val patients: List<Patient>
get() =
listOf(
Patient(
id = "mock-patient-1",
fullname = "Eleanor Whitmore",
photoUrl = "",
dateOfBirth = "1948-06-15T00:00:00Z",
_age = 77,
gender = "female",
healthConditions =
listOf(
"type 2 diabetes",
"mild hypertension",
"early stage osteoarthritis",
),
caretaker = stubCaretaker,
assignedNurses = stubNurses,
),
Patient(
id = "mock-patient-2",
fullname = "Robert Singh",
photoUrl = "",
dateOfBirth = "1939-03-08T10:30:00Z",
_age = 87,
gender = "male",
healthConditions =
listOf(
"congestive heart failure",
"chronic kidney disease stage 3",
),
caretaker = stubCaretaker,
assignedNurses = listOf(stubNurses.first()),
),
Patient(
id = "mock-patient-3",
fullname = "Maria Santos",
photoUrl = "",
dateOfBirth = "1962-11-02T00:00:00Z",
_age = 62,
gender = "female",
healthConditions = emptyList(),
caretaker = stubCaretaker,
assignedNurses = emptyList(),
),
)

fun patientById(id: String): Patient? = patients.find { it.id == id }

fun patientForOverviewOrDefault(id: String): Patient =
patientById(id) ?: patients.first()

/** Fills the “Clinical / admin overview” section during mock previews. */
fun adminOverviewForPatient(id: String): PatientAdminOverviewResponse {
val name = patientById(id)?.fullname ?: patients.first().fullname
return PatientAdminOverviewResponse(
summary =
"$name — routine follow-up cadence weekly. Stable on current medication plan.",
overview =
"Focus areas: hydration, mobilisation indoors, adherence to diabetic diet sheet.",
carePlanSummary = "Morning vitals weekly; pharmacist review fortnightly.",
notes = "Prefers reminders via SMS. Son visits Tuesdays.",
riskLevel = when (patientById(id)?.id) {
"mock-patient-2" -> "elevated"
"mock-patient-3" -> "low"
else -> "moderate"
},
alerts =
listOf(
"Renew script for ACE inhibitor due next month.",
"Updated emergency contact verified Jan 2026.",
),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package deakin.gopher.guardian.model

import com.google.gson.annotations.SerializedName
import java.io.Serializable

/** Lenient decoding for GET /admin/patient-overview/{patientId}; unknown fields ignored by Gson. */
data class PatientAdminOverviewResponse(
@SerializedName("patient") val patient: Patient? = null,
@SerializedName("summary") val summary: String? = null,
@SerializedName("notes") val notes: String? = null,
@SerializedName("overview") val overview: String? = null,
@SerializedName("carePlanSummary") val carePlanSummary: String? = null,
@SerializedName("riskLevel") val riskLevel: String? = null,
@SerializedName("alerts") val alerts: List<String>? = null,
) : Serializable
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import deakin.gopher.guardian.model.AddPatientResponse
import deakin.gopher.guardian.model.BaseModel
import deakin.gopher.guardian.model.Patient
import deakin.gopher.guardian.model.PatientActivity
import deakin.gopher.guardian.model.PatientAdminOverviewResponse
import deakin.gopher.guardian.model.register.AuthResponse
import deakin.gopher.guardian.model.register.RegisterRequest
import okhttp3.MultipartBody
Expand Down Expand Up @@ -60,6 +61,24 @@ interface ApiService {
@Header("Authorization") token: String,
): Response<List<Patient>>

@GET("patients/{patientId}")
suspend fun getPatientById(
@Header("Authorization") token: String,
@Path("patientId") patientId: String,
): Response<Patient>

@GET("admin/patient-overview/{patientId}")
suspend fun getPatientAdminOverview(
@Header("Authorization") token: String,
@Path("patientId") patientId: String,
): Response<PatientAdminOverviewResponse>

@GET("doctors/{doctorId}/patients")
suspend fun getDoctorPatients(
@Header("Authorization") token: String,
@Path("doctorId") doctorId: String,
): Response<List<Patient>>

@Multipart
@POST("patients/add")
suspend fun addPatient(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,104 @@
package deakin.gopher.guardian.view.general

import android.content.Intent
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import deakin.gopher.guardian.adapter.PatientListAdapter
import deakin.gopher.guardian.BuildConfig
import deakin.gopher.guardian.R
import deakin.gopher.guardian.model.MockPatientData
import deakin.gopher.guardian.model.login.SessionManager
import deakin.gopher.guardian.services.EmailPasswordAuthService
import deakin.gopher.guardian.services.api.ApiClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class Homepage4doctor : BaseActivity() {
private lateinit var patientListAdapter: PatientListAdapter
private lateinit var progressBar: ProgressBar
private lateinit var emptyText: TextView

class Homepage4doctor : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_homepage4doctor)

progressBar = findViewById(R.id.progressBarPatients)
emptyText = findViewById(R.id.tvEmptyPatients)

patientListAdapter =
PatientListAdapter(
emptyList(),
onPatientClick = { patient ->
val intent = Intent(this, PatientOverviewActivity::class.java)
intent.putExtra(PatientOverviewActivity.EXTRA_PATIENT_ID, patient.id)
startActivity(intent)
},
)

val recyclerView: RecyclerView = findViewById(R.id.recyclerViewDoctorPatients)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = patientListAdapter

val signOutButton: Button = findViewById(R.id.signOutButton_doctor)
signOutButton.setOnClickListener {
EmailPasswordAuthService.signOut(this)
finish()
}
}

override fun onResume() {
super.onResume()
fetchPatients()
}

private fun fetchPatients() {
if (BuildConfig.USE_MOCK_PATIENT_FLOW) {
progressBar.visibility = android.view.View.GONE
val mocks = MockPatientData.patients
patientListAdapter.updateData(mocks)
emptyText.visibility =
if (mocks.isEmpty()) android.view.View.VISIBLE else android.view.View.GONE
return
}

val token = "Bearer ${SessionManager.getToken()}"
val doctorId = SessionManager.getCurrentUser().id
CoroutineScope(Dispatchers.IO).launch {
withContext(Dispatchers.Main) {
progressBar.visibility = android.view.View.VISIBLE
emptyText.visibility = android.view.View.GONE
}

val response =
try {
ApiClient.apiService.getDoctorPatients(token, doctorId)
} catch (e: Exception) {
null
}

withContext(Dispatchers.Main) {
progressBar.visibility = android.view.View.GONE
if (response?.isSuccessful == true) {
val patients = response.body().orEmpty()
patientListAdapter.updateData(patients)
emptyText.visibility =
if (patients.isEmpty()) android.view.View.VISIBLE else android.view.View.GONE
} else {
emptyText.visibility = android.view.View.VISIBLE
Toast.makeText(
this@Homepage4doctor,
"Failed to load patients",
Toast.LENGTH_SHORT,
).show()
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ class PatientListActivity : BaseActivity() {
PatientListAdapter(
emptyList(),
onPatientClick = { patient ->
val intent = Intent(this, PatientDetailsActivity::class.java)
intent.putExtra("patient", patient)
val intent = Intent(this, PatientOverviewActivity::class.java)
intent.putExtra(PatientOverviewActivity.EXTRA_PATIENT_ID, patient.id)
startActivity(intent)
},
onAssignNurseClick = { patient ->
Expand Down
Loading
Loading