diff --git a/App/Sources/View Models/ContentViewModel.swift b/App/Sources/View Models/ContentViewModel.swift new file mode 100644 index 0000000..ecddcec --- /dev/null +++ b/App/Sources/View Models/ContentViewModel.swift @@ -0,0 +1,39 @@ +import SwiftData +import SwiftUI + +extension ContentView { + + /// View model for managing the logic for the content view. + /// + /// This observable class encapsulates the state and business logic for the main content view. + /// It follows the MVVM pattern to separate presentation logic from the view layer. + /// + /// The view model manages: + /// - Presentation state for the new task view within a sheet. + @Observable + class ViewModel { + + // MARK: Properties + + /// Whether the new task view should be shown. + var newTaskShown: Bool + + // MARK: Initialization + + /// Initializes a new view model instance with default values. + /// + /// The view model is initialized with: + /// - New task view hidden + init() { + newTaskShown = false + } + + // MARK: Functions + + /// Shows the new task view by setting the presentation state to true. + func showNewTask() { + newTaskShown = true + } + + } +} diff --git a/App/Sources/Views/ContentView.swift b/App/Sources/Views/ContentView.swift index 96ad30f..34bb847 100644 --- a/App/Sources/Views/ContentView.swift +++ b/App/Sources/Views/ContentView.swift @@ -1,63 +1,52 @@ import SwiftData import SwiftUI +/// The main content view of the app. +/// +/// This view serves as the primary interface for managing todo tasks. +/// +/// The view uses a dedicated view model (`ViewModel`) to manage state and business logic, following the MVVM pattern. +/// User interactions are handled through the view model, maintaining separation of concerns. struct ContentView: View { - @Environment(\.modelContext) - private var modelContext + // MARK: Properties - @Query - private var items: [Todo] + /// The view model managing the state and logic for this view. + @State + private var viewModel = ViewModel() + // MARK: Body + + /// The content and behavior of the view. + /// + /// Displays a navigation view containing: + /// - A floating action button for adding new tasks + /// + /// The view presents a sheet when `viewModel.newTaskShown` is true, + /// allowing users to create new todo tasks. 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) - } - #if os(macOS) - .navigationSplitViewColumnWidth(min: 180, ideal: 200) - #endif - .toolbar { - #if os(iOS) - ToolbarItem(placement: .navigationBarTrailing) { - EditButton() - } - #endif - ToolbarItem { - Button(action: addItem) { - Label("Add Item", systemImage: "plus") - } - } - } - } detail: { - Text("Select an item") - } - } + NavigationView { + ZStack(alignment: .bottomTrailing) { + List {} - private func addItem() { - withAnimation { - let newItem = Todo(title: "New Todo", due: Date()) - modelContext.insert(newItem) - } - } - - private func deleteItems(offsets: IndexSet) { - withAnimation { - for index in offsets { - modelContext.delete(items[index]) + Button(action: viewModel.showNewTask) { + Image(systemName: "plus") + .frame(width: 48, height: 48) + .foregroundStyle(.white) + } + .glassEffect(.regular.tint(.blue.opacity(0.8)).interactive()) + .padding(.trailing, 20) } + .navigationTitle("Todos") + } + .sheet(isPresented: $viewModel.newTaskShown) { + NewTaskView() } } } +// MARK: - Previews + #Preview { ContentView() - .modelContainer(for: Todo.self, inMemory: true) } diff --git a/UnitTests/Tests/View Models/ContentViewModelTests.swift b/UnitTests/Tests/View Models/ContentViewModelTests.swift new file mode 100644 index 0000000..668bd6e --- /dev/null +++ b/UnitTests/Tests/View Models/ContentViewModelTests.swift @@ -0,0 +1,36 @@ +import Foundation +import SwiftData +import Testing + +@testable import Todos + +@Suite("ContentView.ViewModel Tests") +struct ContentViewModelTests { + + // MARK: Initialization Tests + + @Test("View model initializes with default values") + func initialization() { + // GIVEN + // WHEN + let viewModel = ContentView.ViewModel() + + // THEN + #expect(viewModel.newTaskShown == false) + } + + // MARK: - Functions Tests + + @Test("showNewTask sets showNewTaskView to true") + func newTaskShown() { + // GIVEN + let viewModel = ContentView.ViewModel() + + // WHEN + viewModel.showNewTask() + + // THEN + #expect(viewModel.newTaskShown == true) + } + +}