Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9e3da8b
[fix] #208 설정 -> 닉네임 다크모드 문제 수정
ShapeKim98 Oct 28, 2025
06dce6d
[fix] #208 포킷 상세 내 이동 바텀시트 높이 수정
ShapeKim98 Oct 28, 2025
cccfd97
[fix] #208 pluscategory 바텀시트 ios 26 대응
ShapeKim98 Oct 28, 2025
dfc1f39
[fix] #208 프로필 기본 이미지 가장자리 추가
ShapeKim98 Oct 28, 2025
d543a11
[feat] #208 Amplitude 의존성 추가
ShapeKim98 Oct 28, 2025
54bb9b5
[feat] #208 apmplitude 객체 구성
ShapeKim98 Nov 25, 2025
80b30fd
[feat] #208 amplitude - 앱 실행 시
ShapeKim98 Nov 25, 2025
f21a080
[feat] #208 amplitude - 스플래시 화면 노출
ShapeKim98 Nov 25, 2025
2cfce98
[feat] #208 amplitude - 로그인 시도, 로그인 완료
ShapeKim98 Nov 25, 2025
bffdacc
[feat] #208 amplitude - 온보딩 완료
ShapeKim98 Nov 25, 2025
8832cb4
[feat] #208 amplitude - 관심사 선택 완료
ShapeKim98 Nov 25, 2025
de4cec7
[feat] #208 amplitude - 포킷, 추천 페이지 진입
ShapeKim98 Nov 25, 2025
9f475f4
[feat] #208 amplitude - 폴더 생성, 링크 저장 완료
ShapeKim98 Nov 25, 2025
0f05a0f
[feat] #208 amplitude - 링크, 폴더 상세보기
ShapeKim98 Nov 25, 2025
0c63176
[feat] #208 amplitude - 공유하기
ShapeKim98 Nov 25, 2025
1d8e15e
[feat] #208 amplitude - 링크 상세, 저장
ShapeKim98 Nov 25, 2025
a2d3436
[chore] #208 amplitude 링크 저장 트리거 변경
ShapeKim98 Nov 25, 2025
6293faa
[feat] #208 amplitude 초기화
ShapeKim98 Nov 25, 2025
71eada9
[refactor] #208 amplitude track 호출 수정
ShapeKim98 Nov 25, 2025
e67dd02
[feat] #208 amplitude - 사용자 정보
ShapeKim98 Nov 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Projects/App/Resources/Pokit-info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,7 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>AMPLITUDE_API_KEY</key>
<string>$(AMPLITUDE_API_KEY)</string>
</dict>
</plist>
15 changes: 15 additions & 0 deletions Projects/App/Sources/AppDelegate/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,6 +36,15 @@ 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 ?? ""
let amplitudeKey = Bundle.main.infoDictionary?["AMPLITUDE_API_KEY"] as? String ?? ""
amplitude.initialize(amplitudeKey, nil)
amplitude.track(.app_open(deviceOS: osVersion, appVersion: appVersion))
return true
}

Expand Down
18 changes: 18 additions & 0 deletions Projects/App/Sources/MainTab/MainTabFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public struct MainTabFeature {
private var categoryClient
@Dependency(UserDefaultsClient.self)
private var userDefaults
@Dependency(\.amplitude.track)
private var amplitudeTrack

/// - State
@ObservableState
public struct State: Equatable {
Expand Down Expand Up @@ -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:
amplitudeTrack(.view_home_pokit(entryPoint: "pokit"))
case .recommend:
amplitudeTrack(.view_home_recommend(entryPoint: "recommend"))
}
return .none
case .binding:
return .none
case let .pushAlertTapped(isTapped):
Expand Down Expand Up @@ -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:
amplitudeTrack(.view_home_pokit(entryPoint: "deeplink"))
case .recommend:
amplitudeTrack(.view_home_recommend(entryPoint: "deeplink"))
}

return .send(.async(.공유받은_카테고리_조회(categoryId: categoryId)))
case .경고_확인버튼_클릭:
Expand Down
12 changes: 10 additions & 2 deletions Projects/App/Sources/MainTab/MainTabFeatureView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -302,7 +309,7 @@ private extension MainTabView {

Spacer()
}
.padding(.bottom, 48 - bottomSafeArea)
.padding(.bottom, bottomPadding)
.padding(.top, 36)
.pokitPresentationCornerRadius()
.pokitPresentationBackground()
Expand All @@ -315,6 +322,7 @@ private extension MainTabView {
}
.presentationDetents([.height(self.height)])
}
.ignoresSafeArea(edges: .bottom)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions Projects/CoreKit/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ let coreKit: Target = .target(
.external(name: "KakaoSDKCommon"),
.external(name: "KakaoSDKShare"),
.external(name: "KakaoSDKTemplate"),
.external(name: "AmplitudeSwift"),
],
settings: .settings()
)
Expand Down
73 changes: 73 additions & 0 deletions Projects/CoreKit/Sources/Core/Analytics/AmplitudeManager.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}
164 changes: 164 additions & 0 deletions Projects/CoreKit/Sources/Core/Analytics/AnalyticsEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
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, 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, entryPoint: String? = nil, positionIndex: Int? = nil, cardType: String? = nil, algoVersion: String? = nil)
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, 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, 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 [
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
case position_index
case card_type
case algo_version
}

/// 로그인 방식
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
}
}
}
Loading
Loading