diff --git a/.editorconfig b/.editorconfig
index 93eb1e0e..069caad9 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -2,5 +2,4 @@
# This configuration is used by ktlint when spotless invokes it
[*.{kt,kts}]
-ktlint_function_naming_ignore_when_annotated_with=Composable, Test
-ktlint_disabled_rules=no-wildcard-imports
\ No newline at end of file
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 837d337b..0694920f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,25 +2,17 @@
-
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:grantUriPermissions="true">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_paths" />
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
-
-
\ No newline at end of file
+
diff --git a/app/src/main/java/deakin/gopher/guardian/ActivityLogScreen.kt b/app/src/main/java/deakin/gopher/guardian/ActivityLogScreen.kt
index 09496595..82d230a9 100644
--- a/app/src/main/java/deakin/gopher/guardian/ActivityLogScreen.kt
+++ b/app/src/main/java/deakin/gopher/guardian/ActivityLogScreen.kt
@@ -34,7 +34,7 @@ import androidx.navigation.NavController
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun ActivityLogScreen(navController: NavController) {
+fun activityLogScreen(navController: NavController) {
Scaffold(
topBar = {
TopAppBar(
diff --git a/app/src/main/java/deakin/gopher/guardian/adapter/PatientListAdapter.kt b/app/src/main/java/deakin/gopher/guardian/adapter/PatientListAdapter.kt
index 297137b4..a50fe7ef 100644
--- a/app/src/main/java/deakin/gopher/guardian/adapter/PatientListAdapter.kt
+++ b/app/src/main/java/deakin/gopher/guardian/adapter/PatientListAdapter.kt
@@ -14,9 +14,10 @@ import deakin.gopher.guardian.model.Patient
class PatientListAdapter(
private var patients: List,
- private val onPatientClick: ((Patient) -> Unit)? = null,
- private val onAssignNurseClick: ((Patient) -> Unit)? = null,
- private val onDeleteClick: ((Patient) -> Unit)? = null,
+ private val onPatientClick: (Patient) -> Unit,
+ private val onAssignNurseClick: (Patient) -> Unit,
+ private val onEditClick: (Patient) -> Unit,
+ private val onDeleteClick: (Patient) -> Unit,
) : RecyclerView.Adapter() {
inner class PatientViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val nameText: TextView = itemView.findViewById(R.id.tvName)
@@ -68,11 +69,15 @@ class PatientListAdapter(
popupMenu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.assign_nurse -> {
- onAssignNurseClick?.invoke(patient)
+ onAssignNurseClick(patient)
true
}
- R.id.action_delete -> { // Handle delete click
- onDeleteClick?.invoke(patient)
+ R.id.action_edit -> {
+ onEditClick(patient) // NEW
+ true
+ }
+ R.id.action_delete -> {
+ onDeleteClick(patient)
true
}
else -> false
diff --git a/app/src/main/java/deakin/gopher/guardian/model/Nurse.kt b/app/src/main/java/deakin/gopher/guardian/model/Nurse.kt
new file mode 100644
index 00000000..bbef10f3
--- /dev/null
+++ b/app/src/main/java/deakin/gopher/guardian/model/Nurse.kt
@@ -0,0 +1,18 @@
+package deakin.gopher.guardian.model.user
+
+data class Nurse(
+ val _id: String,
+ val fullname: String,
+ val email: String,
+ val assignedPatients: List?,
+ val failedLoginAttempts: Int,
+ val lastPasswordChange: String?,
+ val created_at: String,
+ val updated_at: String,
+ val role: RoleDto,
+)
+
+data class RoleDto(
+ val _id: String,
+ val name: String,
+)
diff --git a/app/src/main/java/deakin/gopher/guardian/model/NursesResponse.kt b/app/src/main/java/deakin/gopher/guardian/model/NursesResponse.kt
new file mode 100644
index 00000000..6d58e23c
--- /dev/null
+++ b/app/src/main/java/deakin/gopher/guardian/model/NursesResponse.kt
@@ -0,0 +1,5 @@
+package deakin.gopher.guardian.model.user
+
+data class NursesResponse(
+ val nurses: List,
+)
diff --git a/app/src/main/java/deakin/gopher/guardian/model/Patient.kt b/app/src/main/java/deakin/gopher/guardian/model/Patient.kt
index 96853851..2d31812e 100644
--- a/app/src/main/java/deakin/gopher/guardian/model/Patient.kt
+++ b/app/src/main/java/deakin/gopher/guardian/model/Patient.kt
@@ -15,6 +15,7 @@ data class Patient(
@SerializedName("dateOfBirth") val dateOfBirth: String,
@SerializedName("age") val _age: Int,
@SerializedName("gender") val gender: String,
+ @SerializedName("isDeleted") val isDeleted: Boolean,
@SerializedName("healthConditions") val healthConditions: List,
@SerializedName("caretaker") val caretaker: User,
@SerializedName("assignedNurses") val assignedNurses: List,
diff --git a/app/src/main/java/deakin/gopher/guardian/model/register/User.kt b/app/src/main/java/deakin/gopher/guardian/model/register/User.kt
index c906ee66..1bead93f 100644
--- a/app/src/main/java/deakin/gopher/guardian/model/register/User.kt
+++ b/app/src/main/java/deakin/gopher/guardian/model/register/User.kt
@@ -30,3 +30,11 @@ data class AuthResponse(
@SerializedName("user") val user: User,
@SerializedName("token") val token: String,
) : BaseModel()
+
+data class NotificationItem(
+ val userId: String,
+ val title: String,
+ val message: String,
+ val isRead: Boolean,
+ val createdAt: String,
+)
diff --git a/app/src/main/java/deakin/gopher/guardian/services/api/ApiClient.kt b/app/src/main/java/deakin/gopher/guardian/services/api/ApiClient.kt
index 48eff44c..0a8ca63c 100644
--- a/app/src/main/java/deakin/gopher/guardian/services/api/ApiClient.kt
+++ b/app/src/main/java/deakin/gopher/guardian/services/api/ApiClient.kt
@@ -6,8 +6,9 @@ import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitClient {
-// private const val BASE_URL = "http://10.0.2.2:3000/api/v1/"
- private const val BASE_URL = "https://guardian-backend-opal.vercel.app/api/v1/"
+ // private const val BASE_URL = "http://10.0.2.2:3000/api/v1/"
+// private const val BASE_URL = "https://guardian-backend-opal.vercel.app/api/v1/"
+ private const val BASE_URL = "https://guardian-backend-x5v6.vercel.app/api/v1/"
private val client = OkHttpClient()
private val interceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
diff --git a/app/src/main/java/deakin/gopher/guardian/services/api/ApiService.kt b/app/src/main/java/deakin/gopher/guardian/services/api/ApiService.kt
index 0e9ea177..3a930c12 100644
--- a/app/src/main/java/deakin/gopher/guardian/services/api/ApiService.kt
+++ b/app/src/main/java/deakin/gopher/guardian/services/api/ApiService.kt
@@ -6,7 +6,10 @@ import deakin.gopher.guardian.model.BaseModel
import deakin.gopher.guardian.model.Patient
import deakin.gopher.guardian.model.PatientActivity
import deakin.gopher.guardian.model.register.AuthResponse
+import deakin.gopher.guardian.model.register.NotificationItem
import deakin.gopher.guardian.model.register.RegisterRequest
+import deakin.gopher.guardian.model.user.NursesResponse
+import deakin.gopher.guardian.view.general.UpdatePatientRequest
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.Call
@@ -19,6 +22,7 @@ import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
+import retrofit2.http.PUT
import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query
@@ -91,4 +95,35 @@ interface ApiService {
@Header("Authorization") token: String,
@Path("id") patientId: String,
): Response
+
+ @GET("/api/v1/nurse/all")
+ suspend fun getAllNurses(
+ @Header("Authorization") token: String,
+ @Query("page") page: Int = 1,
+ @Query("limit") limit: Int = 100,
+ ): Response
+
+ @POST("patients/assign-nurse")
+ suspend fun assignNurse(
+ @Header("Authorization") token: String,
+ @Body body: Map,
+ ): Response
+
+ @PUT("/api/v1/patients/{patientId}")
+ suspend fun updatePatient(
+ @Header("Authorization") token: String,
+ @Path("patientId") patientId: String,
+ @Body request: UpdatePatientRequest,
+ ): Response
+
+ @POST("notifications")
+ suspend fun createNotification(
+ @Header("Authorization") token: String,
+ @Body body: Map,
+ ): Response
+
+ @GET("notifications")
+ suspend fun getMyNotifications(
+ @Header("Authorization") token: String,
+ ): Response>
}
diff --git a/app/src/main/java/deakin/gopher/guardian/view/caretaker/AssignNurseScreen.kt b/app/src/main/java/deakin/gopher/guardian/view/caretaker/AssignNurseScreen.kt
index 8de11deb..69d65e14 100644
--- a/app/src/main/java/deakin/gopher/guardian/view/caretaker/AssignNurseScreen.kt
+++ b/app/src/main/java/deakin/gopher/guardian/view/caretaker/AssignNurseScreen.kt
@@ -34,7 +34,7 @@ import androidx.navigation.NavController
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun AssignNurseScreen(navController: NavController) {
+fun assignNurseScreen(navController: NavController) {
Scaffold(
topBar = {
TopAppBar(
diff --git a/app/src/main/java/deakin/gopher/guardian/view/caretaker/MainActivity.kt b/app/src/main/java/deakin/gopher/guardian/view/caretaker/MainActivity.kt
index 5d7c949b..4fdb3fba 100644
--- a/app/src/main/java/deakin/gopher/guardian/view/caretaker/MainActivity.kt
+++ b/app/src/main/java/deakin/gopher/guardian/view/caretaker/MainActivity.kt
@@ -50,12 +50,12 @@ class MainActivity : ComponentActivity() {
// 7. Assign nurse
composable("assign_nurse") {
- AssignNurseScreen(navController)
+ assignNurseScreen(navController)
}
// 8. Activity log
composable("activity_log") {
- ActivityLogScreen(navController)
+ activityLogScreen(navController)
}
// 9. Appointment
diff --git a/app/src/main/java/deakin/gopher/guardian/view/general/AddNewPatientActivity.kt b/app/src/main/java/deakin/gopher/guardian/view/general/AddNewPatientActivity.kt
index df96346a..2a8f618d 100644
--- a/app/src/main/java/deakin/gopher/guardian/view/general/AddNewPatientActivity.kt
+++ b/app/src/main/java/deakin/gopher/guardian/view/general/AddNewPatientActivity.kt
@@ -1,20 +1,24 @@
+@file:Suppress("ktlint")
+
package deakin.gopher.guardian.view.general
import android.Manifest
-import android.R.style.Theme_Holo_Light_Dialog
+import android.app.Activity
import android.app.DatePickerDialog
-import android.content.Context
+import android.content.Intent
import android.content.pm.PackageManager
-import android.graphics.Bitmap
import android.net.Uri
+import java.text.SimpleDateFormat
+import java.util.Locale
import android.os.Bundle
+import android.provider.MediaStore
import android.view.View
-import android.widget.ArrayAdapter
-import android.widget.Toast
-import androidx.activity.result.contract.ActivityResultContracts
+import android.widget.*
+import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
-import androidx.viewbinding.ViewBinding
+import androidx.core.content.FileProvider
+import com.google.android.material.button.MaterialButton
import com.google.gson.Gson
import deakin.gopher.guardian.R
import deakin.gopher.guardian.databinding.ActivityAddNewPatientBinding
@@ -32,378 +36,248 @@ import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.toRequestBody
-import java.io.ByteArrayOutputStream
-import java.util.Calendar
-import java.util.Locale
-
-class AddNewPatientActivity : BaseActivity() {
- private lateinit var binding: ViewBinding
-
- private var selectedPhotoUri: Uri? = null
- private var capturedPhotoBitmap: Bitmap? = null
+import java.io.File
+import java.util.*
- companion object {
- private const val CAMERA_PERMISSION_CODE = 1001
- }
+class AddNewPatientActivity : AppCompatActivity() {
- // Launcher for picking image from gallery
- private val galleryLauncher =
- registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
- if (uri != null) {
- selectedPhotoUri = uri
- capturedPhotoBitmap = null
- val localBinding = binding
- when (localBinding) {
- is ActivityAddNewPatientBinding -> localBinding.imgPreview.setImageURI(uri)
- is ActivityAddNewPatientNurseBinding -> localBinding.imgPreview.setImageURI(uri)
- }
- }
- }
+ private lateinit var txtName: EditText
+ private lateinit var txtDob: EditText
+ private lateinit var txtAge: EditText
+ private lateinit var genderSpinner: Spinner
+ private lateinit var btnSave: MaterialButton
+ private lateinit var btnSelectFromGallery: MaterialButton
+ private lateinit var btnTakePhoto: MaterialButton
+ private lateinit var imgPreview: ImageView
+ private lateinit var progressBar: ProgressBar
- // Launcher for taking photo with camera (returns Bitmap)
- private val cameraLauncher =
- registerForActivityResult(ActivityResultContracts.TakePicturePreview()) { bitmap: Bitmap? ->
- if (bitmap != null) {
- capturedPhotoBitmap = bitmap
- selectedPhotoUri = null
- val localBinding = binding
- when (localBinding) {
- is ActivityAddNewPatientBinding -> localBinding.imgPreview.setImageBitmap(bitmap)
- is ActivityAddNewPatientNurseBinding -> localBinding.imgPreview.setImageBitmap(bitmap)
- }
- }
- }
+ private var selectedPhotoUri: Uri? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_add_new_patient)
val currentUser = SessionManager.getCurrentUser()
if (currentUser.role == Role.Nurse) {
val nurseBinding = ActivityAddNewPatientNurseBinding.inflate(layoutInflater)
- binding = nurseBinding
+ var binding = nurseBinding
setContentView(nurseBinding.root)
} else {
val caretakerBinding = ActivityAddNewPatientBinding.inflate(layoutInflater)
- binding = caretakerBinding
+ var binding = caretakerBinding
setContentView(caretakerBinding.root)
}
- val localBinding = binding
- when (localBinding) {
- is ActivityAddNewPatientBinding -> {
- setSupportActionBar(localBinding.toolbar)
- localBinding.toolbar.setNavigationOnClickListener {
- onBackPressedDispatcher.onBackPressed()
- }
- setupUI(localBinding)
- }
- is ActivityAddNewPatientNurseBinding -> {
- setSupportActionBar(localBinding.toolbar)
- localBinding.toolbar.setNavigationOnClickListener {
- onBackPressedDispatcher.onBackPressed()
- }
- setupUI(localBinding)
- }
- }
- }
- // Common UI setup for both bindings
- private fun setupUI(localBinding: ViewBinding) {
- when (localBinding) {
- is ActivityAddNewPatientBinding -> {
- setupGenderSpinner(localBinding.genderSpinner)
- setupDOBPicker(localBinding.txtDob)
- localBinding.btnSelectFromGallery.setOnClickListener { openGallery() }
- localBinding.btnTakePhoto.setOnClickListener { checkCameraPermissionAndOpen() }
- localBinding.btnSave.setOnClickListener { savePatientInfo() }
- }
- is ActivityAddNewPatientNurseBinding -> {
- setupGenderSpinner(localBinding.genderSpinner)
- setupDOBPicker(localBinding.txtDob)
- localBinding.btnSelectFromGallery.setOnClickListener { openGallery() }
- localBinding.btnTakePhoto.setOnClickListener { checkCameraPermissionAndOpen() }
- localBinding.btnSave.setOnClickListener { savePatientInfo() }
- }
+ txtName = findViewById(R.id.txtName)
+ txtDob = findViewById(R.id.txtDob)
+ txtAge = findViewById(R.id.txtAge)
+ genderSpinner = findViewById(R.id.genderSpinner)
+ btnSave = findViewById(R.id.btnSave)
+ btnSelectFromGallery = findViewById(R.id.btnSelectFromGallery)
+ btnTakePhoto = findViewById(R.id.btnTakePhoto)
+ imgPreview = findViewById(R.id.imgPreview)
+ progressBar = findViewById(R.id.progressBar)
+
+ val toolbar: androidx.appcompat.widget.Toolbar = findViewById(R.id.toolbar)
+ setSupportActionBar(toolbar)
+ toolbar.setNavigationOnClickListener { onBackPressed() }
+
+
+ btnSelectFromGallery.setOnClickListener {
+ if (checkPermissions()) openGallery()
+ }
+ btnTakePhoto.setOnClickListener {
+ if (checkPermissions()) openCamera()
}
+
+
+ setupGenderSpinner()
+ setupDOBPicker()
+
+ btnSave.setOnClickListener { savePatientInfo() }
+ btnSelectFromGallery.setOnClickListener { openGallery() }
+ btnTakePhoto.setOnClickListener { openCamera() }
}
- private fun setupGenderSpinner(genderSpinner: android.widget.Spinner) {
- val genderOptions = listOf("Select gender", "Male", "Female", "Other")
- val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, genderOptions)
+ private fun setupGenderSpinner() {
+ val options = listOf("Select gender", "Male", "Female", "Other")
+ val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, options)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
genderSpinner.adapter = adapter
}
- private fun setupDOBPicker(txtDob: android.widget.EditText) {
+ private fun calculateAge(year: Int, month: Int, day: Int): Int {
+ val today = Calendar.getInstance()
+ var age = today.get(Calendar.YEAR) - year
+
+ // Adjust if birthday hasn't occurred yet this year
+ if (today.get(Calendar.MONTH) < month ||
+ (today.get(Calendar.MONTH) == month && today.get(Calendar.DAY_OF_MONTH) < day)) {
+ age--
+ }
+ return age
+ }
+
+
+ private fun setupDOBPicker() {
+ val calendar = Calendar.getInstance()
+
txtDob.setOnClickListener {
- val calendar = Calendar.getInstance()
val year = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH)
val day = calendar.get(Calendar.DAY_OF_MONTH)
- val datePickerDialog =
- DatePickerDialog(
- this,
- Theme_Holo_Light_Dialog,
- { _, selectedYear, selectedMonth, selectedDay ->
- val formattedDate =
- String.format(
- Locale.getDefault(),
- "%04d-%02d-%02d",
- selectedYear,
- selectedMonth + 1,
- selectedDay,
- )
- txtDob.setText(formattedDate)
- },
- year,
- month,
- day,
- )
-
- // Force spinner mode (for API 21+)
- try {
- val datePickerField = datePickerDialog.datePicker.javaClass.getDeclaredField("mDelegate")
- datePickerField.isAccessible = true
- val delegate = datePickerField.get(datePickerDialog.datePicker)
-
- val spinnerDelegateClass = Class.forName("android.widget.DatePickerSpinnerDelegate")
-
- if (delegate.javaClass != spinnerDelegateClass) {
- datePickerField.set(datePickerDialog.datePicker, null) // Clear the current delegate
-
- val constructor =
- datePickerDialog.datePicker.javaClass.getDeclaredConstructor(
- Context::class.java,
- android.util.AttributeSet::class.java,
- Int::class.javaPrimitiveType,
- Int::class.javaPrimitiveType,
- )
- constructor.isAccessible = true
-
- val spinnerDelegate =
- constructor.newInstance(
- datePickerDialog.datePicker.context,
- null,
- android.R.attr.datePickerStyle,
- 0,
- )
- datePickerField.set(datePickerDialog.datePicker, spinnerDelegate)
-
- // Re-initialize the date picker with current date
- datePickerDialog.datePicker.updateDate(year, month, day)
- }
- } catch (e: Exception) {
- e.printStackTrace() // Fallback gracefully if reflection fails
- }
-
- datePickerDialog.datePicker.maxDate = System.currentTimeMillis()
- datePickerDialog.show()
- }
- }
-
- // Permission check before opening camera
- private fun checkCameraPermissionAndOpen() {
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
- == PackageManager.PERMISSION_GRANTED
- ) {
- openCamera()
- } else {
- ActivityCompat.requestPermissions(
+ val dpd = DatePickerDialog(
this,
- arrayOf(Manifest.permission.CAMERA),
- CAMERA_PERMISSION_CODE,
+ android.R.style.Theme_Holo_Light_Dialog,
+ { _, selectedYear, selectedMonth, selectedDay ->
+ txtDob.setText("${selectedDay}/${selectedMonth + 1}/${selectedYear}")
+ txtAge.setText(calculateAge(selectedYear, selectedMonth, selectedDay).toString())
+ },
+ year,
+ month,
+ day
)
- }
- }
- override fun onRequestPermissionsResult(
- requestCode: Int,
- permissions: Array,
- grantResults: IntArray,
- ) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- if (requestCode == CAMERA_PERMISSION_CODE) {
- if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
- openCamera()
- } else {
- showMessage("Camera permission is required to take photos")
- }
+ dpd.datePicker.maxDate = System.currentTimeMillis()
+ dpd.show()
}
}
- private fun openGallery() {
- galleryLauncher.launch("image/*")
- }
- private fun openCamera() {
- cameraLauncher.launch(null)
+ private fun validateInputs(): Boolean {
+ if (txtName.text.toString().trim().isEmpty()) {
+ showMessage(getString(R.string.validation_empty_name))
+ return false
+ }
+ if (txtDob.text.toString().trim().isEmpty()) {
+ showMessage(getString(R.string.validation_empty_dob))
+ return false
+ }
+ if (genderSpinner.selectedItemPosition == 0) {
+ showMessage(getString(R.string.validation_empty_gender))
+ return false
+ }
+ return true
}
private fun savePatientInfo() {
if (!validateInputs()) return
- val localBinding = binding
-
- val fullname =
- when (localBinding) {
- is ActivityAddNewPatientBinding -> localBinding.txtName.text.toString().trim()
- is ActivityAddNewPatientNurseBinding -> localBinding.txtName.text.toString().trim()
- else -> ""
- }
-
- val dob =
- when (localBinding) {
- is ActivityAddNewPatientBinding -> localBinding.txtDob.text.toString().trim()
- is ActivityAddNewPatientNurseBinding -> localBinding.txtDob.text.toString().trim()
- else -> ""
- }
-
- val gender =
- when (localBinding) {
- is ActivityAddNewPatientBinding -> localBinding.genderSpinner.selectedItem?.toString()?.lowercase() ?: ""
- is ActivityAddNewPatientNurseBinding -> localBinding.genderSpinner.selectedItem?.toString()?.lowercase() ?: ""
- else -> ""
- }
+ val fullname = txtName.text.toString().trim()
+ val dobInput = txtDob.text.toString().trim()
+ val gender = genderSpinner.selectedItem.toString().lowercase()
+
+ // Convert dob to ISO 8601 format
+ val sdfInput = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
+ val sdfOutput = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+ val dobFormatted = try {
+ val date = sdfInput.parse(dobInput)
+ sdfOutput.format(date!!)
+ } catch (e: Exception) {
+ showMessage("Invalid date format")
+ return
+ }
val namePart = fullname.toRequestBody("text/plain".toMediaTypeOrNull())
- val dobPart = dob.toRequestBody("text/plain".toMediaTypeOrNull())
+ val dobPart = dobFormatted.toRequestBody("text/plain".toMediaTypeOrNull())
val genderPart = gender.toRequestBody("text/plain".toMediaTypeOrNull())
- val photoPart: MultipartBody.Part? =
- when {
- selectedPhotoUri != null -> prepareFilePart("photo", selectedPhotoUri!!, this)
- capturedPhotoBitmap != null -> prepareBitmapPart("photo", capturedPhotoBitmap!!)
- else -> null
- }
+ val photoPart: MultipartBody.Part? = selectedPhotoUri?.let { prepareFilePart("photo", it) }
val token = "Bearer ${SessionManager.getToken()}"
CoroutineScope(Dispatchers.IO).launch {
withContext(Dispatchers.Main) {
- when (localBinding) {
- is ActivityAddNewPatientBinding -> {
- localBinding.progressBar.show()
- localBinding.btnSave.visibility = View.GONE
- }
- is ActivityAddNewPatientNurseBinding -> {
- localBinding.progressBar.show()
- localBinding.btnSave.visibility = View.GONE
- }
- }
+ progressBar.show()
+ btnSave.visibility = View.GONE
}
val response = ApiClient.apiService.addPatient(token, namePart, dobPart, genderPart, photoPart)
withContext(Dispatchers.Main) {
- when (localBinding) {
- is ActivityAddNewPatientBinding -> {
- localBinding.progressBar.hide()
- localBinding.btnSave.visibility = View.VISIBLE
- }
- is ActivityAddNewPatientNurseBinding -> {
- localBinding.progressBar.hide()
- localBinding.btnSave.visibility = View.VISIBLE
- }
- }
+ progressBar.hide()
+ btnSave.visibility = View.VISIBLE
if (response.isSuccessful) {
if (response.body()?.patient != null) {
- showMessage(response.message())
- onBackPressedDispatcher.onBackPressed()
+ showMessage("Patient added successfully")
+ onBackPressed()
} else {
showMessage(response.body()?.apiError ?: "Failed to add patient")
}
} else {
- val errorResponse =
- Gson().fromJson(
- response.errorBody()?.string(),
- ApiErrorResponse::class.java,
- )
+ val errorResponse = Gson().fromJson(response.errorBody()?.string(), ApiErrorResponse::class.java)
showMessage(errorResponse.apiError ?: response.message())
}
}
}
}
- private fun validateInputs(): Boolean {
- val localBinding = binding
- return when (localBinding) {
- is ActivityAddNewPatientBinding -> {
- if (localBinding.txtName.text.toString().trim().isEmpty()) {
- showMessage(getString(R.string.validation_empty_name))
- false
- } else if (localBinding.txtDob.text.toString().trim().isEmpty()) {
- showMessage(getString(R.string.validation_empty_dob))
- false
- } else if (localBinding.genderSpinner.selectedItemPosition == 0) {
- showMessage(getString(R.string.validation_empty_gender))
- false
- } else {
- true
+
+ private val GALLERY_REQUEST_CODE = 100
+
+ private fun openGallery() {
+ val intent = Intent(Intent.ACTION_PICK)
+ intent.type = "image/*"
+ startActivityForResult(intent, GALLERY_REQUEST_CODE)
+ }
+
+
+ private val CAMERA_REQUEST_CODE = 101
+ private var photoUri: Uri? = null
+
+ private fun openCamera() {
+ val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+ // Prepare a temporary file for the photo
+ val photoFile = File(externalCacheDir, "temp_photo.jpg")
+ photoUri = FileProvider.getUriForFile(this, "${packageName}.provider", photoFile)
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
+ startActivityForResult(intent, CAMERA_REQUEST_CODE)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+
+ if (resultCode == Activity.RESULT_OK) {
+ when (requestCode) {
+ GALLERY_REQUEST_CODE -> {
+ val selectedImage = data?.data
+ selectedImage?.let { imgPreview.setImageURI(it) }
}
- }
- is ActivityAddNewPatientNurseBinding -> {
- if (localBinding.txtName.text.toString().trim().isEmpty()) {
- showMessage(getString(R.string.validation_empty_name))
- false
- } else if (localBinding.txtDob.text.toString().trim().isEmpty()) {
- showMessage(getString(R.string.validation_empty_dob))
- false
- } else if (localBinding.genderSpinner.selectedItemPosition == 0) {
- showMessage(getString(R.string.validation_empty_gender))
- false
- } else {
- true
+ CAMERA_REQUEST_CODE -> {
+ photoUri?.let { imgPreview.setImageURI(it) }
}
}
- else -> false
}
}
- private fun showMessage(message: String) {
- Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
- }
-
- private fun calculateAge(
- year: Int,
- month: Int,
- day: Int,
- ): Int {
- val today = Calendar.getInstance()
- val birthDate = Calendar.getInstance()
- birthDate.set(year, month, day)
- var age = today.get(Calendar.YEAR) - birthDate.get(Calendar.YEAR)
-
- // If birthday hasn't happened yet this year, subtract one
- if (today.get(Calendar.DAY_OF_YEAR) < birthDate.get(Calendar.DAY_OF_YEAR)) {
- age--
+ private fun checkPermissions(): Boolean {
+ val cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ val storagePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
+ return if (cameraPermission == PackageManager.PERMISSION_GRANTED &&
+ storagePermission == PackageManager.PERMISSION_GRANTED) {
+ true
+ } else {
+ ActivityCompat.requestPermissions(
+ this,
+ arrayOf(Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE),
+ 102
+ )
+ false
}
- return age
}
- private fun prepareFilePart(
- partName: String,
- fileUri: Uri,
- context: Context,
- ): MultipartBody.Part? {
- val contentResolver = context.contentResolver
+ private fun prepareFilePart(partName: String, fileUri: Uri): MultipartBody.Part {
val inputStream = contentResolver.openInputStream(fileUri)
- val fileBytes = inputStream?.readBytes()
- if (fileBytes == null) return null
- val requestFile = fileBytes.toRequestBody("image/*".toMediaTypeOrNull())
- return MultipartBody.Part.createFormData(partName, "profile.jpg", requestFile)
+ val bytes = inputStream?.readBytes()
+ val requestFile = bytes?.toRequestBody("image/*".toMediaTypeOrNull())
+ return MultipartBody.Part.createFormData(partName, "profile.jpg", requestFile!!)
}
- private fun prepareBitmapPart(
- partName: String,
- bitmap: Bitmap,
- ): MultipartBody.Part {
- val stream = ByteArrayOutputStream()
- bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream)
- val byteArray = stream.toByteArray()
- val requestFile = byteArray.toRequestBody("image/jpeg".toMediaTypeOrNull())
- return MultipartBody.Part.createFormData(partName, "profile.jpg", requestFile)
+ private fun showMessage(msg: String) {
+ Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/deakin/gopher/guardian/view/general/AssignNurseActivity.kt b/app/src/main/java/deakin/gopher/guardian/view/general/AssignNurseActivity.kt
new file mode 100644
index 00000000..49933d0f
--- /dev/null
+++ b/app/src/main/java/deakin/gopher/guardian/view/general/AssignNurseActivity.kt
@@ -0,0 +1,158 @@
+@file:Suppress("ktlint")
+
+package deakin.gopher.guardian.view.general
+
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.widget.*
+import androidx.appcompat.app.AppCompatActivity
+import deakin.gopher.guardian.R
+import deakin.gopher.guardian.model.user.Nurse
+import deakin.gopher.guardian.model.login.SessionManager
+import deakin.gopher.guardian.services.api.ApiClient
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class AssignNurseActivity : AppCompatActivity() {
+
+ private lateinit var spinnerNurses: Spinner
+ private lateinit var btnAssign: Button
+ private lateinit var progressBar: ProgressBar
+
+
+ private var nursesList: MutableList = mutableListOf()
+ private var assignedNurseIds: MutableSet = mutableSetOf()
+ private var selectedNurse: Nurse? = null
+ private var patientId: String = ""
+ private lateinit var patientName: String
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_assign_nurse)
+
+ spinnerNurses = findViewById(R.id.spinnerNurses)
+ btnAssign = findViewById(R.id.btnAssign)
+ progressBar = findViewById(R.id.progressBar)
+ progressBar.visibility = View.GONE
+
+ // Get patientId from intent extras
+ patientId = intent.getStringExtra("patientId") ?: ""
+
+ fetchNurses()
+
+ spinnerNurses.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ selectedNurse = if (position > 0) nursesList[position - 1] else null
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {}
+ }
+
+ btnAssign.setOnClickListener {
+ if (selectedNurse != null) {
+ assignNurse(selectedNurse!!)
+ } else {
+ Toast.makeText(this, "Please select a nurse", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ private fun fetchNurses() {
+ val token = "Bearer ${SessionManager.getToken()}"
+ progressBar.visibility = View.VISIBLE
+ CoroutineScope(Dispatchers.IO).launch {
+ try {
+ val response = ApiClient.apiService.getAllNurses(token)
+ if (response.isSuccessful && response.body() != null) {
+ val allNurses = response.body()!!.nurses
+ // Filter out nurses already assigned to this patient
+ nursesList.clear()
+ nursesList.addAll(allNurses.filter { it._id !in assignedNurseIds })
+
+ withContext(Dispatchers.Main) {
+ populateSpinner()
+ progressBar.visibility = View.GONE
+ }
+ } else {
+ withContext(Dispatchers.Main) {
+ progressBar.visibility = View.GONE
+ Toast.makeText(this@AssignNurseActivity, "Failed to fetch nurses", Toast.LENGTH_SHORT).show()
+ }
+ }
+ } catch (e: Exception) {
+ withContext(Dispatchers.Main) {
+ progressBar.visibility = View.GONE
+ Toast.makeText(this@AssignNurseActivity, "Error: ${e.localizedMessage}", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ }
+
+ private fun populateSpinner() {
+ val names = mutableListOf("Select a nurse")
+ names.addAll(nursesList.map { it.fullname })
+ val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, names)
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ spinnerNurses.adapter = adapter
+ }
+
+ private fun assignNurse(nurse: Nurse) {
+ val token = "Bearer ${SessionManager.getToken()}"
+ val body = mapOf(
+ "nurseId" to nurse._id,
+ "patientId" to patientId
+ )
+
+ progressBar.visibility = View.VISIBLE
+ CoroutineScope(Dispatchers.IO).launch {
+ try {
+ val response = ApiClient.apiService.assignNurse(token, body)
+ withContext(Dispatchers.Main) {
+ progressBar.visibility = View.GONE
+ if (response.isSuccessful) {
+ Toast.makeText(
+ this@AssignNurseActivity,
+ "Assigned nurse: ${nurse.fullname}",
+ Toast.LENGTH_SHORT
+ ).show()
+
+ sendAssignmentNotification(nurse, patientName = patientName )
+ nursesList.remove(nurse)
+ populateSpinner()
+ } else {
+ Toast.makeText(this@AssignNurseActivity, "Failed to assign nurse", Toast.LENGTH_SHORT).show()
+ }
+ }
+ } catch (e: Exception) {
+ withContext(Dispatchers.Main) {
+ progressBar.visibility = View.GONE
+ Toast.makeText(this@AssignNurseActivity, "Error: ${e.localizedMessage}", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ }
+
+ private fun sendAssignmentNotification(nurse: Nurse, patientName: String) {
+ val token = "Bearer ${SessionManager.getToken()}"
+ val body = mapOf(
+ "userId" to nurse._id,
+ "title" to "New Patient Assigned",
+ "message" to "Patient has been assigned to you"
+ )
+
+ CoroutineScope(Dispatchers.IO).launch {
+ try {
+ val resp = ApiClient.apiService.createNotification(token, body)
+ if (!resp.isSuccessful) {
+ Log.e("AssignNurse", "Notification API failed: ${resp.code()}")
+ }
+ } catch (e: Exception) {
+ Log.e("AssignNurse", "Notification API error", e)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/deakin/gopher/guardian/view/general/EditPatientActivity.kt b/app/src/main/java/deakin/gopher/guardian/view/general/EditPatientActivity.kt
new file mode 100644
index 00000000..436caa0d
--- /dev/null
+++ b/app/src/main/java/deakin/gopher/guardian/view/general/EditPatientActivity.kt
@@ -0,0 +1,83 @@
+@file:Suppress("ktlint")
+
+package deakin.gopher.guardian.view.general
+
+import android.os.Bundle
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import deakin.gopher.guardian.databinding.ActivityEditPatientBinding
+import deakin.gopher.guardian.model.Patient
+import deakin.gopher.guardian.model.login.SessionManager
+import deakin.gopher.guardian.services.api.ApiClient
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import retrofit2.Response
+
+// DTO for update request (matches API spec)
+data class UpdatePatientRequest(
+ val fullname: String,
+ val dateOfBirth: String,
+ val gender: String
+)
+
+class EditPatientActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityEditPatientBinding
+ private lateinit var patient: Patient
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityEditPatientBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ // Get patient from Intent
+ patient = intent.getSerializableExtra("patient") as Patient
+
+ // Pre-fill fields with current data
+ binding.etFullname.setText(patient.fullname)
+ binding.etDateOfBirth.setText(patient.dateOfBirth)
+ binding.etGender.setText(patient.gender)
+
+ // Save button
+ binding.btnSave.setOnClickListener {
+ updatePatient()
+ }
+ }
+
+ private fun updatePatient() {
+ val updatedPatient = UpdatePatientRequest(
+ fullname = binding.etFullname.text.toString().trim(),
+ dateOfBirth = binding.etDateOfBirth.text.toString().trim(),
+ gender = binding.etGender.text.toString().trim()
+ )
+
+ val token = "Bearer ${SessionManager.getToken()}"
+
+ CoroutineScope(Dispatchers.IO).launch {
+ val response = try {
+ ApiClient.apiService.updatePatient(
+ token = token,
+ patientId = patient.id, // 👈 ensure this is the DB ObjectId
+ request = updatedPatient
+ )
+ } catch (e: Exception) {
+ null
+ }
+
+ withContext(Dispatchers.Main) {
+ if (response?.isSuccessful == true) {
+ Toast.makeText(this@EditPatientActivity, "Patient updated successfully", Toast.LENGTH_SHORT).show()
+ finish()
+ } else {
+ Toast.makeText(
+ this@EditPatientActivity,
+ "Update failed: ${response?.errorBody()?.string() ?: response?.message() ?: "Unknown"}",
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/deakin/gopher/guardian/view/general/Homepage4nurse.kt b/app/src/main/java/deakin/gopher/guardian/view/general/Homepage4nurse.kt
index 4b529e37..3e55414a 100644
--- a/app/src/main/java/deakin/gopher/guardian/view/general/Homepage4nurse.kt
+++ b/app/src/main/java/deakin/gopher/guardian/view/general/Homepage4nurse.kt
@@ -1,23 +1,40 @@
package deakin.gopher.guardian.view.general
import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
import android.widget.Button
+import android.widget.ImageView
import android.widget.TextView
+import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
+import androidx.coordinatorlayout.widget.CoordinatorLayout
import deakin.gopher.guardian.R
import deakin.gopher.guardian.model.login.SessionManager
+import deakin.gopher.guardian.model.register.NotificationItem
import deakin.gopher.guardian.services.EmailPasswordAuthService
import deakin.gopher.guardian.services.NavigationService
+import deakin.gopher.guardian.services.api.ApiClient
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
class Homepage4nurse : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_homepage4nurse)
+ // Get current nurse user
val user = SessionManager.getCurrentUser()
+ val userId = user.id // nurse ID
+
+ // Update welcome title
val titleText: TextView = findViewById(R.id.medicalDiagnosticsTitleTextView)
titleText.append(" ${user.name.split(" ").getOrNull(0) ?: ""}")
+ // Initialize buttons
val patientsButton: Button = findViewById(R.id.patientsButton_nurse)
val settingsButton: Button = findViewById(R.id.settingsButton_nurse)
val signOutButton: Button = findViewById(R.id.sighOutButton_nurse)
@@ -26,15 +43,99 @@ class Homepage4nurse : AppCompatActivity() {
NavigationService(this).onLaunchPatientList()
}
- // settings button
settingsButton.setOnClickListener {
NavigationService(this).onSettings()
}
- // sign out button
signOutButton.setOnClickListener {
EmailPasswordAuthService.signOut(this)
finish()
}
+
+ // Fetch notifications from backend
+ fetchNotifications()
+
+ val coordinatorLayout: CoordinatorLayout = findViewById(R.id.homepageCoordinator)
+
+ showDropdownNotification(
+ coordinatorLayout,
+ "New Patient Assigned",
+ "A new Patient has been assigned to you",
+ )
+ }
+
+ private fun fetchNotifications() {
+ val token = "Bearer ${SessionManager.getToken()}"
+
+ CoroutineScope(Dispatchers.IO).launch {
+ try {
+ val response = ApiClient.apiService.getMyNotifications(token)
+ if (response.isSuccessful) {
+ val notifications: List = response.body() ?: emptyList()
+
+ // Display notifications (toast for now)
+ withContext(Dispatchers.Main) {
+ notifications.forEach { notification ->
+ Toast.makeText(
+ this@Homepage4nurse,
+ notification.message,
+ Toast.LENGTH_LONG,
+ ).show()
+ }
+ }
+ } else {
+ Log.e("Homepage4nurse", "Failed to fetch notifications: ${response.code()}")
+ }
+ } catch (e: Exception) {
+ Log.e("Homepage4nurse", "Error fetching notifications", e)
+ }
+ }
+ }
+
+ fun showDropdownNotification(
+ coordinatorLayout: CoordinatorLayout,
+ title: String,
+ message: String,
+ ) {
+ val inflater = LayoutInflater.from(coordinatorLayout.context)
+ val notificationView =
+ inflater.inflate(R.layout.dropdown_notification, coordinatorLayout, false)
+
+ val titleText: TextView = notificationView.findViewById(R.id.notificationTitle)
+ val messageText: TextView = notificationView.findViewById(R.id.notificationMessage)
+ val dismissBtn: ImageView = notificationView.findViewById(R.id.notificationDismiss)
+
+ titleText.text = title
+ messageText.text = message
+
+ // Add view to CoordinatorLayout
+ coordinatorLayout.addView(notificationView)
+
+ // Slide-down animation
+ notificationView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
+ val height = notificationView.measuredHeight.toFloat()
+
+ notificationView.translationY = -height
+ notificationView.animate()
+ .translationY(0f)
+ .setDuration(300)
+ .start()
+
+ // Auto-dismiss after 2s
+ val dismissRunnable =
+ Runnable {
+ notificationView.animate()
+ .translationY(-height)
+ .setDuration(300)
+ .withEndAction { coordinatorLayout.removeView(notificationView) }
+ .start()
+ }
+ notificationView.postDelayed(dismissRunnable, 2000)
+
+ // Manual dismiss
+ dismissBtn.setOnClickListener {
+ notificationView.removeCallbacks(dismissRunnable)
+ dismissRunnable.run()
+ }
}
}
diff --git a/app/src/main/java/deakin/gopher/guardian/view/general/LoginActivity.kt b/app/src/main/java/deakin/gopher/guardian/view/general/LoginActivity.kt
index c03d645d..0093652f 100644
--- a/app/src/main/java/deakin/gopher/guardian/view/general/LoginActivity.kt
+++ b/app/src/main/java/deakin/gopher/guardian/view/general/LoginActivity.kt
@@ -87,7 +87,7 @@ class LoginActivity : BaseActivity() {
val user = response.body()!!.user
val token = response.body()!!.token
SessionManager.createLoginSession(user, token)
- NavigationService(this@LoginActivity).toPinCodeActivity(user.role)
+ NavigationService(this@LoginActivity).toHomeScreenForRole(user.role)
} else {
// Handle error
val errorResponse =
diff --git a/app/src/main/java/deakin/gopher/guardian/view/general/MedicalSummaryScreen.kt b/app/src/main/java/deakin/gopher/guardian/view/general/MedicalSummaryScreen.kt
index cce54bbd..37484864 100644
--- a/app/src/main/java/deakin/gopher/guardian/view/general/MedicalSummaryScreen.kt
+++ b/app/src/main/java/deakin/gopher/guardian/view/general/MedicalSummaryScreen.kt
@@ -1,3 +1,6 @@
+
+@file:Suppress("ktlint")
+
package deakin.gopher.guardian
import androidx.compose.foundation.Image
@@ -165,4 +168,4 @@ fun MedicalSummaryScreen(
)
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/deakin/gopher/guardian/view/general/PatientDetailsActivity.kt b/app/src/main/java/deakin/gopher/guardian/view/general/PatientDetailsActivity.kt
index 0e9f2ae4..facd4de5 100644
--- a/app/src/main/java/deakin/gopher/guardian/view/general/PatientDetailsActivity.kt
+++ b/app/src/main/java/deakin/gopher/guardian/view/general/PatientDetailsActivity.kt
@@ -53,18 +53,6 @@ class PatientDetailsActivity : BaseActivity() {
}
}"
- if (patient.healthConditions.isNotEmpty()) {
- val formattedConditions =
- patient.healthConditions.joinToString(", ") { condition ->
- condition.split(" ").joinToString(" ") { word ->
- word.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
- }
- }
- binding.tvHealthConditions.text = "Health Conditions: $formattedConditions"
- } else {
- binding.tvHealthConditions.text = "Health Conditions: No conditions listed"
- }
-
Glide.with(this)
.load(patient.photoUrl)
.placeholder(R.drawable.profile)
diff --git a/app/src/main/java/deakin/gopher/guardian/view/general/PatientListActivity.kt b/app/src/main/java/deakin/gopher/guardian/view/general/PatientListActivity.kt
index c2bbfe07..ab3b20fd 100644
--- a/app/src/main/java/deakin/gopher/guardian/view/general/PatientListActivity.kt
+++ b/app/src/main/java/deakin/gopher/guardian/view/general/PatientListActivity.kt
@@ -34,9 +34,15 @@ class PatientListActivity : BaseActivity() {
startActivity(intent)
},
onAssignNurseClick = { patient ->
-// val intent = Intent(this, AssignNurseActivity::class.java)
-// intent.putExtra("patientId", patient.id)
-// startActivity(intent)
+ val intent = Intent(this, AssignNurseActivity::class.java)
+ intent.putExtra("patientId", patient.id)
+ intent.putExtra("patientName", patient.fullname)
+ startActivity(intent)
+ },
+ onEditClick = { patient ->
+ val intent = Intent(this, EditPatientActivity::class.java)
+ intent.putExtra("patient", patient)
+ startActivity(intent)
},
onDeleteClick = { patient ->
confirmDeletePatient(patient)
@@ -44,8 +50,37 @@ class PatientListActivity : BaseActivity() {
)
private fun confirmDeletePatient(patient: Patient) {
- // Optional: show a confirmation dialog before deleting
- deletePatient(patient)
+ val builder = androidx.appcompat.app.AlertDialog.Builder(this)
+ builder.setTitle("Delete Patient")
+ builder.setMessage("Are you sure you want to delete?")
+
+ builder.setPositiveButton("Delete") { dialog, _ ->
+ deletePatient(patient)
+ dialog.dismiss()
+ }
+
+ builder.setNegativeButton("Cancel") { dialog, _ ->
+ dialog.dismiss()
+ }
+
+ val dialog = builder.create()
+ dialog.show()
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ R.id.action_add_patient -> {
+ val intent = Intent(this, AddNewPatientActivity::class.java)
+ startActivity(intent)
+ true
+ }
+ R.id.assign_nurse -> {
+ val intent = Intent(this, AssignNurseActivity::class.java)
+ startActivity(intent)
+ true
+ }
+ else -> super.onOptionsItemSelected(item)
+ }
}
private fun deletePatient(patient: Patient) {
@@ -60,7 +95,7 @@ class PatientListActivity : BaseActivity() {
withContext(Dispatchers.Main) {
if (response?.isSuccessful == true) {
showMessage("Patient deleted")
- fetchPatients() // Refresh the patient list
+ fetchPatients()
} else {
showMessage("Failed to delete patient")
}
@@ -101,24 +136,25 @@ class PatientListActivity : BaseActivity() {
binding.progressBar.show()
}
}
+
val response = ApiClient.apiService.getAssignedPatients(token)
+
withContext(Dispatchers.Main) {
- withContext(Dispatchers.Main) {
- binding.progressBar.hide()
- }
+ binding.progressBar.hide()
+
if (response.isSuccessful) {
- if (!response.body().isNullOrEmpty()) {
- patientListAdapter.updateData(response.body()!!)
- withContext(Dispatchers.Main) {
- binding.tvEmptyMessage.visibility = View.GONE
+ val patients =
+ response.body()?.filter { patient ->
+ patient.isDeleted != true
}
+
+ if (!patients.isNullOrEmpty()) {
+ patientListAdapter.updateData(patients)
+ binding.tvEmptyMessage.visibility = View.GONE
} else {
- withContext(Dispatchers.Main) {
- binding.tvEmptyMessage.visibility = View.VISIBLE
- }
+ binding.tvEmptyMessage.visibility = View.VISIBLE
}
} else {
- // Handle error
val errorResponse =
Gson().fromJson(
response.errorBody()?.string(),
@@ -131,21 +167,10 @@ class PatientListActivity : BaseActivity() {
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
- if (currentUser.organization != null) {
- return false
- }
- menuInflater.inflate(R.menu.menu_patient_list, menu)
+ menuInflater.inflate(R.menu.menu_add_patient, menu)
return true
}
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (item.itemId == R.id.action_add_patient) {
- startActivity(Intent(this, AddNewPatientActivity::class.java))
- return true
- }
- return super.onOptionsItemSelected(item)
- }
-
private fun showMessage(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
diff --git a/app/src/main/java/deakin/gopher/guardian/view/patient/DoctorHomeScreen.kt b/app/src/main/java/deakin/gopher/guardian/view/patient/DoctorHomeScreen.kt
index 83c86b44..90b8ed27 100644
--- a/app/src/main/java/deakin/gopher/guardian/view/patient/DoctorHomeScreen.kt
+++ b/app/src/main/java/deakin/gopher/guardian/view/patient/DoctorHomeScreen.kt
@@ -1,3 +1,6 @@
+@file:Suppress("ktlint")
+
+
package deakin.gopher.guardian
import androidx.compose.foundation.Image
@@ -139,4 +142,4 @@ fun HomeCard(
Text(text = label, fontSize = 13.sp, color = Color.White)
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/deakin/gopher/guardian/view/patient/patientdata/heartrate/ui/components/HeartRateInput.kt b/app/src/main/java/deakin/gopher/guardian/view/patient/patientdata/heartrate/ui/components/HeartRateInput.kt
index 40068b5c..0f5a9caa 100644
--- a/app/src/main/java/deakin/gopher/guardian/view/patient/patientdata/heartrate/ui/components/HeartRateInput.kt
+++ b/app/src/main/java/deakin/gopher/guardian/view/patient/patientdata/heartrate/ui/components/HeartRateInput.kt
@@ -1,3 +1,6 @@
+@file:Suppress("ktlint")
+
+
package deakin.gopher.guardian.view.patient.patientdata.heartrate.ui.components
import androidx.compose.foundation.layout.Arrangement
@@ -81,4 +84,4 @@ fun HeartRateInputPreview() {
GuardianTheme {
HeartRateInput { _, _ -> }
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/deakin/gopher/guardian/view/patient/patientdata/heartrate/ui/screens/AddHeartRateScreen.kt b/app/src/main/java/deakin/gopher/guardian/view/patient/patientdata/heartrate/ui/screens/AddHeartRateScreen.kt
index a50a19d2..3205077b 100644
--- a/app/src/main/java/deakin/gopher/guardian/view/patient/patientdata/heartrate/ui/screens/AddHeartRateScreen.kt
+++ b/app/src/main/java/deakin/gopher/guardian/view/patient/patientdata/heartrate/ui/screens/AddHeartRateScreen.kt
@@ -1,3 +1,6 @@
+@file:Suppress("ktlint")
+
+
package deakin.gopher.guardian.view.patient.patientdata.heartrate.ui.screens
import android.widget.Toast
@@ -75,4 +78,4 @@ fun AddHeartRateScreen(navController: NavController) {
})
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml
new file mode 100644
index 00000000..7bd1f481
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/notification_background.xml b/app/src/main/res/drawable/notification_background.xml
new file mode 100644
index 00000000..a4edcefa
--- /dev/null
+++ b/app/src/main/res/drawable/notification_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/layout/activity_assign_nurse.xml b/app/src/main/res/layout/activity_assign_nurse.xml
new file mode 100644
index 00000000..f7cb19a0
--- /dev/null
+++ b/app/src/main/res/layout/activity_assign_nurse.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_edit_patient.xml b/app/src/main/res/layout/activity_edit_patient.xml
new file mode 100644
index 00000000..330fb948
--- /dev/null
+++ b/app/src/main/res/layout/activity_edit_patient.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_homepage4nurse.xml b/app/src/main/res/layout/activity_homepage4nurse.xml
index 2cd321f1..f7bfb528 100644
--- a/app/src/main/res/layout/activity_homepage4nurse.xml
+++ b/app/src/main/res/layout/activity_homepage4nurse.xml
@@ -1,161 +1,176 @@
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+ android:layout_height="match_parent">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_patient_details.xml b/app/src/main/res/layout/activity_patient_details.xml
index 64059d2d..0d0de905 100644
--- a/app/src/main/res/layout/activity_patient_details.xml
+++ b/app/src/main/res/layout/activity_patient_details.xml
@@ -98,15 +98,7 @@
app:layout_constraintTop_toBottomOf="@id/tvDob"
tools:text="Gender: Male" />
-
+
diff --git a/app/src/main/res/layout/activity_patient_list.xml b/app/src/main/res/layout/activity_patient_list.xml
index d1f3af3e..75ca84b6 100644
--- a/app/src/main/res/layout/activity_patient_list.xml
+++ b/app/src/main/res/layout/activity_patient_list.xml
@@ -7,6 +7,7 @@
android:layout_height="match_parent"
tools:context="deakin.gopher.guardian.view.general.PatientListActivity">
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/menu_add_patient.xml b/app/src/main/res/menu/menu_add_patient.xml
new file mode 100644
index 00000000..4c5b4b97
--- /dev/null
+++ b/app/src/main/res/menu/menu_add_patient.xml
@@ -0,0 +1,10 @@
+
+
diff --git a/app/src/main/res/menu/menu_patient_item.xml b/app/src/main/res/menu/menu_patient_item.xml
index 1f11cf04..49d88207 100644
--- a/app/src/main/res/menu/menu_patient_item.xml
+++ b/app/src/main/res/menu/menu_patient_item.xml
@@ -3,6 +3,9 @@
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9fe4ecab..c6bb77d7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -21,6 +21,7 @@
Second Fragment
Next
< Choose a Different Patient
+ < Dismiss
Hello first fragment
Hello second fragment. Arg: %1$s
diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 00000000..f4adfacc
--- /dev/null
+++ b/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,6 @@
+
+
+
+