Skip to content

Commit

Permalink
Some progress. Simplify model
Browse files Browse the repository at this point in the history
  • Loading branch information
Supereg committed Nov 24, 2024
1 parent 2638f81 commit 2d09ab9
Show file tree
Hide file tree
Showing 28 changed files with 504 additions and 164 deletions.
7 changes: 0 additions & 7 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -367,13 +367,6 @@ only_rules:
# The variable should be placed on the left, the constant on the right of a comparison operator.
- yoda_condition

attributes:
attributes_with_arguments_always_on_line_above: false

deployment_target: # Availability checks or attributes shouldn’t be using older versions that are satisfied by the deployment target.
iOSApplicationExtension_deployment_target: 16.0
iOS_deployment_target: 16.0

excluded: # paths to ignore during linting. Takes precedence over `included`.
- .build
- .swiftpm
Expand Down
3 changes: 3 additions & 0 deletions Sources/SpeziMedication/Log/LogEntry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

import Foundation

// TODO: where do we store everything?
// => should we maintain a custom storage databse, or do we store it alongisde the Task and Outcome?


public struct LogEntry: Codable, Equatable, Sendable {
public let scheduledTime: Date?
Expand Down
51 changes: 48 additions & 3 deletions Sources/SpeziMedication/Medication/Dosage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,57 @@
//

import Foundation

import HealthKit

/// Dosage of a medication.
///
/// Defines the dosage of a ``MedicationInstance`` as a subset of the ``Medication/dosages`` of a ``Medication``.
public protocol Dosage: Codable, Hashable {
/// Localized description of the dosage.
@available(*, deprecated, message: "This will be removed")
public protocol LegacyDosage: Codable, Hashable {
/// Localized description of the dosage.
var localizedDescription: String { get }
}


/// Describes a dosage of a medication.
public struct Dosage {
/// The strength or quantity of a single dosage.
public var strength: UInt
/// The unit of a single dosage.
public var unit: HKUnit // TODO: does that work with SwiftData?
/// The form the dosage is delivered (e.g., capsule)
public var form: MedicationType? // TODO: make a single value container? => must be rawRepresentable then for SwiftData!

public init(strength: UInt, unit: HKUnit, form: MedicationType? = nil) {
self.strength = strength
self.unit = unit
self.form = form
}
}


extension Dosage: Hashable, Sendable, Codable {
enum CodingKeys: String, CodingKey {
case strength
case unit
case form
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

self.strength = try container.decode(UInt.self, forKey: .strength)
self.form = try container.decode(MedicationType.self, forKey: .form)

let unitString = try container.decode(String.self, forKey: .unit) // TODO: is that doable with SwiftData?
self.unit = HKUnit(from: unitString)
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

try container.encode(strength, forKey: .strength)
try container.encode(unit.unitString, forKey: .unit)
try container.encode(form, forKey: .form)
}
}
52 changes: 46 additions & 6 deletions Sources/SpeziMedication/Medication/Medication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,66 @@
//

import Foundation

import SpeziScheduler

/// Defines a medication type.
///
/// ``Medication``'s are instanced as ``MedicationInstance``s.
public protocol Medication: Codable, Comparable, Hashable {
@available(*, deprecated, message: "This will be removed")
public protocol LegacyMedication: Codable, Comparable, Hashable {
/// The dosage type associated with the medication.
associatedtype MedicationDosage: Dosage
associatedtype MedicationDosage: LegacyDosage


/// Localized description of the medication.
var localizedDescription: String { get }
/// Dosage options defining a set of options when instantiating a ``MedicationInstance``.
var dosages: [MedicationDosage] { get }
}


extension Medication {
extension LegacyMedication {

Check warning on line 28 in Sources/SpeziMedication/Medication/Medication.swift

View workflow job for this annotation

GitHub Actions / Build and Test iPadOS / Test using xcodebuild or run fastlane

'LegacyMedication' is deprecated: This will be removed

Check warning on line 28 in Sources/SpeziMedication/Medication/Medication.swift

View workflow job for this annotation

GitHub Actions / Build and Test iPadOS / Test using xcodebuild or run fastlane

'LegacyMedication' is deprecated: This will be removed

Check warning on line 28 in Sources/SpeziMedication/Medication/Medication.swift

View workflow job for this annotation

GitHub Actions / Build and Test iOS / Test using xcodebuild or run fastlane

'LegacyMedication' is deprecated: This will be removed

Check warning on line 28 in Sources/SpeziMedication/Medication/Medication.swift

View workflow job for this annotation

GitHub Actions / Build and Test iOS / Test using xcodebuild or run fastlane

'LegacyMedication' is deprecated: This will be removed

Check warning on line 28 in Sources/SpeziMedication/Medication/Medication.swift

View workflow job for this annotation

GitHub Actions / Build and Test Swift Package iOS / Test using xcodebuild or run fastlane

'LegacyMedication' is deprecated: This will be removed

Check warning on line 28 in Sources/SpeziMedication/Medication/Medication.swift

View workflow job for this annotation

GitHub Actions / Build and Test Swift Package iOS / Test using xcodebuild or run fastlane

'LegacyMedication' is deprecated: This will be removed
/// See Comparable
public static func < (lhs: Self, rhs: Self) -> Bool {
lhs.localizedDescription < rhs.localizedDescription
}
}

public struct MedicationOption: Identifiable { // TODO: how to make identifiable?
public let id: String
public let label: LocalizedStringResource
public let dosageOptions: [Dosage]

public init(id: String, label: LocalizedStringResource, dosageOptions: [Dosage]) {
self.id = id
self.label = label
self.dosageOptions = dosageOptions
}
}


import SwiftUI // TODO: extension?
public struct MedicationDescription {
// TODO: let id: UUID?
// TODO: dosages!
let description: String.LocalizationValue? // TODO: do we need Hashable?
let name: String?

var label: Text {
if let name {
Text(name)
} else if let description {
Text(LocalizedStringResource(description)) // TODO: which bundle? we assume main bundle?
} else {
Text("Medication", bundle: .module) // TODO: generic label!???
}
}

let dosage: Dosage

// TODO: schedule is part of the task!
}

extension MedicationDescription: Codable, Equatable, Sendable {

}
7 changes: 4 additions & 3 deletions Sources/SpeziMedication/Medication/MedicationInstance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@

import Foundation


/// Instance of a ``Medication``.
///
/// The ``MedicationInstance``'s identifier (`id`) must be stable across chances to the dosage and therefore should not be derived from a combination of values including the dosage.
///
/// > Important: We recommend making the Medication Instance a value type (`struct`) to best work within the ``MedicationSettings``.
@available(*, deprecated, message: "This will be removed")
public protocol MedicationInstance: Codable, Identifiable, Comparable, Hashable where InstanceType.MedicationDosage == InstanceDosage {
/// Associated dosage.
associatedtype InstanceDosage: Dosage
associatedtype InstanceDosage: LegacyDosage
/// Associated medication type.
associatedtype InstanceType: Medication
associatedtype InstanceType: LegacyMedication


/// Type of the medication instance.
Expand Down Expand Up @@ -52,3 +52,4 @@ extension MedicationInstance {
hasher.combine(id)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

import Foundation

// TODO: remove

/// Marks a ``MedicationInstance`` as being initiailzable using ``MedicationInstanceInitializable/init(type:dosage:)``.
@available(*, deprecated, message: "This will be removed")
public protocol MedicationInstanceInitializable: MedicationInstance {
/// - Parameters:
/// - type: Type of the medication.
Expand Down
74 changes: 74 additions & 0 deletions Sources/SpeziMedication/Medication/MedicationType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import Foundation
import SpeziViews


/// Describes the form or type of a medication.
public struct MedicationType {
// TODO: just make it a fixed list? enum => so we can provide the translations!

/// The localized description of the medication type.
public let description: LocalizedStringResource // TODO: we must not store LocalizedStringResource with SwiftData!

private var key: String {
description.key
}

fileprivate init(_ description: LocalizedStringResource) {
self.description = description
}
}


extension MedicationType {
public static let capsule = MedicationType(.init("capsule", defaultValue: "Capsule", bundle: .atURL(from: .module), comment: "Medication Type"))
public static let tablet = MedicationType(.init("tablet", defaultValue: "Tablet", bundle: .atURL(from: .module), comment: "Medication Type"))
public static let liquid = MedicationType(.init("liquid", defaultValue: "Liquid", bundle: .atURL(from: .module), comment: "Medication Type"))
public static let gel = MedicationType(.init("gel", defaultValue: "Gel", bundle: .atURL(from: .module), comment: "Medication Type"))
public static let drops = MedicationType(.init("drops", defaultValue: "Drops", bundle: .atURL(from: .module), comment: "Medication Type"))


/// Creates a custom medication type.
///
/// The `key` of the `LocalizedStringResource` is used to identify a medication type.
/// - Parameter description: The localized description of the type.
/// - Returns: Returns the medication type.
public static func custom(_ description: LocalizedStringResource) -> MedicationType {
MedicationType(description)
}
}


extension MedicationType: Sendable, Codable {}


extension MedicationType: Identifiable {
public var id: String {
key
}
}


extension MedicationType: CustomLocalizedStringResourceConvertible {
public var localizedStringResource: LocalizedStringResource {
description
}
}


extension MedicationType: Hashable {
public static func == (lhs: MedicationType, rhs: MedicationType) -> Bool {
lhs.key == rhs.key
}

public func hash(into hasher: inout Hasher) {
hasher.combine(key)
}
}
18 changes: 18 additions & 0 deletions Sources/SpeziMedication/Medication/SchedulerExperiments.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// 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 SpeziScheduler


extension Task.Context {
@Property var medication: MedicationDescription?
}

extension Outcome {
@Property var logEntry: LogEntry?
}
63 changes: 63 additions & 0 deletions Sources/SpeziMedication/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,30 @@
"strings" : {
"As Needed" : {

},
"capsule" : {
"comment" : "Medication Type",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Capsule"
}
}
}
},
"drops" : {
"comment" : "Medication Type",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Drops"
}
}
}
},
"Every %lld days" : {
"localizations" : {
Expand Down Expand Up @@ -31,9 +55,48 @@
},
"Every other Day" : {

},
"gel" : {
"comment" : "Medication Type",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Gel"
}
}
}
},
"liquid" : {
"comment" : "Medication Type",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Liquid"
}
}
}
},
"Medication" : {

},
"Skipped" : {

},
"tablet" : {
"comment" : "Medication Type",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Tablet"
}
}
}
},
"Taken" : {

Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziMedication/Schedule/Frequency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ extension Locale.Weekday { // TODO: combine and move to Scheduler!

public struct CreateScheduleViewModel {
public var selection: MedicationScheduleSelection = .daily
public var dayInterval = 2
public var dayInterval = 1
public var times: [Date] = []

public var weekdays: [Calendar.RecurrenceRule.Weekday] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Foundation
import SpeziMedication


// TODO: remove?

Check failure on line 13 in Sources/SpeziMedicationSettings/Extensions/ScheduledTime+Date.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Todo Violation: TODOs should be resolved (remove?) (todo)
extension ScheduledTime {
var date: Date {
get {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import SwiftUI


// TODO: why was that needed?
struct ScheduledTimeDatePicker: UIViewRepresentable {
@MainActor
class Coordinator: NSObject {
Expand Down
Loading

0 comments on commit 2d09ab9

Please sign in to comment.