Skip to content

Commit ad594f5

Browse files
committed
[feat] #187 관심사 선택 시트 추가
1 parent 17ce45e commit ad594f5

File tree

6 files changed

+167
-10
lines changed

6 files changed

+167
-10
lines changed

Projects/Domain/Sources/Base/BaseInterest.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,19 @@
77

88
import Foundation
99

10-
public struct BaseInterest: Equatable, Identifiable {
10+
public struct BaseInterest: Equatable, Identifiable, Hashable {
1111
public let id = UUID()
1212
public let code: String
1313
public let description: String
1414

15+
public func hash(into hasher: inout Hasher) {
16+
hasher.combine(code)
17+
}
18+
19+
public static func ==(lhs: BaseInterest, rhs: BaseInterest) -> Bool {
20+
lhs.code == rhs.code
21+
}
22+
1523
public init(code: String, description: String) {
1624
self.code = code
1725
self.description = description

Projects/Domain/Sources/Recommend/Recommend.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public struct Recommend: Equatable {
1212
/// 콘텐츠 목록
1313
public var contentList: BaseContentListInquiry
1414
public var pageable: BasePageable
15+
public var myInterests: [BaseInterest]
1516
public var interests: [BaseInterest]
1617

1718
public init() {
@@ -25,6 +26,7 @@ public struct Recommend: Equatable {
2526
page: 0, size: 10,
2627
sort: ["createdAt,desc"]
2728
)
29+
self.myInterests = []
2830
self.interests = []
2931
}
3032
}

Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendFeature.swift

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,17 @@ public struct RecommendFeature {
3737
array.append(contentsOf: list)
3838
return array
3939
}
40-
var interestList: IdentifiedArrayOf<BaseInterest> {
40+
var myInterestList: IdentifiedArrayOf<BaseInterest> {
4141
var array = IdentifiedArrayOf<BaseInterest>()
42-
array.append(contentsOf: domain.interests)
42+
array.append(contentsOf: domain.myInterests)
4343
return array
4444
}
4545
var isLoading: Bool = true
4646
var selectedInterest: BaseInterest?
4747
var shareContent: BaseContentItem?
48+
var interests: [BaseInterest] { domain.interests }
49+
var showKeywordSheet: Bool = false
50+
var selectedInterestList = Set<BaseInterest>()
4851
}
4952

5053
/// - Action
@@ -68,6 +71,8 @@ public struct RecommendFeature {
6871
case 신고하기_버튼_눌렀을때(BaseContentItem)
6972
case 전체보기_버튼_눌렀을때(ScrollViewProxy)
7073
case 관심사_버튼_눌렀을때(BaseInterest, ScrollViewProxy)
74+
case 관심사_편집_버튼_눌렀을때
75+
case 키워드_선택_버튼_눌렀을때
7176
case 링크_공유_완료되었을때
7277
case 검색_버튼_눌렀을때
7378
case 알림_버튼_눌렀을때
@@ -78,12 +83,14 @@ public struct RecommendFeature {
7883
case 추천_조회_API_반영(BaseContentListInquiry)
7984
case 추천_조회_페이징_API_반영(BaseContentListInquiry)
8085
case 유저_관심사_조회_API_반영([BaseInterest])
86+
case 관심사_조회_API_반영([BaseInterest])
8187
}
8288

8389
public enum AsyncAction: Equatable {
8490
case 추천_조회_API
8591
case 추천_조회_페이징_API
8692
case 유저_관심사_조회_API
93+
case 관심사_조회_API
8794
}
8895

8996
public enum ScopeAction: Equatable { case doNothing }
@@ -182,6 +189,11 @@ private extension RecommendFeature {
182189
case let .추천_컨텐츠_눌렀을때(urlString):
183190
guard let url = URL(string: urlString) else { return .none }
184191
return .run { _ in await openURL(url) }
192+
case .관심사_편집_버튼_눌렀을때:
193+
return shared(.async(.관심사_조회_API), state: &state)
194+
case .키워드_선택_버튼_눌렀을때:
195+
state.showKeywordSheet = false
196+
return .none
185197
}
186198
}
187199

@@ -201,7 +213,12 @@ private extension RecommendFeature {
201213
state.isLoading = false
202214
return .none
203215
case let .유저_관심사_조회_API_반영(interests):
216+
state.domain.myInterests = interests
217+
interests.forEach { state.selectedInterestList.insert($0) }
218+
return .none
219+
case let .관심사_조회_API_반영(interests):
204220
state.domain.interests = interests
221+
state.showKeywordSheet = true
205222
return .none
206223
}
207224
}
@@ -234,6 +251,11 @@ private extension RecommendFeature {
234251
let interests = try await userClient.유저_관심사_목록_조회().map { $0.toDomian() }
235252
await send(.inner(.유저_관심사_조회_API_반영(interests)))
236253
}
254+
case .관심사_조회_API:
255+
return .run { send in
256+
let interests = try await userClient.관심사_목록_조회().map { $0.toDomian() }
257+
await send(.inner(.관심사_조회_API_반영(interests)))
258+
}
237259
}
238260
}
239261

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//
2+
// RecommendKeywordBottomSheet.swift
3+
// FeatureRecommend
4+
//
5+
// Created by 김도형 on 2/22/25.
6+
//
7+
8+
import SwiftUI
9+
10+
import Domain
11+
import DSKit
12+
import Util
13+
14+
struct RecommendKeywordBottomSheet: View {
15+
@Binding
16+
private var selectedInterests: Set<BaseInterest>
17+
@State
18+
private var height: CGFloat = 0
19+
20+
private let interests: [BaseInterest]
21+
private let action: () -> Void
22+
23+
init(
24+
selectedInterests: Binding<Set<BaseInterest>>,
25+
interests: [BaseInterest],
26+
action: @escaping () -> Void
27+
) {
28+
self._selectedInterests = selectedInterests
29+
self.interests = interests
30+
self.action = action
31+
}
32+
33+
34+
var body: some View {
35+
VStack(spacing: 0) {
36+
title
37+
.padding(.top, 52)
38+
.pokitMaxWidth()
39+
40+
fieldsFlow
41+
.padding(.top, 36)
42+
.padding(.bottom, 40)
43+
44+
PokitBottomButton(
45+
"키워드 선택",
46+
state: selectedInterests.count == 0 ? .disable : .filled(.primary),
47+
action: action
48+
)
49+
.pokitMaxWidth()
50+
}
51+
.padding(.horizontal, 20)
52+
.background(.pokit(.bg(.base)))
53+
.pokitPresentationCornerRadius()
54+
.pokitPresentationBackground()
55+
.presentationDragIndicator(.visible)
56+
.readHeight()
57+
.onPreferenceChange(HeightPreferenceKey.self) { height in
58+
if let height {
59+
self.height = height
60+
}
61+
}
62+
.presentationDetents([.height(height)])
63+
.ignoresSafeArea(edges: [.bottom, .top])
64+
}
65+
}
66+
67+
//MARK: - Configure View
68+
extension RecommendKeywordBottomSheet {
69+
private var title: some View {
70+
HStack {
71+
VStack(alignment: .leading, spacing: 12) {
72+
Text("관심 키워드를 선택해 주세요")
73+
.pokitFont(.title1)
74+
.foregroundStyle(.pokit(.text(.primary)))
75+
76+
Text("최대 3개의 관심 키워드를 선택하시면\n관련 링크가 추천됩니다")
77+
.pokitFont(.title3)
78+
.foregroundStyle(.pokit(.text(.secondary)))
79+
}
80+
81+
Spacer()
82+
}
83+
}
84+
85+
private var fieldsFlow: some View {
86+
PokitFlowLayout(rowSpacing: 12, colSpacing: 10) {
87+
ForEach(interests, id: \.self) { field in
88+
let isSelected = selectedInterests.contains(field)
89+
let isMaxCount = selectedInterests.count >= 3
90+
91+
PokitTextChip(
92+
field.description,
93+
state: isSelected
94+
? .filled(.primary)
95+
: isMaxCount ? .disable : .default(.primary),
96+
size: .medium
97+
) {
98+
if isSelected {
99+
selectedInterests.remove(field)
100+
} else {
101+
selectedInterests.insert(field)
102+
}
103+
}
104+
}
105+
.animation(.pokitDissolve, value: selectedInterests)
106+
}
107+
}
108+
}
109+
110+
@available(iOS 18.0, *)
111+
#Preview {
112+
@Previewable
113+
@State
114+
var selectedInterests = Set<BaseInterest>()
115+
116+
RecommendKeywordBottomSheet(
117+
selectedInterests: $selectedInterests,
118+
interests: BaseInterestType.allCases.map {
119+
BaseInterest(code: "", description: $0.title)
120+
}
121+
) { }
122+
}

Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendView.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ public extension RecommendView {
4242
}
4343
}
4444
.task { await send(.onAppear).finish() }
45+
.sheet(isPresented: $store.showKeywordSheet) {
46+
RecommendKeywordBottomSheet(
47+
selectedInterests: $store.selectedInterestList,
48+
interests: store.interests,
49+
action: { send(.키워드_선택_버튼_눌렀을때) }
50+
)
51+
}
4552
}
4653
}
4754
}
@@ -68,7 +75,7 @@ private extension RecommendView {
6875
state: .default(.secondary),
6976
size: .small,
7077
shape: .round,
71-
action: { }
78+
action: { send(.관심사_편집_버튼_눌렀을때) }
7279
)
7380
.padding([.leading, .vertical], 8)
7481
.padding(.trailing, 20)
@@ -110,7 +117,7 @@ private extension RecommendView {
110117
}
111118
.id("전체보기")
112119

113-
ForEach(store.interestList) { interest in
120+
ForEach(store.myInterestList) { interest in
114121
let isSelected = store.selectedInterest == interest
115122

116123
PokitTextButton(

Projects/Feature/FeatureRecommendDemo/Sources/FeatureRecommendDemoApp.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,7 @@ struct FeatureRecommendDemoApp: App {
2323
)) {
2424
RecommendView(store: .init(
2525
initialState: .init(),
26-
reducer: { RecommendFeature()._printChanges() },
27-
withDependencies: {
28-
$0[ContentClient.self] = .testValue
29-
$0[UserClient.self] = .testValue
30-
}
26+
reducer: { RecommendFeature()._printChanges() }
3127
))
3228
}
3329
}

0 commit comments

Comments
 (0)