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 { struct ContentView: View {
@StateObject private var viewModel = ContentViewModel() @StateObject private var viewModel = ContentViewModel()
@State private var isShowingAddSheet = false @State private var isShowingAddSheet = false
@State private var isShowingSettings = false
@State private var selectedStopwatch: Stopwatch? @State private var selectedStopwatch: Stopwatch?
@Environment(\.scenePhase) private var scenePhase @Environment(\.scenePhase) private var scenePhase
@@ -78,6 +79,13 @@ struct ContentView: View {
.listStyle(.plain) .listStyle(.plain)
.navigationTitle("MultiChrono") .navigationTitle("MultiChrono")
.toolbar { .toolbar {
ToolbarItem(placement: .topBarLeading) {
Button {
isShowingSettings = true
} label: {
Image(systemName: "gearshape")
}
}
ToolbarItem(placement: .primaryAction) { ToolbarItem(placement: .primaryAction) {
Button(action: { Button(action: {
isShowingAddSheet = true isShowingAddSheet = true
@@ -92,6 +100,9 @@ struct ContentView: View {
isShowingAddSheet = false isShowingAddSheet = false
} }
} }
.sheet(isPresented: $isShowingSettings) {
SettingsView()
}
.sheet(item: $selectedStopwatch) { stopwatch in .sheet(item: $selectedStopwatch) { stopwatch in
StopwatchDetailView( StopwatchDetailView(
stopwatch: stopwatch, stopwatch: stopwatch,
@@ -115,6 +126,7 @@ struct ContentView: View {
} }
} }
} }
.onChange(of: scenePhase) { newPhase in .onChange(of: scenePhase) { newPhase in
if newPhase == .background || newPhase == .inactive { if newPhase == .background || newPhase == .inactive {
viewModel.save() 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() { private func startTimer() {
timer = Timer.publish(every: 0.01, on: .main, in: .common) timer = Timer.publish(every: 0.001, on: .main, in: .common)
.autoconnect() .autoconnect()
.sink { [weak self] _ in .sink { [weak self] _ in
self?.tick() self?.tick()
@@ -104,16 +104,44 @@ class Stopwatch: ObservableObject, Identifiable, Codable {
elapsedTime = accumulatedTime + Date().timeIntervalSince(startTime) elapsedTime = accumulatedTime + Date().timeIntervalSince(startTime)
} }
var formattedTime: String { func formattedTime(format: TimeFormat = .cents) -> String {
let hours = Int(elapsedTime) / 3600 let hours = Int(elapsedTime) / 3600
let minutes = Int(elapsedTime) / 60 % 60 let minutes = Int(elapsedTime) / 60 % 60
let seconds = Int(elapsedTime) % 60 let seconds = Int(elapsedTime) % 60
let tenths = Int((elapsedTime.truncatingRemainder(dividingBy: 1)) * 100)
switch format {
case .millis:
let millis = Int((elapsedTime.truncatingRemainder(dividingBy: 1)) * 1000)
if hours > 0 { if hours > 0 {
return String(format: "%02i:%02i:%02i.%02i", hours, minutes, seconds, tenths) return String(format: "%02i:%02i:%02i.%03i", hours, minutes, seconds, millis)
} else { } else {
return String(format: "%02i:%02i.%02i", minutes, seconds, tenths) 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 { struct StopwatchRow: View {
@ObservedObject var stopwatch: Stopwatch @ObservedObject var stopwatch: Stopwatch
@ObservedObject var settings = AppSettings.shared
var onDelete: () -> Void var onDelete: () -> Void
var body: some View { var body: some View {
@@ -16,7 +17,7 @@ struct StopwatchRow: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(stopwatch.name) Text(stopwatch.name)
.font(.headline) .font(.headline)
Text("\(stopwatch.formattedTime)") Text("\(stopwatch.formattedTime(format: settings.timeFormat))")
.font(.largeTitle) .font(.largeTitle)
.monospacedDigit() .monospacedDigit()
} }
@@ -33,19 +34,9 @@ struct StopwatchRow: View {
Image(systemName: stopwatch.isRunning ? "pause.circle.fill" : "play.circle.fill") Image(systemName: stopwatch.isRunning ? "pause.circle.fill" : "play.circle.fill")
.resizable() .resizable()
.frame(width: 44, height: 44) .frame(width: 44, height: 44)
.foregroundStyle(stopwatch.isRunning ? .orange : .green) .foregroundStyle(stopwatch.isRunning ? .yellow : .green)
} }
.buttonStyle(PlainButtonStyle()) .buttonStyle(PlainButtonStyle())
Button(action: onDelete) {
Image(systemName: "trash.circle.fill")
.resizable()
.frame(width: 44, height: 44)
.foregroundColor(.red)
}
.buttonStyle(PlainButtonStyle())
.padding(.leading, 10)
} }
.padding(.vertical, 8) .padding(.vertical, 8)
} }