implement settings

This commit is contained in:
2026-01-27 02:39:24 +01:00
parent f45f7db10b
commit 5f05da552d
5 changed files with 124 additions and 19 deletions

View File

@@ -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
}
}

View File

@@ -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()

View File

@@ -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()
}

View File

@@ -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)
}
}

View File

@@ -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)
}