Improved timer widget

This commit is contained in:
VincentMeilinger
2024-01-11 14:15:12 +01:00
parent b069555950
commit 9b225f63b5
5 changed files with 90 additions and 44 deletions

View File

@@ -10,6 +10,22 @@ import SwiftUI
class DurationComponents: ObservableObject { class DurationComponents: ObservableObject {
@Published var secondComponent: String = "00" {
didSet {
if secondComponent.count > 2 {
secondComponent = oldValue
} else if secondComponent.count == 1 {
secondComponent = "0\(secondComponent)"
} else if secondComponent.count == 0 {
secondComponent = "00"
}
let filtered = secondComponent.filter { $0.isNumber }
if secondComponent != filtered {
secondComponent = filtered
}
}
}
@Published var minuteComponent: String = "00" { @Published var minuteComponent: String = "00" {
didSet { didSet {
if minuteComponent.count > 2 { if minuteComponent.count > 2 {
@@ -42,6 +58,19 @@ class DurationComponents: ObservableObject {
} }
} }
static func fromPTString(_ PTRepresentation: String) -> DurationComponents {
let duration = DurationComponents()
let hourRegex = /([0-9]{1,2})H/
let minuteRegex = /([0-9]{1,2})M/
if let match = PTRepresentation.firstMatch(of: hourRegex) {
duration.hourComponent = String(match.1)
}
if let match = PTRepresentation.firstMatch(of: minuteRegex) {
duration.minuteComponent = String(match.1)
}
return duration
}
func fromPTString(_ PTRepresentation: String) { func fromPTString(_ PTRepresentation: String) {
let hourRegex = /([0-9]{1,2})H/ let hourRegex = /([0-9]{1,2})H/
let minuteRegex = /([0-9]{1,2})M/ let minuteRegex = /([0-9]{1,2})M/
@@ -60,6 +89,7 @@ class DurationComponents: ObservableObject {
func toText() -> LocalizedStringKey { func toText() -> LocalizedStringKey {
let intHour = Int(hourComponent) ?? 0 let intHour = Int(hourComponent) ?? 0
let intMinute = Int(minuteComponent) ?? 0 let intMinute = Int(minuteComponent) ?? 0
if intHour != 0 && intMinute != 0 { if intHour != 0 && intMinute != 0 {
return "\(intHour) h, \(intMinute) min" return "\(intHour) h, \(intMinute) min"
} else if intHour == 0 && intMinute != 0 { } else if intHour == 0 && intMinute != 0 {
@@ -71,6 +101,32 @@ class DurationComponents: ObservableObject {
} }
} }
func toTimerText() -> String {
var timeString = ""
if hourComponent != "00" {
timeString.append("\(hourComponent):")
}
timeString.append("\(minuteComponent):")
timeString.append("\(secondComponent)")
return timeString
}
func toSeconds() -> Double {
guard let hours = Double(hourComponent) else { return 0 }
guard let minutes = Double(minuteComponent) else { return 0 }
guard let seconds = Double(secondComponent) else { return 0 }
return hours * 3600 + minutes * 60 + seconds
}
func fromSeconds(_ totalSeconds: Int) {
let hours = totalSeconds / 3600
let minutes = (totalSeconds % 3600) / 60
let seconds = totalSeconds % 60
self.hourComponent = String(hours)
self.minuteComponent = String(minutes)
self.secondComponent = String(seconds)
}
static func ptToText(_ ptString: String) -> String? { static func ptToText(_ ptString: String) -> String? {
let hourRegex = /([0-9]{1,2})H/ let hourRegex = /([0-9]{1,2})H/
let minuteRegex = /([0-9]{1,2})M/ let minuteRegex = /([0-9]{1,2})M/

View File

@@ -614,33 +614,13 @@ extension DateFormatter {
// Timer logic // Timer logic
extension MainViewModel { extension MainViewModel {
func createTimer(forRecipe recipeId: String, timeTotal: Double) -> RecipeTimer { func createTimer(forRecipe recipeId: String, duration: DurationComponents) -> RecipeTimer {
let timer = RecipeTimer(timeTotal: timeTotal) let timer = RecipeTimer(duration: duration)
timers[recipeId] = timer timers[recipeId] = timer
return timer return timer
} }
func getTimer(forRecipe recipeId: String, timeTotal: Double) -> RecipeTimer { func getTimer(forRecipe recipeId: String, duration: DurationComponents) -> RecipeTimer {
return timers[recipeId] ?? createTimer(forRecipe: recipeId, timeTotal: timeTotal) return timers[recipeId] ?? createTimer(forRecipe: recipeId, duration: duration)
}
func startTimer(forRecipe recipeId: String, timeTotal: Double) {
let timer = RecipeTimer(timeTotal: timeTotal)
timer.start()
timers[recipeId] = timer
}
func pauseTimer(forRecipe recipeId: String) {
timers[recipeId]?.pause()
}
func resumeTimer(forRecipe recipeId: String) {
timers[recipeId]?.resume()
}
func cancelTimer(forRecipe recipeId: String) {
timers[recipeId]?.cancel()
timers[recipeId] = nil
} }
} }

View File

@@ -61,8 +61,8 @@ struct RecipeDetailView: View {
} }
Divider() Divider()
TimerView(timer: viewModel.getTimer(forRecipe: recipeDetail.id, timeTotal: 20))
RecipeDurationSection(recipeDetail: recipeDetail) RecipeDurationSection(viewModel: viewModel, recipeDetail: recipeDetail)
LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) { LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) {
if(!recipeDetail.recipeIngredient.isEmpty) { if(!recipeDetail.recipeIngredient.isEmpty) {
@@ -214,10 +214,11 @@ fileprivate struct ShareView: View {
fileprivate struct RecipeDurationSection: View { fileprivate struct RecipeDurationSection: View {
@ObservedObject var viewModel: MainViewModel
@State var recipeDetail: RecipeDetail @State var recipeDetail: RecipeDetail
var body: some View { var body: some View {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 150), alignment: .leading)]) { LazyVGrid(columns: [GridItem(.adaptive(minimum: 250), alignment: .leading)]) {
if let prepTime = recipeDetail.prepTime, let time = DurationComponents.ptToText(prepTime) { if let prepTime = recipeDetail.prepTime, let time = DurationComponents.ptToText(prepTime) {
VStack(alignment: .leading) { VStack(alignment: .leading) {
HStack { HStack {
@@ -229,6 +230,12 @@ fileprivate struct RecipeDurationSection: View {
}.padding() }.padding()
} }
if let cookTime = recipeDetail.cookTime, let time = DurationComponents.ptToText(cookTime) {
TimerView(timer: viewModel.getTimer(forRecipe: recipeDetail.id, duration: DurationComponents.fromPTString(cookTime)))
.padding()
}
/*
if let cookTime = recipeDetail.cookTime, let time = DurationComponents.ptToText(cookTime) { if let cookTime = recipeDetail.cookTime, let time = DurationComponents.ptToText(cookTime) {
VStack(alignment: .leading) { VStack(alignment: .leading) {
HStack { HStack {
@@ -238,7 +245,7 @@ fileprivate struct RecipeDurationSection: View {
Text(time) Text(time)
.lineLimit(1) .lineLimit(1)
}.padding() }.padding()
} }*/
if let totalTime = recipeDetail.totalTime, let time = DurationComponents.ptToText(totalTime) { if let totalTime = recipeDetail.totalTime, let time = DurationComponents.ptToText(totalTime) {
VStack(alignment: .leading) { VStack(alignment: .leading) {

View File

@@ -27,29 +27,27 @@ struct TimerView: View {
} label: { } label: {
if timer.isRunning { if timer.isRunning {
Image(systemName: "pause.fill") Image(systemName: "pause.fill")
.foregroundStyle(.blue)
} else { } else {
Image(systemName: "play.fill") Image(systemName: "play.fill")
.foregroundStyle(.blue)
} }
} }
} }
.gaugeStyle(.accessoryCircularCapacity) .gaugeStyle(.accessoryCircularCapacity)
.animation(.easeInOut, value: timer.timeElapsed) .animation(.easeInOut, value: timer.timeElapsed)
.tint(.white) .tint(timer.isRunning ? .green : .nextcloudBlue)
.foregroundStyle(timer.isRunning ? Color.green : Color.nextcloudBlue)
VStack(alignment: .leading) { VStack(alignment: .leading) {
HStack { Text("Cooking")
Text("Cooking time") Text(timer.duration.toTimerText())
}
.padding(.horizontal) .padding(.horizontal)
Button { Button {
timer.cancel() timer.cancel()
} label: { } label: {
Image(systemName: "xmark.circle.fill") Image(systemName: "xmark.circle.fill")
} .foregroundStyle(timer.isRunning ? Color.nextcloudBlue : Color.secondary)
}
Text("\(Int(timer.timeTotal - timer.timeElapsed))")
.padding(.horizontal)
} }
} }
.bold() .bold()
@@ -65,6 +63,7 @@ struct TimerView: View {
class RecipeTimer: ObservableObject { class RecipeTimer: ObservableObject {
var timeTotal: Double var timeTotal: Double
@Published var duration: DurationComponents
private var startDate: Date? private var startDate: Date?
private var pauseDate: Date? private var pauseDate: Date?
@Published var timeElapsed: Double = 0 @Published var timeElapsed: Double = 0
@@ -72,8 +71,9 @@ class RecipeTimer: ObservableObject {
private var timer: Timer.TimerPublisher? private var timer: Timer.TimerPublisher?
private var timerCancellable: Cancellable? private var timerCancellable: Cancellable?
init(timeTotal: Double) { init(duration: DurationComponents) {
self.timeTotal = timeTotal self.duration = duration
self.timeTotal = duration.toSeconds()
} }
func start() { func start() {
@@ -93,8 +93,10 @@ class RecipeTimer: ObservableObject {
let elapsed = Date().timeIntervalSince(startTime) let elapsed = Date().timeIntervalSince(startTime)
if elapsed < self.timeTotal { if elapsed < self.timeTotal {
self.timeElapsed = elapsed self.timeElapsed = elapsed
self.duration.fromSeconds(Int(self.timeTotal - self.timeElapsed))
} else { } else {
self.timeElapsed = self.timeTotal self.timeElapsed = self.timeTotal
self.duration.fromSeconds(Int(self.timeTotal - self.timeElapsed))
self.pause() self.pause()
} }
} }
@@ -123,5 +125,6 @@ class RecipeTimer: ObservableObject {
self.timeElapsed = 0 self.timeElapsed = 0
self.startDate = nil self.startDate = nil
self.pauseDate = nil self.pauseDate = nil
self.duration.fromSeconds(Int(timeTotal))
} }
} }