From 5f05da552d348150e6a60bf7007e1af63fc6ec98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beatrice=20Dellac=C3=A0?= Date: Tue, 27 Jan 2026 02:39:24 +0100 Subject: [PATCH] implement settings --- MultiChrono/AppSettings.swift | 33 ++++++++++++++++++++++++++ MultiChrono/ContentView.swift | 12 ++++++++++ MultiChrono/SettingsView.swift | 41 +++++++++++++++++++++++++++++++++ MultiChrono/Stopwatch.swift | 42 ++++++++++++++++++++++++++++------ MultiChrono/StopwatchRow.swift | 15 +++--------- 5 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 MultiChrono/AppSettings.swift create mode 100644 MultiChrono/SettingsView.swift diff --git a/MultiChrono/AppSettings.swift b/MultiChrono/AppSettings.swift new file mode 100644 index 0000000..c820e78 --- /dev/null +++ b/MultiChrono/AppSettings.swift @@ -0,0 +1,33 @@ +// +// AppSettings.swift +// MultiChrono +// +// Created by Beatrice DellacĂ  on 27/01/26. +// + +import Foundation +import Combine + +enum TimeFormat: String, CaseIterable, Codable { + case millis = "Millis (00:00.000)" + case cents = "Cents (00:00.00)" + case tenths = "Tenths (00:00.0)" + case seconds = "Seconds (00:00)" + + var displayName: String { self.rawValue } +} + +class AppSettings: ObservableObject { + static let shared = AppSettings() + + @Published var timeFormat: TimeFormat { + didSet { + UserDefaults.standard.set(timeFormat.rawValue, forKey: "timeFormat") + } + } + + private init() { + let savedFormat = UserDefaults.standard.string(forKey: "timeFormat") ?? TimeFormat.tenths.rawValue + self.timeFormat = TimeFormat(rawValue: savedFormat) ?? .tenths + } +} diff --git a/MultiChrono/ContentView.swift b/MultiChrono/ContentView.swift index 569554f..1c28e11 100644 --- a/MultiChrono/ContentView.swift +++ b/MultiChrono/ContentView.swift @@ -53,6 +53,7 @@ class ContentViewModel: ObservableObject { struct ContentView: View { @StateObject private var viewModel = ContentViewModel() @State private var isShowingAddSheet = false + @State private var isShowingSettings = false @State private var selectedStopwatch: Stopwatch? @Environment(\.scenePhase) private var scenePhase @@ -78,6 +79,13 @@ struct ContentView: View { .listStyle(.plain) .navigationTitle("MultiChrono") .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button { + isShowingSettings = true + } label: { + Image(systemName: "gearshape") + } + } ToolbarItem(placement: .primaryAction) { Button(action: { isShowingAddSheet = true @@ -92,6 +100,9 @@ struct ContentView: View { isShowingAddSheet = false } } + .sheet(isPresented: $isShowingSettings) { + SettingsView() + } .sheet(item: $selectedStopwatch) { stopwatch in StopwatchDetailView( stopwatch: stopwatch, @@ -115,6 +126,7 @@ struct ContentView: View { } } } + .onChange(of: scenePhase) { newPhase in if newPhase == .background || newPhase == .inactive { viewModel.save() diff --git a/MultiChrono/SettingsView.swift b/MultiChrono/SettingsView.swift new file mode 100644 index 0000000..262af39 --- /dev/null +++ b/MultiChrono/SettingsView.swift @@ -0,0 +1,41 @@ +// +// SettingsView.swift +// MultiChrono +// +// Created by Beatrice DellacĂ  on 27/01/26. +// + +import SwiftUI + +struct SettingsView: View { + @ObservedObject var settings = AppSettings.shared + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + Form { + Section(header: Text("Time Display Format")) { + Picker("Format", selection: $settings.timeFormat) { + ForEach(TimeFormat.allCases, id: \.self) { format in + Text(format.displayName).tag(format) + } + } + .pickerStyle(.menu) + } + } + .navigationTitle("Settings") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button("Done") { + dismiss() + } + } + } + } + } +} + +#Preview { + SettingsView() +} diff --git a/MultiChrono/Stopwatch.swift b/MultiChrono/Stopwatch.swift index 6e68a5f..e9318e6 100644 --- a/MultiChrono/Stopwatch.swift +++ b/MultiChrono/Stopwatch.swift @@ -69,7 +69,7 @@ class Stopwatch: ObservableObject, Identifiable, Codable { } private func startTimer() { - timer = Timer.publish(every: 0.01, on: .main, in: .common) + timer = Timer.publish(every: 0.001, on: .main, in: .common) .autoconnect() .sink { [weak self] _ in self?.tick() @@ -104,16 +104,44 @@ class Stopwatch: ObservableObject, Identifiable, Codable { elapsedTime = accumulatedTime + Date().timeIntervalSince(startTime) } - var formattedTime: String { + func formattedTime(format: TimeFormat = .cents) -> String { let hours = Int(elapsedTime) / 3600 let minutes = Int(elapsedTime) / 60 % 60 let seconds = Int(elapsedTime) % 60 - let tenths = Int((elapsedTime.truncatingRemainder(dividingBy: 1)) * 100) - if hours > 0 { - return String(format: "%02i:%02i:%02i.%02i", hours, minutes, seconds, tenths) - } else { - return String(format: "%02i:%02i.%02i", minutes, seconds, tenths) + switch format { + case .millis: + let millis = Int((elapsedTime.truncatingRemainder(dividingBy: 1)) * 1000) + if hours > 0 { + return String(format: "%02i:%02i:%02i.%03i", hours, minutes, seconds, millis) + } else { + return String(format: "%02i:%02i.%03i", minutes, seconds, millis) + } + case .cents: + let cents = Int((elapsedTime.truncatingRemainder(dividingBy: 1)) * 100) + if hours > 0 { + return String(format: "%02i:%02i:%02i.%02i", hours, minutes, seconds, cents) + } else { + return String(format: "%02i:%02i.%02i", minutes, seconds, cents) + } + case .tenths: + let tenths = Int((elapsedTime.truncatingRemainder(dividingBy: 1)) * 10) + if hours > 0 { + return String(format: "%02i:%02i:%02i.%01i", hours, minutes, seconds, tenths) + } else { + return String(format: "%02i:%02i.%01i", minutes, seconds, tenths) + } + case .seconds: + if hours > 0 { + return String(format: "%02i:%02i:%02i", hours, minutes, seconds) + } else { + return String(format: "%02i:%02i", minutes, seconds) + } } } + + // Legacy support to avoid breaking existing code immediately + var formattedTime: String { + formattedTime(format: AppSettings.shared.timeFormat) + } } diff --git a/MultiChrono/StopwatchRow.swift b/MultiChrono/StopwatchRow.swift index e5f2471..23783e5 100644 --- a/MultiChrono/StopwatchRow.swift +++ b/MultiChrono/StopwatchRow.swift @@ -9,6 +9,7 @@ import SwiftUI struct StopwatchRow: View { @ObservedObject var stopwatch: Stopwatch + @ObservedObject var settings = AppSettings.shared var onDelete: () -> Void var body: some View { @@ -16,7 +17,7 @@ struct StopwatchRow: View { VStack(alignment: .leading) { Text(stopwatch.name) .font(.headline) - Text("\(stopwatch.formattedTime)") + Text("\(stopwatch.formattedTime(format: settings.timeFormat))") .font(.largeTitle) .monospacedDigit() } @@ -33,19 +34,9 @@ struct StopwatchRow: View { Image(systemName: stopwatch.isRunning ? "pause.circle.fill" : "play.circle.fill") .resizable() .frame(width: 44, height: 44) - .foregroundStyle(stopwatch.isRunning ? .orange : .green) - } - .buttonStyle(PlainButtonStyle()) - - Button(action: onDelete) { - Image(systemName: "trash.circle.fill") - .resizable() - .frame(width: 44, height: 44) - .foregroundColor(.red) + .foregroundStyle(stopwatch.isRunning ? .yellow : .green) } .buttonStyle(PlainButtonStyle()) - .padding(.leading, 10) - } .padding(.vertical, 8) }