Skip to content

Commit a9df218

Browse files
committed
add benchmark user interface within settings
1 parent 0b46b15 commit a9df218

18 files changed

Lines changed: 490 additions & 0 deletions
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
protocol BenchmarkModuleType {
2+
func provide() -> BenchmarkViewModel
3+
func provide() -> OutputConfigurationViewModel
4+
func provide() -> AddDirectoryViewModel
5+
}
6+
7+
class BenchmarkModule: BenchmarkModuleType {
8+
private var benchmarkSettingsRepository: BenchmarkSettingsRepository?
9+
10+
static let shared: BenchmarkModuleType = BenchmarkModule()
11+
12+
private func component() -> LocalStorageManager {
13+
UserDefaultsLocalStorageManager()
14+
}
15+
16+
private func component() -> BenchmarkSettingsRepository {
17+
if let repository = benchmarkSettingsRepository {
18+
return repository
19+
} else {
20+
let repository = LocalBenchmarkSettingsRepository(
21+
localStorageManager: component()
22+
)
23+
benchmarkSettingsRepository = repository
24+
return repository
25+
}
26+
}
27+
28+
func provide() -> BenchmarkViewModel {
29+
BenchmarkViewModel(
30+
benchmarkSettingsRepository: component()
31+
)
32+
}
33+
34+
func provide() -> OutputConfigurationViewModel {
35+
OutputConfigurationViewModel(
36+
benchmarkSettingsRepository: component()
37+
)
38+
}
39+
40+
func provide() -> AddDirectoryViewModel {
41+
AddDirectoryViewModel(
42+
benchmarkSettingsRepository: component()
43+
)
44+
}
45+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Foundation
2+
3+
struct BenchmarkDirectoryDTO: Codable {
4+
let name: String
5+
let url: URL
6+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import Combine
2+
3+
class LocalBenchmarkSettingsRepository: BenchmarkSettingsRepository {
4+
private let localStorageManager: LocalStorageManager
5+
private let benchmarkDirectoriesKey = "benchmarkDirectories"
6+
private let benchmarkOutputDirectoryKey = "benchmarkOutputDirectory"
7+
private let defaultOutputDirectory = "~Desktop/benchmark_output"
8+
9+
private let currentBenchmarkDirectories: CurrentValueSubject<[BenchmarkDirectory], Never> = CurrentValueSubject([])
10+
var benchmarkDirectories: AnyPublisher<[BenchmarkDirectory], Never> {
11+
currentBenchmarkDirectories.eraseToAnyPublisher()
12+
}
13+
14+
private let benchmarkOutputDirectory: CurrentValueSubject<String, Never> = CurrentValueSubject("")
15+
var outputDirectory: AnyPublisher<String, Never> {
16+
benchmarkOutputDirectory.eraseToAnyPublisher()
17+
}
18+
19+
init(localStorageManager: LocalStorageManager) {
20+
self.localStorageManager = localStorageManager
21+
if let benchmarkDirectories = try? retrieveBenchmarkDirectories() {
22+
currentBenchmarkDirectories.send(benchmarkDirectories)
23+
}
24+
if let outputDirectory = try? loadBenchmarkOutputDirectory() {
25+
benchmarkOutputDirectory.send(outputDirectory)
26+
}
27+
}
28+
29+
func saveBenchmarkDirectory(_ directory: BenchmarkDirectory) throws {
30+
let currentEntries = currentBenchmarkDirectories.value
31+
let updatedEntries = currentEntries + [directory]
32+
let data = updatedEntries.mapToDTOs()
33+
try localStorageManager.save(codable: data, key: benchmarkDirectoriesKey)
34+
currentBenchmarkDirectories.send(updatedEntries)
35+
}
36+
37+
func deleteBenchmarkDirectory(_ directory: BenchmarkDirectory) throws {
38+
var currentEntries = currentBenchmarkDirectories.value
39+
currentEntries.removeAll { $0 == directory }
40+
let data = currentEntries.mapToDTOs()
41+
try localStorageManager.save(codable: data, key: benchmarkDirectoriesKey)
42+
currentBenchmarkDirectories.send(currentEntries)
43+
}
44+
45+
private func retrieveBenchmarkDirectories() throws -> [BenchmarkDirectory] {
46+
let data: [BenchmarkDirectoryDTO] = try localStorageManager.load(key: benchmarkDirectoriesKey)
47+
return data.map { $0.mapToDomain() }
48+
}
49+
50+
func saveBenchmarkOutputDirectory(_ directory: String) throws {
51+
try localStorageManager.save(codable: directory, key: benchmarkOutputDirectoryKey)
52+
benchmarkOutputDirectory.send(directory)
53+
}
54+
55+
private func loadBenchmarkOutputDirectory() throws -> String {
56+
do {
57+
return try localStorageManager.load(key: benchmarkOutputDirectoryKey)
58+
} catch LocalStorageError.noDataForKey {
59+
try saveBenchmarkOutputDirectory(defaultOutputDirectory)
60+
return defaultOutputDirectory
61+
}
62+
63+
}
64+
65+
}
66+
67+
extension Array where Element == BenchmarkDirectory {
68+
func mapToDTOs() -> [BenchmarkDirectoryDTO] {
69+
return self.map { $0.mapToDTO() }
70+
}
71+
}
72+
73+
extension BenchmarkDirectory {
74+
func mapToDTO() -> BenchmarkDirectoryDTO {
75+
return BenchmarkDirectoryDTO(name: name, url: url)
76+
}
77+
}
78+
79+
extension BenchmarkDirectoryDTO {
80+
func mapToDomain() -> BenchmarkDirectory {
81+
return BenchmarkDirectory(name: name, url: url)
82+
}
83+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Foundation
2+
3+
struct BenchmarkDirectory: Hashable {
4+
let name: String
5+
let url: URL
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Combine
2+
3+
protocol BenchmarkSettingsRepository {
4+
var benchmarkDirectories: AnyPublisher<[BenchmarkDirectory], Never> { get }
5+
var outputDirectory: AnyPublisher<String, Never> { get }
6+
func saveBenchmarkDirectory(_ directory: BenchmarkDirectory) throws
7+
func deleteBenchmarkDirectory(_ directory: BenchmarkDirectory) throws
8+
func saveBenchmarkOutputDirectory(_ directory: String) throws
9+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
enum LocalStorageError: Error {
2+
case encodingError
3+
case noDataForKey
4+
case decodingError
5+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Foundation
2+
3+
class UserDefaultsLocalStorageManager: LocalStorageManager {
4+
5+
private let userDefaults: UserDefaults = UserDefaults()
6+
7+
func save<T: Codable>(codable: T, key: String) throws {
8+
let encoder = JSONEncoder()
9+
guard let encoded = try? encoder.encode(codable) else {
10+
throw LocalStorageError.encodingError
11+
}
12+
userDefaults.set(encoded, forKey: key)
13+
}
14+
15+
func load<T: Codable>(key: String) throws -> T {
16+
guard let data = userDefaults.data(forKey: key) else {
17+
throw LocalStorageError.noDataForKey
18+
}
19+
let decoder = JSONDecoder()
20+
guard let decoded = try? decoder.decode(T.self, from: data) else {
21+
throw LocalStorageError.decodingError
22+
}
23+
return decoded
24+
}
25+
26+
func delete(key: String) {
27+
userDefaults.removeObject(forKey: key)
28+
}
29+
30+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
protocol LocalStorageManager {
2+
func save<T: Codable>(codable: T, key: String) throws
3+
func load<T: Codable>(key: String) throws -> T
4+
func delete(key: String)
5+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import SwiftUI
2+
3+
struct AddDirectoryButtonView: View {
4+
private let module: BenchmarkModuleType
5+
@State private var isShowingSheet = false
6+
7+
init(module: BenchmarkModuleType) {
8+
self.module = module
9+
}
10+
11+
var body: some View {
12+
Button {
13+
isShowingSheet.toggle()
14+
} label : {
15+
Label("add directory", systemImage: "plus")
16+
.foregroundStyle(.foreground) // to remove green accent color from system image
17+
}
18+
.sheet(isPresented: $isShowingSheet) {
19+
AddDirectoryView(module: module)
20+
}
21+
}
22+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import SwiftUI
2+
3+
struct AddDirectoryView: View {
4+
@State private var currentDirectoryName: String = ""
5+
@State private var currentDirectory: String = ""
6+
@StateObject var viewModel: AddDirectoryViewModel
7+
@Environment(\.dismiss) private var dismiss
8+
9+
private let labelWidth: CGFloat = 100
10+
11+
init(module: BenchmarkModuleType) {
12+
_viewModel = StateObject(wrappedValue: module.provide())
13+
}
14+
15+
var body: some View {
16+
VStack {
17+
HStack {
18+
Text("Name")
19+
.frame(width: labelWidth, alignment: .leading)
20+
TextField("Name", text: $currentDirectoryName, prompt: Text("Project X"))
21+
.textFieldStyle(PlainTextFieldStyle())
22+
}
23+
HStack {
24+
Text("Directory")
25+
.frame(width: labelWidth, alignment: .leading)
26+
TextField("Directory", text: $currentDirectory, prompt: Text("/path/to/directory"))
27+
.textFieldStyle(PlainTextFieldStyle())
28+
}
29+
30+
}
31+
.padding()
32+
33+
HStack {
34+
Button("Cancel") {
35+
dismiss()
36+
}
37+
Button("Save") {
38+
viewModel.saveNewDirectory(name: currentDirectoryName, directory: currentDirectory)
39+
dismiss()
40+
}
41+
}
42+
.padding([.horizontal, .bottom])
43+
}
44+
}

0 commit comments

Comments
 (0)