Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions Cubby/Views/Home/AddLocationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import SwiftUI
struct AddLocationView: View {
let homeId: UUID?
let parentLocation: AppStorageLocation?
let onLocationCreated: ((AppStorageLocation) -> Void)?

@Environment(\.dismiss) private var dismiss
@Environment(\.sharedHomesGateService) private var sharedHomesGateService
Expand All @@ -12,10 +13,32 @@ struct AddLocationView: View {
@State private var showingError = false
@State private var errorMessage = ""

init(
homeId: UUID?,
parentLocation: AppStorageLocation?,
onLocationCreated: ((AppStorageLocation) -> Void)? = nil
) {
self.homeId = homeId
self.parentLocation = parentLocation
self.onLocationCreated = onLocationCreated
}

private var resolvedHome: AppHome? {
parentLocation.flatMap { appStore.home(id: $0.homeID) } ?? appStore.home(id: homeId)
}

private var isAddingSubLocation: Bool {
parentLocation != nil
}

private var title: String {
isAddingSubLocation ? "Add Sub-location" : "Add Storage Location"
}

private var nameFieldTitle: String {
isAddingSubLocation ? "Sub-location name" : "Location name"
}

var body: some View {
NavigationStack {
Form {
Expand All @@ -30,13 +53,13 @@ struct AddLocationView: View {
}
}

Section("Location Details") {
TextField("Location Name", text: $locationName)
Section(nameFieldTitle) {
TextField(nameFieldTitle, text: $locationName)
.textInputAutocapitalization(.words)

if let parentLocation {
HStack {
Text("Parent Location")
Text("Inside")
Spacer()
Text(parentLocation.name)
.foregroundStyle(.secondary)
Expand All @@ -46,7 +69,7 @@ struct AddLocationView: View {

if let parentLocation {
Section {
Label("This location will be nested under \"\(parentLocation.name)\"", systemImage: "info.circle")
Label("Creates a sub-location inside \"\(parentLocation.name)\".", systemImage: "info.circle")
.font(.footnote)
.foregroundStyle(.secondary)
}
Expand All @@ -58,7 +81,7 @@ struct AddLocationView: View {
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
Text("Add Storage Location")
Text(title)
.font(.custom("AwesomeSerif-ExtraTall", size: 20))
.foregroundStyle(.primary)
}
Expand All @@ -68,6 +91,7 @@ struct AddLocationView: View {
ToolbarItem(placement: .confirmationAction) {
Button("Save") { saveLocation() }
.disabled(locationName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || !canCreateLocationsInHome)
.accessibilityIdentifier("save-location-button")
}
}
.alert("Error", isPresented: $showingError) {
Expand Down Expand Up @@ -95,11 +119,12 @@ struct AddLocationView: View {
guard !trimmedName.isEmpty else { return }

do {
_ = try appStore.createLocation(
let location = try appStore.createLocation(
name: trimmedName,
homeID: home.id,
parentLocationID: parentLocation?.id
)
onLocationCreated?(location)
dismiss()
} catch {
errorMessage = error.localizedDescription
Expand Down
35 changes: 31 additions & 4 deletions Cubby/Views/Home/StorageLocationPicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ struct StorageLocationPicker: View {
location: location,
selectedLocation: $selectedLocation,
expandedLocations: $expandedLocations,
searchText: searchText
searchText: searchText,
onLocationCreated: handleLocationCreated
)
}
} else {
Expand Down Expand Up @@ -81,11 +82,29 @@ struct StorageLocationPicker: View {
}
}
.sheet(isPresented: $showingAddLocation) {
AddLocationView(homeId: selectedHomeId, parentLocation: nil)
AddLocationView(
homeId: selectedHomeId,
parentLocation: nil,
onLocationCreated: handleLocationCreated
)
}
}
}

private func handleLocationCreated(_ location: AppStorageLocation) {
selectedLocation = location
searchText = ""
expandAncestors(of: location)
}

private func expandAncestors(of location: AppStorageLocation) {
var parentID = location.parentLocationID
while let id = parentID {
expandedLocations.insert(id)
parentID = appStore.location(id: id)?.parentLocationID
}
}

@Environment(\.colorScheme) private var colorScheme
private var appBackground: Color {
if colorScheme == .light, UIColor(named: "AppBackground") != nil {
Expand All @@ -101,6 +120,7 @@ struct LocationPickerRow: View {
@Binding var selectedLocation: AppStorageLocation?
@Binding var expandedLocations: Set<UUID>
let searchText: String
let onLocationCreated: (AppStorageLocation) -> Void

@State private var showingAddLocation = false
@State private var showingDeleteConfirmation = false
Expand Down Expand Up @@ -161,7 +181,8 @@ struct LocationPickerRow: View {
location: childLocation,
selectedLocation: $selectedLocation,
expandedLocations: $expandedLocations,
searchText: searchText
searchText: searchText,
onLocationCreated: onLocationCreated
)
.padding(.leading, 20)
}
Expand All @@ -187,6 +208,8 @@ struct LocationPickerRow: View {
.foregroundStyle(isSelected ? .white : Color.accentColor)
}
.buttonStyle(.plain)
.accessibilityLabel("Add sub-location under \(location.name)")
.accessibilityHint("Creates a nested storage location inside \(location.name).")
}
.padding(.vertical, 4)
.padding(.horizontal, 8)
Expand All @@ -213,7 +236,11 @@ struct LocationPickerRow: View {
}
}
.sheet(isPresented: $showingAddLocation) {
AddLocationView(homeId: location.homeID, parentLocation: location)
AddLocationView(
homeId: location.homeID,
parentLocation: location,
onLocationCreated: onLocationCreated
)
}
.alert("Delete Location?", isPresented: $showingDeleteConfirmation) {
Button("Cancel", role: .cancel) {}
Expand Down
7 changes: 6 additions & 1 deletion Cubby/Views/Home/StorageLocationRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ struct StorageLocationRow: View {
locationLabel
}
.sheet(isPresented: $showingAddLocation) {
AddLocationView(homeId: location.homeID, parentLocation: location)
AddLocationView(
homeId: location.homeID,
parentLocation: location
) { _ in
expandedLocations.insert(location.id)
}
}
.alert("Delete Location", isPresented: $showingDeleteAlert) {
Button("Cancel", role: .cancel) { }
Expand Down
3 changes: 3 additions & 0 deletions Cubby/Views/Items/AddItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ struct AddItemView: View {
}
}
.foregroundColor(.primary)
.accessibilityIdentifier("add-item-location-picker-button")
.accessibilityLabel("Storage Location")
.accessibilityValue(selectedLocation?.fullPath ?? "Select")
}

Section("Tags") {
Expand Down
50 changes: 50 additions & 0 deletions CubbyUITests/StorageLocationPickerUITests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import XCTest

final class StorageLocationPickerUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
}

@MainActor
func testSubLocationCreatedFromItemPickerIsSelected() throws {
let app = XCUIApplication(bundleIdentifier: "com.barronroth.Cubby")
app.launchArguments.append(contentsOf: ["UI-TESTING", "SEED_MOCK_DATA", "FORCE_PRO_TIER"])
app.launch()

let addItemButton = app.buttons["Add Item"]
XCTAssertTrue(addItemButton.waitForExistence(timeout: 10))
addItemButton.tap()

let locationButton = app.buttons["add-item-location-picker-button"]
XCTAssertTrue(locationButton.waitForExistence(timeout: 5))
locationButton.tap()

let searchField = app.searchFields["Search locations"]
XCTAssertTrue(searchField.waitForExistence(timeout: 5))
searchField.tap()
searchField.typeText("Walk-in Closet")

let addSubLocationButton = app.buttons["Add sub-location under Walk-in Closet"]
XCTAssertTrue(addSubLocationButton.waitForExistence(timeout: 5))
addSubLocationButton.tap()

XCTAssertTrue(app.staticTexts["Add Sub-location"].waitForExistence(timeout: 5))

let nameField = app.textFields["Sub-location name"]
XCTAssertTrue(nameField.waitForExistence(timeout: 5))
nameField.tap()
nameField.typeText("Top Shelf")

app.buttons["save-location-button"].tap()

XCTAssertTrue(app.staticTexts["Top Shelf"].waitForExistence(timeout: 5))
app.navigationBars.buttons["Done"].tap()

let updatedLocationButton = app.buttons["add-item-location-picker-button"]
XCTAssertTrue(updatedLocationButton.waitForExistence(timeout: 5))
XCTAssertEqual(
updatedLocationButton.value as? String,
"Master Bedroom > Walk-in Closet > Top Shelf"
)
}
}
Loading