Skip to content

Commit

Permalink
Restructure and add schedule view model
Browse files Browse the repository at this point in the history
  • Loading branch information
Supereg committed Nov 12, 2024
1 parent 0c5cec4 commit 2638f81
Show file tree
Hide file tree
Showing 17 changed files with 226 additions and 152 deletions.
8 changes: 5 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ let package = Package(
name: "SpeziMedication",
defaultLocalization: "en",
platforms: [
.iOS(.v17)
.iOS(.v18)
],
products: [
.library(name: "SpeziMedication", targets: ["SpeziMedication"]),
Expand All @@ -27,14 +27,16 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/StanfordSpezi/SpeziFoundation.git", from: "2.0.0"),
.package(url: "https://github.com/StanfordSpezi/Spezi.git", from: "1.8.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziViews.git", from: "1.7.0")
.package(url: "https://github.com/StanfordSpezi/SpeziViews.git", from: "1.7.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziScheduler.git", from: "1.1.0")
] + swiftLintPackage(),
targets: [
.target(
name: "SpeziMedication",
dependencies: [
.product(name: "Spezi", package: "Spezi"),
.product(name: "SpeziViews", package: "SpeziViews")
.product(name: "SpeziViews", package: "SpeziViews"),
.product(name: "SpeziScheduler", package: "SpeziScheduler")
],
resources: [
.process("Resources")
Expand Down
File renamed without changes.
File renamed without changes.
82 changes: 0 additions & 82 deletions Sources/SpeziMedication/Models/Frequency.swift

This file was deleted.

22 changes: 0 additions & 22 deletions Sources/SpeziMedication/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,6 @@
"strings" : {
"As Needed" : {

},
"Cancel" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cancel"
}
}
}
},
"Close" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Close"
}
}
}
},
"Every %lld days" : {
"localizations" : {
Expand Down
191 changes: 191 additions & 0 deletions Sources/SpeziMedication/Schedule/Frequency.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
//
// 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 SpeziScheduler

// TODO: Frequencies:
// - EveryDay, EveryFewDays (2x=every other day, 3x, 4x ...),
// - AsNeeded,
// - Specific Days of the Week, Cyclical Schedule(used for x days/weeks, pause for y days/weeks)

public enum MedicationScheduleSelection {

Check failure on line 17 in Sources/SpeziMedication/Schedule/Frequency.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Missing Docs Violation: public declarations should be documented (missing_docs)
case asNeeded
case daily
case interval // TODO: every x days
case weekdayBased
}

extension Locale.Weekday { // TODO: combine and move to Scheduler!

Check failure on line 24 in Sources/SpeziMedication/Schedule/Frequency.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

File Types Order Violation: An 'extension' should not be placed amongst the file type(s) 'supporting_type' (file_types_order)
public init?(ordinal: Int) {
switch ordinal {
case 1:
self = .sunday
case 2:
self = .monday
case 3:
self = .tuesday
case 4:
self = .wednesday
case 5:
self = .thursday
case 6:
self = .friday
case 7:
self = .saturday
default:
return nil
}
}

public init(from date: Date) {
let weekdayOrdinal = Calendar.current.component(.weekday, from: date)
guard let weekday = Locale.Weekday(ordinal: weekdayOrdinal) else {
preconditionFailure("Failed to derive weekday from ordinal \(weekdayOrdinal)")
}
self = weekday
}
}

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

public var weekdays: [Calendar.RecurrenceRule.Weekday] = []

public var start: Date
public var end: Date?

public var schedule: SpeziScheduler.Schedule? {
if case .asNeeded = selection {
return nil // no recurrence or scheduled tasks at all
}

let end = end.map {
Calendar.RecurrenceRule.End.afterDate($0)
} ?? .never



let (hours, minutes) = times.reduce(into: ([Int](), [Int]())) { partialResult, date in
partialResult.0.append(Calendar.current.component(.hour, from: date))
partialResult.1.append(Calendar.current.component(.minute, from: date))
}

let interval = if case .interval = selection {
dayInterval
} else {
1
}

let weekdays: [Calendar.RecurrenceRule.Weekday] = if case .weekdayBased = selection {
self.weekdays
} else {
[]
}


// TODO: days and minutes doesn't work right?
let recurrence = Calendar.RecurrenceRule.daily(
calendar: .current,
interval: interval,
end: end,
weekdays: weekdays,
hours: hours,
minutes: minutes
)

// TODO: do we care about event duration?
return SpeziScheduler.Schedule(startingAt: start, recurrence: recurrence)
}

public init() {
let now = Date.now

self.start = .today // TODO: start of day?
// populate some default selections
times.append(now)

let todayWeekday = Locale.Weekday(from: now)
self.weekdays.append(.every(todayWeekday))
}
}


/// Defines the frequency of a schedule.
public enum Frequency: Codable, CustomStringConvertible, Equatable, Hashable, Sendable {
case regularDayIntervals(Int)
case specificDaysOfWeek(Weekdays)
case asNeeded


enum CodingKeys: CodingKey {
case regularDayIntervals
case specificDaysOfWeek
case asNeeded
}


public var description: String {
switch self {
case let .regularDayIntervals(dayInterval):
switch dayInterval {
case 1:
String(localized: "Every Day", bundle: .module)
case 2:
String(localized: "Every other Day", bundle: .module)
default:
String(localized: "Every \(dayInterval) days", bundle: .module)
}
case let .specificDaysOfWeek(weekdays):
weekdays.localizedShortDescription
case .asNeeded:
String(localized: "As Needed", bundle: .module)
}
}


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

guard let onlyKey = allKeys.popFirst(), allKeys.isEmpty else {
throw DecodingError.typeMismatch(
Frequency.self,
DecodingError.Context.init(
codingPath: container.codingPath,
debugDescription: "Invalid number of keys found, expected one.",
underlyingError: nil
)
)
}

switch onlyKey {
case .regularDayIntervals:
self = Frequency.regularDayIntervals(try container.decode(Int.self, forKey: CodingKeys.regularDayIntervals))
case .specificDaysOfWeek:
self = Frequency.specificDaysOfWeek(try container.decode(Weekdays.self, forKey: CodingKeys.specificDaysOfWeek))
case .asNeeded:
self = Frequency.asNeeded
}
}


public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .regularDayIntervals(dayInterval):
try container.encode(dayInterval, forKey: .regularDayIntervals)
case let .specificDaysOfWeek(weekdays):
try container.encode(weekdays, forKey: .specificDaysOfWeek)
case .asNeeded:
try container.encode(true, forKey: .asNeeded)
}
}
}
Loading

0 comments on commit 2638f81

Please sign in to comment.