From 214fa8e34a9608b16258db75308ed09ffac5ed0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Sat, 6 Sep 2025 23:39:35 +0900 Subject: [PATCH 01/42] =?UTF-8?q?[fix]=20#203=20=EC=A6=90=EA=B2=A8?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=ED=95=84=ED=84=B0=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=9B=84=20=ED=95=84=ED=84=B0=20=ED=95=B4=EC=A0=9C=20=EC=8B=9C?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/CategoryDetailFeature.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift index 0425f105..9ae4dc36 100644 --- a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift +++ b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift @@ -199,11 +199,14 @@ private extension CategoryDetailFeature { ) case let .분류_버튼_눌렀을때(type): - if type == .즐겨찾기 { + switch type { + case .즐겨찾기: state.domain.condition.isFavoriteFlitered.toggle() + guard state.domain.condition.isFavoriteFlitered else { break } state.domain.condition.isUnreadFlitered = !state.domain.condition.isFavoriteFlitered - } else { + case .안읽음: state.domain.condition.isUnreadFlitered.toggle() + guard state.domain.condition.isUnreadFlitered else { break } state.domain.condition.isFavoriteFlitered = !state.domain.condition.isUnreadFlitered } return .concatenate( From a73d572d2d9d98490e05f92b439e23bc8fd822a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Sat, 6 Sep 2025 23:57:22 +0900 Subject: [PATCH 02/42] =?UTF-8?q?[refactor]=20#203=20PokitCauction=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8=ED=98=95=EC=9C=BC=EB=A1=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Components/PokitCaution.swift | 21 +++++++++++++------ .../FeaturePokit/Sources/PokitRootView.swift | 12 ++++------- .../Sources/Alert/PokitAlertBoxView.swift | 2 +- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Projects/DSKit/Sources/Components/PokitCaution.swift b/Projects/DSKit/Sources/Components/PokitCaution.swift index e2ced148..82baabdd 100644 --- a/Projects/DSKit/Sources/Components/PokitCaution.swift +++ b/Projects/DSKit/Sources/Components/PokitCaution.swift @@ -84,9 +84,14 @@ public struct PokitCaution: View { private let type: CautionType private let action: (() -> Void)? - public init( + public init(type: CautionType) { + self.type = type + self.action = nil + } + + private init( type: CautionType, - action: (() -> Void)? = nil + action: (() -> Void)? ) { self.type = type self.action = action @@ -132,11 +137,15 @@ public struct PokitCaution: View { .frame(maxHeight: .infinity) .padding(.bottom, 92) } + + public func onAction(_ action: @escaping () -> Void) -> Self { + PokitCaution(type: self.type, action: action) + } } #Preview { - PokitCaution( - type: .미분류_링크없음, - action: {} - ) + PokitCaution(type: .미분류_링크없음) + .onAction { + + } } diff --git a/Projects/Feature/FeaturePokit/Sources/PokitRootView.swift b/Projects/Feature/FeaturePokit/Sources/PokitRootView.swift index f5771274..91e46d4f 100644 --- a/Projects/Feature/FeaturePokit/Sources/PokitRootView.swift +++ b/Projects/Feature/FeaturePokit/Sources/PokitRootView.swift @@ -134,10 +134,8 @@ private extension PokitRootView { var pokitView: some View { if let categories = store.categories { if categories.isEmpty { - PokitCaution( - type: .카테고리없음, - action: { send(.포킷추가_버튼_눌렀을때) } - ) + PokitCaution(type: .카테고리없음) + .onAction { send(.포킷추가_버튼_눌렀을때) } } else { pokitList(categories) } @@ -181,10 +179,8 @@ private extension PokitRootView { var unclassifiedView: some View { if !store.isLoading { if store.contents.isEmpty { - PokitCaution( - type: .미분류_링크없음, - action: { send(.링크추가_버튼_눌렀을때) } - ) + PokitCaution(type: .미분류_링크없음) + .onAction { send(.링크추가_버튼_눌렀을때) } } else { unclassifiedList .padding(.top, 20) diff --git a/Projects/Feature/FeatureSetting/Sources/Alert/PokitAlertBoxView.swift b/Projects/Feature/FeatureSetting/Sources/Alert/PokitAlertBoxView.swift index f0e1e0ce..d658ebca 100644 --- a/Projects/Feature/FeatureSetting/Sources/Alert/PokitAlertBoxView.swift +++ b/Projects/Feature/FeatureSetting/Sources/Alert/PokitAlertBoxView.swift @@ -30,7 +30,7 @@ public extension PokitAlertBoxView { if alertContents.isEmpty { VStack { PokitCaution(type: .알림없음) - .padding(.top, 84) + .padding(.top, 84) Spacer() } } else { From 5f1b88bb0c2a58c855f0f9fd3cc085c34e97b838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Sun, 7 Sep 2025 00:07:06 +0900 Subject: [PATCH 03/42] =?UTF-8?q?[fix]=20#203=20empty=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=97=90=20=EB=A7=81=ED=81=AC=20=EC=B6=94=EA=B0=80=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=A0=9C=EA=B1=B0,=20empty=20=EB=85=B8=EC=B6=9C=20?= =?UTF-8?q?=EC=8B=9C=20=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0/=EC=95=88?= =?UTF-8?q?=EC=9D=BD=EC=9D=8C=20=EB=B2=84=ED=8A=BC=20=EB=85=B8=EC=B6=9C=20?= =?UTF-8?q?=ED=95=84=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/CategoryDetailFeature.swift | 3 - .../Sources/CategoryDetailView.swift | 124 +++++++++--------- 2 files changed, 60 insertions(+), 67 deletions(-) diff --git a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift index 9ae4dc36..9b3a9c24 100644 --- a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift +++ b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift @@ -65,9 +65,6 @@ public struct CategoryDetailFeature { domain.contentList.hasNext } var isLoading: Bool = true - var isContentsNotEmpty: Bool { - (isFavoriteCategory && contents.contains { $0.content.isFavorite == true }) || (!isFavoriteCategory && !contents.isEmpty) - } public init(category: BaseCategoryItem) { self.domain = .init(categpry: category) diff --git a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailView.swift b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailView.swift index 5d0d80f6..7e319ed7 100644 --- a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailView.swift +++ b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailView.swift @@ -65,7 +65,7 @@ public extension CategoryDetailView { .padding(.top, 12) .pokitNavigationBar { navigationBar } .overlay( - if: store.isContentsNotEmpty, + if: !store.isFavoriteCategory, alignment: .bottomTrailing ) { Button(action: { send(.링크_추가_버튼_눌렀을때) }) { @@ -215,30 +215,32 @@ private extension CategoryDetailView { var filterHeader: some View { let isFavoriteCategory = store.isFavoriteCategory let favoriteContentsCount = store.contents.filter { $0.content.isFavorite ?? false }.count - if store.isContentsNotEmpty { - HStack(spacing: isFavoriteCategory ? 2 : 8) { - if isFavoriteCategory { - Image(.icon(.link)) - .resizable() - .frame(width: 16, height: 16) - .foregroundStyle(.pokit(.icon(.secondary))) - Text("\(favoriteContentsCount)개") - .foregroundStyle(.pokit(.text(.tertiary))) - .pokitFont(.b2(.m)) - } else { - favoriteButton - - unreadButton - } + + HStack(spacing: isFavoriteCategory ? 2 : 8) { + if isFavoriteCategory { + Image(.icon(.link)) + .resizable() + .frame(width: 16, height: 16) + .foregroundStyle(.pokit(.icon(.secondary))) - Spacer() - PokitIconLTextLink( - store.sortType.title, - icon: .icon(.align), - action: { send(.정렬_버튼_눌렀을때) } - ) - .contentTransition(.numericText()) + Text("\(favoriteContentsCount)개") + .foregroundStyle(.pokit(.text(.tertiary))) + .pokitFont(.b2(.m)) + + } else { + favoriteButton + + unreadButton } + + Spacer() + + PokitIconLTextLink( + store.sortType.title, + icon: .icon(.align), + action: { send(.정렬_버튼_눌렀을때) } + ) + .contentTransition(.numericText()) } } @@ -286,53 +288,47 @@ private extension CategoryDetailView { } } + @ViewBuilder var contentScrollView: some View { - Group { - if !store.isLoading { - if store.contents.isEmpty { - PokitCaution( - type: .포킷상세_링크없음, - action: { send(.링크_추가_버튼_눌렀을때) } - ) - } else { - LazyVStack(spacing: 0) { - ForEach( - Array(store.scope(state: \.contents, action: \.contents)) - ) { store in - let isFirst = store.state.id == self.store.contents.first?.id - let isLast = store.state.id == self.store.contents.last?.id - - if !self.store.isFavoriteCategory { - ContentCardView( - store: store, - type: .linkList, - isFirst: isFirst, - isLast: isLast - ) - } else { - if store.content.isFavorite == true { - ContentCardView( - store: store, - type: .linkList, - isFirst: isFirst, - isLast: isLast - ) - } - } - } + if !store.isLoading { + if store.contents.isEmpty { + PokitCaution(type: .포킷상세_링크없음) + } else { + LazyVStack(spacing: 0) { + ForEach( + Array(store.scope(state: \.contents, action: \.contents)) + ) { store in + let isFirst = store.state.id == self.store.contents.first?.id + let isLast = store.state.id == self.store.contents.last?.id - if store.hasNext { - PokitLoading() - .task { await send(.pagenation).finish() } + if !self.store.isFavoriteCategory { + ContentCardView( + store: store, + type: .linkList, + isFirst: isFirst, + isLast: isLast + ) + } else if store.content.isFavorite == true { + ContentCardView( + store: store, + type: .linkList, + isFirst: isFirst, + isLast: isLast + ) } - - Spacer() } - .padding(.bottom, 36) + + if store.hasNext { + PokitLoading() + .task { await send(.pagenation).finish() } + } + + Spacer() } - } else { - PokitLoading() + .padding(.bottom, 36) } + } else { + PokitLoading() } } From d1ce305f277fd1dedc7144e74f978df5d030e79d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Sun, 7 Sep 2025 00:23:26 +0900 Subject: [PATCH 04/42] =?UTF-8?q?[fix]=20#203=20=EA=B2=80=EC=83=89=20empty?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80,=20=EC=A0=95=EB=A0=AC=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EB=88=84=EB=9D=BD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Search/PokitSearchView.swift | 78 +++++++++++++------ 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchView.swift b/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchView.swift index 6a819894..2225fda2 100644 --- a/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchView.swift +++ b/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchView.swift @@ -265,30 +265,21 @@ private extension PokitSearchView { var resultList: some View { VStack(alignment: .leading, spacing: 20) { + if store.isSearching { + PokitIconLTextLink( + store.isResultAscending ? "오래된순" : "최신순", + icon: .icon(.align), + action: { send(.정렬_버튼_눌렀을때) } + ) + .contentTransition(.numericText()) + .padding(.horizontal, 20) + } + if !store.isLoading { - ScrollView { - LazyVStack(spacing: 0) { - ForEach( - Array(store.scope(state: \.contents, action: \.contents)) - ) { store in - let isFirst = store.state.id == self.store.contents.first?.id - let isLast = store.state.id == self.store.contents.last?.id - - ContentCardView( - store: store, - type: .linkList, - isFirst: isFirst, - isLast: isLast - ) - } - - if store.hasNext { - PokitLoading() - .task { await send(.로딩중일때, animation: .pokitDissolve).finish() } - } - } - .padding(.horizontal, 20) - .padding(.bottom, 36) + if store.contents.isEmpty && store.isSearching { + resultEmptyLabel + } else { + resultListContent } } else { PokitLoading() @@ -296,6 +287,47 @@ private extension PokitSearchView { } .padding(.top, 24) } + + var resultListContent: some View { + ScrollView { + LazyVStack(spacing: 0) { + ForEach( + Array(store.scope(state: \.contents, action: \.contents)) + ) { store in + let isFirst = store.state.id == self.store.contents.first?.id + let isLast = store.state.id == self.store.contents.last?.id + + ContentCardView( + store: store, + type: .linkList, + isFirst: isFirst, + isLast: isLast + ) + } + + if store.hasNext { + PokitLoading() + .task { await send(.로딩중일때, animation: .pokitDissolve).finish() } + } + } + .padding(.horizontal, 20) + .padding(.bottom, 36) + } + } + + var resultEmptyLabel: some View { + VStack(spacing: 8) { + Text("검색어가 없어요") + .pokitFont(.title2) + + Text("링크 제목, 포킷으로 검색해주세요") + .pokitFont(.b2(.m)) + } + .padding(.top, 100) + .padding(.bottom, 80) + .frame(maxWidth: .infinity) + .foregroundStyle(.pokit(.text(.tertiary))) + } } //MARK: - Preview #Preview { From 56850eae7251a57cd5ccd8a3cfb2cad3a92cfe98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Sun, 7 Sep 2025 00:52:33 +0900 Subject: [PATCH 05/42] =?UTF-8?q?[fix]=20#203=20=ED=82=A4=EC=9B=8C?= =?UTF-8?q?=EB=93=9C=20=EC=84=A0=ED=83=9D=20=EB=B0=94=ED=85=80=EC=8B=9C?= =?UTF-8?q?=ED=8A=B8=EC=99=80=20=EC=84=A0=ED=83=9D=ED=95=9C=20=ED=82=A4?= =?UTF-8?q?=EC=9B=8C=EB=93=9C=EC=9D=98=20=EA=B0=80=EB=82=98=EB=8B=A4=20?= =?UTF-8?q?=EC=88=9C=20=EC=A0=95=EB=A0=AC=20=EC=B6=94=EA=B0=80,=20=20?= =?UTF-8?q?=ED=82=A4=EC=9B=8C=EB=93=9C=20=EC=84=A0=ED=83=9D=20=EB=B0=94?= =?UTF-8?q?=ED=85=80=EC=8B=9C=ED=8A=B8=EC=97=90=EC=84=9C=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=EB=90=9C=20=ED=82=A4=EC=9B=8C=EB=93=9C=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Recommend/RecommendFeature.swift | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendFeature.swift b/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendFeature.swift index 9f3376db..df1ba34b 100644 --- a/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendFeature.swift +++ b/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendFeature.swift @@ -168,7 +168,7 @@ private extension RecommendFeature { case .onAppear: return .merge( shared(.async(.추천_조회_API), state: &state), - shared(.async(.유저_관심사_조회_API), state: &state) + shared(.async(.관심사_조회_API), state: &state) ) case .pagination: return shared(.async(.추천_조회_페이징_API), state: &state) @@ -215,7 +215,8 @@ private extension RecommendFeature { guard let url = URL(string: urlString) else { return .none } return .run { _ in await openURL(url) } case .관심사_편집_버튼_눌렀을때: - return shared(.async(.관심사_조회_API), state: &state) + state.showKeywordSheet = true + return .none case let .키워드_선택_버튼_눌렀을때(interests): state.showKeywordSheet = false state.selectedInterest = nil @@ -255,14 +256,18 @@ private extension RecommendFeature { state.isLoading = false return .none case let .유저_관심사_조회_API_반영(interests): - state.domain.myInterests = interests - interests.forEach { state.selectedInterestList.insert($0) } + state.domain.myInterests = interests.filter { interest in + state.interests.contains(interest) + } + interests.forEach { + guard state.interests.contains($0) else { return } + 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 }) @@ -319,13 +324,20 @@ private extension RecommendFeature { return contentListFetch(state: &state) case .유저_관심사_조회_API: return .run { send in - let interests = try await userClient.유저_관심사_목록_조회().map { $0.toDomian() } + let interests = try await userClient.유저_관심사_목록_조회() + .map { $0.toDomian() } + .sorted { $0.description < $1.description } + await send(.inner(.유저_관심사_조회_API_반영(interests))) } case .관심사_조회_API: return .run { send in - let interests = try await userClient.관심사_목록_조회().map { $0.toDomian() } + let interests = try await userClient.관심사_목록_조회() + .map { $0.toDomian() } + .sorted { $0.description < $1.description } + await send(.inner(.관심사_조회_API_반영(interests))) + await send(.async(.유저_관심사_조회_API)) } case let .컨텐츠_신고_API(contentId): return .run { send in From e338c4d45aba5051bc4c97569920e585c3ab188c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Sun, 7 Sep 2025 01:01:35 +0900 Subject: [PATCH 06/42] =?UTF-8?q?[fix]=20#203=20=20=EB=A7=81=ED=81=AC=20?= =?UTF-8?q?=EC=9D=BD=EC=9D=8C=20=EC=B2=98=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Sources/Base/BaseContentItem.swift | 2 +- .../Sources/ContentCard/ContentCardFeature.swift | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Projects/Domain/Sources/Base/BaseContentItem.swift b/Projects/Domain/Sources/Base/BaseContentItem.swift index 27ebd387..bddcb66d 100644 --- a/Projects/Domain/Sources/Base/BaseContentItem.swift +++ b/Projects/Domain/Sources/Base/BaseContentItem.swift @@ -19,7 +19,7 @@ public struct BaseContentItem: Identifiable, Equatable, PokitLinkCardItem, Sorta public let data: String public let domain: String public let createdAt: String - public let isRead: Bool? + public var isRead: Bool? public var isFavorite: Bool? public let keyword: String? diff --git a/Projects/Feature/FeatureContentCard/Sources/ContentCard/ContentCardFeature.swift b/Projects/Feature/FeatureContentCard/Sources/ContentCard/ContentCardFeature.swift index d5bfae8c..12fd5eb3 100644 --- a/Projects/Feature/FeatureContentCard/Sources/ContentCard/ContentCardFeature.swift +++ b/Projects/Feature/FeatureContentCard/Sources/ContentCard/ContentCardFeature.swift @@ -52,6 +52,7 @@ public struct ContentCardFeature { public enum InnerAction: Equatable { case 메타데이터_조회_수행_반영(String) case 즐겨찾기_API_반영(Bool) + case 컨텐츠_상세_조회_API_반영 } @CasePathable @@ -60,6 +61,7 @@ public struct ContentCardFeature { case 즐겨찾기_API case 즐겨찾기_취소_API case 썸네일_수정_API + case 컨텐츠_상세_조회_API } public enum ScopeAction: Equatable { case doNothing } @@ -111,7 +113,10 @@ private extension ContentCardFeature { guard let url = URL(string: state.content.data) else { return .none } - return .run { _ in await openURL(url) } + return .run { send in + await send(.async(.컨텐츠_상세_조회_API)) + await openURL(url) + } case .컨텐츠_항목_케밥_버튼_눌렀을때: return .send(.delegate(.컨텐츠_항목_케밥_버튼_눌렀을때(content: state.content))) case .메타데이터_조회: @@ -137,6 +142,9 @@ private extension ContentCardFeature { case .즐겨찾기_API_반영(let favorite): state.content.isFavorite = favorite return .none + case .컨텐츠_상세_조회_API_반영: + state.content.isRead = true + return .none } } @@ -167,6 +175,11 @@ private extension ContentCardFeature { try await contentClient.썸네일_수정("\(content.id)", request) } + case .컨텐츠_상세_조회_API: + return .run { [id = state.content.id] send in + let _ = try await contentClient.컨텐츠_상세_조회("\(id)") + await send(.inner(.컨텐츠_상세_조회_API_반영)) + } } } From 0b2523ab064a131a7b27628d442fbba253300d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Sun, 7 Sep 2025 01:19:45 +0900 Subject: [PATCH 07/42] =?UTF-8?q?[refactor]=20#203=20PokitLinkPopup=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8=ED=98=95=EC=9C=BC=EB=A1=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Components/PokitLinkPopup.swift | 59 ++++++++++++++----- .../ContentSetting/ContentSettingView.swift | 7 +-- .../Sources/PokitLinkEditView.swift | 8 +-- 3 files changed, 49 insertions(+), 25 deletions(-) diff --git a/Projects/DSKit/Sources/Components/PokitLinkPopup.swift b/Projects/DSKit/Sources/Components/PokitLinkPopup.swift index 866acb2d..84057cc3 100644 --- a/Projects/DSKit/Sources/Components/PokitLinkPopup.swift +++ b/Projects/DSKit/Sources/Components/PokitLinkPopup.swift @@ -15,17 +15,36 @@ public struct PokitLinkPopup: View { @State private var second: Int = 0 private let action: (() -> Void)? + private let until: Int private let timer = Timer.publish( every: 1, on: .main, in: .common ).autoconnect() - public init( + public init(type: Binding) { + self._type = type + switch type.wrappedValue { + case let .link(_, _, until), + let .text(_, until), + let .success(_, until), + let .error(_, until), + let .warning(_, until), + let .report(_, until): + self.until = until + default: + self.until = 2 + } + self.action = nil + } + + private init( type: Binding, - action: (() -> Void)? = nil + until: Int, + action: (() -> Void)? ) { self._type = type + self.until = until self.action = action } @@ -40,7 +59,7 @@ public struct PokitLinkPopup: View { .frame(width: 335, height: 60) .transition(.move(edge: .bottom).combined(with: .opacity)) .onReceive(timer) { _ in - guard second < 2 else { + guard second < until else { closedPopup() return } @@ -61,7 +80,7 @@ public struct PokitLinkPopup: View { .multilineTextAlignment(.leading) .foregroundStyle(textColor) - if case let .link(_, url) = type { + if case let .link(_, url, _) = type { Text(url) .lineLimit(1) .pokitFont(.detail2) @@ -167,26 +186,34 @@ public struct PokitLinkPopup: View { private var title: String { switch type { - case let .link(title, _), - let .text(title), - let .success(title), - let .error(title), - let .warning(title), - let .report(title): + case let .link(title, _, _), + let .text(title, _), + let .success(title, _), + let .error(title, _), + let .warning(title, _), + let .report(title, _): return title default: return "" } } + + public func onAction(_ action: @escaping () -> Void) -> Self { + PokitLinkPopup( + type: self.$type, + until: self.until, + action: action + ) + } } public extension PokitLinkPopup { enum PopupType: Equatable { - case link(title: String, url: String) - case text(title: String) - case success(title: String) - case error(title: String) - case warning(title: String) - case report(title: String) + case link(title: String, url: String, until: Int = 2) + case text(title: String, until: Int = 2) + case success(title: String, until: Int = 2) + case error(title: String, until: Int = 2) + case warning(title: String, until: Int = 2) + case report(title: String, until: Int = 2) } } diff --git a/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingView.swift b/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingView.swift index e8d33455..76c032e9 100644 --- a/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingView.swift +++ b/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingView.swift @@ -46,10 +46,9 @@ public extension ContentSettingView { } .overlay(alignment: .bottom) { if store.linkPopup != nil { - PokitLinkPopup( - type: $store.linkPopup, - action: { send(.링크팝업_버튼_눌렀을때, animation: .pokitSpring) } - ) + PokitLinkPopup(type: $store.linkPopup) + .onAction { send(.링크팝업_버튼_눌렀을때, animation: .pokitSpring) } + } } .pokitMaxWidth() diff --git a/Projects/Feature/FeaturePokit/Sources/PokitLinkEditView.swift b/Projects/Feature/FeaturePokit/Sources/PokitLinkEditView.swift index 1c6b7e56..a92a1689 100644 --- a/Projects/Feature/FeaturePokit/Sources/PokitLinkEditView.swift +++ b/Projects/Feature/FeaturePokit/Sources/PokitLinkEditView.swift @@ -63,11 +63,9 @@ public extension PokitLinkEditView { } .overlay(alignment: .bottom) { if store.linkPopup != nil { - PokitLinkPopup( - type: $store.linkPopup, - action: { send(.링크팝업_버튼_눌렀을때, animation: .pokitSpring) } - ) - .pokitMaxWidth() + PokitLinkPopup(type: $store.linkPopup) + .onAction { send(.링크팝업_버튼_눌렀을때, animation: .pokitSpring) } + .pokitMaxWidth() } } /// fullScreenCover를 통해 새로운 Destination을 만들었음 From caed06068dab9c7b4f2f3d3c35734a2f3adec6cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Sun, 7 Sep 2025 01:19:59 +0900 Subject: [PATCH 08/42] =?UTF-8?q?[fix]=20#203=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=EC=99=84=EB=A1=9C=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EB=85=B8?= =?UTF-8?q?=EC=B6=9C=EC=8B=9C=EA=B0=84=204=EC=B4=88=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20=EB=B0=8F=20=EB=B3=B4=EA=B8=B0=20=ED=85=8D=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../App/Sources/MainTab/MainTabFeatureView.swift | 14 +++++--------- Projects/App/Sources/MainTab/MainTabPath.swift | 6 +++--- Projects/Util/Sources/Constants.swift | 2 +- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/Projects/App/Sources/MainTab/MainTabFeatureView.swift b/Projects/App/Sources/MainTab/MainTabFeatureView.swift index 0fbfd373..4dcee06a 100644 --- a/Projects/App/Sources/MainTab/MainTabFeatureView.swift +++ b/Projects/App/Sources/MainTab/MainTabFeatureView.swift @@ -74,10 +74,8 @@ public extension MainTabView { } if self.store.linkPopup != nil { - PokitLinkPopup( - type: $store.linkPopup, - action: { send(.링크팝업_버튼_눌렀을때, animation: .pokitSpring) } - ) + PokitLinkPopup(type: $store.linkPopup) + .onAction { send(.링크팝업_버튼_눌렀을때, animation: .pokitSpring) } } } } @@ -93,11 +91,9 @@ private extension MainTabView { .overlay(alignment: .bottom) { VStack(spacing: 0) { if store.linkPopup != nil { - PokitLinkPopup( - type: $store.linkPopup, - action: { send(.링크팝업_버튼_눌렀을때, animation: .pokitSpring) } - ) - .padding(.bottom, 20) + PokitLinkPopup(type: $store.linkPopup) + .onAction { send(.링크팝업_버튼_눌렀을때, animation: .pokitSpring) } + .padding(.bottom, 20) } bottomTabBar diff --git a/Projects/App/Sources/MainTab/MainTabPath.swift b/Projects/App/Sources/MainTab/MainTabPath.swift index 68d1e33c..fbd1782d 100644 --- a/Projects/App/Sources/MainTab/MainTabPath.swift +++ b/Projects/App/Sources/MainTab/MainTabPath.swift @@ -194,13 +194,13 @@ public extension MainTabFeature { case .검색: return .merge( .send(.path(.element(id: stackElementId, action: .검색(.delegate(.컨텐츠_검색))))), - .send(.inner(.링크팝업_활성화(.success(title: Constants.링크_저장_완료_문구))), animation: .pokitSpring) + .send(.inner(.링크팝업_활성화(.success(title: Constants.링크_저장_완료_문구, until: 4))), animation: .pokitSpring) ) default: - return .send(.inner(.링크팝업_활성화(.success(title: Constants.링크_저장_완료_문구))), animation: .pokitSpring) + return .send(.inner(.링크팝업_활성화(.success(title: Constants.링크_저장_완료_문구, until: 4))), animation: .pokitSpring) } case .recommend(.delegate(.저장하기_완료)): - return .send(.inner(.링크팝업_활성화(.success(title: Constants.링크_저장_완료_문구))), animation: .pokitSpring) + return .send(.inner(.링크팝업_활성화(.success(title: Constants.링크_저장_완료_문구, until: 4))), animation: .pokitSpring) /// - 각 화면에서 링크 복사 감지했을 때 (링크 추가 및 수정 화면 제외) case let .path(.element(_, action: .알림함(.delegate(.linkCopyDetected(url))))), let .path(.element(_, action: .검색(.delegate(.linkCopyDetected(url))))), diff --git a/Projects/Util/Sources/Constants.swift b/Projects/Util/Sources/Constants.swift index 4c12153f..a5ff72ed 100644 --- a/Projects/Util/Sources/Constants.swift +++ b/Projects/Util/Sources/Constants.swift @@ -28,7 +28,7 @@ public enum Constants { public static let 포킷_최대_갯수_문구: String = "최대 30개의 포킷을 생성할 수 있습니다.\n포킷을 삭제한 뒤에 추가해주세요." public static let 복사한_링크_저장하기_문구: String = "복사한 링크 저장하기" public static let 제목을_입력해주세요_문구: String = "제목을 입력해주세요" - public static let 링크_저장_완료_문구: String = "링크 저장 완료" + public static let 링크_저장_완료_문구: String = "저정한 링크 보러가기" public static let 메모_수정_완료_문구: String = "메모 수정 완료" public static let 한글_영어_숫자_입력_문구: String = "한글, 영어, 숫자로만 입력이 가능합니다." From a775de1cac60af58f9d0e45e269c02bda4387e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 9 Sep 2025 21:36:46 +0900 Subject: [PATCH 09/42] =?UTF-8?q?[fix]=20#203=20=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=EB=A7=81=ED=81=AC=20=ED=8F=AC=ED=82=B7=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=ED=9B=84=20=EC=84=A0=ED=83=9D=EB=90=9C=20=EB=A7=81=ED=81=AC?= =?UTF-8?q?=EA=B0=80=20=EC=97=86=EC=9D=84=20=EC=8B=9C=EC=97=90=20"?= =?UTF-8?q?=EB=A7=81=ED=81=AC=20=EC=82=AD=EC=A0=9C",=20"=ED=8F=AC=ED=82=B7?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99"=20=EC=9D=B4=20=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94=20=EB=90=98=EC=96=B4=EC=9E=88=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FeaturePokit/Sources/PokitLinkEditFeature.swift | 6 +----- .../Feature/FeaturePokit/Sources/PokitLinkEditView.swift | 2 +- .../Sources/Sheet/PokitLinkEditFloatView.swift | 8 ++++---- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Projects/Feature/FeaturePokit/Sources/PokitLinkEditFeature.swift b/Projects/Feature/FeaturePokit/Sources/PokitLinkEditFeature.swift index 61a291ce..2b9df4b5 100644 --- a/Projects/Feature/FeaturePokit/Sources/PokitLinkEditFeature.swift +++ b/Projects/Feature/FeaturePokit/Sources/PokitLinkEditFeature.swift @@ -32,7 +32,7 @@ public struct PokitLinkEditFeature { var list = IdentifiedArrayOf() /// 선택한 링크 목록 var selectedItems = IdentifiedArrayOf() - var isActive: Bool = false + var isActive: Bool { !selectedItems.isEmpty } /// 포킷 이동 눌렀을 때 sheet var categorySelectSheetPresetend: Bool = false var linkDeleteSheetPresented: Bool = false @@ -160,8 +160,6 @@ private extension PokitLinkEditFeature { } else { state.selectedItems.append(item) } - - state.isActive = !state.selectedItems.isEmpty return .none case let .카테고리_선택했을때(pokit): @@ -257,12 +255,10 @@ private extension PokitLinkEditFeature { case .전체선택_버튼_눌렀을때: state.selectedItems = state.list - state.isActive = !state.selectedItems.isEmpty return .none case .전체해제_버튼_눌렀을때: state.selectedItems.removeAll() - state.isActive = !state.selectedItems.isEmpty return .none case .포킷이동_버튼_눌렀을때: diff --git a/Projects/Feature/FeaturePokit/Sources/PokitLinkEditView.swift b/Projects/Feature/FeaturePokit/Sources/PokitLinkEditView.swift index a92a1689..26307a7c 100644 --- a/Projects/Feature/FeaturePokit/Sources/PokitLinkEditView.swift +++ b/Projects/Feature/FeaturePokit/Sources/PokitLinkEditView.swift @@ -121,7 +121,7 @@ private extension PokitLinkEditView { var actionFloatButtonView: some View { PokitLinkEditFloatView( - isActive: $store.isActive, + isActive: store.isActive, delegateSend: { store.send(.scope(.floatButtonAction($0))) } ) } diff --git a/Projects/Feature/FeaturePokit/Sources/Sheet/PokitLinkEditFloatView.swift b/Projects/Feature/FeaturePokit/Sources/Sheet/PokitLinkEditFloatView.swift index 5b94da51..db0bc79b 100644 --- a/Projects/Feature/FeaturePokit/Sources/Sheet/PokitLinkEditFloatView.swift +++ b/Projects/Feature/FeaturePokit/Sources/Sheet/PokitLinkEditFloatView.swift @@ -11,14 +11,14 @@ import SwiftUI public struct PokitLinkEditFloatView: View { /// 전체 선택/해제 toggle @State private var isChecked: Bool = false - @Binding private var isActive: Bool + private let isActive: Bool private let delegateSend: ((PokitLinkEditFloatView.Delegate) -> Void)? public init( - isActive: Binding, + isActive: Bool, delegateSend: ((PokitLinkEditFloatView.Delegate) -> Void)? ) { - self._isActive = isActive + self.isActive = isActive self.delegateSend = delegateSend } @@ -114,7 +114,7 @@ public extension PokitLinkEditFloatView { } #Preview { PokitLinkEditFloatView( - isActive: .constant(true), + isActive: true, delegateSend: {_ in } ).padding(20) } From f2a840324f3b97f48acce1ac5f8f895e24f3bb3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 9 Sep 2025 22:06:25 +0900 Subject: [PATCH 10/42] =?UTF-8?q?[chore]=20#203=202.0.1=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/App/Resources/Pokit-info.plist | 2 +- Projects/App/ShareExtension/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Projects/App/Resources/Pokit-info.plist b/Projects/App/Resources/Pokit-info.plist index 98d454b2..ff684a8a 100644 --- a/Projects/App/Resources/Pokit-info.plist +++ b/Projects/App/Resources/Pokit-info.plist @@ -21,7 +21,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.0.0 + 2.0.1 CFBundleURLTypes diff --git a/Projects/App/ShareExtension/Info.plist b/Projects/App/ShareExtension/Info.plist index 8a334c06..c2ade449 100644 --- a/Projects/App/ShareExtension/Info.plist +++ b/Projects/App/ShareExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleName Pokit CFBundleShortVersionString - 1.0.9 + 2.0.1 CFBundleURLTypes From 9b9e9bbd6709b1ebac7f2e5d2274f1f0d8af9dd5 Mon Sep 17 00:00:00 2001 From: minhokim Date: Sun, 14 Sep 2025 20:15:01 +0900 Subject: [PATCH 11/42] =?UTF-8?q?fix:=20build=20test=20=EC=8B=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EA=B0=80=EB=8A=A5=ED=95=9C=20iOS=20?= =?UTF-8?q?=EC=8B=9C=EB=AE=AC=EB=A0=88=EC=9D=B4=ED=84=B0=EB=A5=BC=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=A0=20=EC=88=98=20=EC=9E=88=EA=B2=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 1 + 1 file changed, 1 insertion(+) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 164214f1..d3b79d90 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -41,6 +41,7 @@ platform :ios do workspace: "Pokit.xcworkspace", scheme: "App", configuration: "Debug", + destination: "generic/platform=iOS Simulator", export_method: "development", export_options: { provisioningProfiles: { From 320879737539bcde5f7f718de730487b8277207a Mon Sep 17 00:00:00 2001 From: minhokim Date: Sun, 14 Sep 2025 20:42:51 +0900 Subject: [PATCH 12/42] =?UTF-8?q?fix:=20destination=20=EB=AA=85=EC=8B=9C?= =?UTF-8?q?=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=A7=80=EC=A0=95=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index d3b79d90..04ed08e4 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -41,7 +41,7 @@ platform :ios do workspace: "Pokit.xcworkspace", scheme: "App", configuration: "Debug", - destination: "generic/platform=iOS Simulator", + destination: "platform=iOS Simulator,name=iPhone 15,OS=17.5", export_method: "development", export_options: { provisioningProfiles: { @@ -73,6 +73,7 @@ platform :ios do workspace: "Pokit.xcworkspace", scheme: "App", configuration: "Release", + destination: "generic/platform=iOS", export_method: "app-store", export_options: { provisioningProfiles: { From d49f81c4a4a6b2afdef371ec1811dbb610505923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Sun, 21 Sep 2025 23:04:29 +0900 Subject: [PATCH 13/42] =?UTF-8?q?[fix]=20#205=20=EC=9B=8C=ED=81=AC?= =?UTF-8?q?=ED=94=8C=EB=A1=9C=EC=9A=B0=20=ED=99=98=EA=B2=BD=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/appstore_release.yml | 15 ++++++++++++++- .github/workflows/build_test.yml | 15 ++++++++++++++- .github/workflows/develop_hotfix.yml | 13 +++++++++++++ .github/workflows/release_update.yml | 7 ++++++- .github/workflows/testflight_release.yml | 15 ++++++++++++++- 5 files changed, 61 insertions(+), 4 deletions(-) diff --git a/.github/workflows/appstore_release.yml b/.github/workflows/appstore_release.yml index 0f63aeb7..dcbd8880 100644 --- a/.github/workflows/appstore_release.yml +++ b/.github/workflows/appstore_release.yml @@ -15,10 +15,23 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true + - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '16.1' + xcode-version: latest-stable + + - name: Init Xcode + run: | + sudo xcode-select -s "$(xcode-select -p)" + sudo xcodebuild -runFirstLaunch + sudo xcodebuild -license accept + xcodebuild -version + xcodebuild -showsdks - uses: shimataro/ssh-key-action@v2 with: diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index f7b026c3..4486d2d3 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -17,10 +17,23 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true + - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '16.1' + xcode-version: latest-stable + + - name: Init Xcode + run: | + sudo xcode-select -s "$(xcode-select -p)" + sudo xcodebuild -runFirstLaunch + sudo xcodebuild -license accept + xcodebuild -version + xcodebuild -showsdks - uses: shimataro/ssh-key-action@v2 with: diff --git a/.github/workflows/develop_hotfix.yml b/.github/workflows/develop_hotfix.yml index 01172def..ba4a2729 100644 --- a/.github/workflows/develop_hotfix.yml +++ b/.github/workflows/develop_hotfix.yml @@ -17,11 +17,24 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true + - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: latest-stable + - name: Init Xcode + run: | + sudo xcode-select -s "$(xcode-select -p)" + sudo xcodebuild -runFirstLaunch + sudo xcodebuild -license accept + xcodebuild -version + xcodebuild -showsdks + - uses: shimataro/ssh-key-action@v2 with: key: ${{ secrets.SSH_KEY }} diff --git a/.github/workflows/release_update.yml b/.github/workflows/release_update.yml index 8104f099..77d3f563 100644 --- a/.github/workflows/release_update.yml +++ b/.github/workflows/release_update.yml @@ -18,8 +18,13 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true + - name: Install GitHub CLI - run: sudo apt-get install gh + run: brew install gh - name: Update Release env: diff --git a/.github/workflows/testflight_release.yml b/.github/workflows/testflight_release.yml index 6b7a0b6f..9c18aa02 100644 --- a/.github/workflows/testflight_release.yml +++ b/.github/workflows/testflight_release.yml @@ -20,10 +20,23 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true + - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '16.1' + xcode-version: latest-stable + + - name: Init Xcode + run: | + sudo xcode-select -s "$(xcode-select -p)" + sudo xcodebuild -runFirstLaunch + sudo xcodebuild -license accept + xcodebuild -version + xcodebuild -showsdks - uses: shimataro/ssh-key-action@v2 with: From 765e31f57faa36cdc95a892cb24c96d6f211efcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Sun, 21 Sep 2025 23:15:48 +0900 Subject: [PATCH 14/42] =?UTF-8?q?[fix]=20#205=20fastlane=20=EC=84=A4?= =?UTF-8?q?=EC=B9=98=20=EC=BB=A4=EB=A7=A8=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/appstore_release.yml | 3 +++ .github/workflows/build_test.yml | 3 +++ .github/workflows/develop_hotfix.yml | 3 +++ .github/workflows/release_update.yml | 3 +++ .github/workflows/testflight_release.yml | 3 +++ 5 files changed, 15 insertions(+) diff --git a/.github/workflows/appstore_release.yml b/.github/workflows/appstore_release.yml index dcbd8880..07c8f5d7 100644 --- a/.github/workflows/appstore_release.yml +++ b/.github/workflows/appstore_release.yml @@ -20,6 +20,9 @@ jobs: ruby-version: '3.2' bundler-cache: true + - name: Install Fastlane + run: gem install fastlane -N + - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 4486d2d3..e33da7f3 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -22,6 +22,9 @@ jobs: ruby-version: '3.2' bundler-cache: true + - name: Install Fastlane + run: gem install fastlane -N + - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: diff --git a/.github/workflows/develop_hotfix.yml b/.github/workflows/develop_hotfix.yml index ba4a2729..f4c30320 100644 --- a/.github/workflows/develop_hotfix.yml +++ b/.github/workflows/develop_hotfix.yml @@ -22,6 +22,9 @@ jobs: ruby-version: '3.2' bundler-cache: true + - name: Install Fastlane + run: gem install fastlane -N + - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: diff --git a/.github/workflows/release_update.yml b/.github/workflows/release_update.yml index 77d3f563..e54d08a6 100644 --- a/.github/workflows/release_update.yml +++ b/.github/workflows/release_update.yml @@ -23,6 +23,9 @@ jobs: ruby-version: '3.2' bundler-cache: true + - name: Install Fastlane + run: gem install fastlane -N + - name: Install GitHub CLI run: brew install gh diff --git a/.github/workflows/testflight_release.yml b/.github/workflows/testflight_release.yml index 9c18aa02..07815561 100644 --- a/.github/workflows/testflight_release.yml +++ b/.github/workflows/testflight_release.yml @@ -25,6 +25,9 @@ jobs: ruby-version: '3.2' bundler-cache: true + - name: Install Fastlane + run: gem install fastlane -N + - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: From 84b6d597dec0a824c245c22c446389b245cc8985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Mon, 22 Sep 2025 23:02:28 +0900 Subject: [PATCH 15/42] =?UTF-8?q?Revert=20"[fix]=20#205=20fastlane=20?= =?UTF-8?q?=EC=84=A4=EC=B9=98=20=EC=BB=A4=EB=A7=A8=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 765e31f57faa36cdc95a892cb24c96d6f211efcc. --- .github/workflows/appstore_release.yml | 3 --- .github/workflows/build_test.yml | 3 --- .github/workflows/develop_hotfix.yml | 3 --- .github/workflows/release_update.yml | 3 --- .github/workflows/testflight_release.yml | 3 --- 5 files changed, 15 deletions(-) diff --git a/.github/workflows/appstore_release.yml b/.github/workflows/appstore_release.yml index 07c8f5d7..dcbd8880 100644 --- a/.github/workflows/appstore_release.yml +++ b/.github/workflows/appstore_release.yml @@ -20,9 +20,6 @@ jobs: ruby-version: '3.2' bundler-cache: true - - name: Install Fastlane - run: gem install fastlane -N - - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index e33da7f3..4486d2d3 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -22,9 +22,6 @@ jobs: ruby-version: '3.2' bundler-cache: true - - name: Install Fastlane - run: gem install fastlane -N - - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: diff --git a/.github/workflows/develop_hotfix.yml b/.github/workflows/develop_hotfix.yml index f4c30320..ba4a2729 100644 --- a/.github/workflows/develop_hotfix.yml +++ b/.github/workflows/develop_hotfix.yml @@ -22,9 +22,6 @@ jobs: ruby-version: '3.2' bundler-cache: true - - name: Install Fastlane - run: gem install fastlane -N - - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: diff --git a/.github/workflows/release_update.yml b/.github/workflows/release_update.yml index e54d08a6..77d3f563 100644 --- a/.github/workflows/release_update.yml +++ b/.github/workflows/release_update.yml @@ -23,9 +23,6 @@ jobs: ruby-version: '3.2' bundler-cache: true - - name: Install Fastlane - run: gem install fastlane -N - - name: Install GitHub CLI run: brew install gh diff --git a/.github/workflows/testflight_release.yml b/.github/workflows/testflight_release.yml index 07815561..9c18aa02 100644 --- a/.github/workflows/testflight_release.yml +++ b/.github/workflows/testflight_release.yml @@ -25,9 +25,6 @@ jobs: ruby-version: '3.2' bundler-cache: true - - name: Install Fastlane - run: gem install fastlane -N - - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: From c683dfc15a40e01c2f7e4fb0bc75828f18616d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Mon, 22 Sep 2025 23:02:40 +0900 Subject: [PATCH 16/42] =?UTF-8?q?Revert=20"[fix]=20#205=20=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=ED=94=8C=EB=A1=9C=EC=9A=B0=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EC=88=98=EC=A0=95"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit d49f81c4a4a6b2afdef371ec1811dbb610505923. --- .github/workflows/appstore_release.yml | 15 +-------------- .github/workflows/build_test.yml | 15 +-------------- .github/workflows/develop_hotfix.yml | 13 ------------- .github/workflows/release_update.yml | 7 +------ .github/workflows/testflight_release.yml | 15 +-------------- 5 files changed, 4 insertions(+), 61 deletions(-) diff --git a/.github/workflows/appstore_release.yml b/.github/workflows/appstore_release.yml index dcbd8880..0f63aeb7 100644 --- a/.github/workflows/appstore_release.yml +++ b/.github/workflows/appstore_release.yml @@ -15,23 +15,10 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.2' - bundler-cache: true - - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable - - - name: Init Xcode - run: | - sudo xcode-select -s "$(xcode-select -p)" - sudo xcodebuild -runFirstLaunch - sudo xcodebuild -license accept - xcodebuild -version - xcodebuild -showsdks + xcode-version: '16.1' - uses: shimataro/ssh-key-action@v2 with: diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 4486d2d3..f7b026c3 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -17,23 +17,10 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.2' - bundler-cache: true - - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable - - - name: Init Xcode - run: | - sudo xcode-select -s "$(xcode-select -p)" - sudo xcodebuild -runFirstLaunch - sudo xcodebuild -license accept - xcodebuild -version - xcodebuild -showsdks + xcode-version: '16.1' - uses: shimataro/ssh-key-action@v2 with: diff --git a/.github/workflows/develop_hotfix.yml b/.github/workflows/develop_hotfix.yml index ba4a2729..01172def 100644 --- a/.github/workflows/develop_hotfix.yml +++ b/.github/workflows/develop_hotfix.yml @@ -17,24 +17,11 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.2' - bundler-cache: true - - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: latest-stable - - name: Init Xcode - run: | - sudo xcode-select -s "$(xcode-select -p)" - sudo xcodebuild -runFirstLaunch - sudo xcodebuild -license accept - xcodebuild -version - xcodebuild -showsdks - - uses: shimataro/ssh-key-action@v2 with: key: ${{ secrets.SSH_KEY }} diff --git a/.github/workflows/release_update.yml b/.github/workflows/release_update.yml index 77d3f563..8104f099 100644 --- a/.github/workflows/release_update.yml +++ b/.github/workflows/release_update.yml @@ -18,13 +18,8 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.2' - bundler-cache: true - - name: Install GitHub CLI - run: brew install gh + run: sudo apt-get install gh - name: Update Release env: diff --git a/.github/workflows/testflight_release.yml b/.github/workflows/testflight_release.yml index 9c18aa02..6b7a0b6f 100644 --- a/.github/workflows/testflight_release.yml +++ b/.github/workflows/testflight_release.yml @@ -20,23 +20,10 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.2' - bundler-cache: true - - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable - - - name: Init Xcode - run: | - sudo xcode-select -s "$(xcode-select -p)" - sudo xcodebuild -runFirstLaunch - sudo xcodebuild -license accept - xcodebuild -version - xcodebuild -showsdks + xcode-version: '16.1' - uses: shimataro/ssh-key-action@v2 with: From d66ec2a30c503be44b1ace5124ae368d28480949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Mon, 22 Sep 2025 23:06:01 +0900 Subject: [PATCH 17/42] =?UTF-8?q?[fix]=20#206=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20xcode=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index f7b026c3..d27957cc 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -20,7 +20,7 @@ jobs: - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '16.1' + xcode-version: latest-stable - uses: shimataro/ssh-key-action@v2 with: From a0deb0703db176da76aedc5d837d0f57edba68d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Mon, 22 Sep 2025 23:16:59 +0900 Subject: [PATCH 18/42] =?UTF-8?q?[fix]=20#206=20=EB=A7=A5os=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index d27957cc..e7a6fb32 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -12,7 +12,7 @@ on: jobs: build: - runs-on: macos-latest + runs-on: macos-26 steps: - uses: actions/checkout@v4 From 81b991dd5cc5d4a5d79c5df21bec1ed454d4b94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Mon, 22 Sep 2025 23:19:11 +0900 Subject: [PATCH 19/42] =?UTF-8?q?[fix]=20#206=20Fastfile=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 04ed08e4..164214f1 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -41,7 +41,6 @@ platform :ios do workspace: "Pokit.xcworkspace", scheme: "App", configuration: "Debug", - destination: "platform=iOS Simulator,name=iPhone 15,OS=17.5", export_method: "development", export_options: { provisioningProfiles: { @@ -73,7 +72,6 @@ platform :ios do workspace: "Pokit.xcworkspace", scheme: "App", configuration: "Release", - destination: "generic/platform=iOS", export_method: "app-store", export_options: { provisioningProfiles: { From 754ad1c8b74ff1c2fb82cb6015d8ca34d14bee40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Mon, 22 Sep 2025 23:28:30 +0900 Subject: [PATCH 20/42] =?UTF-8?q?[fix]=20#206=20=EC=9B=8C=ED=81=AC?= =?UTF-8?q?=ED=94=8C=EB=A1=9C=EC=9A=B0=20=ED=99=98=EA=B2=BD=20macos=2026,?= =?UTF-8?q?=20xcode=20latest=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/appstore_release.yml | 4 ++-- .github/workflows/develop_hotfix.yml | 2 +- .github/workflows/testflight_release.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/appstore_release.yml b/.github/workflows/appstore_release.yml index 0f63aeb7..86232d01 100644 --- a/.github/workflows/appstore_release.yml +++ b/.github/workflows/appstore_release.yml @@ -10,7 +10,7 @@ on: jobs: build: - runs-on: macos-latest + runs-on: macos-26 steps: - uses: actions/checkout@v4 @@ -18,7 +18,7 @@ jobs: - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '16.1' + xcode-version: latest-stable - uses: shimataro/ssh-key-action@v2 with: diff --git a/.github/workflows/develop_hotfix.yml b/.github/workflows/develop_hotfix.yml index 01172def..8a0ef29f 100644 --- a/.github/workflows/develop_hotfix.yml +++ b/.github/workflows/develop_hotfix.yml @@ -12,7 +12,7 @@ on: jobs: build: if: startsWith(github.event.head_commit.message, '[hotfix]') - runs-on: macos-latest + runs-on: macos-26 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/testflight_release.yml b/.github/workflows/testflight_release.yml index 6b7a0b6f..f8aebfd3 100644 --- a/.github/workflows/testflight_release.yml +++ b/.github/workflows/testflight_release.yml @@ -15,7 +15,7 @@ on: jobs: build: if: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true }} - runs-on: macos-latest + runs-on: macos-26 steps: - uses: actions/checkout@v4 @@ -23,7 +23,7 @@ jobs: - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '16.1' + xcode-version: latest-stable - uses: shimataro/ssh-key-action@v2 with: From 9e3da8b6dadc719134c8c55c672a5e3071b804a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 28 Oct 2025 20:32:00 +0900 Subject: [PATCH 21/42] =?UTF-8?q?[fix]=20#208=20=EC=84=A4=EC=A0=95=20->=20?= =?UTF-8?q?=EB=8B=89=EB=84=A4=EC=9E=84=20=EB=8B=A4=ED=81=AC=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FeatureSetting/Sources/Setting/PokitSettingView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Projects/Feature/FeatureSetting/Sources/Setting/PokitSettingView.swift b/Projects/Feature/FeatureSetting/Sources/Setting/PokitSettingView.swift index ac0f33ec..9beed405 100644 --- a/Projects/Feature/FeatureSetting/Sources/Setting/PokitSettingView.swift +++ b/Projects/Feature/FeatureSetting/Sources/Setting/PokitSettingView.swift @@ -90,6 +90,8 @@ private extension PokitSettingView { .frame(width: 40, height: 40) Text(store.user?.nickname ?? "") .pokitFont(.b1(.m)) + .foregroundStyle(.pokit(.text(.primary))) + Spacer() PokitTextButton( "프로필 편집", From 06dce6dccdecac5fea19a97b22ee9fe571916607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 28 Oct 2025 20:35:23 +0900 Subject: [PATCH 22/42] =?UTF-8?q?[fix]=20#208=20=ED=8F=AC=ED=82=B7=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EB=82=B4=20=EC=9D=B4=EB=8F=99=20=EB=B0=94?= =?UTF-8?q?=ED=85=80=EC=8B=9C=ED=8A=B8=20=EB=86=92=EC=9D=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FeatureCategoryDetail/Sources/CategoryDetailView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailView.swift b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailView.swift index 7e319ed7..0e66d0ff 100644 --- a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailView.swift +++ b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailView.swift @@ -98,6 +98,7 @@ public extension CategoryDetailView { action: { send(.카테고리_선택했을때($0)) } ) .presentationDragIndicator(.visible) + .presentationDetents([.medium, .large]) } else { PokitLoading() .presentationDragIndicator(.visible) From cccfd97da61dc4c689715a3dd37b5bd407b867e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 28 Oct 2025 20:35:52 +0900 Subject: [PATCH 23/42] =?UTF-8?q?[fix]=20#208=20pluscategory=20=EB=B0=94?= =?UTF-8?q?=ED=85=80=EC=8B=9C=ED=8A=B8=20ios=2026=20=EB=8C=80=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../App/Sources/MainTab/MainTabFeatureView.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Projects/App/Sources/MainTab/MainTabFeatureView.swift b/Projects/App/Sources/MainTab/MainTabFeatureView.swift index 4dcee06a..6388ef08 100644 --- a/Projects/App/Sources/MainTab/MainTabFeatureView.swift +++ b/Projects/App/Sources/MainTab/MainTabFeatureView.swift @@ -269,7 +269,14 @@ private extension MainTabView { var body: some View { GeometryReader { proxy in - let bottomSafeArea = proxy.safeAreaInsets.bottom + let bottomPadding: CGFloat = { + if #available(iOS 26.0, *) { + return 32 + } else { + return 48 + } + }() + HStack(spacing: 20) { Spacer() @@ -302,7 +309,7 @@ private extension MainTabView { Spacer() } - .padding(.bottom, 48 - bottomSafeArea) + .padding(.bottom, bottomPadding) .padding(.top, 36) .pokitPresentationCornerRadius() .pokitPresentationBackground() @@ -315,6 +322,7 @@ private extension MainTabView { } .presentationDetents([.height(self.height)]) } + .ignoresSafeArea(edges: .bottom) } } } From dfc1f399ca0d351d30f237f1080b5a12e8d5d263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 28 Oct 2025 20:36:09 +0900 Subject: [PATCH 24/42] =?UTF-8?q?[fix]=20#208=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EA=B8=B0=EB=B3=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EA=B0=80=EC=9E=A5=EC=9E=90=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/PokitProfileBottomSheet.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Projects/DSKit/Sources/Components/PokitProfileBottomSheet.swift b/Projects/DSKit/Sources/Components/PokitProfileBottomSheet.swift index 8a3df705..55f76f00 100644 --- a/Projects/DSKit/Sources/Components/PokitProfileBottomSheet.swift +++ b/Projects/DSKit/Sources/Components/PokitProfileBottomSheet.swift @@ -44,17 +44,21 @@ public extension PokitProfileBottomSheet { transaction: .init(animation: .pokitDissolve) ) { phase in if let image = phase.image { + let isSelected = item.imageURL == selectedImage?.imageURL + Button(action: { delegateSend?(.이미지_선택했을때(item)) }) { image .resizable() .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous)) } .buttonStyle(.plain) - .overlay { - if let selectedImage, item.imageURL == selectedImage.imageURL { - RoundedRectangle(cornerRadius: 12, style: .continuous) - .stroke(.pokit(.border(.brand)), lineWidth: 2) - } + .overlay(if: isSelected) { + RoundedRectangle(cornerRadius: 12, style: .continuous) + .stroke(.pokit(.border(.brand)), lineWidth: 2) + } + .overlay(if: !isSelected && item.id == 33) { + RoundedRectangle(cornerRadius: 12, style: .continuous) + .stroke(.pokit(.border(.tertiary)), lineWidth: 2) } } else { PokitSpinner() From d543a11a870e73d610f7ada3171c4267462135ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 28 Oct 2025 20:42:26 +0900 Subject: [PATCH 25/42] =?UTF-8?q?[feat]=20#208=20Amplitude=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tuist/Package.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tuist/Package.swift b/Tuist/Package.swift index d669a4e3..f6dc4df2 100644 --- a/Tuist/Package.swift +++ b/Tuist/Package.swift @@ -29,6 +29,7 @@ let package = Package( .package(url: "https://github.com/Kitura/Swift-JWT", from: "4.0.1"), .package(url: "https://github.com/kean/Nuke", from: "12.8.0"), .package(url: "https://github.com/scinfu/SwiftSoup", "2.7.0" ..< "2.7.5"), - .package(url: "https://github.com/kakao/kakao-ios-sdk", from: "2.22.5") + .package(url: "https://github.com/kakao/kakao-ios-sdk", from: "2.22.5"), + .package(url: "https://github.com/amplitude/Amplitude-Swift", from: "1.15.2") ] ) From 54bb9b5c5faefed24e185263d8be7a1b44457413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 18:30:40 +0900 Subject: [PATCH 26/42] =?UTF-8?q?[feat]=20#208=20apmplitude=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/CoreKit/Project.swift | 1 + .../Core/Analytics/AmplitudeManager.swift | 73 ++++++++++ .../Core/Analytics/AnalyticsEvent.swift | 135 ++++++++++++++++++ .../DependencyValues+Analytics.swift | 51 +++++++ 4 files changed, 260 insertions(+) create mode 100644 Projects/CoreKit/Sources/Core/Analytics/AmplitudeManager.swift create mode 100644 Projects/CoreKit/Sources/Core/Analytics/AnalyticsEvent.swift create mode 100644 Projects/CoreKit/Sources/Core/Analytics/DependencyValues+Analytics.swift diff --git a/Projects/CoreKit/Project.swift b/Projects/CoreKit/Project.swift index 2679bc2b..a42f182a 100644 --- a/Projects/CoreKit/Project.swift +++ b/Projects/CoreKit/Project.swift @@ -36,6 +36,7 @@ let coreKit: Target = .target( .external(name: "KakaoSDKCommon"), .external(name: "KakaoSDKShare"), .external(name: "KakaoSDKTemplate"), + .external(name: "AmplitudeSwift"), ], settings: .settings() ) diff --git a/Projects/CoreKit/Sources/Core/Analytics/AmplitudeManager.swift b/Projects/CoreKit/Sources/Core/Analytics/AmplitudeManager.swift new file mode 100644 index 00000000..dfb58ae8 --- /dev/null +++ b/Projects/CoreKit/Sources/Core/Analytics/AmplitudeManager.swift @@ -0,0 +1,73 @@ +import Foundation +import AmplitudeSwift + +/// Amplitude Analytics 관리자 +public final class AmplitudeManager { + public static let shared = AmplitudeManager() + + private var amplitude: Amplitude? + + private init() {} + + /// Amplitude 초기화 + /// - Parameters: + /// - apiKey: Amplitude API Key + /// - userId: 사용자 ID (옵셔널) + public func initialize(apiKey: String, userId: String? = nil) { + amplitude = Amplitude(configuration: Configuration( + apiKey: apiKey + )) + + if let userId = userId { + setUserId(userId) + } + } + + /// 사용자 ID 설정 + /// - Parameter userId: 사용자 ID + public func setUserId(_ userId: String) { + amplitude?.setUserId(userId: userId) + } + + /// 사용자 속성 설정 + /// - Parameter properties: 사용자 속성 + public func setUserProperties(_ properties: [String: Any]) { + let identify = Identify() + properties.forEach { key, value in + identify.set(property: key, value: value) + } + amplitude?.identify(identify: identify) + } + + /// 이벤트 전송 + /// - Parameter event: AnalyticsEvent + public func track(_ event: AnalyticsEvent) { + guard let amplitude = amplitude else { + print("⚠️ Amplitude가 초기화되지 않았습니다.") + return + } + + let eventProperties = event.properties.isEmpty ? nil : event.properties + amplitude.track( + eventType: event.eventName, + eventProperties: eventProperties + ) + + #if DEBUG + print("📊 [Analytics] \(event.eventName)") + if let properties = eventProperties { + print(" Properties: \(properties)") + } + #endif + } + + /// 이벤트 버퍼 즉시 전송 + public func flush() { + amplitude?.flush() + } + + /// Amplitude 리셋 (로그아웃 시 사용) + public func reset() { + amplitude?.reset() + } +} diff --git a/Projects/CoreKit/Sources/Core/Analytics/AnalyticsEvent.swift b/Projects/CoreKit/Sources/Core/Analytics/AnalyticsEvent.swift new file mode 100644 index 00000000..b877f1a6 --- /dev/null +++ b/Projects/CoreKit/Sources/Core/Analytics/AnalyticsEvent.swift @@ -0,0 +1,135 @@ +import Foundation + +/// Analytics 이벤트 타입 +public enum AnalyticsEvent { + case app_open(deviceOS: String, appVersion: String) + case view_splash + case login_start(method: LoginMethod) + case login_complete(method: LoginMethod) + case interest_select(interests: [String]) + case onboarding_complete + case view_home_pokit(entryPoint: String) + case view_home_recommend(entryPoint: String) + case add_folder(folderName: String) + case add_link(folderId: String, linkDomain: String) + case view_folder_detail(folderId: String) + case view_link_detail(linkId: String, linkDomain: String) + case share_link(linkId: String, shareTarget: String) + case session_end(duration: Int) + + /// 이벤트명 (rawValue) + public var eventName: String { + switch self { + case .app_open: return "app_open" + case .view_splash: return "view_splash" + case .login_start: return "login_start" + case .login_complete: return "login_complete" + case .interest_select: return "interest_select" + case .onboarding_complete: return "onboarding_complete" + case .view_home_pokit: return "view_home_pokit" + case .view_home_recommend: return "view_home_recommend" + case .add_folder: return "add_folder" + case .add_link: return "add_link" + case .view_folder_detail: return "view_folder_detail" + case .view_link_detail: return "view_link_detail" + case .share_link: return "share_link" + case .session_end: return "session_end" + } + } + + /// Amplitude에 전송할 속성값 + public var properties: [String: Any] { + switch self { + case let .app_open(deviceOS, appVersion): + return [ + PropertyKey.device_os.rawValue: deviceOS, + PropertyKey.app_version.rawValue: appVersion + ] + + case .view_splash: + return [:] + + case let .login_start(method): + return [PropertyKey.method.rawValue: method.rawValue] + + case let .login_complete(method): + return [PropertyKey.method.rawValue: method.rawValue] + + case let .interest_select(interests): + return [PropertyKey.interests.rawValue: interests] + + case .onboarding_complete: + return [:] + + case let .view_home_pokit(entryPoint): + return [PropertyKey.entry_point.rawValue: entryPoint] + + case let .view_home_recommend(entryPoint): + return [PropertyKey.entry_point.rawValue: entryPoint] + + case let .add_folder(folderName): + return [PropertyKey.folder_name.rawValue: folderName] + + case let .add_link(folderId, linkDomain): + return [ + PropertyKey.folder_id.rawValue: folderId, + PropertyKey.link_domain.rawValue: linkDomain + ] + + case let .view_folder_detail(folderId): + return [PropertyKey.folder_id.rawValue: folderId] + + case let .view_link_detail(linkId, linkDomain): + return [ + PropertyKey.link_id.rawValue: linkId, + PropertyKey.link_domain.rawValue: linkDomain + ] + + case let .share_link(linkId, shareTarget): + return [ + PropertyKey.link_id.rawValue: linkId, + PropertyKey.share_target.rawValue: shareTarget + ] + + case let .session_end(duration): + return [PropertyKey.duration.rawValue: duration] + } + } +} + +/// Analytics 속성 키 +public enum PropertyKey: String { + case device_os + case app_version + case method + case interests + case entry_point + case folder_name + case folder_id + case link_domain + case link_id + case share_target + case duration +} + +/// 로그인 방식 +public enum LoginMethod: String { + case apple = "Apple" + case google = "Google" + case kakao = "Kakao" +} + +extension LoginMethod { + init?(authPlatform: String) { + switch authPlatform.lowercased() { + case "apple": + self = .apple + case "google": + self = .google + case "kakao": + self = .kakao + default: + return nil + } + } +} diff --git a/Projects/CoreKit/Sources/Core/Analytics/DependencyValues+Analytics.swift b/Projects/CoreKit/Sources/Core/Analytics/DependencyValues+Analytics.swift new file mode 100644 index 00000000..034ac5b6 --- /dev/null +++ b/Projects/CoreKit/Sources/Core/Analytics/DependencyValues+Analytics.swift @@ -0,0 +1,51 @@ +import Dependencies +import Foundation + +/// Amplitude Client Protocol +public struct AmplitudeClient { + public var initialize: @Sendable (String, String?) -> Void + public var track: @Sendable (AnalyticsEvent) -> Void + public var setUserId: @Sendable (String) -> Void + public var setUserProperties: @Sendable ([String: Any]) -> Void + public var flush: @Sendable () -> Void + public var reset: @Sendable () -> Void +} + +public extension DependencyValues { + var amplitude: AmplitudeClient { + get { self[AmplitudeClientKey.self] } + set { self[AmplitudeClientKey.self] = newValue } + } +} + +private enum AmplitudeClientKey: DependencyKey { + static let liveValue = AmplitudeClient( + initialize: { apiKey, userId in + AmplitudeManager.shared.initialize(apiKey: apiKey, userId: userId) + }, + track: { event in + AmplitudeManager.shared.track(event) + }, + setUserId: { userId in + AmplitudeManager.shared.setUserId(userId) + }, + setUserProperties: { properties in + AmplitudeManager.shared.setUserProperties(properties) + }, + flush: { + AmplitudeManager.shared.flush() + }, + reset: { + AmplitudeManager.shared.reset() + } + ) + + static let testValue = AmplitudeClient( + initialize: { _, _ in }, + track: { _ in }, + setUserId: { _ in }, + setUserProperties: { _ in }, + flush: { }, + reset: { } + ) +} From 80b30fdc41f6170040aae2b81329c2544677d905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 18:30:56 +0900 Subject: [PATCH 27/42] =?UTF-8?q?[feat]=20#208=20amplitude=20-=20=EC=95=B1?= =?UTF-8?q?=20=EC=8B=A4=ED=96=89=20=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/App/Sources/AppDelegate/AppDelegate.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Projects/App/Sources/AppDelegate/AppDelegate.swift b/Projects/App/Sources/AppDelegate/AppDelegate.swift index f72ea97e..309c8128 100644 --- a/Projects/App/Sources/AppDelegate/AppDelegate.swift +++ b/Projects/App/Sources/AppDelegate/AppDelegate.swift @@ -6,19 +6,25 @@ // import SwiftUI +import UIKit import ComposableArchitecture import Firebase import FirebaseMessaging import GoogleSignIn +import Dependencies final class AppDelegate: NSObject { + @Dependency(\.amplitude) + private var amplitude + let store = Store(initialState: AppDelegateFeature.State()) { AppDelegateFeature() } } //MARK: - UIApplicationDelegate extension AppDelegate: UIApplicationDelegate { + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { if GIDSignIn.sharedInstance.handle(url) { return true } return false @@ -30,6 +36,14 @@ extension AppDelegate: UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { self.store.send(.didFinishLaunching) + + // 운영체제 버전 (ex: "iOS 18.0.0") + let osVersion = "iOS \(UIDevice.current.systemVersion)" + + // 앱 번들 버전 (ex: "2.0.1") + let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" + + amplitude.track(.app_open(deviceOS: osVersion, appVersion: appVersion)) return true } From f21a0809aa5d17d9dd1cee1219421a65ae124dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 18:32:40 +0900 Subject: [PATCH 28/42] =?UTF-8?q?[feat]=20#208=20amplitude=20-=20=EC=8A=A4?= =?UTF-8?q?=ED=94=8C=EB=9E=98=EC=8B=9C=20=ED=99=94=EB=A9=B4=20=EB=85=B8?= =?UTF-8?q?=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/FeatureIntro/Sources/Splash/SplashFeature.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Projects/Feature/FeatureIntro/Sources/Splash/SplashFeature.swift b/Projects/Feature/FeatureIntro/Sources/Splash/SplashFeature.swift index 3f3cdcdd..4874c209 100644 --- a/Projects/Feature/FeatureIntro/Sources/Splash/SplashFeature.swift +++ b/Projects/Feature/FeatureIntro/Sources/Splash/SplashFeature.swift @@ -27,6 +27,9 @@ public struct SplashFeature { var keychain @Dependency(VersionClient.self) var versionClient + @Dependency(\.amplitude) + var amplitude + /// - State @ObservableState public struct State { @@ -103,6 +106,7 @@ private extension SplashFeature { case .onAppear: return .run { [isNeedSessionDeleted = state.isNeedSessionDeleted] send in + amplitude.track(.view_splash) try await self.clock.sleep(for: .milliseconds(2000)) /// Version Check let response = try await versionClient.버전체크().toDomain() From 2cfce98479e5a064614924697479ad21bf0376b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 18:35:20 +0900 Subject: [PATCH 29/42] =?UTF-8?q?[feat]=20#208=20amplitude=20-=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=EB=8F=84,=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/FeatureLogin/Sources/Login/LoginFeature.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift b/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift index edc5e139..ce591cc9 100644 --- a/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift +++ b/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift @@ -23,6 +23,8 @@ public struct LoginFeature { var userDefaults @Dependency(KeychainClient.self) var keychain + @Dependency(\.amplitude) + var amplitude /// - State @ObservableState public struct State { @@ -108,8 +110,10 @@ private extension LoginFeature { func handleViewAction(_ action: Action.View, state: inout State) -> Effect { switch action { case .애플로그인_버튼_눌렀을때: + amplitude.track(.login_start(method: .apple)) return .send(.async(.애플로그인_소셜_API)) case .구글로그인_버튼_눌렀을때: + amplitude.track(.login_start(method: .google)) return .send(.async(.구글로그인_소셜_API)) } } @@ -169,7 +173,7 @@ private extension LoginFeature { let appleTokenRequest = AppleTokenRequest(authCode: authCode, jwt: jwt) let appleTokenResponse = try await authClient.apple(appleTokenRequest) keychain.save(.serverRefresh, appleTokenResponse.refresh_token) - + amplitude.track(.login_complete(method: .apple)) await send(.inner(.로그인_이후_화면이동(isRegistered: tokenResponse.isRegistered))) } case let .구글로그인_API(response): @@ -185,6 +189,7 @@ private extension LoginFeature { keychain.save(.accessToken, tokenResponse.accessToken) keychain.save(.refreshToken, tokenResponse.refreshToken) keychain.save(.serverRefresh, response.serverRefreshToken) + amplitude.track(.login_complete(method: .google)) await send(.inner(.로그인_이후_화면이동(isRegistered: tokenResponse.isRegistered))) } From bffdacca48b90fa878c2f6223c281dcadf551d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 18:38:01 +0900 Subject: [PATCH 30/42] =?UTF-8?q?[feat]=20#208=20amplitude=20-=20=EC=98=A8?= =?UTF-8?q?=EB=B3=B4=EB=94=A9=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FeatureLogin/Sources/SignUpDone/SignUpDoneFeature.swift | 6 ++++++ .../FeatureLogin/Sources/SignUpDone/SignUpDoneView.swift | 1 + 2 files changed, 7 insertions(+) diff --git a/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneFeature.swift b/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneFeature.swift index 4a9a50ff..6a510157 100644 --- a/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneFeature.swift +++ b/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneFeature.swift @@ -14,6 +14,8 @@ import Util public struct SignUpDoneFeature { /// - Dependency @Dependency(\.dismiss) var dismiss + @Dependency(\.amplitude) + var amplitude /// - State @ObservableState public struct State: Equatable { @@ -41,6 +43,7 @@ public struct SignUpDoneFeature { case 제목_나타났을때 case 폭죽_이미지_나타났을때 case 푸키_이미지_나타났을때 + case 뷰가_나타났을때 } public enum InnerAction: Equatable { case 없음 } public enum AsyncAction: Equatable { case 없음 } @@ -95,6 +98,9 @@ private extension SignUpDoneFeature { case .푸키_이미지_나타났을때: state.pookiIsAppear = true return .none + case .뷰가_나타났을때: + amplitude.track(.onboarding_complete) + return .none } } /// - Inner Effect diff --git a/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneView.swift b/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneView.swift index 018e4360..8c47530d 100644 --- a/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneView.swift +++ b/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneView.swift @@ -49,6 +49,7 @@ public extension SignUpDoneView { } .ignoresSafeArea(edges: .bottom) .navigationBarBackButtonHidden() + .onAppear { send(.뷰가_나타났을때) } } } } From 8832cb4509719fdd8abc9c56b744ef8f691666fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 18:39:55 +0900 Subject: [PATCH 31/42] =?UTF-8?q?[feat]=20#208=20amplitude=20-=20=EA=B4=80?= =?UTF-8?q?=EC=8B=AC=EC=82=AC=20=EC=84=A0=ED=83=9D=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FeatureLogin/Sources/SelectField/SelectFieldFeature.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Projects/Feature/FeatureLogin/Sources/SelectField/SelectFieldFeature.swift b/Projects/Feature/FeatureLogin/Sources/SelectField/SelectFieldFeature.swift index 38771fd6..7fa8b6ba 100644 --- a/Projects/Feature/FeatureLogin/Sources/SelectField/SelectFieldFeature.swift +++ b/Projects/Feature/FeatureLogin/Sources/SelectField/SelectFieldFeature.swift @@ -14,6 +14,8 @@ public struct SelectFieldFeature { /// - Dependency @Dependency(\.dismiss) var dismiss @Dependency(UserClient.self) var userClient + @Dependency(\.amplitude) + var amplitude /// - State @ObservableState public struct State: Equatable { @@ -93,6 +95,7 @@ private extension SelectFieldFeature { } case .nextButtonTapped: let interests = Array(state.selectedFields) + amplitude.track(.interest_select(interests: interests)) return .send(.delegate(.pushSignUpDoneView(interests: interests))) case .backButtonTapped: return .run { _ in await self.dismiss() } From de4cec7737a83a2b94cac2d43e7eb791ac7df6d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 18:46:15 +0900 Subject: [PATCH 32/42] =?UTF-8?q?[feat]=20#208=20amplitude=20-=20=ED=8F=AC?= =?UTF-8?q?=ED=82=B7,=20=EC=B6=94=EC=B2=9C=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20=EC=A7=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../App/Sources/MainTab/MainTabFeature.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Projects/App/Sources/MainTab/MainTabFeature.swift b/Projects/App/Sources/MainTab/MainTabFeature.swift index 186d6bda..9e4695ea 100644 --- a/Projects/App/Sources/MainTab/MainTabFeature.swift +++ b/Projects/App/Sources/MainTab/MainTabFeature.swift @@ -24,6 +24,9 @@ public struct MainTabFeature { private var categoryClient @Dependency(UserDefaultsClient.self) private var userDefaults + @Dependency(\.amplitude) + private var amplitude + /// - State @ObservableState public struct State: Equatable { @@ -103,6 +106,14 @@ public struct MainTabFeature { guard state.linkPopup == nil else { return .none } state.categoryOfSavedContent = nil return .none + case .binding(\.selectedTab): + switch state.selectedTab { + case .pokit: + amplitude.track(.view_home_pokit(entryPoint: "pokit")) + case .recommend: + amplitude.track(.view_home_recommend(entryPoint: "recommend")) + } + return .none case .binding: return .none case let .pushAlertTapped(isTapped): @@ -197,6 +208,13 @@ private extension MainTabFeature { let categoryIdString = queryItems.first(where: { $0.name == "categoryId" })?.value, let categoryId = Int(categoryIdString) else { return .none } + + switch state.selectedTab { + case .pokit: + amplitude.track(.view_home_pokit(entryPoint: "deeplink")) + case .recommend: + amplitude.track(.view_home_recommend(entryPoint: "deeplink")) + } return .send(.async(.공유받은_카테고리_조회(categoryId: categoryId))) case .경고_확인버튼_클릭: From 9f475f4d2a7aa4c63767947739fe170a99341da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 18:53:44 +0900 Subject: [PATCH 33/42] =?UTF-8?q?[feat]=20#208=20amplitude=20-=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EC=83=9D=EC=84=B1,=20=EB=A7=81=ED=81=AC=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Network/Category/CategoryClient+LiveKey.swift | 6 +++++- .../Data/Network/Content/ContentClient+LiveKey.swift | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Projects/CoreKit/Sources/Data/Network/Category/CategoryClient+LiveKey.swift b/Projects/CoreKit/Sources/Data/Network/Category/CategoryClient+LiveKey.swift index 05fc56e0..64611eec 100644 --- a/Projects/CoreKit/Sources/Data/Network/Category/CategoryClient+LiveKey.swift +++ b/Projects/CoreKit/Sources/Data/Network/Category/CategoryClient+LiveKey.swift @@ -12,6 +12,7 @@ import Moya extension CategoryClient: DependencyKey { public static let liveValue: Self = { + @Dependency(\.amplitude.track) var amplitudeTrack let provider = MoyaProvider.build() return Self( @@ -31,7 +32,10 @@ extension CategoryClient: DependencyKey { ) }, 카테고리_생성: { model in - try await provider.request(.카테고리생성(model: model)) + let response: CategoryEditResponse + response = try await provider.request(.카테고리생성(model: model)) + amplitudeTrack(.add_folder(folderName: response.categoryName)) + return response }, 카테고리_프로필_목록_조회: { try await provider.request(.카테고리_프로필_목록_조회) diff --git a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift index 27db10d0..757b37be 100644 --- a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift +++ b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift @@ -10,8 +10,9 @@ import Moya extension ContentClient: DependencyKey { public static let liveValue: Self = { + @Dependency(\.amplitude.track) var amplitudeTrack let provider = MoyaProvider.build() - + return Self( 컨텐츠_삭제: { id in try await provider.requestNoBody(.컨텐츠_삭제(contentId: id)) @@ -23,7 +24,13 @@ extension ContentClient: DependencyKey { try await provider.request(.컨텐츠_수정(contentId: id, model: model)) }, 컨텐츠_추가: { model in - try await provider.request(.컨텐츠_추가(model: model)) + let response: ContentDetailResponse + response = try await provider.request(.컨텐츠_추가(model: model)) + amplitudeTrack(.add_link( + folderId: "\(response.category.categoryId)", + linkDomain: response.data + )) + return response }, 즐겨찾기: { id in try await provider.request(.즐겨찾기(contentId: id)) From 0f05a0f40fc4e3f77d8eacd56bf9dcbc38294b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 18:57:31 +0900 Subject: [PATCH 34/42] =?UTF-8?q?[feat]=20#208=20amplitude=20-=20=EB=A7=81?= =?UTF-8?q?=ED=81=AC,=20=ED=8F=B4=EB=8D=94=20=EC=83=81=EC=84=B8=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Network/Category/CategoryClient+LiveKey.swift | 3 ++- .../Data/Network/Content/ContentClient+LiveKey.swift | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Projects/CoreKit/Sources/Data/Network/Category/CategoryClient+LiveKey.swift b/Projects/CoreKit/Sources/Data/Network/Category/CategoryClient+LiveKey.swift index 64611eec..d6fe994a 100644 --- a/Projects/CoreKit/Sources/Data/Network/Category/CategoryClient+LiveKey.swift +++ b/Projects/CoreKit/Sources/Data/Network/Category/CategoryClient+LiveKey.swift @@ -44,7 +44,8 @@ extension CategoryClient: DependencyKey { try await provider.request(.유저_카테고리_개수_조회) }, 카테고리_상세_조회: { id in - try await provider.request(.카테고리_상세_조회(categoryId: id)) + amplitudeTrack(.view_folder_detail(folderId: id)) + return try await provider.request(.카테고리_상세_조회(categoryId: id)) }, 공유받은_카테고리_조회: { id, model in try await provider.request(.공유받은_카테고리_조회(categoryId: id, model: model)) diff --git a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift index 757b37be..ede2bf2c 100644 --- a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift +++ b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift @@ -18,7 +18,13 @@ extension ContentClient: DependencyKey { try await provider.requestNoBody(.컨텐츠_삭제(contentId: id)) }, 컨텐츠_상세_조회: { id in - try await provider.request(.컨텐츠_상세_조회(contentId: id)) + let response: ContentDetailResponse + response = try await provider.request(.컨텐츠_상세_조회(contentId: id)) + amplitudeTrack(.view_link_detail( + linkId: "\(id)", + linkDomain: response.data + )) + return response }, 컨텐츠_수정: { id, model in try await provider.request(.컨텐츠_수정(contentId: id, model: model)) From 0c63176c0966e0e192409932a76f16f254beb3a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 19:11:52 +0900 Subject: [PATCH 35/42] =?UTF-8?q?[feat]=20#208=20amplitude=20-=20=EA=B3=B5?= =?UTF-8?q?=EC=9C=A0=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/CategoryDetailFeature.swift | 7 +++++++ .../Sources/ContentSetting/ContentSettingFeature.swift | 3 +++ 2 files changed, 10 insertions(+) diff --git a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift index 9b3a9c24..a94ac92a 100644 --- a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift +++ b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift @@ -26,6 +26,9 @@ public struct CategoryDetailFeature { private var contentClient @Dependency(KakaoShareClient.self) private var kakaoShareClient + @Dependency(\.amplitude.track) + private var amplitudeTrack + /// - State @ObservableState public struct State: Equatable { @@ -212,6 +215,10 @@ private extension CategoryDetailFeature { ) case .공유_버튼_눌렀을때: + amplitudeTrack(.share_link( + linkId: "\(state.domain.category.id)", + shareTarget: "kakaotalk" + )) kakaoShareClient.카테고리_카카오톡_공유( CategoryKaKaoShareModel( categoryName: state.domain.category.categoryName, diff --git a/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingFeature.swift b/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingFeature.swift index a10909a1..d9d3a951 100644 --- a/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingFeature.swift +++ b/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingFeature.swift @@ -28,6 +28,9 @@ public struct ContentSettingFeature { private var categoryClient @Dependency(KeyboardClient.self) private var keyboardClient + @Dependency(\.amplitude.track) + private var amplitudeTrack + /// - State @ObservableState public struct State: Equatable { From 1d8e15e85f1e4138e910738bf987ebc89f5b518a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 19:36:26 +0900 Subject: [PATCH 36/42] =?UTF-8?q?[feat]=20#208=20amplitude=20-=20=EB=A7=81?= =?UTF-8?q?=ED=81=AC=20=EC=83=81=EC=84=B8,=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Analytics/AnalyticsEvent.swift | 41 ++++++++++++++++--- .../Content/ContentClient+LiveKey.swift | 8 +--- .../ContentCard/ContentCardFeature.swift | 9 +++- .../ContentSettingFeature.swift | 6 ++- .../Sources/Recommend/RecommendFeature.swift | 37 ++++++++++++----- .../Sources/Recommend/RecommendView.swift | 2 +- 6 files changed, 77 insertions(+), 26 deletions(-) diff --git a/Projects/CoreKit/Sources/Core/Analytics/AnalyticsEvent.swift b/Projects/CoreKit/Sources/Core/Analytics/AnalyticsEvent.swift index b877f1a6..7891168d 100644 --- a/Projects/CoreKit/Sources/Core/Analytics/AnalyticsEvent.swift +++ b/Projects/CoreKit/Sources/Core/Analytics/AnalyticsEvent.swift @@ -11,9 +11,9 @@ public enum AnalyticsEvent { case view_home_pokit(entryPoint: String) case view_home_recommend(entryPoint: String) case add_folder(folderName: String) - case add_link(folderId: String, linkDomain: String) + case add_link(folderId: String, linkDomain: String, entryPoint: String? = nil, linkId: String? = nil, positionIndex: Int? = nil, algoVersion: String? = nil) case view_folder_detail(folderId: String) - case view_link_detail(linkId: String, linkDomain: String) + case view_link_detail(linkId: String, linkDomain: String, entryPoint: String? = nil, positionIndex: Int? = nil, cardType: String? = nil, algoVersion: String? = nil) case share_link(linkId: String, shareTarget: String) case session_end(duration: Int) @@ -70,20 +70,46 @@ public enum AnalyticsEvent { case let .add_folder(folderName): return [PropertyKey.folder_name.rawValue: folderName] - case let .add_link(folderId, linkDomain): - return [ + case let .add_link(folderId, linkDomain, entryPoint, linkId, positionIndex, algoVersion): + var props: [String: Any] = [ PropertyKey.folder_id.rawValue: folderId, PropertyKey.link_domain.rawValue: linkDomain ] + if let entryPoint = entryPoint { + props[PropertyKey.entry_point.rawValue] = entryPoint + } + if let linkId = linkId { + props[PropertyKey.link_id.rawValue] = linkId + } + if let positionIndex = positionIndex { + props[PropertyKey.position_index.rawValue] = positionIndex + } + if let algoVersion = algoVersion { + props[PropertyKey.algo_version.rawValue] = algoVersion + } + return props case let .view_folder_detail(folderId): return [PropertyKey.folder_id.rawValue: folderId] - case let .view_link_detail(linkId, linkDomain): - return [ + case let .view_link_detail(linkId, linkDomain, entryPoint, positionIndex, cardType, algoVersion): + var props: [String: Any] = [ PropertyKey.link_id.rawValue: linkId, PropertyKey.link_domain.rawValue: linkDomain ] + if let entryPoint = entryPoint { + props[PropertyKey.entry_point.rawValue] = entryPoint + } + if let positionIndex = positionIndex { + props[PropertyKey.position_index.rawValue] = positionIndex + } + if let cardType = cardType { + props[PropertyKey.card_type.rawValue] = cardType + } + if let algoVersion = algoVersion { + props[PropertyKey.algo_version.rawValue] = algoVersion + } + return props case let .share_link(linkId, shareTarget): return [ @@ -110,6 +136,9 @@ public enum PropertyKey: String { case link_id case share_target case duration + case position_index + case card_type + case algo_version } /// 로그인 방식 diff --git a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift index ede2bf2c..757b37be 100644 --- a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift +++ b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift @@ -18,13 +18,7 @@ extension ContentClient: DependencyKey { try await provider.requestNoBody(.컨텐츠_삭제(contentId: id)) }, 컨텐츠_상세_조회: { id in - let response: ContentDetailResponse - response = try await provider.request(.컨텐츠_상세_조회(contentId: id)) - amplitudeTrack(.view_link_detail( - linkId: "\(id)", - linkDomain: response.data - )) - return response + try await provider.request(.컨텐츠_상세_조회(contentId: id)) }, 컨텐츠_수정: { id, model in try await provider.request(.컨텐츠_수정(contentId: id, model: model)) diff --git a/Projects/Feature/FeatureContentCard/Sources/ContentCard/ContentCardFeature.swift b/Projects/Feature/FeatureContentCard/Sources/ContentCard/ContentCardFeature.swift index 12fd5eb3..fe5c081a 100644 --- a/Projects/Feature/FeatureContentCard/Sources/ContentCard/ContentCardFeature.swift +++ b/Projects/Feature/FeatureContentCard/Sources/ContentCard/ContentCardFeature.swift @@ -21,6 +21,9 @@ public struct ContentCardFeature { private var openURL @Dependency(ContentClient.self) private var contentClient + @Dependency(\.amplitude.track) + private var amplitudeTrack + /// - State @ObservableState public struct State: Equatable, Identifiable { @@ -113,7 +116,11 @@ private extension ContentCardFeature { guard let url = URL(string: state.content.data) else { return .none } - return .run { send in + return .run { [content = state.content] send in + amplitudeTrack(.view_link_detail( + linkId: "\(content.id)", + linkDomain: content.data + )) await send(.async(.컨텐츠_상세_조회_API)) await openURL(url) } diff --git a/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingFeature.swift b/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingFeature.swift index d9d3a951..7b0bc337 100644 --- a/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingFeature.swift +++ b/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingFeature.swift @@ -444,7 +444,11 @@ private extension ContentSettingFeature { thumbNail: state.domain.thumbNail ) return .run { send in - let content = try await contentClient.컨텐츠_추가(request) + let response = try await contentClient.컨텐츠_추가(request) + amplitudeTrack(.add_link( + folderId: "\(response.contentId)", + linkDomain: response.data + )) await send(.inner(.선택한_포킷_인메모리_삭제)) await send(.delegate(.저장하기_완료(category: category))) } catch: { error, send in diff --git a/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendFeature.swift b/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendFeature.swift index df1ba34b..07d83460 100644 --- a/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendFeature.swift +++ b/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendFeature.swift @@ -23,6 +23,9 @@ public struct RecommendFeature { private var categoryClient @Dependency(\.openURL) private var openURL + @Dependency(\.amplitude.track) + private var amplitudeTrack + /// - State @ObservableState public struct State: Equatable { @@ -87,7 +90,7 @@ public struct RecommendFeature { case 링크_공유_완료되었을때 case 검색_버튼_눌렀을때 case 알림_버튼_눌렀을때 - case 추천_컨텐츠_눌렀을때(String) + case 추천_컨텐츠_눌렀을때(BaseContentItem) case 경고시트_dismiss case 포킷선택_항목_눌렀을때(pokit: BaseCategoryItem) case 포킷_추가하기_버튼_눌렀을때 @@ -211,8 +214,17 @@ private extension RecommendFeature { return .send(.delegate(.검색_버튼_눌렀을때)) case .알림_버튼_눌렀을때: return .send(.delegate(.알림_버튼_눌렀을때)) - case let .추천_컨텐츠_눌렀을때(urlString): - guard let url = URL(string: urlString) else { return .none } + case let .추천_컨텐츠_눌렀을때(content): + guard let url = URL(string: content.data) else { return .none } + let index = state.recommendedList?.index(id: content.id) + amplitudeTrack(.view_link_detail( + linkId: "\(content.id)", + linkDomain: content.data, + entryPoint: "recommend", + positionIndex: index, + cardType: "list", + algoVersion: "v1.2" + )) return .run { _ in await openURL(url) } case .관심사_편집_버튼_눌렀을때: state.showKeywordSheet = true @@ -355,12 +367,8 @@ private extension RecommendFeature { ) return categoryListFetch(request: request) case .컨텐츠_추가_API: - guard - let categoryId = state.selectedPokit?.id, - let category = state.domain.categoryListInQuiry.data?.first(where: { - $0.id == categoryId - }), - let content = state.addContent + guard let categoryId = state.selectedPokit?.id, + let content = state.addContent else { return .none } let request = ContentBaseRequest( data: content.data, @@ -370,8 +378,17 @@ private extension RecommendFeature { alertYn: "NO", thumbNail: content.thumbNail ) + let index = state.recommendedList?.index(id: content.id) return .run { send in - let content = try await contentClient.컨텐츠_추가(request) + let response = try await contentClient.컨텐츠_추가(request) + amplitudeTrack(.add_link( + folderId: "\(categoryId)", + linkDomain: content.data, + entryPoint: "recommend", + linkId: "\(response.contentId)", + positionIndex: index, + algoVersion: "v1.2" + )) await send(.delegate(.저장하기_완료)) } } diff --git a/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendView.swift b/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendView.swift index 509a264e..19a13ddc 100644 --- a/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendView.swift +++ b/Projects/Feature/FeatureRecommend/Sources/Recommend/RecommendView.swift @@ -196,7 +196,7 @@ private extension RecommendView { @ViewBuilder func recommendedCard(_ content: BaseContentItem) -> some View { - Button(action: { send(.추천_컨텐츠_눌렀을때(content.data)) }) { + Button(action: { send(.추천_컨텐츠_눌렀을때(content)) }) { recomendedCardLabel(content) } } From a2d34369bc7798a7fed951f8d1b4599856c9b855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 19:37:20 +0900 Subject: [PATCH 37/42] =?UTF-8?q?[chore]=20#208=20amplitude=20=EB=A7=81?= =?UTF-8?q?=ED=81=AC=20=EC=A0=80=EC=9E=A5=20=ED=8A=B8=EB=A6=AC=EA=B1=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Network/Content/ContentClient+LiveKey.swift | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift index 757b37be..27db10d0 100644 --- a/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift +++ b/Projects/CoreKit/Sources/Data/Network/Content/ContentClient+LiveKey.swift @@ -10,9 +10,8 @@ import Moya extension ContentClient: DependencyKey { public static let liveValue: Self = { - @Dependency(\.amplitude.track) var amplitudeTrack let provider = MoyaProvider.build() - + return Self( 컨텐츠_삭제: { id in try await provider.requestNoBody(.컨텐츠_삭제(contentId: id)) @@ -24,13 +23,7 @@ extension ContentClient: DependencyKey { try await provider.request(.컨텐츠_수정(contentId: id, model: model)) }, 컨텐츠_추가: { model in - let response: ContentDetailResponse - response = try await provider.request(.컨텐츠_추가(model: model)) - amplitudeTrack(.add_link( - folderId: "\(response.category.categoryId)", - linkDomain: response.data - )) - return response + try await provider.request(.컨텐츠_추가(model: model)) }, 즐겨찾기: { id in try await provider.request(.즐겨찾기(contentId: id)) From 6293faa58dc22cfb95a0b2e20a9c186dc7b67073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 19:54:07 +0900 Subject: [PATCH 38/42] =?UTF-8?q?[feat]=20#208=20amplitude=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/App/Resources/Pokit-info.plist | 2 ++ Projects/App/Sources/AppDelegate/AppDelegate.swift | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Projects/App/Resources/Pokit-info.plist b/Projects/App/Resources/Pokit-info.plist index ff684a8a..a20747c6 100644 --- a/Projects/App/Resources/Pokit-info.plist +++ b/Projects/App/Resources/Pokit-info.plist @@ -94,5 +94,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + AMPLITUDE_API_KEY + $(AMPLITUDE_API_KEY) diff --git a/Projects/App/Sources/AppDelegate/AppDelegate.swift b/Projects/App/Sources/AppDelegate/AppDelegate.swift index 309c8128..cdae5ca3 100644 --- a/Projects/App/Sources/AppDelegate/AppDelegate.swift +++ b/Projects/App/Sources/AppDelegate/AppDelegate.swift @@ -42,7 +42,9 @@ extension AppDelegate: UIApplicationDelegate { // 앱 번들 버전 (ex: "2.0.1") let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" - + let amplitudeKey = Bundle.main.infoDictionary?["AMPLITUDE_API_KEY"] as? String ?? "" + amplitude.initialize(amplitudeKey, nil) + amplitude.track(.app_open(deviceOS: osVersion, appVersion: appVersion)) return true } From 71eada91858e772be012e7a52d5a00c049ec7ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 19:54:34 +0900 Subject: [PATCH 39/42] =?UTF-8?q?[refactor]=20#208=20amplitude=20track=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/App/Sources/MainTab/MainTabFeature.swift | 12 ++++++------ .../FeatureIntro/Sources/Splash/SplashFeature.swift | 6 +++--- .../FeatureLogin/Sources/Login/LoginFeature.swift | 12 ++++++------ .../Sources/SelectField/SelectFieldFeature.swift | 6 +++--- .../Sources/SignUpDone/SignUpDoneFeature.swift | 6 +++--- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Projects/App/Sources/MainTab/MainTabFeature.swift b/Projects/App/Sources/MainTab/MainTabFeature.swift index 9e4695ea..b6096ec8 100644 --- a/Projects/App/Sources/MainTab/MainTabFeature.swift +++ b/Projects/App/Sources/MainTab/MainTabFeature.swift @@ -24,8 +24,8 @@ public struct MainTabFeature { private var categoryClient @Dependency(UserDefaultsClient.self) private var userDefaults - @Dependency(\.amplitude) - private var amplitude + @Dependency(\.amplitude.track) + private var amplitudeTrack /// - State @ObservableState @@ -109,9 +109,9 @@ public struct MainTabFeature { case .binding(\.selectedTab): switch state.selectedTab { case .pokit: - amplitude.track(.view_home_pokit(entryPoint: "pokit")) + amplitudeTrack(.view_home_pokit(entryPoint: "pokit")) case .recommend: - amplitude.track(.view_home_recommend(entryPoint: "recommend")) + amplitudeTrack(.view_home_recommend(entryPoint: "recommend")) } return .none case .binding: @@ -211,9 +211,9 @@ private extension MainTabFeature { switch state.selectedTab { case .pokit: - amplitude.track(.view_home_pokit(entryPoint: "deeplink")) + amplitudeTrack(.view_home_pokit(entryPoint: "deeplink")) case .recommend: - amplitude.track(.view_home_recommend(entryPoint: "deeplink")) + amplitudeTrack(.view_home_recommend(entryPoint: "deeplink")) } return .send(.async(.공유받은_카테고리_조회(categoryId: categoryId))) diff --git a/Projects/Feature/FeatureIntro/Sources/Splash/SplashFeature.swift b/Projects/Feature/FeatureIntro/Sources/Splash/SplashFeature.swift index 4874c209..d8a53b53 100644 --- a/Projects/Feature/FeatureIntro/Sources/Splash/SplashFeature.swift +++ b/Projects/Feature/FeatureIntro/Sources/Splash/SplashFeature.swift @@ -27,8 +27,8 @@ public struct SplashFeature { var keychain @Dependency(VersionClient.self) var versionClient - @Dependency(\.amplitude) - var amplitude + @Dependency(\.amplitude.track) + private var amplitudeTrack /// - State @ObservableState @@ -106,7 +106,7 @@ private extension SplashFeature { case .onAppear: return .run { [isNeedSessionDeleted = state.isNeedSessionDeleted] send in - amplitude.track(.view_splash) + amplitudeTrack(.view_splash) try await self.clock.sleep(for: .milliseconds(2000)) /// Version Check let response = try await versionClient.버전체크().toDomain() diff --git a/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift b/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift index ce591cc9..c2ecb014 100644 --- a/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift +++ b/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift @@ -23,8 +23,8 @@ public struct LoginFeature { var userDefaults @Dependency(KeychainClient.self) var keychain - @Dependency(\.amplitude) - var amplitude + @Dependency(\.amplitude.track) + private var amplitudeTrack /// - State @ObservableState public struct State { @@ -110,10 +110,10 @@ private extension LoginFeature { func handleViewAction(_ action: Action.View, state: inout State) -> Effect { switch action { case .애플로그인_버튼_눌렀을때: - amplitude.track(.login_start(method: .apple)) + amplitudeTrack(.login_start(method: .apple)) return .send(.async(.애플로그인_소셜_API)) case .구글로그인_버튼_눌렀을때: - amplitude.track(.login_start(method: .google)) + amplitudeTrack(.login_start(method: .google)) return .send(.async(.구글로그인_소셜_API)) } } @@ -173,7 +173,7 @@ private extension LoginFeature { let appleTokenRequest = AppleTokenRequest(authCode: authCode, jwt: jwt) let appleTokenResponse = try await authClient.apple(appleTokenRequest) keychain.save(.serverRefresh, appleTokenResponse.refresh_token) - amplitude.track(.login_complete(method: .apple)) + amplitudeTrack(.login_complete(method: .apple)) await send(.inner(.로그인_이후_화면이동(isRegistered: tokenResponse.isRegistered))) } case let .구글로그인_API(response): @@ -189,7 +189,7 @@ private extension LoginFeature { keychain.save(.accessToken, tokenResponse.accessToken) keychain.save(.refreshToken, tokenResponse.refreshToken) keychain.save(.serverRefresh, response.serverRefreshToken) - amplitude.track(.login_complete(method: .google)) + amplitudeTrack(.login_complete(method: .google)) await send(.inner(.로그인_이후_화면이동(isRegistered: tokenResponse.isRegistered))) } diff --git a/Projects/Feature/FeatureLogin/Sources/SelectField/SelectFieldFeature.swift b/Projects/Feature/FeatureLogin/Sources/SelectField/SelectFieldFeature.swift index 7fa8b6ba..fa1e5e6f 100644 --- a/Projects/Feature/FeatureLogin/Sources/SelectField/SelectFieldFeature.swift +++ b/Projects/Feature/FeatureLogin/Sources/SelectField/SelectFieldFeature.swift @@ -14,8 +14,8 @@ public struct SelectFieldFeature { /// - Dependency @Dependency(\.dismiss) var dismiss @Dependency(UserClient.self) var userClient - @Dependency(\.amplitude) - var amplitude + @Dependency(\.amplitude.track) + private var amplitudeTrack /// - State @ObservableState public struct State: Equatable { @@ -95,7 +95,7 @@ private extension SelectFieldFeature { } case .nextButtonTapped: let interests = Array(state.selectedFields) - amplitude.track(.interest_select(interests: interests)) + amplitudeTrack(.interest_select(interests: interests)) return .send(.delegate(.pushSignUpDoneView(interests: interests))) case .backButtonTapped: return .run { _ in await self.dismiss() } diff --git a/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneFeature.swift b/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneFeature.swift index 6a510157..b0429803 100644 --- a/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneFeature.swift +++ b/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneFeature.swift @@ -14,8 +14,8 @@ import Util public struct SignUpDoneFeature { /// - Dependency @Dependency(\.dismiss) var dismiss - @Dependency(\.amplitude) - var amplitude + @Dependency(\.amplitude.track) + private var amplitudeTrack /// - State @ObservableState public struct State: Equatable { @@ -99,7 +99,7 @@ private extension SignUpDoneFeature { state.pookiIsAppear = true return .none case .뷰가_나타났을때: - amplitude.track(.onboarding_complete) + amplitudeTrack(.onboarding_complete) return .none } } From e67dd024ab9bf0613ed33e05cdfe867712ca1895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 20:15:51 +0900 Subject: [PATCH 40/42] =?UTF-8?q?[feat]=20#208=20amplitude=20-=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=EC=A0=95=EB=B3=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../App/Sources/AppDelegate/AppDelegate.swift | 1 - .../Network/User/UserClient+LiveKey.swift | 7 ++++++- .../Sources/Splash/SplashFeature.swift | 12 +++++++++--- .../Sources/Login/LoginFeature.swift | 19 +++++++++++++------ 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/Projects/App/Sources/AppDelegate/AppDelegate.swift b/Projects/App/Sources/AppDelegate/AppDelegate.swift index cdae5ca3..c51a2024 100644 --- a/Projects/App/Sources/AppDelegate/AppDelegate.swift +++ b/Projects/App/Sources/AppDelegate/AppDelegate.swift @@ -44,7 +44,6 @@ extension AppDelegate: UIApplicationDelegate { let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" let amplitudeKey = Bundle.main.infoDictionary?["AMPLITUDE_API_KEY"] as? String ?? "" amplitude.initialize(amplitudeKey, nil) - amplitude.track(.app_open(deviceOS: osVersion, appVersion: appVersion)) return true } diff --git a/Projects/CoreKit/Sources/Data/Network/User/UserClient+LiveKey.swift b/Projects/CoreKit/Sources/Data/Network/User/UserClient+LiveKey.swift index 900ec4d1..415be9bb 100644 --- a/Projects/CoreKit/Sources/Data/Network/User/UserClient+LiveKey.swift +++ b/Projects/CoreKit/Sources/Data/Network/User/UserClient+LiveKey.swift @@ -12,6 +12,8 @@ import Moya extension UserClient: DependencyKey { public static let liveValue: Self = { + @Dependency(\.amplitude.setUserProperties) + var amplitudeSetUserProperties let provider = MoyaProvider.build() return Self( @@ -22,7 +24,10 @@ extension UserClient: DependencyKey { try await provider.request(.닉네임_수정(model: model)) }, 회원등록: { model in - try await provider.request(.회원등록(model: model)) + let response: BaseUserResponse + response = try await provider.request(.회원등록(model: model)) + amplitudeSetUserProperties(["userId": response.id]) + return response }, 닉네임_중복_체크: { nickname in try await provider.request(.닉네임_중복_체크(nickname: nickname)) diff --git a/Projects/Feature/FeatureIntro/Sources/Splash/SplashFeature.swift b/Projects/Feature/FeatureIntro/Sources/Splash/SplashFeature.swift index d8a53b53..c06021fa 100644 --- a/Projects/Feature/FeatureIntro/Sources/Splash/SplashFeature.swift +++ b/Projects/Feature/FeatureIntro/Sources/Splash/SplashFeature.swift @@ -27,8 +27,10 @@ public struct SplashFeature { var keychain @Dependency(VersionClient.self) var versionClient - @Dependency(\.amplitude.track) - private var amplitudeTrack + @Dependency(\.amplitude) + private var amplitude + @Dependency(UserClient.self) + var userClient /// - State @ObservableState @@ -106,7 +108,7 @@ private extension SplashFeature { case .onAppear: return .run { [isNeedSessionDeleted = state.isNeedSessionDeleted] send in - amplitudeTrack(.view_splash) + amplitude.track(.view_splash) try await self.clock.sleep(for: .milliseconds(2000)) /// Version Check let response = try await versionClient.버전체크().toDomain() @@ -177,6 +179,10 @@ private extension SplashFeature { let tokenRequest = ReissueRequest(refreshToken: refreshToken) let tokenResponse = try await authClient.토큰재발급(tokenRequest) keychain.save(.accessToken, tokenResponse.accessToken) + + let user = try await userClient.닉네임_조회() + amplitude.setUserProperties(["userId": user.id]) + await send(.delegate(.autoLoginSuccess)) } catch { await send(.delegate(.loginNeeded), animation: .smooth) diff --git a/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift b/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift index c2ecb014..c7cec094 100644 --- a/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift +++ b/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift @@ -23,8 +23,8 @@ public struct LoginFeature { var userDefaults @Dependency(KeychainClient.self) var keychain - @Dependency(\.amplitude.track) - private var amplitudeTrack + @Dependency(\.amplitude) + private var amplitude /// - State @ObservableState public struct State { @@ -110,10 +110,10 @@ private extension LoginFeature { func handleViewAction(_ action: Action.View, state: inout State) -> Effect { switch action { case .애플로그인_버튼_눌렀을때: - amplitudeTrack(.login_start(method: .apple)) + amplitude.track(.login_start(method: .apple)) return .send(.async(.애플로그인_소셜_API)) case .구글로그인_버튼_눌렀을때: - amplitudeTrack(.login_start(method: .google)) + amplitude.track(.login_start(method: .google)) return .send(.async(.구글로그인_소셜_API)) } } @@ -173,7 +173,11 @@ private extension LoginFeature { let appleTokenRequest = AppleTokenRequest(authCode: authCode, jwt: jwt) let appleTokenResponse = try await authClient.apple(appleTokenRequest) keychain.save(.serverRefresh, appleTokenResponse.refresh_token) - amplitudeTrack(.login_complete(method: .apple)) + amplitude.track(.login_complete(method: .apple)) + + let user = try await userClient.닉네임_조회() + amplitude.setUserProperties(["userId": user.id]) + await send(.inner(.로그인_이후_화면이동(isRegistered: tokenResponse.isRegistered))) } case let .구글로그인_API(response): @@ -189,7 +193,10 @@ private extension LoginFeature { keychain.save(.accessToken, tokenResponse.accessToken) keychain.save(.refreshToken, tokenResponse.refreshToken) keychain.save(.serverRefresh, response.serverRefreshToken) - amplitudeTrack(.login_complete(method: .google)) + amplitude.track(.login_complete(method: .google)) + + let user = try await userClient.닉네임_조회() + amplitude.setUserProperties(["userId": user.id]) await send(.inner(.로그인_이후_화면이동(isRegistered: tokenResponse.isRegistered))) } From 66d494b460522efd474e25409bb0c2e6e1882558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 21:51:46 +0900 Subject: [PATCH 41/42] =?UTF-8?q?[fix]=202.0.2=20QA=20=EC=82=AC=ED=95=AD?= =?UTF-8?q?=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...okitPresentationCornerRadiusModifier.swift | 4 +++- .../Sources/CategoryDetailFeature.swift | 2 +- .../Sources/PokitFavoriteCard.swift | 21 ++++++++++++++++--- Projects/Util/Sources/Constants.swift | 1 + 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Projects/DSKit/Sources/Modifiers/PokitPresentationCornerRadiusModifier.swift b/Projects/DSKit/Sources/Modifiers/PokitPresentationCornerRadiusModifier.swift index ad650d98..eca60334 100644 --- a/Projects/DSKit/Sources/Modifiers/PokitPresentationCornerRadiusModifier.swift +++ b/Projects/DSKit/Sources/Modifiers/PokitPresentationCornerRadiusModifier.swift @@ -11,7 +11,9 @@ struct PokitPresentationCornerRadiusModifier: ViewModifier { init() { } func body(content: Content) -> some View { - if #available(iOS 16.4, *) { + if #available(iOS 26.0, *) { + content + } else if #available(iOS 16.4, *) { content .presentationCornerRadius(20) } else { diff --git a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift index a94ac92a..ba1239d5 100644 --- a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift +++ b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift @@ -321,7 +321,7 @@ private extension CategoryDetailFeature { case .카테고리_목록_조회_API: return .run { send in let request = BasePageableRequest(page: 0, size: 30, sort: ["createdAt,desc"]) - let response = try await categoryClient.카테고리_목록_조회(request, true, true).toDomain() + let response = try await categoryClient.카테고리_목록_조회(request, true, false).toDomain() await send(.inner(.카테고리_목록_조회_API_반영(response))) } diff --git a/Projects/Feature/FeaturePokit/Sources/PokitFavoriteCard.swift b/Projects/Feature/FeaturePokit/Sources/PokitFavoriteCard.swift index 9d6b783e..fffbb38f 100644 --- a/Projects/Feature/FeaturePokit/Sources/PokitFavoriteCard.swift +++ b/Projects/Feature/FeaturePokit/Sources/PokitFavoriteCard.swift @@ -7,6 +7,10 @@ import SwiftUI +import NukeUI +import DSKit +import Util + public struct PokitFavoriteCard: View { private let linkCount: Int private let action: () -> Void @@ -76,9 +80,20 @@ public struct PokitFavoriteCard: View { } private var thumbNail: some View { - Image(.character(.pooki)) - .resizable() - .frame(width: 84, height: 84) + LazyImage(url: Constants.즐겨찾기_썸네일_주소) { state in + Group { + if let image = state.image { + image + .resizable() + } else { + PokitSpinner() + .foregroundStyle(.pokit(.icon(.brand))) + .frame(width: 48, height: 48) + } + } + .animation(.pokitDissolve, value: state.image) + } + .frame(width: 84, height: 84) } private var background: some View { diff --git a/Projects/Util/Sources/Constants.swift b/Projects/Util/Sources/Constants.swift index a5ff72ed..8bb76877 100644 --- a/Projects/Util/Sources/Constants.swift +++ b/Projects/Util/Sources/Constants.swift @@ -24,6 +24,7 @@ public enum Constants { public static let 마케팅_정보_수신_주소: URL = URL(string: "https://www.notion.so/bb6d0d6569204d5e9a7b67e5825f9d10")! public static let 고객문의_주소: URL = URL(string: "https://www.instagram.com/pokit.official/")! public static let 기본_썸네일_주소: URL = URL(string: "https://pokit-s3.s3.ap-northeast-2.amazonaws.com/logo/pokit.png")! + public static let 즐겨찾기_썸네일_주소: URL = URL(string: "https://pokit-s3.s3.ap-northeast-2.amazonaws.com/category-image/character.png")! public static let 포킷_최대_갯수_문구: String = "최대 30개의 포킷을 생성할 수 있습니다.\n포킷을 삭제한 뒤에 추가해주세요." public static let 복사한_링크_저장하기_문구: String = "복사한 링크 저장하기" From a7839d82c8f89f6c69e552c949ec45b203d7ff9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= Date: Tue, 25 Nov 2025 21:51:55 +0900 Subject: [PATCH 42/42] [chore] 2.0.2 --- Projects/App/Resources/Pokit-info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/App/Resources/Pokit-info.plist b/Projects/App/Resources/Pokit-info.plist index a20747c6..02fec033 100644 --- a/Projects/App/Resources/Pokit-info.plist +++ b/Projects/App/Resources/Pokit-info.plist @@ -21,7 +21,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.0.1 + 2.0.2 CFBundleURLTypes