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
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct AppCoordinatorView: View {
destination: IfLetStore(
store.scope(state: \.search, action: \.search),
then: { store in
SearchView(store: store)
SearchView(store: store, coordinatorStore: self.store)
.background(searchToDetailNavigationLink(viewStore: viewStore))
}
),
Expand Down
141 changes: 141 additions & 0 deletions HotSpot/Sources/Presentation/Search/Component/SearchFilterView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import SwiftUI
import ComposableArchitecture

struct SearchFilterView: View {
let store: StoreOf<SearchStore>
let coordinatorStore: StoreOf<AppCoordinator>

var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
WithViewStore(coordinatorStore, observe: { $0 }) { coordinatorViewStore in
NavigationView {
FilterForm(viewStore: viewStore, coordinatorViewStore: coordinatorViewStore)
}
}
}
}
}

private struct FilterForm: View {
let viewStore: ViewStore<SearchStore.State, SearchStore.Action>
let coordinatorViewStore: ViewStore<AppCoordinator.State, AppCoordinator.Action>

var body: some View {
Form {
BudgetSection(viewStore: viewStore)
FeaturesSection(viewStore: viewStore)
CuisineSection(viewStore: viewStore)
DistanceSection(viewStore: viewStore)
}
.navigationTitle("Filter")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Reset") {
viewStore.send(.resetFilters)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Apply") {
viewStore.send(.search(viewStore.searchText))
viewStore.send(.toggleFilterSheet)
}
}
}
}
}

private struct BudgetSection: View {
let viewStore: ViewStore<SearchStore.State, SearchStore.Action>

var body: some View {
Section(header: Text("Budget")) {
Picker("Budget", selection: viewStore.binding(
get: \.selectedBudget,
send: SearchStore.Action.updateBudget
)) {
Text("Any").tag(0)
Text("¥1,000~").tag(1)
Text("¥3,000~").tag(2)
Text("¥5,000~").tag(3)
Text("¥10,000~").tag(4)
}
}
}
}

private struct FeaturesSection: View {
let viewStore: ViewStore<SearchStore.State, SearchStore.Action>

var body: some View {
Section(header: Text("Features")) {
Toggle("WiFi Available", isOn: viewStore.binding(
get: \.hasWiFi,
send: SearchStore.Action.toggleWiFi
))
Toggle("Private Room", isOn: viewStore.binding(
get: \.hasPrivateRoom,
send: SearchStore.Action.togglePrivateRoom
))
Toggle("Non-Smoking", isOn: viewStore.binding(
get: \.isNonSmoking,
send: SearchStore.Action.toggleNonSmoking
))
Toggle("Parking Available", isOn: viewStore.binding(
get: \.hasParking,
send: SearchStore.Action.toggleParking
))
}
}
}

private struct CuisineSection: View {
let viewStore: ViewStore<SearchStore.State, SearchStore.Action>

var body: some View {
Section(header: Text("Cuisine")) {
Picker("Cuisine", selection: viewStore.binding(
get: \.selectedCuisine,
send: SearchStore.Action.updateCuisine
)) {
Text("Any").tag(0)
Text("Japanese").tag(1)
Text("Italian").tag(2)
Text("French").tag(3)
Text("Chinese").tag(4)
}
}
}
}

private struct DistanceSection: View {
let viewStore: ViewStore<SearchStore.State, SearchStore.Action>

var body: some View {
Section(header: Text("Distance")) {
Picker("Distance", selection: viewStore.binding(
get: \.selectedDistance,
send: SearchStore.Action.updateDistance
)) {
Text("300m").tag(1)
Text("500m").tag(2)
Text("1km").tag(3)
Text("2km").tag(4)
Text("3km").tag(5)
}
}
}
}

