From ceca9b26ffaaf15daf9069f3fba01f6237d2b380 Mon Sep 17 00:00:00 2001 From: minhokim Date: Sun, 29 Sep 2024 18:08:40 +0900 Subject: [PATCH] =?UTF-8?q?[refactor]=20Setting=20->=20Search=20=EC=95=A1?= =?UTF-8?q?=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Search/PokitSearchFeature.swift | 483 ++++++++++-------- .../Sources/Search/PokitSearchView.swift | 44 +- .../Search/Sheet/FilterBottomFeature.swift | 63 ++- .../Search/Sheet/FilterBottomSheet.swift | 20 +- 4 files changed, 327 insertions(+), 283 deletions(-) diff --git a/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchFeature.swift b/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchFeature.swift index c66315be..82e11b91 100644 --- a/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchFeature.swift +++ b/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchFeature.swift @@ -70,11 +70,8 @@ public struct PokitSearchFeature { set { domain.condition.endDate = newValue } } var startDateString: String? { - guard let startDate = domain.condition.startDate else { - return nil - } + guard let startDate = domain.condition.startDate else { return nil} let formatter = DateFormat.searchCondition.formatter - return formatter.string(from: startDate) } var hasNext: Bool { @@ -98,64 +95,59 @@ public struct PokitSearchFeature { @CasePathable public enum View: Equatable, BindableAction { - /// - Binding case binding(BindingAction) - /// - Button Tapped - case autoSaveButtonTapped - case searchTextInputIconTapped - case searchTextChipButtonTapped(text: String) - case filterButtonTapped - case contentTypeFilterButtonTapped - case favoriteChipTapped - case unreadChipTapped - case dateFilterButtonTapped - case categoryFilterButtonTapped - case categoryFilterChipTapped(category: BaseCategoryItem) - case recentSearchAllRemoveButtonTapped - case recentSearchChipIconTapped(searchText: String) - case linkCardTapped(content: BaseContentItem) - case kebabButtonTapped(content: BaseContentItem) - case bottomSheetButtonTapped( + case dismiss + case bottomSheet( delegate: PokitBottomSheet.Delegate, content: BaseContentItem ) - case deleteAlertConfirmTapped - case sortTextLinkTapped - case backButtonTapped - /// - TextInput OnSubmitted - case searchTextInputOnSubmitted - - case 링크_공유_완료 - - case onAppear - case 로딩_isPresented + case 자동저장_버튼_눌렀을때 + case 검색_버튼_눌렀을때 + case 최근검색_태그_눌렀을때(text: String) + case 최근검색_태그_삭제_눌렀을때(searchText: String) + case 필터링_버튼_눌렀을때 + case 카테고리_버튼_눌렀을때 + case 모아보기_버튼_눌렀을때 + case 기간_버튼_눌렀을때 + case 카테고리_태그_눌렀을때(category: BaseCategoryItem) + case 즐겨찾기_태그_눌렀을때 + case 안읽음_태그_눌렀을때 + case 전체_삭제_버튼_눌렀을때 + case 컨텐츠_항목_눌렀을때(content: BaseContentItem) + case 컨텐츠_항목_케밥_버튼_눌렀을때(content: BaseContentItem) + case 정렬_버튼_눌렀을때 + case 검색_키보드_엔터_눌렀을때 + case 링크_삭제_눌렀을때 + case 링크_공유_완료되었을때 + case 뷰가_나타났을때 + case 로딩중일때 } public enum InnerAction: Equatable { - case enableIsSearching - case disableIsSearching - case updateDateFilter(startDate: Date?, endDate: Date?) - case showFilterBottomSheet(filterType: FilterBottomFeature.FilterType) - case updateContentTypeFilter(favoriteFilter: Bool, unreadFilter: Bool) - case dismissBottomSheet - case updateIsFiltered - case updateCategoryIds - case 컨텐츠_목록_갱신(BaseContentListInquiry) + case filterBottomSheet(filterType: FilterBottomFeature.FilterType) + case 검색창_활성화(Bool) + case 기간_업데이트(startDate: Date?, endDate: Date?) + case 모아보기_업데이트(favoriteFilter: Bool, unreadFilter: Bool) + case 필터_업데이트 + case 카테고리_ID_목록_업데이트 + case 바텀시트_해제 + case 컨텐츠_검색_API_반영(BaseContentListInquiry) + case 컨텐츠_검색_페이징_API_반영(BaseContentListInquiry) + case 컨텐츠_삭제_API_반영(id: Int) case 최근검색어_불러오기 - case 자동저장_켜기_불러오기 + case 자동저장_불러오기 case 최근검색어_추가 - case 컨텐츠_삭제_반영(id: Int) - case 컨텐츠_검색_결과_페이징_갱신(BaseContentListInquiry) case 페이징_초기화 } public enum AsyncAction: Equatable { - case 컨텐츠_검색 - case 최근검색어_갱신 - case 자동저장_켜기_갱신 - case 컨텐츠_삭제(id: Int) - case 컨텐츠_검색_결과_페이징_조회 + case 컨텐츠_검색_API + case 최근검색어_갱신_수행 + case 자동저장_수행 + case 컨텐츠_삭제_API(id: Int) + case 컨텐츠_검색_페이징_API + case 클립보드_감지 } public enum ScopeAction: Equatable { @@ -183,24 +175,22 @@ public struct PokitSearchFeature { /// - View case .view(let viewAction): return handleViewAction(viewAction, state: &state) - /// - Inner case .inner(let innerAction): return handleInnerAction(innerAction, state: &state) - /// - Async case .async(let asyncAction): return handleAsyncAction(asyncAction, state: &state) - /// - Scope case .scope(let scopeAction): return handleScopeAction(scopeAction, state: &state) - /// - Delegate case .delegate(let delegateAction): return handleDelegateAction(delegateAction, state: &state) + case .fiterBottomSheet(.presented(.delegate(let delegate))): return .send(.scope(.filterBottomSheet(delegate))) + case .fiterBottomSheet: return .none } @@ -223,152 +213,169 @@ private extension PokitSearchFeature { switch action { case .binding(\.searchText): guard !state.searchText.isEmpty else { - /// 🚨 Error Case [1]: 빈 문자열 일 때 - return .send(.inner(.disableIsSearching)) + /// 🚨 Error Case: 빈 문자열 일 때 + return .send(.inner(.검색창_활성화(false))) } return .none + case .binding: return .none - case .autoSaveButtonTapped: + + case .자동저장_버튼_눌렀을때: state.isAutoSaveSearch.toggle() - return .send(.async(.자동저장_켜기_갱신)) - case .searchTextInputOnSubmitted: + return .send(.async(.자동저장_수행)) + + case .검색_키보드_엔터_눌렀을때: return .run { send in await send(.inner(.최근검색어_추가)) await send(.inner(.페이징_초기화), animation: .pokitDissolve) } - case .searchTextInputIconTapped: + + case .검색_버튼_눌렀을때: /// - 검색 중일 경우 `문자열 지우기 버튼 동작` if state.isSearching { state.domain.condition.searchWord = "" - return .send(.inner(.disableIsSearching)) + return .send(.inner(.검색창_활성화(false))) } else { return .run { send in await send(.inner(.최근검색어_추가)) await send(.inner(.페이징_초기화), animation: .pokitDissolve) } } - case .searchTextChipButtonTapped(text: let text): + + case let .최근검색_태그_눌렀을때(text): state.searchText = text return .send(.inner(.페이징_초기화), animation: .pokitDissolve) - case .filterButtonTapped: - return .send(.inner(.showFilterBottomSheet(filterType: .pokit))) - case .contentTypeFilterButtonTapped: - return .send(.inner(.showFilterBottomSheet(filterType: .contentType))) - case .dateFilterButtonTapped: + + case .필터링_버튼_눌렀을때: + return .send(.inner(.filterBottomSheet(filterType: .pokit))) + + case .모아보기_버튼_눌렀을때: + return .send(.inner(.filterBottomSheet(filterType: .contentType))) + + case .기간_버튼_눌렀을때: guard state.domain.condition.startDate != nil && state.domain.condition.endDate != nil else { /// - 선택된 기간이 없을 경우 - return .send(.inner(.showFilterBottomSheet(filterType: .date))) + return .send(.inner(.filterBottomSheet(filterType: .date))) } state.domain.condition.startDate = nil state.domain.condition.endDate = nil return .run { send in - await send(.inner(.updateDateFilter(startDate: nil, endDate: nil))) + await send(.inner(.기간_업데이트(startDate: nil, endDate: nil))) await send(.inner(.페이징_초기화), animation: .pokitDissolve) } - case .categoryFilterButtonTapped: - return .send(.inner(.showFilterBottomSheet(filterType: .pokit))) - case .recentSearchAllRemoveButtonTapped: + + case .카테고리_버튼_눌렀을때: + return .send(.inner(.filterBottomSheet(filterType: .pokit))) + + case .전체_삭제_버튼_눌렀을때: state.recentSearchTexts.removeAll() - return .send(.async(.최근검색어_갱신)) - case .recentSearchChipIconTapped(searchText: let searchText): + return .send(.async(.최근검색어_갱신_수행)) + + case let .최근검색_태그_삭제_눌렀을때(searchText): guard let predicate = state.recentSearchTexts.firstIndex(of: searchText) else { return .none } state.recentSearchTexts.remove(at: predicate) - return .send(.async(.최근검색어_갱신)) - case .linkCardTapped(content: let content): + return .send(.async(.최근검색어_갱신_수행)) + + case let .컨텐츠_항목_눌렀을때(content): return .send(.delegate(.linkCardTapped(content: content))) - case .kebabButtonTapped(content: let content): + + case let .컨텐츠_항목_케밥_버튼_눌렀을때(content): state.bottomSheetItem = content return .none - case .bottomSheetButtonTapped(delegate: let delegate, content: let content): + + case .bottomSheet(delegate: let delegate, content: let content): return .run { send in - await send(.inner(.dismissBottomSheet)) + await send(.inner(.바텀시트_해제)) await send(.scope(.bottomSheet(delegate: delegate, content: content))) } - case .deleteAlertConfirmTapped: + + case .링크_삭제_눌렀을때: guard let id = state.alertItem?.id else { return .none } state.alertItem = nil - return .run { [id] send in - await send(.async(.컨텐츠_삭제(id: id))) - } - case .sortTextLinkTapped: + return .send(.async(.컨텐츠_삭제_API(id: id))) + + case .정렬_버튼_눌렀을때: state.isResultAscending.toggle() state.domain.pageable.sort = [ state.isResultAscending ? "createdAt,asc" : "createdAt,desc" ] return .send(.inner(.페이징_초기화)) - case .backButtonTapped: - return .run { _ in - await dismiss() - } - case .onAppear: - return .run { [ - contentList = state.domain.contentList.data - ] send in - async let _ = send(.inner(.자동저장_켜기_불러오기)) - async let _ = send(.inner(.최근검색어_불러오기)) - if let contentList, !contentList.isEmpty { - async let _ = send(.async(.컨텐츠_검색)) - } - for await _ in self.pasteboard.changes() { - let url = try await pasteboard.probableWebURL() - await send(.delegate(.linkCopyDetected(url)), animation: .pokitSpring) - } + case .dismiss: + return .run { _ in await dismiss() } + + case .뷰가_나타났을때: + let contentList = state.domain.contentList.data + + var effectBox: [Effect] = [ + .send(.inner(.자동저장_불러오기)), + .send(.inner(.최근검색어_불러오기)), + .send(.async(.클립보드_감지)) + ] + + if let contentList, !contentList.isEmpty { + effectBox.append(.send(.async(.컨텐츠_검색_API))) } - case .categoryFilterChipTapped(category: let category): + + return .merge(effectBox) + + case let .카테고리_태그_눌렀을때(category): state.categoryFilter.remove(category) return .run { send in - await send(.inner(.updateCategoryIds)) + await send(.inner(.카테고리_ID_목록_업데이트)) await send(.inner(.페이징_초기화)) } - case .favoriteChipTapped: + + case .즐겨찾기_태그_눌렀을때: state.domain.condition.favorites = false return .send(.inner(.페이징_초기화)) - case .unreadChipTapped: + + case .안읽음_태그_눌렀을때: state.domain.condition.isRead = false return .send(.inner(.페이징_초기화)) - case .링크_공유_완료: + + case .링크_공유_완료되었을때: state.shareSheetItem = nil return .none - case .로딩_isPresented: - return .send(.async(.컨텐츠_검색_결과_페이징_조회)) + + case .로딩중일때: + return .send(.async(.컨텐츠_검색_페이징_API)) } } /// - Inner Effect func handleInnerAction(_ action: Action.InnerAction, state: inout State) -> Effect { switch action { - case .enableIsSearching: - state.isSearching = true - return .none - case .disableIsSearching: - state.isSearching = false - state.domain.contentList.data = [] + case let .검색창_활성화(isActive): + if isActive { + state.isSearching = true + } else { + state.isSearching = false + state.domain.contentList.data = [] + } return .none - case .updateDateFilter(startDate: let startDate, endDate: let endDate): + + case let .기간_업데이트(startDate, endDate): let formatter = DateFormat.dateFilter.formatter state.domain.condition.startDate = startDate state.domain.condition.endDate = endDate - guard let startDate, let endDate else { - /// - 날짜 필터가 선택 안되었을 경우 + guard let startDate, + let endDate else { + /// 🚨 Error Case : 날짜 필터가 선택 안되었을 경우 state.dateFilterText = "기간" return .none } - - if startDate == endDate { - /// - 날짜 필터를 하루만 선택했을 경우 - state.dateFilterText = "\(formatter.string(from: startDate))" - } else { - state.dateFilterText = "\(formatter.string(from: startDate))~\(formatter.string(from: endDate))" - } - + state.dateFilterText = startDate == endDate + ? "\(formatter.string(from: startDate))" /// - 날짜 필터를 하루만 선택했을 경우 + : "\(formatter.string(from: startDate))~\(formatter.string(from: endDate))" return .none - case .showFilterBottomSheet(filterType: let filterType): + + case let .filterBottomSheet(filterType): state.filterBottomSheet = .init( filterType: filterType, pokitFilter: state.categoryFilter, @@ -378,113 +385,80 @@ private extension PokitSearchFeature { endDateFilter: state.endDateFilter ) return .none - case .updateContentTypeFilter(favoriteFilter: let favoriteFilter, unreadFilter: let unreadFilter): + + case let .모아보기_업데이트(favoriteFilter, unreadFilter): state.domain.condition.favorites = favoriteFilter state.domain.condition.isRead = unreadFilter return .none - case .dismissBottomSheet: + + case .바텀시트_해제: state.bottomSheetItem = nil return .none - case .updateIsFiltered: - state.isFiltered = !state.categoryFilter.isEmpty || - state.favoriteFilter || - state.unreadFilter || - state.startDateFilter != nil || - state.endDateFilter != nil + + case .필터_업데이트: + state.isFiltered = !state.categoryFilter.isEmpty + || state.favoriteFilter + || state.unreadFilter + || state.startDateFilter != nil + || state.endDateFilter != nil return .none - case .updateCategoryIds: + + case .카테고리_ID_목록_업데이트: state.domain.condition.categoryIds = state.categoryFilter.map { $0.id } return .none - case .컨텐츠_목록_갱신(let contentList): + + case .컨텐츠_검색_API_반영(let contentList): state.domain.contentList = contentList - return .send(.inner(.enableIsSearching)) + return .send(.inner(.검색창_활성화(true))) + case .최근검색어_불러오기: - guard state.isAutoSaveSearch else { - return .none - } + guard state.isAutoSaveSearch else { return .none } state.recentSearchTexts = userDefaults.stringArrayKey(.searchWords) ?? [] return .none - case .자동저장_켜기_불러오기: + + case .자동저장_불러오기: state.isAutoSaveSearch = userDefaults.boolKey(.autoSaveSearch) return .none case .최근검색어_추가: - guard state.isAutoSaveSearch else { return .none } - guard !state.domain.condition.searchWord.isEmpty else { return .none } - if !state.recentSearchTexts.contains(state.domain.condition.searchWord) { - state.recentSearchTexts.append(state.domain.condition.searchWord) + let searchWord = state.domain.condition.searchWord + guard state.isAutoSaveSearch && !searchWord.isEmpty else { + /// 🚨 Error Case: 검색어 자동저장이 `off`거나, 검색 키워드가 없다면 종료 + return .none + } + + if !state.recentSearchTexts.contains(searchWord) { + state.recentSearchTexts.append(searchWord) } - return .send(.async(.최근검색어_갱신)) - case .컨텐츠_삭제_반영(id: let id): + return .send(.async(.최근검색어_갱신_수행)) + + case let .컨텐츠_삭제_API_반영(id): state.alertItem = nil state.domain.contentList.data?.removeAll { $0.id == id } return .none - case let .컨텐츠_검색_결과_페이징_갱신(contentList): + + case let .컨텐츠_검색_페이징_API_반영(contentList): let list = state.domain.contentList.data ?? [] guard let newList = contentList.data else { return .none } state.domain.contentList = contentList state.domain.contentList.data = list + newList - return .send(.inner(.enableIsSearching)) + return .send(.inner(.검색창_활성화(true))) + case .페이징_초기화: state.domain.pageable.page = 0 state.domain.contentList.data = nil - return .send(.async(.컨텐츠_검색), animation: .pokitDissolve) + return .send(.async(.컨텐츠_검색_API), animation: .pokitDissolve) } } /// - Async Effect func handleAsyncAction(_ action: Action.AsyncAction, state: inout State) -> Effect { switch action { - case .컨텐츠_검색: - let formatter = DateFormat.yearMonthDate.formatter + case .컨텐츠_검색_API: + return contentSearch(state: &state) - var startDateString: String? = nil - var endDateString: String? = nil - if let startDate = state.domain.condition.startDate { - startDateString = formatter.string(from: startDate) - } - if let endDate = state.domain.condition.endDate { - endDateString = formatter.string(from: endDate) - } - return .run { [ - pageable = state.domain.pageable, - condition = BaseConditionRequest( - searchWord: state.domain.condition.searchWord, - categoryIds: state.domain.condition.categoryIds, - isRead: state.domain.condition.isRead, - favorites: state.domain.condition.favorites, - startDate: startDateString, - endDate: endDateString - ) - ] send in - let stream = AsyncThrowingStream { continuation in - Task { - for page in 0...pageable.page { - let contentList = try await contentClient.컨텐츠_검색( - BasePageableRequest( - page: page, - size: pageable.size, - sort: pageable.sort - ), - condition - ).toDomain() - continuation.yield(contentList) - } - continuation.finish() - } - } - var contentItems: BaseContentListInquiry? = nil - for try await contentList in stream { - let items = contentItems?.data ?? [] - let newItems = contentList.data ?? [] - contentItems = contentList - contentItems?.data = items + newItems - } - guard let contentItems else { return } - await send(.inner(.컨텐츠_목록_갱신(contentItems)), animation: .pokitSpring) - } - case .최근검색어_갱신: + case .최근검색어_갱신_수행: guard state.isAutoSaveSearch else { return .none } return .run { [ searchWords = state.recentSearchTexts ] _ in await userDefaults.setStringArray( @@ -492,18 +466,19 @@ private extension PokitSearchFeature { .searchWords ) } - case .자동저장_켜기_갱신: - return .run { [ - isAutoSaveSearch = state.isAutoSaveSearch - ] send in + + case .자동저장_수행: + return .run { [isAutoSaveSearch = state.isAutoSaveSearch] _ in await userDefaults.setBool(isAutoSaveSearch, .autoSaveSearch) } - case .컨텐츠_삭제(id: let id): - return .run { [id] send in + + case let .컨텐츠_삭제_API(id): + return .run { send in let _ = try await contentClient.컨텐츠_삭제("\(id)") - await send(.inner(.컨텐츠_삭제_반영(id: id)), animation: .pokitSpring) + await send(.inner(.컨텐츠_삭제_API_반영(id: id)), animation: .pokitSpring) } - case .컨텐츠_검색_결과_페이징_조회: + + case .컨텐츠_검색_페이징_API: state.domain.pageable.page += 1 let formatter = DateFormat.yearMonthDate.formatter @@ -521,30 +496,42 @@ private extension PokitSearchFeature { startDateString, endDateString ] send in + let pageableRequest = BasePageableRequest( + page: pageable.page, + size: pageable.size, + sort: pageable.sort + ) + + let conditionRequest = BaseConditionRequest( + searchWord: condition.searchWord, + categoryIds: condition.categoryIds, + isRead: condition.isRead, + favorites: condition.favorites, + startDate: startDateString, + endDate: endDateString + ) + let contentList = try await contentClient.컨텐츠_검색( - BasePageableRequest( - page: pageable.page, - size: pageable.size, - sort: pageable.sort - ), - BaseConditionRequest( - searchWord: condition.searchWord, - categoryIds: condition.categoryIds, - isRead: condition.isRead, - favorites: condition.favorites, - startDate: startDateString, - endDate: endDateString - ) + pageableRequest, + conditionRequest ).toDomain() - await send(.inner(.컨텐츠_검색_결과_페이징_갱신(contentList))) + } + + case .클립보드_감지: + return .run { send in + for await _ in self.pasteboard.changes() { + let url = try await pasteboard.probableWebURL() + await send(.delegate(.linkCopyDetected(url)), animation: .pokitSpring) + } } } } + /// - Scope Effect func handleScopeAction(_ action: Action.ScopeAction, state: inout State) -> Effect { switch action { - case .filterBottomSheet(.searchButtonTapped( + case .filterBottomSheet(.검색_버튼_눌렀을때( categories: let categories, isFavorite: let isFavorite, isUnread: let isUnread, @@ -552,12 +539,13 @@ private extension PokitSearchFeature { endDate: let endDate)): state.categoryFilter = categories return .run { send in - await send(.inner(.updateCategoryIds)) - await send(.inner(.updateContentTypeFilter(favoriteFilter: isFavorite, unreadFilter: isUnread))) - await send(.inner(.updateDateFilter(startDate: startDate, endDate: endDate))) - await send(.inner(.updateIsFiltered)) + await send(.inner(.카테고리_ID_목록_업데이트)) + await send(.inner(.모아보기_업데이트(favoriteFilter: isFavorite, unreadFilter: isUnread))) + await send(.inner(.기간_업데이트(startDate: startDate, endDate: endDate))) + await send(.inner(.필터_업데이트)) await send(.inner(.페이징_초기화)) } + case .bottomSheet(let delegate, let content): switch delegate { case .deleteCellButtonTapped: @@ -581,8 +569,55 @@ private extension PokitSearchFeature { guard let contentList = state.domain.contentList.data, !contentList.isEmpty else { return .none } - return .send(.async(.컨텐츠_검색), animation: .pokitSpring) + return .send(.async(.컨텐츠_검색_API), animation: .pokitSpring) default: return .none } } + + func contentSearch(state: inout State) -> Effect { + let formatter = DateFormat.yearMonthDate.formatter + + var startDateString: String? = nil + var endDateString: String? = nil + if let startDate = state.domain.condition.startDate { + startDateString = formatter.string(from: startDate) + } + if let endDate = state.domain.condition.endDate { + endDateString = formatter.string(from: endDate) + } + let condition = BaseConditionRequest( + searchWord: state.domain.condition.searchWord, + categoryIds: state.domain.condition.categoryIds, + isRead: state.domain.condition.isRead, + favorites: state.domain.condition.favorites, + startDate: startDateString, + endDate: endDateString + ) + + return .run { [pageable = state.domain.pageable, condition] send in + let stream = AsyncThrowingStream { continuation in + Task { + for page in 0...pageable.page { + let request = BasePageableRequest( + page: page, + size: pageable.size, + sort: pageable.sort + ) + let contentList = try await contentClient.컨텐츠_검색(request, condition).toDomain() + continuation.yield(contentList) + } + continuation.finish() + } + } + var contentItems: BaseContentListInquiry? = nil + for try await contentList in stream { + let items = contentItems?.data ?? [] + let newItems = contentList.data ?? [] + contentItems = contentList + contentItems?.data = items + newItems + } + guard let contentItems else { return } + await send(.inner(.컨텐츠_검색_API_반영(contentItems)), animation: .pokitSpring) + } + } } diff --git a/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchView.swift b/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchView.swift index 8a1cfc22..d92902e0 100644 --- a/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchView.swift +++ b/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchView.swift @@ -54,13 +54,13 @@ public extension PokitSearchView { PokitBottomSheet( items: [.share, .edit, .delete], height: 224 - ) { send(.bottomSheetButtonTapped(delegate: $0, content: content)) } + ) { send(.bottomSheet(delegate: $0, content: content)) } } .sheet(item: $store.shareSheetItem) { content in if let shareURL = URL(string: content.data) { PokitShareSheet( items: [shareURL], - completion: { send(.링크_공유_완료) } + completion: { send(.링크_공유_완료되었을때) } ) .presentationDetents([.medium, .large]) } @@ -70,9 +70,9 @@ public extension PokitSearchView { "링크를 정말 삭제하시겠습니까?", message: "함께 저장한 모든 정보가 삭제되며, \n복구하실 수 없습니다.", confirmText: "삭제" - ) { send(.deleteAlertConfirmTapped) } + ) { send(.링크_삭제_눌렀을때) } } - .task { await send(.onAppear).finish() } + .task { await send(.뷰가_나타났을때).finish() } } } } @@ -82,7 +82,7 @@ private extension PokitSearchView { HStack(spacing: 8) { PokitToolbarButton( .icon(.arrowLeft), - action: { send(.backButtonTapped) } + action: { send(.dismiss) } ) PokitIconRInput( @@ -91,8 +91,8 @@ private extension PokitSearchView { shape: .round, focusState: $focused, equals: true, - onSubmit: { send(.searchTextInputOnSubmitted) }, - iconTappedAction: store.isSearching ? { send(.searchTextInputIconTapped) } : nil + onSubmit: { send(.검색_키보드_엔터_눌렀을때) }, + iconTappedAction: store.isSearching ? { send(.검색_버튼_눌렀을때) } : nil ) } .padding(.vertical, 8) @@ -110,7 +110,7 @@ private extension PokitSearchView { PokitTextLink( "전체 삭제", color: .text(.tertiary), - action: { send(.recentSearchAllRemoveButtonTapped) } + action: { send(.전체_삭제_버튼_눌렀을때) } ) Text("|") @@ -120,7 +120,7 @@ private extension PokitSearchView { PokitTextLink( "자동저장 \(store.isAutoSaveSearch ? "끄기" : "켜기")", color: .text(.tertiary), - action: { send(.autoSaveButtonTapped, animation: .pokitSpring) } + action: { send(.자동저장_버튼_눌렀을때, animation: .pokitSpring) } ) .contentTransition(.numericText()) } @@ -162,10 +162,10 @@ private extension PokitSearchView { state: .default(.primary), size: .small, action: { - send(.searchTextChipButtonTapped(text: text), animation: .pokitSpring) + send(.최근검색_태그_눌렀을때(text: text), animation: .pokitSpring) }, iconTappedAction: { - send(.recentSearchChipIconTapped(searchText: text), animation: .pokitSpring) + send(.최근검색_태그_삭제_눌렀을때(searchText: text), animation: .pokitSpring) } ) .pokitScrollTransition(.opacity) @@ -207,7 +207,7 @@ private extension PokitSearchView { state: store.isFiltered ? .filled(.primary) : .stroke(.secondary), size: .small, shape: .round, - action: { send(.filterButtonTapped) } + action: { send(.필터링_버튼_눌렀을때) } ) } @@ -219,7 +219,7 @@ private extension PokitSearchView { icon: .icon(.arrowDown), state: .default(.primary), size: .small, - action: { send(.categoryFilterButtonTapped) } + action: { send(.카테고리_버튼_눌렀을때) } ) } else { ForEach(store.categoryFilter) { category in @@ -227,7 +227,7 @@ private extension PokitSearchView { category.categoryName, state: .stroke(.primary), size: .small, - action: { send(.categoryFilterChipTapped(category: category), animation: .pokitSpring) } + action: { send(.카테고리_태그_눌렀을때(category: category), animation: .pokitSpring) } ) .pokitBlurReplaceTransition(.pokitDissolve) } @@ -243,7 +243,7 @@ private extension PokitSearchView { icon: .icon(.arrowDown), state: .default(.primary), size: .small, - action: { send(.contentTypeFilterButtonTapped) } + action: { send(.모아보기_버튼_눌렀을때) } ) } else { if store.favoriteFilter { @@ -251,7 +251,7 @@ private extension PokitSearchView { "즐겨찾기", state: .stroke(.primary), size: .small, - action: { send(.favoriteChipTapped, animation: .pokitSpring) } + action: { send(.즐겨찾기_태그_눌렀을때, animation: .pokitSpring) } ) .pokitBlurReplaceTransition(.pokitDissolve) } @@ -261,7 +261,7 @@ private extension PokitSearchView { "안읽음", state: .stroke(.primary), size: .small, - action: { send(.unreadChipTapped, animation: .pokitSpring) } + action: { send(.안읽음_태그_눌렀을때, animation: .pokitSpring) } ) .pokitBlurReplaceTransition(.pokitDissolve) } @@ -275,7 +275,7 @@ private extension PokitSearchView { icon: store.dateFilterText == "기간" ? .icon(.arrowDown) : .icon(.x), state: store.dateFilterText == "기간" ? .default(.primary) : .stroke(.primary), size: .small, - action: { send(.dateFilterButtonTapped, animation: .pokitSpring) } + action: { send(.기간_버튼_눌렀을때, animation: .pokitSpring) } ) .pokitBlurReplaceTransition(.pokitDissolve) } @@ -285,7 +285,7 @@ private extension PokitSearchView { PokitIconLTextLink( store.isResultAscending ? "오래된순" : "최신순", icon: .icon(.align), - action: { send(.sortTextLinkTapped) } + action: { send(.정렬_버튼_눌렀을때) } ) .contentTransition(.numericText()) .padding(.horizontal, 20) @@ -299,15 +299,15 @@ private extension PokitSearchView { PokitLinkCard( link: content, - action: { send(.linkCardTapped(content: content)) }, - kebabAction: { send(.kebabButtonTapped(content: content)) } + action: { send(.컨텐츠_항목_눌렀을때(content: content)) }, + kebabAction: { send(.컨텐츠_항목_케밥_버튼_눌렀을때(content: content)) } ) .divider(isFirst: isFirst, isLast: isLast) } if store.hasNext { PokitLoading() - .task { await send(.로딩_isPresented, animation: .pokitDissolve).finish() } + .task { await send(.로딩중일때, animation: .pokitDissolve).finish() } } } .padding(.horizontal, 20) diff --git a/Projects/Feature/FeatureSetting/Sources/Search/Sheet/FilterBottomFeature.swift b/Projects/Feature/FeatureSetting/Sources/Search/Sheet/FilterBottomFeature.swift index 51da4855..19c4f647 100644 --- a/Projects/Feature/FeatureSetting/Sources/Search/Sheet/FilterBottomFeature.swift +++ b/Projects/Feature/FeatureSetting/Sources/Search/Sheet/FilterBottomFeature.swift @@ -73,31 +73,29 @@ public struct FilterBottomFeature { public enum View: Equatable, BindableAction { /// - Binding case binding(BindingAction) - /// - Button Tapped - case pokitListCellTapped(pokit: BaseCategoryItem) - case searchButtonTapped - case pokitChipTapped(BaseCategoryItem) - case favoriteChipTapped - case unreadChipTapped - case dateChipTapped - case favoriteButtonTapped - case unreadButtonTapped - - case pokitListOnAppeared + case 포킷_항목_눌렀을때(pokit: BaseCategoryItem) + case 검색하기_버튼_눌렀을때 + case 포킷_태그_눌렀을때(BaseCategoryItem) + case 즐겨찾기_태그_눌렀을때 + case 안읽음_태그_눌렀을때 + case 기간_태그_눌렀을때 + case 즐겨찾기_체크박스_눌렀을때 + case 안읽음_체크박스_눌렀을때 + case 뷰가_나타났을때 } public enum InnerAction: Equatable { - case 카테고리_목록_갱신(categoryList: BaseCategoryListInquiry) + case 카테고리_목록_조회_API_반영(categoryList: BaseCategoryListInquiry) } public enum AsyncAction: Equatable { - case 카테고리_목록_조회 + case 카테고리_목록_조회_API } public enum ScopeAction: Equatable { case doNothing } public enum DelegateAction: Equatable { - case searchButtonTapped( + case 검색_버튼_눌렀을때( categories: IdentifiedArrayOf, isFavorite: Bool, isUnread: Bool, @@ -149,15 +147,19 @@ private extension FilterBottomFeature { case .binding(\.startDate): state.dateSelected = true return .none + case .binding(\.endDate): state.dateSelected = true return .none + case .binding: return .none - case .pokitListCellTapped(let pokit): + + case .포킷_항목_눌렀을때(let pokit): state.selectedCategories.append(pokit) return .none - case .searchButtonTapped: + + case .검색하기_버튼_눌렀을때: return .run { [ categories = state.selectedCategories, isFavorite = state.isFavorite, @@ -175,35 +177,42 @@ private extension FilterBottomFeature { ))) await dismiss() } - case .pokitChipTapped(let category): + + case .포킷_태그_눌렀을때(let category): state.selectedCategories.remove(category) return .none - case .favoriteChipTapped: + + case .즐겨찾기_태그_눌렀을때: state.isFavorite = false return .none - case .unreadChipTapped: + + case .안읽음_태그_눌렀을때: state.isUnread = false return .none - case .dateChipTapped: + + case .기간_태그_눌렀을때: state.startDate = .now state.endDate = .now state.dateSelected = false return .none - case .favoriteButtonTapped: + + case .즐겨찾기_체크박스_눌렀을때: state.isFavorite.toggle() return .none - case .unreadButtonTapped: + + case .안읽음_체크박스_눌렀을때: state.isUnread.toggle() return .none - case .pokitListOnAppeared: - return .send(.async(.카테고리_목록_조회)) + + case .뷰가_나타났을때: + return .send(.async(.카테고리_목록_조회_API)) } } /// - Inner Effect func handleInnerAction(_ action: Action.InnerAction, state: inout State) -> Effect { switch action { - case .카테고리_목록_갱신(categoryList: let categoryList): + case let .카테고리_목록_조회_API_반영(categoryList): state.domain.categoryList = categoryList return .none } @@ -212,7 +221,7 @@ private extension FilterBottomFeature { /// - Async Effect func handleAsyncAction(_ action: Action.AsyncAction, state: inout State) -> Effect { switch action { - case .카테고리_목록_조회: + case .카테고리_목록_조회_API: return .run { [pageable = state.domain.pageable] send in let categoryList = try await categoryClient.카테고리_목록_조회( BasePageableRequest( @@ -222,7 +231,7 @@ private extension FilterBottomFeature { ), true ).toDomain() - await send(.inner(.카테고리_목록_갱신(categoryList: categoryList)), animation: .pokitDissolve) + await send(.inner(.카테고리_목록_조회_API_반영(categoryList: categoryList)), animation: .pokitDissolve) } } } diff --git a/Projects/Feature/FeatureSetting/Sources/Search/Sheet/FilterBottomSheet.swift b/Projects/Feature/FeatureSetting/Sources/Search/Sheet/FilterBottomSheet.swift index cc263553..724d1d08 100644 --- a/Projects/Feature/FeatureSetting/Sources/Search/Sheet/FilterBottomSheet.swift +++ b/Projects/Feature/FeatureSetting/Sources/Search/Sheet/FilterBottomSheet.swift @@ -35,7 +35,7 @@ public extension FilterBottomSheet { switch store.currentType { case .pokit: pokitList - .onAppear { send(.pokitListOnAppeared) } + .onAppear { send(.뷰가_나타났을때) } case .contentType: contentTypes case .date: @@ -53,7 +53,7 @@ public extension FilterBottomSheet { PokitBottomButton( "검색하기", state: .filled(.primary), - action: { send(.searchButtonTapped, animation: .pokitSpring) } + action: { send(.검색하기_버튼_눌렀을때, animation: .pokitSpring) } ) .padding(.horizontal, 20) } @@ -100,7 +100,7 @@ private extension FilterBottomSheet { PokitList( selectedItem: nil, list: pokitList, - action: { send(.pokitListCellTapped(pokit: $0), animation: .pokitSpring) } + action: { send(.포킷_항목_눌렀을때(pokit: $0), animation: .pokitSpring) } ) } else { PokitLoading() @@ -113,13 +113,13 @@ private extension FilterBottomSheet { contentTypeButton( "즐겨찾기", isSelected: $store.isFavorite, - action: { send(.favoriteButtonTapped, animation: .pokitSpring) } + action: { send(.즐겨찾기_체크박스_눌렀을때, animation: .pokitSpring) } ) contentTypeButton( "안읽음", isSelected: $store.isUnread, - action: { send(.unreadButtonTapped, animation: .pokitSpring) } + action: { send(.안읽음_체크박스_눌렀을때, animation: .pokitSpring) } ) Spacer() @@ -160,7 +160,7 @@ private extension FilterBottomSheet { category.categoryName, state: .stroke(.primary), size: .small, - action: { send(.pokitChipTapped(category), animation: .pokitSpring) } + action: { send(.포킷_태그_눌렀을때(category), animation: .pokitSpring) } ) .pokitBlurReplaceTransition(.pokitDissolve) } @@ -170,7 +170,7 @@ private extension FilterBottomSheet { "즐겨찾기", state: .stroke(.primary), size: .small, - action: { send(.favoriteChipTapped, animation: .pokitSpring) } + action: { send(.즐겨찾기_태그_눌렀을때, animation: .pokitSpring) } ) .pokitBlurReplaceTransition(.pokitDissolve) } @@ -180,7 +180,7 @@ private extension FilterBottomSheet { "안읽음", state: .stroke(.primary), size: .small, - action: { send(.unreadChipTapped, animation: .pokitSpring) } + action: { send(.안읽음_태그_눌렀을때, animation: .pokitSpring) } ) .pokitBlurReplaceTransition(.pokitDissolve) } @@ -191,7 +191,7 @@ private extension FilterBottomSheet { sameDate ? "\(store.startDateText)" : "\(store.startDateText)~\(store.endDateText)", state: .stroke(.primary), size: .small, - action: { send(.dateChipTapped, animation: .pokitSpring) } + action: { send(.기간_태그_눌렀을때, animation: .pokitSpring) } ) .pokitBlurReplaceTransition(.pokitDissolve) .contentTransition(.numericText()) @@ -222,7 +222,7 @@ private extension FilterBottomSheet { startDateFilter: nil, endDateFilter: nil ), - reducer: { FilterBottomFeature() } + reducer: { FilterBottomFeature()._printChanges() } ) ) }