implement settings
This commit is contained in:
33
MultiChrono/AppSettings.swift
Normal file
33
MultiChrono/AppSettings.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
41
MultiChrono/SettingsView.swift
Normal file
41
MultiChrono/SettingsView.swift
Normal 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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user