[App] Open settings (#5)

This PR contains the work done to open the *Settings* view and also, implemented the rendering of its tab items.

Reviewed-on: #5
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
This commit is contained in:
Javier Cicchelli 2024-10-13 22:17:49 +00:00 committed by Javier Cicchelli
parent 3d78c599d1
commit 58ac646e25
17 changed files with 272 additions and 116 deletions

View File

@ -431,7 +431,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "Piper/Sources/Previews/Extensions/Repository+Samples.swift Piper/Sources/Previews/Extensions/ModelContainer+Constants.swift Piper/Resources/Catalogs/Previews.xcassets"; DEVELOPMENT_ASSET_PATHS = "Piper/Sources/Previews/Extensions/PreviewTrait+Properties.swift Piper/Sources/Previews/Extensions/ModelContainer+Constants.swift Piper/Resources/Catalogs/Previews.xcassets Piper/Sources/Previews/Extensions/Repository+Samples.swift Piper/Sources/Previews/Modifiers/SampleDataModifier.swift Piper/Sources/Previews/Modifiers/EmptyDataModifier.swift";
DEVELOPMENT_TEAM = 7FMNM89WKG; DEVELOPMENT_TEAM = 7FMNM89WKG;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@ -463,7 +463,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "Piper/Sources/Previews/Extensions/Repository+Samples.swift Piper/Sources/Previews/Extensions/ModelContainer+Constants.swift Piper/Resources/Catalogs/Previews.xcassets"; DEVELOPMENT_ASSET_PATHS = "Piper/Sources/Previews/Extensions/PreviewTrait+Properties.swift Piper/Sources/Previews/Extensions/ModelContainer+Constants.swift Piper/Resources/Catalogs/Previews.xcassets Piper/Sources/Previews/Extensions/Repository+Samples.swift Piper/Sources/Previews/Modifiers/SampleDataModifier.swift Piper/Sources/Previews/Modifiers/EmptyDataModifier.swift";
DEVELOPMENT_TEAM = 7FMNM89WKG; DEVELOPMENT_TEAM = 7FMNM89WKG;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;

View File

@ -1,12 +1,6 @@
{ {
"sourceLanguage" : "en", "sourceLanguage" : "en",
"strings" : { "strings" : {
"Add Item" : {
},
"Item at %@" : {
},
"menu-bar.item.empty.button.text" : { "menu-bar.item.empty.button.text" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -48,8 +42,15 @@
} }
} }
}, },
"Select an item" : { "settings.tab-bar.repositories.text" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Repositories"
}
}
}
} }
}, },
"version" : "1.0" "version" : "1.0"

View File

@ -35,6 +35,10 @@ struct PiperApp: App {
Image(systemName: "circle.fill") Image(systemName: "circle.fill")
} }
.menuBarExtraStyle(.window) .menuBarExtraStyle(.window)
Settings {
SettingsView()
}
} }
} }

View File

@ -16,5 +16,4 @@ extension Schema {
Repository.self Repository.self
]) ])
} }

View File

@ -1,19 +0,0 @@
//
// Item.swift
// Piper ~ App
//
// Created by Javier Cicchelli on 04/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import Foundation
import SwiftData
@Model
final class Item {
var timestamp: Date
init(timestamp: Date) {
self.timestamp = timestamp
}
}

View File

@ -1,20 +0,0 @@
//
// MenuBarViewModel.swift
// Piper ~ App
//
// Created by Javier Cicchelli on 06/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import Observation
@Observable
final class MenuBarViewModel {
// MARK: Initialisers
init () {
}
}

View File

@ -0,0 +1,20 @@
//
// SettingsViewModel.swift
// Piper ~ App
//
// Created by Javier Cicchelli on 13/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import Observation
@Observable
final class SettingsViewModel {
// MARK: Properties
var tabSelected: SettingsItem = .repositories
let tabs: [SettingsItem] = SettingsItem.allCases
}

View File

@ -1,6 +1,6 @@
// //
// ModelContainer+Constants.swift // ModelContainer+Constants.swift
// Piper // Piper ~ App
// //
// Created by Javier Cicchelli on 06/10/2024. // Created by Javier Cicchelli on 06/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved. // Copyright © 2024 Röck+Cöde. All rights reserved.

View File

@ -0,0 +1,19 @@
//
// PreviewTrait+Properties.swift
// Piper
//
// Created by Javier Cicchelli on 13/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import SwiftUI
@available(macOS 15.0, *)
extension PreviewTrait where T == Preview.ViewTraits {
// MARK: Properties
@MainActor static var emptyData: PreviewTrait = .modifier(EmptyDataModifier())
@MainActor static var sampleData: PreviewTrait = .modifier(SampleDataModifier())
}

View File

@ -1,6 +1,6 @@
// //
// Repository+Samples.swift // Repository+Samples.swift
// Piper // Piper ~ App
// //
// Created by Javier Cicchelli on 06/10/2024. // Created by Javier Cicchelli on 06/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved. // Copyright © 2024 Röck+Cöde. All rights reserved.

View File

@ -0,0 +1,27 @@
//
// EmptyDataModifier.swift
// Piper ~ App
//
// Created by Javier Cicchelli on 13/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import SwiftData
import SwiftUI
struct EmptyDataModifier: PreviewModifier {
// MARK: Functions
static func makeSharedContext() async throws -> ModelContainer {
ModelContainer.preview
}
func body(
content: Content,
context: ModelContainer
) -> some View {
content.modelContainer(context)
}
}

