Skip to content

Commit

Permalink
Implement first mediction tracking UI
Browse files Browse the repository at this point in the history
  • Loading branch information
PSchmiedmayer committed Jan 30, 2024
1 parent 198ec09 commit 8d74cdf
Show file tree
Hide file tree
Showing 25 changed files with 982 additions and 199 deletions.
13 changes: 12 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ let package = Package(
products: [
.library(name: "SpeziMedication", targets: ["SpeziMedication"]),
.library(name: "SpeziMedicationSettings", targets: ["SpeziMedicationSettings"]),
.library(name: "SpeziMedicationTracking", targets: ["SpeziMedicationTracking"])
.library(name: "SpeziMedicationTracking", targets: ["SpeziMedicationTracking"]),
.library(name: "XCTSpeziMedication", targets: ["XCTSpeziMedication"])
],
dependencies: [
.package(url: "https://github.com/StanfordSpezi/SpeziFoundation", .upToNextMinor(from: "0.1.0")),
Expand Down Expand Up @@ -53,6 +54,7 @@ let package = Package(
name: "SpeziMedicationTracking",
dependencies: [
.target(name: "SpeziMedication"),
.target(name: "XCTSpeziMedication"),
.product(name: "Spezi", package: "Spezi"),
.product(name: "SpeziFoundation", package: "SpeziFoundation"),
.product(name: "SpeziViews", package: "SpeziViews")
Expand All @@ -61,6 +63,15 @@ let package = Package(
.process("Resources")
]
),
.target(
name: "XCTSpeziMedication",
dependencies: [
.target(name: "SpeziMedication")
],
resources: [
.process("Resources")
]
),
.testTarget(
name: "SpeziMedicationTests",
dependencies: [
Expand Down
4 changes: 3 additions & 1 deletion Sources/SpeziMedication/Models/LogEntryEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
// Created by Paul Shmiedmayer on 1/28/24.
//

import SwiftUI

public enum LogEntryEvent: Codable {

public enum LogEntryEvent: Codable, CaseIterable {
case skipped
case taken

Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziMedication/Models/MedicationInstance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public protocol MedicationInstance: Codable, Identifiable, Comparable, Hashable
extension MedicationInstance {
/// See Equatable
public static func == (lhs: Self, rhs: Self) -> Bool {
lhs.type == rhs.type && lhs.dosage == rhs.dosage && lhs.schedule == rhs.schedule
lhs.type == rhs.type && lhs.dosage == rhs.dosage && lhs.schedule == rhs.schedule && lhs.logEntries == rhs.logEntries
}

/// See Comparable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import SpeziMedication
import SwiftUI


struct MedicationLogLoggedRow<MI: MedicationInstance>: View {
@Binding private var medicationInstances: [MI]
private var selectedDate: Date = .now


var body: some View {
Text("...")
extension LogEntryEvent {
var icon: Image {
switch self {
case .skipped:
Image(systemName: "x.circle.fill")
case .taken:
Image(systemName: "checkmark.circle.fill")
}
}

Check warning on line 21 in Sources/SpeziMedicationTracking/LogEntryEvent+Icon.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/LogEntryEvent+Icon.swift#L14-L21

Added lines #L14 - L21 were not covered by tests
}
19 changes: 18 additions & 1 deletion Sources/SpeziMedicationTracking/LogEntryEventButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,20 @@ struct LogEntryEventButton: View {
},
label: {
HStack {
Spacer()
if logEntryEvent == role {
Image(systemName: "checkmark.circle.fill")
role.icon
.accessibilityHidden(true)
}
Text(role.localizedDescription)
.foregroundStyle(logEntryEvent == role ? .white : .accentColor)
.fontWeight(.medium)
Spacer()
}
}
)
.tint(.accentColor.opacity(logEntryEvent == role ? 1.0 : 0.2))
.buttonStyle(.borderedProminent)
}

Check warning on line 45 in Sources/SpeziMedicationTracking/LogEntryEventButton.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/LogEntryEventButton.swift#L18-L45

Added lines #L18 - L45 were not covered by tests


Expand All @@ -44,3 +50,14 @@ struct LogEntryEventButton: View {
self._logEntryEvent = logEntryEvent
}

Check warning on line 51 in Sources/SpeziMedicationTracking/LogEntryEventButton.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/LogEntryEventButton.swift#L48-L51

Added lines #L48 - L51 were not covered by tests
}


#Preview {
@State var logEntryEvent: LogEntryEvent?

return HStack {
LogEntryEventButton(role: .skipped, logEntryEvent: $logEntryEvent)
LogEntryEventButton(role: .taken, logEntryEvent: $logEntryEvent)
}
.padding()
}
87 changes: 68 additions & 19 deletions Sources/SpeziMedicationTracking/MedicationDosageAndDateSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import SpeziMedication
import SwiftUI
@_implementationOnly import XCTSpeziMedication


private let numberOfDosageFormatter: NumberFormatter = {
Expand All @@ -28,27 +29,13 @@ struct MedicationDosageAndDateSheet<MI: MedicationInstance>: View {

var body: some View {
NavigationStack {
HStack {
Text(medicationInstance.type.localizedDescription)
.bold()
Text(medicationInstance.dosage.localizedDescription)
.foregroundStyle(.secondary)
VStack {
Form {
Section {
TextField(
"Dosage",
value: $logEntryDosage,
formatter: numberOfDosageFormatter
)
}
Section {
DatePicker(
"Time",
selection: $logEntryDate,
displayedComponents: .hourAndMinute
)
}
titleSection
dosageSection
timeSection
}
.listSectionSpacing(20)
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Expand All @@ -57,14 +44,76 @@ struct MedicationDosageAndDateSheet<MI: MedicationInstance>: View {
}
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Log Details")
}
}

Check warning on line 50 in Sources/SpeziMedicationTracking/MedicationDosageAndDateSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/MedicationDosageAndDateSheet.swift#L30-L50

Added lines #L30 - L50 were not covered by tests

private var titleSection: some View {
Section {
VStack(alignment: .leading, spacing: 8) {
Text(medicationInstance.type.localizedDescription)
.font(.title3)
.bold()
Text(medicationInstance.dosage.localizedDescription)
.foregroundStyle(.secondary)
}
}
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(
EdgeInsets(
top: 0,
leading: 0,
bottom: 0,
trailing: 0
)
)
}

Check warning on line 72 in Sources/SpeziMedicationTracking/MedicationDosageAndDateSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/MedicationDosageAndDateSheet.swift#L52-L72

Added lines #L52 - L72 were not covered by tests

private var dosageSection: some View {
Section {
HStack {
Text("Dosage")
Spacer()
TextField(
"Dosage",
value: $logEntryDosage,
formatter: numberOfDosageFormatter
)
.multilineTextAlignment(.trailing)
.foregroundColor(.accentColor)
}
}
}

Check warning on line 88 in Sources/SpeziMedicationTracking/MedicationDosageAndDateSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/MedicationDosageAndDateSheet.swift#L74-L88

Added lines #L74 - L88 were not covered by tests

private var timeSection: some View {
Section {
DatePicker(
"Time",
selection: $logEntryDate,
displayedComponents: .hourAndMinute
)
.datePickerStyle(.wheel)
}
}

Check warning on line 99 in Sources/SpeziMedicationTracking/MedicationDosageAndDateSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/MedicationDosageAndDateSheet.swift#L90-L99

Added lines #L90 - L99 were not covered by tests


init(medicationInstance: MI, logEntryDosage: Binding<Double>, logEntryDate: Binding<Date>) {
self.medicationInstance = medicationInstance
self._logEntryDosage = logEntryDosage
self._logEntryDate = logEntryDate
}

Check warning on line 106 in Sources/SpeziMedicationTracking/MedicationDosageAndDateSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/MedicationDosageAndDateSheet.swift#L102-L106

Added lines #L102 - L106 were not covered by tests
}


#Preview {
@State var logEntryDosage = 1.0
@State var logEntryDate = Date.now

return MedicationDosageAndDateSheet(
medicationInstance: Mock.medicationInstances.sorted()[0],
logEntryDosage: $logEntryDosage,
logEntryDate: $logEntryDate
)
}
23 changes: 23 additions & 0 deletions Sources/SpeziMedicationTracking/MedicationInstance+LogEntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import Foundation
import SpeziMedication


extension MedicationInstance {
func logEntry(date: Date, asNeeded: Bool = false) -> LogEntry? {
logEntries.first {
if asNeeded {
$0.scheduledTime == nil && $0.date == date
} else {
$0.scheduledTime == date
}
} ?? logEntries.first(where: { $0.date == date })
}

Check warning on line 22 in Sources/SpeziMedicationTracking/MedicationInstance+LogEntry.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/MedicationInstance+LogEntry.swift#L14-L22

Added lines #L14 - L22 were not covered by tests
}
127 changes: 127 additions & 0 deletions Sources/SpeziMedicationTracking/MedicationLogLogged.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import SpeziMedication
import SwiftUI
@_implementationOnly import XCTSpeziMedication


struct MedicationLogLogged<MI: MedicationInstance>: View {

Check failure on line 14 in Sources/SpeziMedicationTracking/MedicationLogLogged.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

File Types Order Violation: A 'main_type' should not be placed amongst the file type(s) 'supporting_type' (file_types_order)
@Binding private var medicationInstances: [MI]
private let selectedDate: Date


var body: some View {
VStack(spacing: 8) {
HStack {
Text("Logged")
.bold()
.padding([.horizontal, .top])
Spacer()
}
ForEach(MedicationLogRowModel.allLoggedTimes(basedOn: $medicationInstances, on: selectedDate)) { medicationLogRowModel in
Divider()
.padding(.leading)
MedicationLogLoggedRow(medicationLogRowModel: medicationLogRowModel)
}
}
.background {
RoundedRectangle(cornerRadius: 5.0)
.foregroundColor(Color(.systemGroupedBackground))
}
}

Check warning on line 37 in Sources/SpeziMedicationTracking/MedicationLogLogged.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/MedicationLogLogged.swift#L19-L37

Added lines #L19 - L37 were not covered by tests


init(
medicationInstances: Binding<[MI]>,
selectedDate: Date
) {
self._medicationInstances = medicationInstances
self.selectedDate = selectedDate
}

Check warning on line 46 in Sources/SpeziMedicationTracking/MedicationLogLogged.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/MedicationLogLogged.swift#L43-L46

Added lines #L43 - L46 were not covered by tests
}

struct MedicationLogLoggedRow<MI: MedicationInstance>: View {
private let medicationLogRowModel: MedicationLogRowModel<MI>

@State var presentMedicationLogSheet = false

Check warning on line 52 in Sources/SpeziMedicationTracking/MedicationLogLogged.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/MedicationLogLogged.swift#L52

Added line #L52 was not covered by tests


var body: some View {
VStack(spacing: 8) {
HStack {
Text(medicationLogRowModel.date ?? .now, style: .time)
Spacer()
Image(systemName: "chevron.right")
.accessibilityHidden(true)
.foregroundColor(.gray)
}
.bold()
.padding(.horizontal)
ForEach(medicationLogRowModel.medications) { medicationInstance in
logEntryView(medicationInstance: medicationInstance.wrappedValue)
.padding(.horizontal)
}
}
.contentShape(Rectangle())
.onTapGesture {
presentMedicationLogSheet.toggle()
}
.sheet(isPresented: $presentMedicationLogSheet) {
MedicationLogSheet(medicationLogRowModel: medicationLogRowModel)
}
}

Check warning on line 78 in Sources/SpeziMedicationTracking/MedicationLogLogged.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/MedicationLogLogged.swift#L55-L78

Added lines #L55 - L78 were not covered by tests


init(medicationLogRowModel: MedicationLogRowModel<MI>) {
self.medicationLogRowModel = medicationLogRowModel
}

Check warning on line 83 in Sources/SpeziMedicationTracking/MedicationLogLogged.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/MedicationLogLogged.swift#L81-L83

Added lines #L81 - L83 were not covered by tests


private func logEntryEvent(medicationInstance: MI) -> LogEntryEvent {
medicationInstance.logEntries
.filter { $0.date == medicationLogRowModel.date }
.reduce(LogEntryEvent.skipped) { result, logEntry in
if logEntry.event == .skipped && result == .skipped {
return .skipped
} else {
return .taken
}
}
}

Check warning on line 96 in Sources/SpeziMedicationTracking/MedicationLogLogged.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/MedicationLogLogged.swift#L86-L96

Added lines #L86 - L96 were not covered by tests

private func logEntryView(medicationInstance: MI) -> some View {
let event = logEntryEvent(medicationInstance: medicationInstance)
let title = if event == .skipped {
Text("\(medicationInstance.type.localizedDescription) (Skipped)")
} else {
Text(medicationInstance.type.localizedDescription)
}

return HStack {
event.icon
.foregroundStyle(event == .taken ? Color.accentColor : Color.gray)
.accessibilityHidden(true)
title
Spacer()
}
}

Check warning on line 113 in Sources/SpeziMedicationTracking/MedicationLogLogged.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziMedicationTracking/MedicationLogLogged.swift#L98-L113

Added lines #L98 - L113 were not covered by tests
}


#Preview {
@State var medicationInstances = Mock.medicationInstances

return ScrollView {
MedicationLogLogged(
medicationInstances: $medicationInstances,
selectedDate: .now
)
.padding()
}
}
Loading

0 comments on commit 8d74cdf

Please sign in to comment.