diff --git a/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate b/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate index c1608cf..2a3afbe 100644 Binary files a/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate and b/Nextcloud Cookbook iOS Client.xcodeproj/project.xcworkspace/xcuserdata/vincie.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Nextcloud Cookbook iOS Client/Data/DurationComponents.swift b/Nextcloud Cookbook iOS Client/Data/DurationComponents.swift index 774d877..edd017a 100644 --- a/Nextcloud Cookbook iOS Client/Data/DurationComponents.swift +++ b/Nextcloud Cookbook iOS Client/Data/DurationComponents.swift @@ -10,6 +10,22 @@ import SwiftUI 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" { didSet { 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) { let hourRegex = /([0-9]{1,2})H/ let minuteRegex = /([0-9]{1,2})M/ @@ -60,6 +89,7 @@ class DurationComponents: ObservableObject { func toText() -> LocalizedStringKey { let intHour = Int(hourComponent) ?? 0 let intMinute = Int(minuteComponent) ?? 0 + if intHour != 0 && intMinute != 0 { return "\(intHour) h, \(intMinute) min" } 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? { let hourRegex = /([0-9]{1,2})H/ let minuteRegex = /([0-9]{1,2})M/ diff --git a/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift b/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift index bf45e8f..cb5be79 100644 --- a/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift +++ b/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift @@ -614,33 +614,13 @@ extension DateFormatter { // Timer logic extension MainViewModel { - func createTimer(forRecipe recipeId: String, timeTotal: Double) -> RecipeTimer { - let timer = RecipeTimer(timeTotal: timeTotal) + func createTimer(forRecipe recipeId: String, duration: DurationComponents) -> RecipeTimer { + let timer = RecipeTimer(duration: duration) timers[recipeId] = timer return timer } - func getTimer(forRecipe recipeId: String, timeTotal: Double) -> RecipeTimer { - return timers[recipeId] ?? createTimer(forRecipe: recipeId, timeTotal: timeTotal) - } - - - 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 + func getTimer(forRecipe recipeId: String, duration: DurationComponents) -> RecipeTimer { + return timers[recipeId] ?? createTimer(forRecipe: recipeId, duration: duration) } } diff --git a/Nextcloud Cookbook iOS Client/Views/RecipeDetailView.swift b/Nextcloud Cookbook iOS Client/Views/RecipeDetailView.swift index 30c2dda..fdf2bc9 100644 --- a/Nextcloud Cookbook iOS Client/Views/RecipeDetailView.swift +++ b/Nextcloud Cookbook iOS Client/Views/RecipeDetailView.swift @@ -61,8 +61,8 @@ struct RecipeDetailView: View { } 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)]) { if(!recipeDetail.recipeIngredient.isEmpty) { @@ -214,10 +214,11 @@ fileprivate struct ShareView: View { fileprivate struct RecipeDurationSection: View { + @ObservedObject var viewModel: MainViewModel @State var recipeDetail: RecipeDetail 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) { VStack(alignment: .leading) { HStack { @@ -229,6 +230,12 @@ fileprivate struct RecipeDurationSection: View { }.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) { VStack(alignment: .leading) { HStack { @@ -238,7 +245,7 @@ fileprivate struct RecipeDurationSection: View { Text(time) .lineLimit(1) }.padding() - } + }*/ if let totalTime = recipeDetail.totalTime, let time = DurationComponents.ptToText(totalTime) { VStack(alignment: .leading) { diff --git a/Nextcloud Cookbook iOS Client/Views/TimerView.swift b/Nextcloud Cookbook iOS Client/Views/TimerView.swift index b95b2c3..5a81a4a 100644 --- a/Nextcloud Cookbook iOS Client/Views/TimerView.swift +++ b/Nextcloud Cookbook iOS Client/Views/TimerView.swift @@ -27,29 +27,27 @@ struct TimerView: View { } label: { if timer.isRunning { Image(systemName: "pause.fill") - .foregroundStyle(.blue) } else { Image(systemName: "play.fill") - .foregroundStyle(.blue) } } } .gaugeStyle(.accessoryCircularCapacity) .animation(.easeInOut, value: timer.timeElapsed) - .tint(.white) + .tint(timer.isRunning ? .green : .nextcloudBlue) + .foregroundStyle(timer.isRunning ? Color.green : Color.nextcloudBlue) VStack(alignment: .leading) { - HStack { - Text("Cooking time") - .padding(.horizontal) - Button { - timer.cancel() - } label: { - Image(systemName: "xmark.circle.fill") - } - } - Text("\(Int(timer.timeTotal - timer.timeElapsed))") - .padding(.horizontal) + Text("Cooking") + Text(timer.duration.toTimerText()) + } + .padding(.horizontal) + + Button { + timer.cancel() + } label: { + Image(systemName: "xmark.circle.fill") + .foregroundStyle(timer.isRunning ? Color.nextcloudBlue : Color.secondary) } } .bold() @@ -65,6 +63,7 @@ struct TimerView: View { class RecipeTimer: ObservableObject { var timeTotal: Double + @Published var duration: DurationComponents private var startDate: Date? private var pauseDate: Date? @Published var timeElapsed: Double = 0 @@ -72,8 +71,9 @@ class RecipeTimer: ObservableObject { private var timer: Timer.TimerPublisher? private var timerCancellable: Cancellable? - init(timeTotal: Double) { - self.timeTotal = timeTotal + init(duration: DurationComponents) { + self.duration = duration + self.timeTotal = duration.toSeconds() } func start() { @@ -93,8 +93,10 @@ class RecipeTimer: ObservableObject { let elapsed = Date().timeIntervalSince(startTime) if elapsed < self.timeTotal { self.timeElapsed = elapsed + self.duration.fromSeconds(Int(self.timeTotal - self.timeElapsed)) } else { self.timeElapsed = self.timeTotal + self.duration.fromSeconds(Int(self.timeTotal - self.timeElapsed)) self.pause() } } @@ -123,5 +125,6 @@ class RecipeTimer: ObservableObject { self.timeElapsed = 0 self.startDate = nil self.pauseDate = nil + self.duration.fromSeconds(Int(timeTotal)) } }