Skip to content

Commit ce5ab1b

Browse files
authored
Combine lab results and medications into one page with two tabs, fix bug, revise ui tests (#35)
# *Combine lab results and medications into one page with two tabs, fix bug, revise ui tests* ## ♻️ Current situation & Problem Currently, lab results and medications are on separate pages. Since we plan to add a dashboard and a connect page, we want to keep the total number of main pages to five. Given the functional similarities between lab results and medications, it makes sense to combine them into a single page with tabs for better organization and navigation. ## ⚙️ Release Notes 1. New Records Page: - Combined lab results and medications into a single page with two tabs. - Users can switch between lab results and medications without navigating to different pages. 2. UI Improvements: - Replaced swipe-to-edit/delete actions for medications with direct "Edit" and "Delete" buttons to avoid conflicts with tab gestures. - Added icons and spacing to make the new buttons more intuitive and clean. 3. Bug Fixes: - Found and Fixed sorting issue in `LabResultsManager` to ensure records are ordered chronologically. 4. Updated UI Tests: - Refactored tests for lab results and medication views to reflect the new tabbed interface. - Added checks for the presence of the new buttons and verified the tab-switching behavior. <img width="385" alt="image" src="https://github.com/user-attachments/assets/a77888a9-b0a5-4846-9b1c-883b2abd0eb7" /> ## 📚 Documentation *Please ensure that you properly document any additions in conformance to [Spezi Documentation Guide](https://github.com/StanfordSpezi/.github/blob/main/DOCUMENTATIONGUIDE.md).* *You can use this section to describe your solution, but we encourage contributors to document your reasoning and changes using in-line documentation.* ## ✅ Testing *Please ensure that the PR meets the testing requirements set by CodeCov and that new functionality is appropriately tested.* *This section describes important information about the tests and why some elements might not be testable.* ## 📝 Code of Conduct & Contributing Guidelines By submitting creating this pull request, you agree to follow our [Code of Conduct](https://github.com/CS342/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/CS342/.github/blob/main/CONTRIBUTING.md): - [x] I agree to follow the [Code of Conduct](https://github.com/CS342/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/CS342/.github/blob/main/CONTRIBUTING.md).
1 parent bf78740 commit ce5ab1b

File tree

11 files changed

+200
-111
lines changed

11 files changed

+200
-111
lines changed

NeutroFeverGuard.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
C42C1C6B2D7A985B00EA417F /* MedicationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C1C6A2D7A985B00EA417F /* MedicationManager.swift */; };
8181
C42C1C712D7AB84300EA417F /* MedicationViewUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C1C702D7AB84300EA417F /* MedicationViewUITests.swift */; };
8282
C42D4DA52D5AC89900B2A169 /* LabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42D4DA42D5AC89500B2A169 /* LabView.swift */; };
83+
C48838FA2D7F849E00D25764 /* RecordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48838F92D7F849800D25764 /* RecordsView.swift */; };
8384
C49EC8342D501C62005C3495 /* DataType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EC8332D501C5C005C3495 /* DataType.swift */; };
8485
C49EC8382D5026EE005C3495 /* DataTypeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EC8372D5026EE005C3495 /* DataTypeTest.swift */; };
8586
C49EC8402D5033F9005C3495 /* AddDataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EC83F2D5033F9005C3495 /* AddDataView.swift */; };
@@ -160,6 +161,7 @@
160161
C42C1C6A2D7A985B00EA417F /* MedicationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MedicationManager.swift; sourceTree = "<group>"; };
161162
C42C1C702D7AB84300EA417F /* MedicationViewUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MedicationViewUITests.swift; sourceTree = "<group>"; };
162163
C42D4DA42D5AC89500B2A169 /* LabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabView.swift; sourceTree = "<group>"; };
164+
C48838F92D7F849800D25764 /* RecordsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsView.swift; sourceTree = "<group>"; };
163165
C49EC8332D501C5C005C3495 /* DataType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataType.swift; sourceTree = "<group>"; };
164166
C49EC8372D5026EE005C3495 /* DataTypeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTypeTest.swift; sourceTree = "<group>"; };
165167
C49EC83F2D5033F9005C3495 /* AddDataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddDataView.swift; sourceTree = "<group>"; };
@@ -338,6 +340,7 @@
338340
EB7D47E02D51433200CEDC78 /* HealthKitVisualizations */,
339341
F22393792D5D4092006C8EB4 /* DataError.swift */,
340342
C42D4DA42D5AC89500B2A169 /* LabView.swift */,
343+
C48838F92D7F849800D25764 /* RecordsView.swift */,
341344
B0C95E382D78D5F2007A4CA6 /* FeverMonitoring.swift */,
342345
C42C1B302D782A7100EA417F /* MedicationView.swift */,
343346
C4C0A78E2D518DB500F37EEC /* HelperFunc.swift */,
@@ -640,6 +643,7 @@
640643
C49EC8402D5033F9005C3495 /* AddDataView.swift in Sources */,
641644
C42D4DA52D5AC89900B2A169 /* LabView.swift in Sources */,
642645
F2E6898F2D52ED8600869C4F /* HealthKitService.swift in Sources */,
646+
C48838FA2D7F849E00D25764 /* RecordsView.swift in Sources */,
643647
C49EC8342D501C62005C3495 /* DataType.swift in Sources */,
644648
2FE5DC5329EDD7FA004B9AB4 /* Bundle+Questionnaire.swift in Sources */,
645649
2F5E32BD297E05EA003432F8 /* NeutroFeverGuardDelegate.swift in Sources */,

