diff --git a/MovieBooking/Domain/Entity/Movie.swift b/MovieBooking/Domain/Entity/Movie.swift index 2045266..c21d273 100644 --- a/MovieBooking/Domain/Entity/Movie.swift +++ b/MovieBooking/Domain/Entity/Movie.swift @@ -7,7 +7,7 @@ import Foundation -public struct Movie: Identifiable, Equatable { +public struct Movie: Identifiable, Equatable, Hashable { public let id: Int public let title: String public let overview: String diff --git a/MovieBooking/Feature/Search/MovieSearchFeature.swift b/MovieBooking/Feature/Search/MovieSearchFeature.swift new file mode 100644 index 0000000..d86d541 --- /dev/null +++ b/MovieBooking/Feature/Search/MovieSearchFeature.swift @@ -0,0 +1,60 @@ +// +// MovieSearchFeature.swift +// MovieBooking +// +// Created by 김민희 on 10/16/25. +// + +import ComposableArchitecture +import Foundation + +@Reducer +struct MovieSearchFeature { + @ObservableState + struct State: Equatable { + var nowPlayingMovies: [Movie] = [] + var upcomingMovies: [Movie] = [] + var popularMovies: [Movie] = [] + var searchText: String = "" + } + + enum Action: BindableAction { + case updateMovieLists(nowPlaying: [Movie], upcoming: [Movie], popular: [Movie]) + case binding(BindingAction) + } + + var body: some Reducer { + BindingReducer() + Reduce { state, action in + switch action { + case let .updateMovieLists(nowPlaying, upcoming, popular): + state.nowPlayingMovies = nowPlaying + state.upcomingMovies = upcoming + state.popularMovies = popular + return .none + + case .binding: + return .none + } + } + } +} + + +extension MovieSearchFeature.State { + var trimmedKeyword: String { + searchText.trimmingCharacters(in: .whitespacesAndNewlines) + } + + var aggregatedMovies: [Movie] { + Array(Set(nowPlayingMovies + upcomingMovies + popularMovies)) + } + + var filteredMovies: [Movie] { + guard !trimmedKeyword.isEmpty else { return [] } + + return aggregatedMovies.filter { + $0.title.localizedCaseInsensitiveContains(trimmedKeyword) + } + } +} diff --git a/MovieBooking/Feature/Search/MovieSearchView.swift b/MovieBooking/Feature/Search/MovieSearchView.swift index 2531548..a04960e 100644 --- a/MovieBooking/Feature/Search/MovieSearchView.swift +++ b/MovieBooking/Feature/Search/MovieSearchView.swift @@ -6,20 +6,42 @@ // import SwiftUI +import ComposableArchitecture struct MovieSearchView: View { - @State private var searchText = "" + @Perception.Bindable var store: StoreOf var body: some View { - VStack(spacing: 20) { - SearchBar(text: $searchText) + WithPerceptionTracking { + VStack(spacing: 0) { + SearchBar(text: $store.searchText) + .padding(.bottom, 20) - SearchView(movies: Movie.mockData) + Group { + if store.trimmedKeyword.isEmpty { + EmptySearchView() + } else { + SearchView(movies: store.filteredMovies) + } + } + .frame(maxHeight: .infinity) + } + .padding(.top, 20) + .padding(.horizontal, 20) } - .padding(.horizontal, 20) } } #Preview { - MovieSearchView() + MovieSearchView( + store: Store( + initialState: MovieSearchFeature.State( + nowPlayingMovies: Movie.mockData, + upcomingMovies: Movie.mockData, + popularMovies: Movie.mockData + ) + ) { + MovieSearchFeature() + } + ) }