View File

@ -0,0 +1,31 @@
//
// SampleDataModifier.swift
// Piper ~ App
//
// Created by Javier Cicchelli on 13/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import SwiftData
import SwiftUI
struct SampleDataModifier: PreviewModifier {
// MARK: Functions
static func makeSharedContext() async throws -> ModelContainer {
let container = ModelContainer.preview
Repository.samples(in: container)
return container
}
func body(
content: Content,
context: ModelContainer
) -> some View {
content.modelContainer(context)
}
}

View File

@ -0,0 +1,35 @@
//
// SettingsItem.swift
// Piper ~ App
//
// Created by Javier Cicchelli on 13/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
enum SettingsItem: Int, Hashable {
case repositories = 0
}
// MARK: - CaseIterable
extension SettingsItem: CaseIterable {
// MARK: Computed
var allCases: [SettingsItem] {
[.repositories]
}
}
// MARK: - Identifiable
extension SettingsItem: Identifiable {
// MARK: Computed
var id: Int {
rawValue
}
}

View File

@ -0,0 +1,27 @@
//
// SettingsItem+Properties.swift
// Piper ~ App
//
// Created by Javier Cicchelli on 13/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import SwiftUI
extension SettingsItem {
// MARK: Computed
var icon: String {
switch self {
case .repositories: "folder"
}
}
var title: LocalizedStringKey {
switch self {
case .repositories: "settings.tab-bar.repositories.text"
}
}
}

View File

@ -1,60 +0,0 @@
//
// ContentView.swift
// Piper ~ App
//
// Created by Javier Cicchelli on 04/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
var body: some View {
NavigationSplitView {
List {
ForEach(items) { item in
NavigationLink {
Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
} label: {
Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
}
}
.onDelete(perform: deleteItems)
}
.navigationSplitViewColumnWidth(min: 180, ideal: 200)
.toolbar {
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
} detail: {
Text("Select an item")
}
}
private func addItem() {
withAnimation {
let newItem = Item(timestamp: Date())
modelContext.insert(newItem)
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(items[index])
}
}
}
}
#Preview {
ContentView()
.modelContainer(for: Item.self, inMemory: true)
}

View File

@ -13,11 +13,11 @@ struct MenuBarView: View {
// MARK: Properties // MARK: Properties
@Environment(\.openSettings) private var openSettings
@Query(sort: \Repository.sortOrder) @Query(sort: \Repository.sortOrder)
private var repositories: [Repository] private var repositories: [Repository]
@State private var viewMode = MenuBarViewModel()
// MARK: Body // MARK: Body
var body: some View { var body: some View {
@ -28,7 +28,7 @@ struct MenuBarView: View {
title: "menu-bar.item.empty.title.text", title: "menu-bar.item.empty.title.text",
button: "menu-bar.item.empty.button.text" button: "menu-bar.item.empty.button.text"
) { ) {
// ... openSettings()
} }
.frame(height: Layout.heightEmpty) .frame(height: Layout.heightEmpty)
} else { } else {
@ -66,11 +66,29 @@ private extension MenuBarView {
// MARK: - Previews // MARK: - Previews
@available(macOS 15.0, *)
#Preview(
"Menu Bar view when no repositories found",
traits: .emptyData
) {
MenuBarView()
}
@available(macOS, obsoleted: 15)
#Preview("Menu Bar view when no repositories found") { #Preview("Menu Bar view when no repositories found") {
MenuBarView() MenuBarView()
.modelContainer(.preview) .modelContainer(.preview)
} }
@available(macOS 15.0, *)
#Preview(
"Menu Bar view when some repositories found",
traits: .sampleData
) {
MenuBarView()
}
@available(macOS, obsoleted: 15)
#Preview("Menu Bar view when some repositories found") { #Preview("Menu Bar view when some repositories found") {
let container = ModelContainer.preview let container = ModelContainer.preview

View File

@ -0,0 +1,74 @@
//
// SettingsView.swift
// Piper ~ App
//
// Created by Javier Cicchelli on 13/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import SwiftUI
struct SettingsView: View {
// MARK: Properties
@State private var viewModel: SettingsViewModel = .init()
// MARK: Body
var body: some View {
Group {
if #available(macOS 15.0, *) {
TabView(selection: $viewModel.tabSelected) {
ForEach(viewModel.tabs) { tabItem in
Tab(
tabItem.title,
systemImage: tabItem.icon,
value: tabItem
) {
switch tabItem {
case .repositories:
Text(tabItem.title)
}
}
}
}
} else {
TabView(selection: $viewModel.tabSelected) {
ForEach(viewModel.tabs) { tabItem in
Group {
switch tabItem {
case .repositories:
Text(tabItem.title)
}
}
.tabItem {
Text(tabItem.title)
}
.tag(tabItem)
}
}
}
}
.scenePadding()
.frame(
width: Layout.sizeView.width,
height: Layout.sizeView.height
)
}
}
// MARK: - Layout
private extension SettingsView {
enum Layout {
static let sizeView = CGSize(width: 350, height: 250)
}
}
// MARK: - Previews
#Preview {
SettingsView()
}