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 {
|
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()
|
||||||
|
|||||||
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() {
|
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)
|
|
||||||
|
|
||||||
if hours > 0 {
|
switch format {
|
||||||
return String(format: "%02i:%02i:%02i.%02i", hours, minutes, seconds, tenths)
|
case .millis:
|
||||||
} else {
|
let millis = Int((elapsedTime.truncatingRemainder(dividingBy: 1)) * 1000)
|
||||||
return String(format: "%02i:%02i.%02i", minutes, seconds, tenths)
|
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 {
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user