#Preview {
SearchFilterView(
store: Store(
initialState: SearchStore.State(),
reducer: { SearchStore() }
),
coordinatorStore: Store(
initialState: AppCoordinator.State(),
reducer: { AppCoordinator() }
)
)
}
98 changes: 85 additions & 13 deletions HotSpot/Sources/Presentation/Search/SearchStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ struct SearchStore {
var currentLocation: CLLocationCoordinate2D?
var selectedShop: ShopModel? = nil
var paginationState: PaginationState = .init()
var isFilterSheetPresented: Bool = false

// Filter states
var selectedBudget: Int = 0
var hasWiFi: Bool = false
var hasPrivateRoom: Bool = false
var isNonSmoking: Bool = false
var hasParking: Bool = false
var selectedCuisine: Int = 0
var selectedDistance: Int = 3

static func == (lhs: State, rhs: State) -> Bool {
lhs.shops == rhs.shops &&
Expand All @@ -24,7 +34,15 @@ struct SearchStore {
lhs.selectedShop == rhs.selectedShop &&
lhs.paginationState.currentPage == rhs.paginationState.currentPage &&
lhs.paginationState.isLastPage == rhs.paginationState.isLastPage &&
lhs.paginationState.isLoading == rhs.paginationState.isLoading
lhs.paginationState.isLoading == rhs.paginationState.isLoading &&
lhs.isFilterSheetPresented == rhs.isFilterSheetPresented &&
lhs.selectedBudget == rhs.selectedBudget &&
lhs.hasWiFi == rhs.hasWiFi &&
lhs.hasPrivateRoom == rhs.hasPrivateRoom &&
lhs.isNonSmoking == rhs.isNonSmoking &&
lhs.hasParking == rhs.hasParking &&
lhs.selectedCuisine == rhs.selectedCuisine &&
lhs.selectedDistance == rhs.selectedDistance
}
}

Expand All @@ -38,6 +56,17 @@ struct SearchStore {
case handleError(Error)
case loadMore
case updatePaginationState(PaginationState)

// Filter actions
case toggleFilterSheet
case updateBudget(Int)
case toggleWiFi
case togglePrivateRoom
case toggleNonSmoking
case toggleParking
case updateCuisine(Int)
case updateDistance(Int)
case resetFilters
}

var body: some ReducerOf<Self> {
Expand Down Expand Up @@ -82,16 +111,16 @@ struct SearchStore {
let request = ShopSearchRequestDTO(
lat: location.latitude,
lng: location.longitude,
range: 5,
range: state.selectedDistance,
count: nil,
keyword: text,
genre: nil,
genre: state.selectedCuisine > 0 ? String(state.selectedCuisine) : nil,
order: nil,
start: nil,
budget: nil,
privateRoom: nil,
wifi: nil,
nonSmoking: nil,
budget: state.selectedBudget > 0 ? String(state.selectedBudget) : nil,
privateRoom: state.hasPrivateRoom ? true : nil,
wifi: state.hasWiFi ? true : nil,
nonSmoking: state.isNonSmoking ? true : nil,
coupon: nil,
openNow: nil
)
Expand Down Expand Up @@ -130,16 +159,16 @@ struct SearchStore {
let request = ShopSearchRequestDTO(
lat: location.latitude,
lng: location.longitude,
range: 5,
range: state.selectedDistance,
count: nil,
keyword: state.searchText,
genre: nil,
genre: state.selectedCuisine > 0 ? String(state.selectedCuisine) : nil,
order: nil,
start: nil,
budget: nil,
privateRoom: nil,
wifi: nil,
nonSmoking: nil,
budget: state.selectedBudget > 0 ? String(state.selectedBudget) : nil,
privateRoom: state.hasPrivateRoom ? true : nil,
wifi: state.hasWiFi ? true : nil,
nonSmoking: state.isNonSmoking ? true : nil,
coupon: nil,
openNow: nil
)
Expand Down Expand Up @@ -178,6 +207,49 @@ struct SearchStore {
state.paginationState = newState
print("PaginationState updated - currentPage: \(newState.currentPage), isLastPage: \(newState.isLastPage), isLoading: \(newState.isLoading)")
return .none

// Filter actions
case .toggleFilterSheet:
state.isFilterSheetPresented.toggle()
return .none

case let .updateBudget(budget):
state.selectedBudget = budget
return .none

case .toggleWiFi:
state.hasWiFi.toggle()
return .none

case .togglePrivateRoom:
state.hasPrivateRoom.toggle()
return .none

case .toggleNonSmoking:
state.isNonSmoking.toggle()
return .none

case .toggleParking:
state.hasParking.toggle()
return .none

case let .updateCuisine(cuisine):
state.selectedCuisine = cuisine
return .none

case let .updateDistance(distance):
state.selectedDistance = distance
return .none

case .resetFilters:
state.selectedBudget = 0
state.hasWiFi = false
state.hasPrivateRoom = false
state.isNonSmoking = false
state.hasParking = false
state.selectedCuisine = 0
state.selectedDistance = 3
return .none
}
}
}
Expand Down
Loading
Loading