NeutroFeverGuard.xcodeproj/xcshareddata/xcschemes/NeutroFeverGuard.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
</CommandLineArgument>
8484
<CommandLineArgument
8585
argument = "--showOnboarding"
86-
isEnabled = "YES">
86+
isEnabled = "NO">
8787
</CommandLineArgument>
8888
<CommandLineArgument
8989
argument = "--skipOnboarding"

NeutroFeverGuard/HomeView.swift

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ struct HomeView: View {
1414
enum Tabs: String {
1515
// case dashboard
1616
case addData
17-
case labResult
18-
case medication
17+
// case labResult
18+
// case medication
19+
case records
1920
// case schedule
2021
case contact
2122
}
@@ -41,19 +42,12 @@ struct HomeView: View {
4142
.customizationID("home.addData")
4243
.accessibilityIdentifier("Add Data")
4344

44-
// Lab tab
45-
Tab("Lab", systemImage: "flask", value: .labResult) {
46-
LabView(presentingAccount: $presentingAccount)
45+
// Record tab
46+
Tab("Records", systemImage: "list.clipboard", value: .records) {
47+
RecordsView(presentingAccount: $presentingAccount)
4748
}
48-
.customizationID("home.lab")
49-
.accessibilityIdentifier("Lab")
50-
51-
// Medication tab
52-
Tab("Medication", systemImage: "pills", value: .medication) {
53-
MedicationView(presentingAccount: $presentingAccount)
54-
}
55-
.customizationID("home.medication")
56-
.accessibilityIdentifier("Medication")
49+
.customizationID("home.records")
50+
.accessibilityIdentifier("Records")
5751

5852
// Schedule tab
5953
// Tab("Schedule", systemImage: "list.clipboard", value: .schedule) {

NeutroFeverGuard/LabResultsManager.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class LabResultsManager: Module, EnvironmentAccessible {
5757
results = []
5858
}
5959

60+
results.sort { $0.date > $1.date }
6061
self.labRecords = results
6162
if let latestRecord = results.first {
6263
latestRecordedTime = formatDate(latestRecord.date)

NeutroFeverGuard/LabView.swift

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
// SPDX-License-Identifier: MIT
77
//
88

9-
import SpeziAccount
109
import SpeziLocalStorage
1110
import SwiftUI
1211

@@ -122,24 +121,19 @@ struct LabResultDetailView: View {
122121

123122
struct LabView: View {
124123
@Environment(LabResultsManager.self) private var labResultsManager
125-
@Environment(Account.self) private var account: Account?
126-
@Binding var presentingAccount: Bool
127124
// @Environment(NeutroFeverGuardScheduler.self) private var scheduler
128125

129126
var body: some View {
130-
NavigationView {
131-
List {
132-
ancSection()
133-
labHistorySection()
134-
}
135-
.listStyle(.insetGrouped)
136-
.navigationTitle("Lab Results")
137-
.background(Color(.systemGray6))
138-
.toolbar { toolbarContent() }
139-
.onAppear {
140-
labResultsManager.refresh()
127+
List {
128+
ancSection()
129+
labHistorySection()
130+
}
131+
.listStyle(.insetGrouped)
132+
.navigationTitle("Lab Results")
133+
.background(Color(.systemGray6))
134+
.onAppear {
135+
labResultsManager.refresh()
141136
// scheduler.printUpcomingLabResultEvents()
142-
}
143137
}
144138
}
145139

@@ -170,16 +164,8 @@ struct LabView: View {
170164
}
171165
}
172166
}
173-
174-
private func toolbarContent() -> some ToolbarContent {
175-
ToolbarItem {
176-
if account != nil {
177-
AccountButton(isPresented: $presentingAccount)
178-
}
179-
}
180-
}
181167
}
182168

183169
#Preview {
184-
LabView(presentingAccount: .constant(false))
170+
LabView()
185171
}

NeutroFeverGuard/MedicationManager.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ class MedicationManager: Module, EnvironmentAccessible {
5858
saveMedResults()
5959
}
6060

61-
func deleteMedEntry(at offsets: IndexSet) {
62-
medications.remove(atOffsets: offsets)
61+
func deleteMedEntry(at index: Int) {
62+
guard medications.indices.contains(index)
63+
else { return }
64+
medications.remove(at: index)
6365
saveMedResults()
6466
}
6567

NeutroFeverGuard/MedicationView.swift

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -110,47 +110,64 @@ struct MedicationEditForm: View {
110110

111111
struct MedicationView: View {
112112
@Environment(MedicationManager.self) private var medicationManager
113-
@Environment(Account.self) private var account: Account?
114113
@State private var isEditing = false
115114
@State private var editingMedicationIndex: Int = 0
116-
@Binding var presentingAccount: Bool
115+
@State private var deleteMedicationIndex: Int = 0
116+
@State private var showDeleteAlert = false
117117

118118
var body: some View {
119-
NavigationView {
120-
List {
121-
if medicationManager.medications.isEmpty {
122-
Text("No medications recorded").foregroundColor(.gray)
123-
} else {
124-
ForEach(Array(medicationManager.medications.enumerated()), id: \.element.date) { index, medication in
119+
List {
120+
if medicationManager.medications.isEmpty {
121+
Text("No medications recorded").foregroundColor(.gray)
122+
} else {
123+
ForEach(Array(medicationManager.medications.enumerated()), id: \.element.date) { index, medication in
124+
HStack {
125125
MedicationRow(medication: medication)
126-
.swipeActions(edge: .leading) {
127-
Button("Edit") {
128-
editingMedicationIndex = index
129-
isEditing = true
130-
}.tint(.blue)
126+
Spacer()
127+
VStack {
128+
Button("Edit") {
129+
editingMedicationIndex = index
130+
isEditing = true
131131
}
132+
.buttonStyle(.plain)
133+
.foregroundColor(.blue)
134+
.padding(.bottom, 4)
135+
136+
Button("Delete") {
137+
deleteMedicationIndex = index
138+
showDeleteAlert = true
139+
}
140+
.buttonStyle(.plain)
141+
.foregroundColor(.red)
142+
}
132143
}
133-
.onDelete { offsets in medicationManager.deleteMedEntry(at: offsets) }
134144
}
135145
}
136-
.navigationTitle("Medication List")
137-
.onAppear { medicationManager.refresh() }
138-
.listStyle(.insetGrouped)
139-
.background(Color(.systemGray6))
140-
.toolbar { if account != nil { AccountButton(isPresented: $presentingAccount) } }
141-
.sheet(isPresented: $isEditing) {
142-
let index = editingMedicationIndex
143-
MedicationEditForm(
144-
medication: medicationManager.medications[index],
145-
onSave: {updatedMedication in
146-
medicationManager.updateMedEntry(at: index, with: updatedMedication)
147-
isEditing = false
148-
},
149-
onCancel: {
150-
isEditing = false
151-
}
152-
)
153-
}
146+
}
147+
.navigationTitle("Medication List")
148+
.onAppear { medicationManager.refresh() }
149+
.listStyle(.insetGrouped)
150+
.background(Color(.systemGray6))
151+
.sheet(isPresented: $isEditing) {
152+
let index = editingMedicationIndex
153+
MedicationEditForm(
154+
medication: medicationManager.medications[index],
155+
onSave: {updatedMedication in
156+
medicationManager.updateMedEntry(at: index, with: updatedMedication)
157+
isEditing = false
158+
},
159+
onCancel: {
160+
isEditing = false
161+
}
162+
)
163+
}
164+
.alert("Delete Medication", isPresented: $showDeleteAlert) {
165+
Button("Delete", role: .destructive) {
166+
medicationManager.deleteMedEntry(at: deleteMedicationIndex)
167+
}.accessibilityIdentifier("MedDeleteAlertButton")
168+
Button("Cancel", role: .cancel) {}
169+
} message: {
170+
Text("Are you sure you want to delete this medication record? This action cannot be undone.")
154171
}
155172
}
156173
}

NeutroFeverGuard/RecordsView.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//
2+
// This source file is part of the NeutroFeverGuard based on the Stanford Spezi Template Application project
3+
//
4+
// SPDX-FileCopyrightText: 2025 Stanford University
5+
//
6+
// SPDX-License-Identifier: MIT
7+
//
8+
9+
import SpeziAccount
10+
import SpeziLocalStorage
11+
import SwiftUI
12+
13+
enum HealthSection {
14+
case labResults
15+
case medications
16+
}
17+
18+
struct RecordsView: View {
19+
@Environment(Account.self) private var account: Account?
20+
@Binding var presentingAccount: Bool
21+
22+
@State private var selectedSection: HealthSection = .labResults
23+
24+
var body: some View {
25+
NavigationView {
26+
VStack {
27+
Picker("Select Section", selection: $selectedSection) {
28+
Text("Lab Results").tag(HealthSection.labResults)
29+
Text("Medications").tag(HealthSection.medications)
30+
}
31+
.pickerStyle(SegmentedPickerStyle())
32+
.padding()
33+
34+
TabView(selection: $selectedSection) {
35+
LabView()
36+
.tag(HealthSection.labResults)
37+
MedicationView()
38+
.tag(HealthSection.medications)
39+
}
40+
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
41+
}
42+
.navigationTitle("Health Data")
43+
.toolbar { toolbarContent() }
44+
.background(Color(.systemGray6))
45+
}
46+
}
47+
48+
private func toolbarContent() -> some ToolbarContent {
49+
ToolbarItem {
50+
if account != nil {
51+
AccountButton(isPresented: $presentingAccount)
52+
}
53+
}
54+
}
55+
}

NeutroFeverGuard/Resources/Localizable.xcstrings

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@
132132
},
133133
"Are you sure you want to delete this lab record? This action cannot be undone." : {
134134

135+
},
136+
"Are you sure you want to delete this medication record? This action cannot be undone." : {
137+
135138
},
136139
"Body Temperature (°F)" : {
137140
"localizations" : {
@@ -222,6 +225,9 @@
222225
},
223226
"Delete Lab Record" : {
224227

228+
},
229+
"Delete Medication" : {
230+
225231
},
226232
"Diastolic (mmHg)" : {
227233
"localizations" : {
@@ -277,6 +283,9 @@
277283
}
278284
}
279285
}
286+
},
287+
"Health Data" : {
288+
280289
},
281290
"Health Data Access" : {
282291

@@ -475,6 +484,7 @@
475484
}
476485
},
477486
"Lab" : {
487+
"extractionState" : "stale",
478488
"localizations" : {
479489
"en" : {
480490
"stringUnit" : {
@@ -558,15 +568,15 @@
558568
}
559569
}
560570
}
561-
},
562-
"Medication" : {
563-
564571
},
565572
"Medication List" : {
566573

567574
},
568575
"Medication Name" : {
569576

577+
},
578+
"Medications" : {
579+
570580
},
571581
"Name" : {
572582

@@ -731,6 +741,9 @@
731741
},
732742
"Record & Track Symptoms" : {
733743

744+
},
745+
"Records" : {
746+
734747
},
735748
"Save" : {
736749

@@ -745,6 +758,9 @@
745758
}
746759
}
747760
}
761+
},
762+
"Select Section" : {
763+
748764
},
749765
"Sign the Consent Form" : {
750766

0 commit comments

Comments
 (0)