This PR contains the work done to improve the app layout for max iPhones and iPads by levering on the navigation split view. Plus, the floating button to add todo tasks was improved and integrated better into the layout and a critical bug occurring on max iPhones and iPads was fixed. Reviewed-on: rock-n-code/todos-app#10 Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
124 lines
3.7 KiB
Swift
124 lines
3.7 KiB
Swift
import SwiftData
|
|
import SwiftUI
|
|
|
|
/// A view for creating new todo tasks.
|
|
///
|
|
/// This view provides a form interface for users to input todo task details including:
|
|
/// - *Title* (required)
|
|
/// - *Note* (optional)
|
|
/// - *Due date* (required)
|
|
struct NewTaskView: View {
|
|
|
|
// MARK: Properties
|
|
|
|
/// Environment value for dismissing this view.
|
|
@Environment(\.dismiss)
|
|
private var dismiss
|
|
|
|
/// SwiftData model context for saving todo tasks.
|
|
@Environment(\.modelContext)
|
|
private var modelContext
|
|
|
|
/// Tracks which text field currently has keyboard focus.
|
|
@FocusState
|
|
private var inputFocus: InputFieldFocus?
|
|
|
|
/// The view model managing the state and logic for this view.
|
|
@State
|
|
private var viewModel: ViewModel
|
|
|
|
// MARK: Initialization
|
|
|
|
/// Initializes a new task view with a fresh view model instance.
|
|
init() {
|
|
_viewModel = State(initialValue: .init())
|
|
}
|
|
|
|
// MARK: Body
|
|
|
|
/// The content and behavior of the view.
|
|
///
|
|
/// Displays a navigation stack containing a form with sections for:
|
|
/// - Task details (title and note fields)
|
|
/// - Due date picker with graphical style
|
|
///
|
|
/// The toolbar includes:
|
|
/// - Cancel button to dismiss without saving
|
|
/// - Confirm button to persist the todo and dismiss (disabled when title is empty)
|
|
var body: some View {
|
|
NavigationStack {
|
|
Form {
|
|
Section("view.new-task.section.task") {
|
|
TextField(
|
|
"view.new-task.placeholder.title",
|
|
text: $viewModel.title,
|
|
axis: .vertical
|
|
)
|
|
.focused(
|
|
$inputFocus,
|
|
equals: .title
|
|
)
|
|
.onNewlineFocus(
|
|
$viewModel.title,
|
|
to: .note,
|
|
on: $inputFocus
|
|
)
|
|
|
|
TextField(
|
|
"view.new-task.placeholder.note",
|
|
text: $viewModel.note,
|
|
axis: .vertical
|
|
)
|
|
.focused(
|
|
$inputFocus,
|
|
equals: .note
|
|
)
|
|
.onEmptyNewlineResignFocus(
|
|
text: $viewModel.note,
|
|
from: $inputFocus
|
|
)
|
|
}
|
|
.autocapitalization(.sentences)
|
|
.autocorrectionDisabled(false)
|
|
.keyboardType(.default)
|
|
|
|
Section("view.new-task.section.due-date") {
|
|
DatePicker(
|
|
"view.new-task.placeholder.due-date",
|
|
selection: $viewModel.due,
|
|
displayedComponents: [.date]
|
|
)
|
|
.datePickerStyle(.graphical)
|
|
}
|
|
}
|
|
.navigationTitle("view.new-task.navigation.title")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.onResignFocus()
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button(role: .cancel) {
|
|
dismiss()
|
|
}
|
|
}
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
Button(role: .confirm) {
|
|
viewModel.save(to: modelContext)
|
|
dismiss()
|
|
}
|
|
.disabled(!viewModel.canConfirm)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Previews
|
|
|
|
#Preview(
|
|
"New todo task",
|
|
traits: .emptyContainer
|
|
) {
|
|
NewTaskView()
|
|
}
|