diff --git a/Projects/App/Sources/MainTab/MainTabFeature.swift b/Projects/App/Sources/MainTab/MainTabFeature.swift index a297d2fb..4cf90359 100644 --- a/Projects/App/Sources/MainTab/MainTabFeature.swift +++ b/Projects/App/Sources/MainTab/MainTabFeature.swift @@ -141,12 +141,7 @@ public struct MainTabFeature { public var body: some ReducerOf { Scope(state: \.pokit, action: \.pokit) { PokitRootFeature() } Scope(state: \.recommend, action: \.recommend) { - withDependencies { - $0[UserClient.self] = .testValue - $0[ContentClient.self] = .testValue - } operation: { - RecommendFeature() - } + RecommendFeature() } BindingReducer() @@ -301,7 +296,7 @@ private extension MainTabFeature { guard let category = state.categoryOfSavedContent else { return .none } state.categoryOfSavedContent = nil return .send(.inner(.카테고리상세_이동(category: category))) - case .error, .text, .warning, .none: + case .error, .text, .warning, .report, .none: return .none } } diff --git a/Projects/App/Sources/MainTab/MainTabPath.swift b/Projects/App/Sources/MainTab/MainTabPath.swift index 31547516..67fa8fdb 100644 --- a/Projects/App/Sources/MainTab/MainTabPath.swift +++ b/Projects/App/Sources/MainTab/MainTabPath.swift @@ -86,6 +86,9 @@ public extension MainTabFeature { category: category ))) return .none + + case .recommend(.delegate(.컨텐츠_신고_API_반영)): + return .send(.inner(.링크팝업_활성화(.report(title: "신고가 완료되었습니다")))) /// - 포킷 `추가` 버튼 눌렀을 때 case .delegate(.포킷추가하기), diff --git a/Projects/CoreKit/Sources/Data/DTO/User/InterestRequest.swift b/Projects/CoreKit/Sources/Data/DTO/User/InterestRequest.swift new file mode 100644 index 00000000..c787f03e --- /dev/null +++ b/Projects/CoreKit/Sources/Data/DTO/User/InterestRequest.swift @@ -0,0 +1,16 @@ +// +// InterestRequest.swift +// CoreKit +// +// Created by 김도형 on 3/1/25. +// + +import Foundation + +public struct InterestRequest: Encodable { + public let interests: [String] + + public init(interests: [String]) { + self.interests = interests + } +} diff --git a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift index 873d4bb2..27db10d0 100644 --- a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift +++ b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift @@ -64,6 +64,9 @@ extension ContentClient: DependencyKey { }, 추천_컨텐츠_조회: { pageable, keyword in try await provider.request(.추천_컨텐츠_조회(pageable: pageable, keyword: keyword)) + }, + 컨텐츠_신고: { id in + try await provider.requestNoBody(.컨텐츠_신고(contentId: id)) } ) }() diff --git a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+TestKey.swift b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+TestKey.swift index 66df7905..25a4a976 100644 --- a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+TestKey.swift +++ b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+TestKey.swift @@ -21,7 +21,8 @@ extension ContentClient: TestDependencyKey { 썸네일_수정: { _, _ in }, 미분류_링크_포킷_이동: { _ in }, 미분류_링크_삭제: { _ in }, - 추천_컨텐츠_조회: { _, _ in .mock } + 추천_컨텐츠_조회: { _, _ in .mock }, + 컨텐츠_신고: { _ in } ) }() } diff --git a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient.swift b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient.swift index 3e11dbed..211468b3 100644 --- a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient.swift +++ b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient.swift @@ -54,5 +54,8 @@ public struct ContentClient { _ pageable: BasePageableRequest, _ keyword: String? ) async throws -> ContentListInquiryResponse + public var 컨텐츠_신고: @Sendable ( + _ contentId: Int + ) async throws -> Void } diff --git a/Projects/CoreKit/Sources/Data/Network/Content/ContentEndpoint.swift b/Projects/CoreKit/Sources/Data/Network/Content/ContentEndpoint.swift index 25e0cf60..a0475b88 100644 --- a/Projects/CoreKit/Sources/Data/Network/Content/ContentEndpoint.swift +++ b/Projects/CoreKit/Sources/Data/Network/Content/ContentEndpoint.swift @@ -34,6 +34,7 @@ public enum ContentEndpoint { pageable: BasePageableRequest, keyword: String? ) + case 컨텐츠_신고(contentId: Int) } extension ContentEndpoint: TargetType { @@ -69,6 +70,8 @@ extension ContentEndpoint: TargetType { return "/uncategorized" case .추천_컨텐츠_조회: return "/recommended" + case let .컨텐츠_신고(contentId): + return "report/\(contentId)" } } @@ -81,7 +84,8 @@ extension ContentEndpoint: TargetType { case .컨텐츠_상세_조회, .즐겨찾기, - .컨텐츠_추가: + .컨텐츠_추가, + .컨텐츠_신고: return .post case .컨텐츠_수정, @@ -169,6 +173,8 @@ extension ContentEndpoint: TargetType { case let .미분류_링크_삭제(model): return .requestJSONEncodable(model) + case .컨텐츠_신고: + return .requestPlain } } diff --git a/Projects/CoreKit/Sources/Data/Network/User/UserClient+LiveKey.swift b/Projects/CoreKit/Sources/Data/Network/User/UserClient+LiveKey.swift index 05ac70f6..900ec4d1 100644 --- a/Projects/CoreKit/Sources/Data/Network/User/UserClient+LiveKey.swift +++ b/Projects/CoreKit/Sources/Data/Network/User/UserClient+LiveKey.swift @@ -41,6 +41,9 @@ extension UserClient: DependencyKey { }, 유저_관심사_목록_조회: { try await provider.request(.유저_관심사_목록_조회) + }, + 관심사_수정: { model in + try await provider.requestNoBody(.관심사_수정(model: model)) } ) }() diff --git a/Projects/CoreKit/Sources/Data/Network/User/UserClient+TestKey.swift b/Projects/CoreKit/Sources/Data/Network/User/UserClient+TestKey.swift index 8d81916c..7383a14b 100644 --- a/Projects/CoreKit/Sources/Data/Network/User/UserClient+TestKey.swift +++ b/Projects/CoreKit/Sources/Data/Network/User/UserClient+TestKey.swift @@ -20,7 +20,8 @@ extension UserClient: TestDependencyKey { 닉네임_조회: { .mock }, fcm_토큰_저장: { _ in .mock }, 프로필_이미지_목록_조회: { [.mock] }, - 유저_관심사_목록_조회: { InterestResponse.mock } + 유저_관심사_목록_조회: { InterestResponse.mock }, + 관심사_수정: { _ in } ) }() } diff --git a/Projects/CoreKit/Sources/Data/Network/User/UserClient.swift b/Projects/CoreKit/Sources/Data/Network/User/UserClient.swift index 905b7244..b0207386 100644 --- a/Projects/CoreKit/Sources/Data/Network/User/UserClient.swift +++ b/Projects/CoreKit/Sources/Data/Network/User/UserClient.swift @@ -18,4 +18,5 @@ public struct UserClient { public var fcm_토큰_저장: @Sendable (_ model: FCMRequest) async throws -> FCMResponse public var 프로필_이미지_목록_조회: @Sendable () async throws -> [BaseProfileImageResponse] public var 유저_관심사_목록_조회: @Sendable () async throws -> [InterestResponse] + public var 관심사_수정: @Sendable (_ model: InterestRequest) async throws -> Void } diff --git a/Projects/CoreKit/Sources/Data/Network/User/UserEndpoint.swift b/Projects/CoreKit/Sources/Data/Network/User/UserEndpoint.swift index 15ea86d4..da003b7d 100644 --- a/Projects/CoreKit/Sources/Data/Network/User/UserEndpoint.swift +++ b/Projects/CoreKit/Sources/Data/Network/User/UserEndpoint.swift @@ -20,6 +20,7 @@ public enum UserEndpoint { case fcm_토큰_저장(model: FCMRequest) case 프로필_이미지_목록_조회 case 유저_관심사_목록_조회 + case 관심사_수정(model: InterestRequest) } extension UserEndpoint: TargetType { @@ -43,14 +44,16 @@ extension UserEndpoint: TargetType { return "/fcm" case .프로필_이미지_목록_조회: return "/profileImage" - case .유저_관심사_목록_조회: + case .유저_관심사_목록_조회, .관심사_수정: return "/myinterests" } } public var method: Moya.Method { switch self { - case .프로필_수정, .닉네임_수정: + case .닉네임_수정, + .관심사_수정, + .프로필_수정: return .put case .회원등록, @@ -76,6 +79,8 @@ extension UserEndpoint: TargetType { return .requestJSONEncodable(model) case let .fcm_토큰_저장(model): return .requestJSONEncodable(model) + case let .관심사_수정(model): + return .requestJSONEncodable(model) case .닉네임_중복_체크, .관심사_목록_조회, .닉네임_조회, diff --git a/Projects/DSKit/Sources/Components/PokitCaution.swift b/Projects/DSKit/Sources/Components/PokitCaution.swift index bc977ccd..3bd03aaa 100644 --- a/Projects/DSKit/Sources/Components/PokitCaution.swift +++ b/Projects/DSKit/Sources/Components/PokitCaution.swift @@ -15,6 +15,7 @@ public enum CautionType { case 즐겨찾기_링크없음 case 링크부족 case 알림없음 + case 추천_링크없음 var image: PokitImage.Character { switch self { @@ -42,6 +43,8 @@ public enum CautionType { return "링크가 부족해요!" case .알림없음: return "알림이 없어요" + case .추천_링크없음: + return "아직 추천된 링크가 없어요!" } } @@ -61,6 +64,8 @@ public enum CautionType { return "링크를 5개 이상 저장하고 추천을 받아보세요" case .알림없음: return "리마인드 알림을 설정하세요" + case .추천_링크없음: + return "" } } diff --git a/Projects/DSKit/Sources/Components/PokitLinkPopup.swift b/Projects/DSKit/Sources/Components/PokitLinkPopup.swift index 7106fb03..866acb2d 100644 --- a/Projects/DSKit/Sources/Components/PokitLinkPopup.swift +++ b/Projects/DSKit/Sources/Components/PokitLinkPopup.swift @@ -90,10 +90,17 @@ public struct PokitLinkPopup: View { private var closeButton: some View { Button(action: closedPopup) { - Image(.icon(.x)) - .resizable() - .frame(width: 24, height: 24) - .foregroundStyle(iconColor) + Group { + if case .report = type { + Image(.icon(.check)) + .resizable() + } else { + Image(.icon(.x)) + .resizable() + } + } + .frame(width: 24, height: 24) + .foregroundStyle(iconColor) } } @@ -109,7 +116,7 @@ public struct PokitLinkPopup: View { case .link, .text, .warning: UINotificationFeedbackGenerator() .notificationOccurred(.warning) - case .success: + case .success, .report: UINotificationFeedbackGenerator() .notificationOccurred(.success) case .error: @@ -125,7 +132,7 @@ public struct PokitLinkPopup: View { return .pokit(.bg(.tertiary)) case .success: return .pokit(.bg(.success)) - case .error: + case .error, .report: return .pokit(.bg(.error)) case .warning: return .pokit(.bg(.warning)) @@ -164,7 +171,8 @@ public struct PokitLinkPopup: View { let .text(title), let .success(title), let .error(title), - let .warning(title): + let .warning(title), + let .report(title): return title default: return "" } @@ -178,6 +186,7 @@ public extension PokitLinkPopup { case success(title: String) case error(title: String) case warning(title: String) + case report(title: String) } } @@ -205,5 +214,9 @@ public extension PokitLinkPopup { PokitLinkPopup( type: .constant(.warning(title: "저장공간 부족")) ) + + PokitLinkPopup( + type: .constant(.report(title: "신고가 완료되었습니다")) + ) } } diff --git a/Projects/Domain/Sources/Base/BaseInterest.swift b/Projects/Domain/Sources/Base/BaseInterest.swift index ea327553..bc84bc10 100644 --- a/Projects/Domain/Sources/Base/BaseInterest.swift +++ b/Projects/Domain/Sources/Base/BaseInterest.swift @@ -7,13 +7,41 @@ import Foundation -public struct BaseInterest: Equatable, Identifiable { +public struct BaseInterest: Equatable, Identifiable, Hashable { public let id = UUID() - public let code: String + public let code: Code public let description: String - public init(code: String, description: String) { + public func hash(into hasher: inout Hasher) { + hasher.combine(code) + } + + public static func ==(lhs: BaseInterest, rhs: BaseInterest) -> Bool { + lhs.code == rhs.code + } + + public init(code: Code, description: String) { self.code = code self.description = description } } + +extension BaseInterest { + public enum Code: String { + case `default` = "DEFAULT" + case 스포츠_레저 = "SPORTS" + case 문구_오피스 = "OFFICE" + case 패션 = "FASHION" + case 여행 = "TRAVEL" + case 경제_시사 = "ECONOMY" + case 영화_드라마 = "MOVIE_DRAMA" + case 맛집 = "RESTAURANT" + case 인테리어 = "INTERIOR" + case IT = "IT" + case 디자인 = "DESIGN" + case 자기계발 = "SELF_IMPROVEMENT" + case 유머 = "HUMOR" + case 음악 = "MUSIC" + case 취업정보 = "JOB_INFO" + } +} diff --git a/Projects/Domain/Sources/DTO/Base/BaseInterest+Extension.swift b/Projects/Domain/Sources/DTO/Base/BaseInterest+Extension.swift index 20b4f677..93b5ff16 100644 --- a/Projects/Domain/Sources/DTO/Base/BaseInterest+Extension.swift +++ b/Projects/Domain/Sources/DTO/Base/BaseInterest+Extension.swift @@ -12,7 +12,7 @@ import CoreKit public extension InterestResponse { func toDomian() -> BaseInterest { return BaseInterest( - code: self.code, + code: BaseInterest.Code(rawValue: self.code) ?? .default, description: self.description ) } diff --git a/Projects/Domain/Sources/Recommend/Recommend.swift b/Projects/Domain/Sources/Recommend/Recommend.swift index e31a41a1..a3ab2810 100644 --- a/Projects/Domain/Sources/Recommend/Recommend.swift +++ b/Projects/Domain/Sources/Recommend/Recommend.swift @@ -12,6 +12,7 @@ public struct Recommend: Equatable { /// 콘텐츠 목록 public var contentList: BaseContentListInquiry public var pageable: BasePageable + public var myInterests: [BaseInterest] public var interests: [BaseInterest] public init() { @@ -25,6 +26,7 @@ public struct Recommend: Equatable { page: 0, size: 10, sort: ["createdAt,desc"] ) + self.myInterests = [] self.interests = [] } } diff --git a/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendFeature.swift b/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendFeature.swift index f65ea9d3..677901c1 100644 --- a/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendFeature.swift +++ b/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendFeature.swift @@ -10,6 +10,7 @@ import ComposableArchitecture import Domain import CoreKit import Util +import DSKit @Reducer public struct RecommendFeature { @@ -37,14 +38,18 @@ public struct RecommendFeature { array.append(contentsOf: list) return array } - var interestList: IdentifiedArrayOf { + var myInterestList: IdentifiedArrayOf { var array = IdentifiedArrayOf() - array.append(contentsOf: domain.interests) + array.append(contentsOf: domain.myInterests) return array } var isLoading: Bool = true var selectedInterest: BaseInterest? var shareContent: BaseContentItem? + var interests: [BaseInterest] { domain.interests } + var showKeywordSheet: Bool = false + var selectedInterestList = Set() + var reportContent: BaseContentItem? } /// - Action @@ -66,24 +71,32 @@ public struct RecommendFeature { case 추가하기_버튼_눌렀을때(BaseContentItem) case 공유하기_버튼_눌렀을때(BaseContentItem) case 신고하기_버튼_눌렀을때(BaseContentItem) + case 신고하기_확인_버튼_눌렀을때(BaseContentItem) case 전체보기_버튼_눌렀을때(ScrollViewProxy) case 관심사_버튼_눌렀을때(BaseInterest, ScrollViewProxy) + case 관심사_편집_버튼_눌렀을때 + case 키워드_선택_버튼_눌렀을때 case 링크_공유_완료되었을때 case 검색_버튼_눌렀을때 case 알림_버튼_눌렀을때 case 추천_컨텐츠_눌렀을때(String) + case 경고시트_dismiss } public enum InnerAction: Equatable { case 추천_조회_API_반영(BaseContentListInquiry) case 추천_조회_페이징_API_반영(BaseContentListInquiry) case 유저_관심사_조회_API_반영([BaseInterest]) + case 관심사_조회_API_반영([BaseInterest]) + case 컨텐츠_신고_API_반영(Int) } public enum AsyncAction: Equatable { case 추천_조회_API case 추천_조회_페이징_API case 유저_관심사_조회_API + case 관심사_조회_API + case 컨텐츠_신고_API(Int) } public enum ScopeAction: Equatable { case doNothing } @@ -92,6 +105,7 @@ public struct RecommendFeature { case 추가하기_버튼_눌렀을때(Int) case 검색_버튼_눌렀을때 case 알림_버튼_눌렀을때 + case 컨텐츠_신고_API_반영 } } @@ -148,7 +162,11 @@ private extension RecommendFeature { case let .공유하기_버튼_눌렀을때(content): state.shareContent = content return .none + case let .신고하기_확인_버튼_눌렀을때(content): + state.reportContent = nil + return shared(.async(.컨텐츠_신고_API(content.id)), state: &state) case let .신고하기_버튼_눌렀을때(content): + state.reportContent = content return .none case let .전체보기_버튼_눌렀을때(proxy): guard state.selectedInterest != nil else { return .none } @@ -160,6 +178,7 @@ private extension RecommendFeature { y: UnitPoint.leading.y ) proxy.scrollTo("전체보기", anchor: anchor) + state.domain.contentList.data = nil return shared(.async(.추천_조회_API), state: &state) case let .관심사_버튼_눌렀을때(interest, proxy): guard state.selectedInterest != interest else { return .none } @@ -171,6 +190,7 @@ private extension RecommendFeature { y: UnitPoint.leading.y ) proxy.scrollTo(interest.description, anchor: anchor) + state.domain.contentList.data = nil return shared(.async(.추천_조회_API), state: &state) case .링크_공유_완료되었을때: state.shareContent = nil @@ -182,6 +202,20 @@ private extension RecommendFeature { case let .추천_컨텐츠_눌렀을때(urlString): guard let url = URL(string: urlString) else { return .none } return .run { _ in await openURL(url) } + case .관심사_편집_버튼_눌렀을때: + return shared(.async(.관심사_조회_API), state: &state) + case .키워드_선택_버튼_눌렀을때: + state.showKeywordSheet = false + state.selectedInterest = nil + return .run { [ interests = state.selectedInterestList ] send in + let request = InterestRequest(interests: interests.map(\.description)) + try await userClient.관심사_수정(model: request) + await send(.async(.유저_관심사_조회_API)) + await send(.async(.추천_조회_API)) + } + case .경고시트_dismiss: + state.reportContent = nil + return .none } } @@ -201,8 +235,18 @@ private extension RecommendFeature { state.isLoading = false return .none case let .유저_관심사_조회_API_반영(interests): - state.domain.interests = interests + state.domain.myInterests = interests + interests.forEach { state.selectedInterestList.insert($0) } return .none + case let .관심사_조회_API_반영(interests): + state.domain.interests = interests.filter({ interest in + interest.code != .default + }) + state.showKeywordSheet = true + return .none + case let .컨텐츠_신고_API_반영(contentId): + state.domain.contentList.data?.removeAll(where: { $0.id == contentId }) + return .send(.delegate(.컨텐츠_신고_API_반영)) } } @@ -234,6 +278,19 @@ private extension RecommendFeature { let interests = try await userClient.유저_관심사_목록_조회().map { $0.toDomian() } await send(.inner(.유저_관심사_조회_API_반영(interests))) } + case .관심사_조회_API: + return .run { send in + let interests = try await userClient.관심사_목록_조회().map { $0.toDomian() } + await send(.inner(.관심사_조회_API_반영(interests))) + } + case let .컨텐츠_신고_API(contentId): + return .run { send in + try await contentClient.컨텐츠_신고(contentId: contentId) + await send( + .inner(.컨텐츠_신고_API_반영(contentId)), + animation: .pokitSpring + ) + } } } diff --git a/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendKeywordBottomSheet.swift b/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendKeywordBottomSheet.swift new file mode 100644 index 00000000..52c8386e --- /dev/null +++ b/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendKeywordBottomSheet.swift @@ -0,0 +1,120 @@ +// +// RecommendKeywordBottomSheet.swift +// FeatureRecommend +// +// Created by 김도형 on 2/22/25. +// + +import SwiftUI + +import Domain +import DSKit +import Util + +struct RecommendKeywordBottomSheet: View { + @Binding + private var selectedInterests: Set + @State + private var height: CGFloat = 0 + + private let interests: [BaseInterest] + private let action: () -> Void + + init( + selectedInterests: Binding>, + interests: [BaseInterest], + action: @escaping () -> Void + ) { + self._selectedInterests = selectedInterests + self.interests = interests + self.action = action + } + + + var body: some View { + VStack(spacing: 0) { + title + .padding(.top, 52) + .pokitMaxWidth() + + fieldsFlow + .padding(.top, 36) + .padding(.bottom, 40) + + PokitBottomButton( + "키워드 선택", + state: selectedInterests.count == 0 ? .disable : .filled(.primary), + action: action + ) + .pokitMaxWidth() + } + .padding(.horizontal, 20) + .background(.pokit(.bg(.base))) + .pokitPresentationCornerRadius() + .pokitPresentationBackground() + .presentationDragIndicator(.visible) + .readHeight() + .onPreferenceChange(HeightPreferenceKey.self) { height in + if let height { + self.height = height + } + } + .presentationDetents([.height(height)]) + .ignoresSafeArea(edges: [.bottom, .top]) + } +} + +//MARK: - Configure View +extension RecommendKeywordBottomSheet { + private var title: some View { + HStack { + VStack(alignment: .leading, spacing: 12) { + Text("관심 키워드를 선택해 주세요") + .pokitFont(.title1) + .foregroundStyle(.pokit(.text(.primary))) + + Text("최대 3개의 관심 키워드를 선택하시면\n관련 링크가 추천됩니다") + .pokitFont(.title3) + .foregroundStyle(.pokit(.text(.secondary))) + } + + Spacer() + } + } + + private var fieldsFlow: some View { + PokitFlowLayout(rowSpacing: 12, colSpacing: 10) { + ForEach(interests, id: \.self) { field in + let isSelected = selectedInterests.contains(field) + let isMaxCount = selectedInterests.count >= 3 + + PokitTextChip( + field.description, + state: isSelected + ? .filled(.primary) + : isMaxCount ? .disable : .default(.primary), + size: .medium + ) { + if isSelected { + selectedInterests.remove(field) + } else { + selectedInterests.insert(field) + } + } + } + .animation(.pokitDissolve, value: selectedInterests) + } + } +} + +@available(iOS 18.0, *) +#Preview { + @Previewable + @State + var selectedInterests = Set() + + RecommendKeywordBottomSheet( + selectedInterests: $selectedInterests, + interests: [] + ) { } +} diff --git a/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendView.swift b/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendView.swift index 3e7a892c..f3e30722 100644 --- a/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendView.swift +++ b/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendView.swift @@ -42,6 +42,22 @@ public extension RecommendView { } } .task { await send(.onAppear).finish() } + .sheet(isPresented: $store.showKeywordSheet) { + RecommendKeywordBottomSheet( + selectedInterests: $store.selectedInterestList, + interests: store.interests, + action: { send(.키워드_선택_버튼_눌렀을때) } + ) + } + .sheet(item: $store.reportContent) { content in + PokitAlert( + "링크를 신고하시겠습니까?", + message: "명확한 사유가 있는 경우 신고해주시기 바랍니다. \nex)음란성/선정성 이미지, 영상, 텍스트 등의 콘텐츠\n욕설, 비속어, 모욕, 저속한 단어 등", + confirmText: "확인", + action: { send(.신고하기_확인_버튼_눌렀을때(content)) }, + cancelAction: { send(.경고시트_dismiss) } + ) + } } } } @@ -68,7 +84,7 @@ private extension RecommendView { state: .default(.secondary), size: .small, shape: .round, - action: { } + action: { send(.관심사_편집_버튼_눌렀을때) } ) .padding([.leading, .vertical], 8) .padding(.trailing, 20) @@ -101,16 +117,13 @@ private extension RecommendView { ? .filled(.primary) : .default(.secondary), size: .small, - shape: .round - ) { - send( - .전체보기_버튼_눌렀을때(proxy), - animation: .pokitDissolve - ) - } + shape: .round, + action: { send(.전체보기_버튼_눌렀을때(proxy)) } + ) + .animation(.pokitDissolve, value: isAllSelected) .id("전체보기") - ForEach(store.interestList) { interest in + ForEach(store.myInterestList) { interest in let isSelected = store.selectedInterest == interest PokitTextButton( @@ -119,16 +132,14 @@ private extension RecommendView { ? .filled(.primary) : .default(.secondary), size: .small, - shape: .round - ) { - send( - .관심사_버튼_눌렀을때(interest, proxy), - animation: .pokitDissolve - ) - } + shape: .round, + action: { send(.관심사_버튼_눌렀을때(interest, proxy)) } + ) + .animation(.pokitDissolve, value: isSelected) .id(interest.description) } } + .animation(.pokitDissolve, value: store.myInterestList) } @ViewBuilder @@ -146,7 +157,7 @@ private extension RecommendView { @ViewBuilder var empty: some View { - PokitCaution(type: .링크없음) + PokitCaution(type: .추천_링크없음) .padding(.top, 100) Spacer() @@ -242,6 +253,8 @@ private extension RecommendView { } PokitBadge(state: .default(content.domain)) + + Spacer() } Text(content.title) diff --git a/Projects/Feature/FeatureRecommendDemo/Sources/FeatureRecommendDemoApp.swift b/Projects/Feature/FeatureRecommendDemo/Sources/FeatureRecommendDemoApp.swift index 2f92fe2a..525a502e 100644 --- a/Projects/Feature/FeatureRecommendDemo/Sources/FeatureRecommendDemoApp.swift +++ b/Projects/Feature/FeatureRecommendDemo/Sources/FeatureRecommendDemoApp.swift @@ -23,11 +23,7 @@ struct FeatureRecommendDemoApp: App { )) { RecommendView(store: .init( initialState: .init(), - reducer: { RecommendFeature()._printChanges() }, - withDependencies: { - $0[ContentClient.self] = .testValue - $0[UserClient.self] = .testValue - } + reducer: { RecommendFeature()._printChanges() } )) } }