Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,10 @@ final class AppLayerAssembler: Assembler {
userService: container.resolve(UserService.self)
)
}
container.register(PushNotificationOpenHandler.self) {
PushNotificationOpenHandler(
trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self)
)
}
}
}
6 changes: 4 additions & 2 deletions Application/DevLogApp/Sources/App/Delegate/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

// 앱이 완전 종료되어도, 알림을 통해 앱이 시작된 경우 처리
if let remoteNotification = launchOptions?[.remoteNotification] as? [AnyHashable: Any] {
let handler = container.resolve(PushNotificationOpenHandler.self)
Task { @MainActor in
PushNotificationRoute.shared.handlePushTap(userInfo: remoteNotification)
handler.handlePushOpen(userInfo: remoteNotification)
}
}

Expand Down Expand Up @@ -110,8 +111,9 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
) {
logger.info("Tapped notification: \(response.notification.request.content.userInfo)")
let userInfo = response.notification.request.content.userInfo
let handler = container.resolve(PushNotificationOpenHandler.self)
Task { @MainActor in
PushNotificationRoute.shared.handlePushTap(userInfo: userInfo)
handler.handlePushOpen(userInfo: userInfo)
}
completionHandler()
}
Expand Down
1 change: 1 addition & 0 deletions Application/DevLogApp/Sources/App/DevLogApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ struct DevLogApp: App {
sessionUseCase: container.resolve(ObserveAuthSessionUseCase.self),
networkConnectivityUseCase: container.resolve(ObserveNetworkConnectivityUseCase.self),
systemThemeUseCase: container.resolve(ObserveSystemThemeUseCase.self),
trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self),
widgetURLTab: { MainTab(widgetURL: $0) },
pushNotificationTodoIdPublisher: PushNotificationRoute.shared.observe(),
clearPushNotificationRoute: { PushNotificationRoute.shared.clear() }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// PushNotificationOpenHandler.swift
// DevLog
//
// Created by opfic on 5/28/26.
//

import Foundation
import DevLogDomain

final class PushNotificationOpenHandler {
Comment thread
opficdev marked this conversation as resolved.
private let trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase

init(trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase) {
self.trackAnalyticsEventUseCase = trackAnalyticsEventUseCase
}

func handlePushOpen(userInfo: [AnyHashable: Any]) {
trackAnalyticsEventUseCase.execute(.pushOpen)
PushNotificationRoute.shared.handlePushTap(userInfo: userInfo)
}
}
2 changes: 2 additions & 0 deletions Application/DevLogApp/Sources/Resource/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
</array>
<key>FirebaseAppDelegateProxyEnabled</key>
<false/>
<key>FirebaseAutomaticScreenReportingEnabled</key>
<false/>
<key>GIDClientID</key>
<string>$(CLIENT_ID)</string>
<key>GITHUB_CLIENT_ID</key>
Expand Down
6 changes: 6 additions & 0 deletions Application/DevLogData/Sources/DataAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ public final class DataAssembler: Assembler {
UserDataRepositoryImpl(userService: container.resolve(UserService.self))
}

container.register(AnalyticsRepository.self) {
AnalyticsRepositoryImpl(
analyticsService: container.resolve(AnalyticsService.self)
)
}

container.register(PushNotificationRepository.self) {
PushNotificationRepositoryImpl(
pushNotificationService: container.resolve(PushNotificationService.self),
Expand Down
14 changes: 14 additions & 0 deletions Application/DevLogData/Sources/Protocol/AnalyticsService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// AnalyticsService.swift
// DevLogData
//
// Created by opfic on 5/27/26.
//

public protocol AnalyticsService {
func trackScreenView(_ name: String)
func trackTodoCreate()
func trackTodoComplete()
func trackWebPageCreate()
func trackPushOpen()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// AnalyticsRepositoryImpl.swift
// DevLogData
//
// Created by opfic on 5/27/26.
//

import DevLogDomain

final class AnalyticsRepositoryImpl: AnalyticsRepository {
private let analyticsService: AnalyticsService

init(analyticsService: AnalyticsService) {
self.analyticsService = analyticsService
}

func track(_ event: AnalyticsEvent) {
switch event {
case .screenView(let name):
analyticsService.trackScreenView(name)
case .todoCreate:
analyticsService.trackTodoCreate()
case .todoComplete:
analyticsService.trackTodoComplete()
case .webPageCreate:
analyticsService.trackWebPageCreate()
case .pushOpen:
analyticsService.trackPushOpen()
}
}
}
7 changes: 7 additions & 0 deletions Application/DevLogDomain/Sources/DomainAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public final class DomainAssembler: Assembler {
public init() { }

public func assemble(_ container: any DIContainer) {
registerAnalyticsUseCases(container)
registerAuthUseCases(container)
registerConnectivityUseCases(container)
registerAuthProviderUseCases(container)
Expand All @@ -24,6 +25,12 @@ public final class DomainAssembler: Assembler {
}

private extension DomainAssembler {
func registerAnalyticsUseCases(_ container: any DIContainer) {
container.register(TrackAnalyticsEventUseCase.self) {
TrackAnalyticsEventUseCaseImpl(container.resolve(AnalyticsRepository.self))
}
}

func registerAuthUseCases(_ container: any DIContainer) {
container.register(SignInUseCase.self) {
SignInUseCaseImpl(container.resolve(AuthenticationRepository.self))
Expand Down
14 changes: 14 additions & 0 deletions Application/DevLogDomain/Sources/Entity/AnalyticsEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// AnalyticsEvent.swift
// DevLogDomain
//
// Created by opfic on 5/28/26.
//

public enum AnalyticsEvent {
case screenView(String)
case todoCreate
case todoComplete
case webPageCreate
case pushOpen
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// AnalyticsRepository.swift
// DevLogDomain
//
// Created by opfic on 5/27/26.
//

public protocol AnalyticsRepository {
func track(_ event: AnalyticsEvent)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// TrackAnalyticsEventUseCase.swift
// DevLogDomain
//
// Created by opfic on 5/27/26.
//

public protocol TrackAnalyticsEventUseCase {
func execute(_ event: AnalyticsEvent)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// TrackAnalyticsEventUseCaseImpl.swift
// DevLogDomain
//
// Created by opfic on 5/27/26.
//

public final class TrackAnalyticsEventUseCaseImpl: TrackAnalyticsEventUseCase {
private let repository: AnalyticsRepository

init(_ repository: AnalyticsRepository) {
self.repository = repository
}

public func execute(_ event: AnalyticsEvent) {
repository.track(event)
}
}
8 changes: 8 additions & 0 deletions Application/DevLogInfra/DevLogInfra.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
3CE81EA6CD7444739AC73633 /* DevLogInfra.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 592C8B7B099933759AB316A5 /* DevLogInfra.framework */; };
5F7A14F2C5294547884212E1 /* FirebaseCore in Frameworks */ = {isa = PBXBuildFile; productRef = 06ADD17826F44543B09286DA /* FirebaseCore */; };
62DB1494C0A5B13B49950914 /* FirebaseAnalyticsCore in Frameworks */ = {isa = PBXBuildFile; productRef = 612F6D27B38A4D058F85C8AC /* FirebaseAnalyticsCore */; };
65623F19F34A9A75BC72EE36 /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = CE8F32B22E13743B3FF3B66B /* FirebaseFirestore */; };
B11111111111111111111111 /* DevLogCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B55555555555555555555555 /* DevLogCore.framework */; };
E747320CAD03A579976E87F8 /* FirebaseFunctions in Frameworks */ = {isa = PBXBuildFile; productRef = 172AF9DE3BC54E9E0B4EFD19 /* FirebaseFunctions */; };
Expand Down Expand Up @@ -76,6 +77,7 @@
files = (
F75AA9259C6636CF733C4D82 /* Foundation.framework in Frameworks */,
5F7A14F2C5294547884212E1 /* FirebaseCore in Frameworks */,
62DB1494C0A5B13B49950914 /* FirebaseAnalyticsCore in Frameworks */,
FB5186BC5A89B7DADAB8A82A /* FirebaseAuth in Frameworks */,
65623F19F34A9A75BC72EE36 /* FirebaseFirestore in Frameworks */,
E747320CAD03A579976E87F8 /* FirebaseFunctions in Frameworks */,
Expand Down Expand Up @@ -181,6 +183,7 @@
);
name = DevLogInfra;
packageProductDependencies = (
612F6D27B38A4D058F85C8AC /* FirebaseAnalyticsCore */,
06ADD17826F44543B09286DA /* FirebaseCore */,
2AC59F98E5A6BFA339C3E5BD /* FirebaseAuth */,
CE8F32B22E13743B3FF3B66B /* FirebaseFirestore */,
Expand Down Expand Up @@ -564,6 +567,11 @@
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
612F6D27B38A4D058F85C8AC /* FirebaseAnalyticsCore */ = {
isa = XCSwiftPackageProductDependency;
package = 6A88F5113FA6A29A059E7035 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseAnalyticsCore;
};
06ADD17826F44543B09286DA /* FirebaseCore */ = {
isa = XCSwiftPackageProductDependency;
package = 6A88F5113FA6A29A059E7035 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
Expand Down
4 changes: 4 additions & 0 deletions Application/DevLogInfra/Sources/InfraAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public final class InfraAssembler: Assembler {
FirebaseAppServiceImpl()
}

container.register(AnalyticsService.self) {
FirebaseAnalyticsServiceImpl()
}

container.register(PushMessagingService.self) {
PushMessagingServiceImpl()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// FirebaseAnalyticsServiceImpl.swift
// DevLogInfra
//
// Created by opfic on 5/27/26.
//

import DevLogData
import FirebaseAnalytics

final class FirebaseAnalyticsServiceImpl: AnalyticsService {
private enum EventName {
static let todoCreate = "todo_create"
static let todoComplete = "todo_complete"
static let webPageCreate = "webpage_create"
static let pushOpen = "push_open"
}

func trackScreenView(_ name: String) {
Analytics.logEvent(
AnalyticsEventScreenView,
parameters: [
AnalyticsParameterScreenName: name,
]
)
}

func trackTodoCreate() {
Analytics.logEvent(EventName.todoCreate, parameters: nil)
}

func trackTodoComplete() {
Analytics.logEvent(EventName.todoComplete, parameters: nil)
}

func trackWebPageCreate() {
Analytics.logEvent(EventName.webPageCreate, parameters: nil)
}

func trackPushOpen() {
Analytics.logEvent(EventName.pushOpen, parameters: nil)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ final class HomeViewCoordinator {
upsertTodoUseCase: container.resolve(UpsertTodoUseCase.self),
fetchTodosUseCase: fetchTodosUseCase,
fetchWebPagesUseCase: fetchWebPagesUseCase,
networkConnectivityUseCase: container.resolve(ObserveNetworkConnectivityUseCase.self)
networkConnectivityUseCase: container.resolve(ObserveNetworkConnectivityUseCase.self),
trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ final class HomeViewModel: Store {
private let fetchTodosUseCase: FetchTodosUseCase
private let fetchWebPagesUseCase: FetchWebPagesUseCase
private let networkConnectivityUseCase: ObserveNetworkConnectivityUseCase
private let trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase
private let loadingState = LoadingState()
private var deletedWebPageURLString: String?
private var cancellables = Set<AnyCancellable>()
Expand All @@ -123,7 +124,8 @@ final class HomeViewModel: Store {
upsertTodoUseCase: UpsertTodoUseCase,
fetchTodosUseCase: FetchTodosUseCase,
fetchWebPagesUseCase: FetchWebPagesUseCase,
networkConnectivityUseCase: ObserveNetworkConnectivityUseCase
networkConnectivityUseCase: ObserveNetworkConnectivityUseCase,
trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase
) {
self.fetchPreferencesUseCase = fetchPreferencesUseCase
self.updatePreferencesUseCase = updatePreferencesUseCase
Expand All @@ -134,6 +136,7 @@ final class HomeViewModel: Store {
self.fetchTodosUseCase = fetchTodosUseCase
self.fetchWebPagesUseCase = fetchWebPagesUseCase
self.networkConnectivityUseCase = networkConnectivityUseCase
self.trackAnalyticsEventUseCase = trackAnalyticsEventUseCase

setupNetworkObserving()
}
Expand Down Expand Up @@ -186,6 +189,7 @@ final class HomeViewModel: Store {
do {
defer { endLoading(for: .overlay, mode: .delayed) }
try await upsertTodoUseCase.execute(todo)
trackAnalyticsEventUseCase.execute(.todoCreate)
let page = try await fetchRecentTodos()
let items = page.items
.filter { $0.createdAt != $0.updatedAt }
Expand Down Expand Up @@ -217,6 +221,7 @@ final class HomeViewModel: Store {
do {
defer { endLoading(for: .overlay, mode: .delayed) }
try await addWebPageUseCase.execute(urlString)
trackAnalyticsEventUseCase.execute(.webPageCreate)
let pages = try await fetchWebPagesUseCase.execute("")
send(.updateWebPages(pages.map { WebPageItem(from: $0) }))
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ final class TodoListViewModel: Store {
private let upsertTodoUseCase: UpsertTodoUseCase
private let deleteTodoUseCase: DeleteTodoUseCase
private let undoDeleteTodoUseCase: UndoDeleteTodoUseCase
private let trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase
private let loadingState = LoadingState()
private var undoTodoId: String?
private var nextCursor: TodoCursor?
Expand All @@ -101,13 +102,15 @@ final class TodoListViewModel: Store {
upsertTodoUseCase: UpsertTodoUseCase,
deleteTodoUseCase: DeleteTodoUseCase,
undoDeleteTodoUseCase: UndoDeleteTodoUseCase,
trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase,
category: TodoCategory
) {
self.fetchTodosUseCase = fetchTodosUseCase
self.fetchTodoByIdUseCase = fetchTodoByIdUseCase
self.upsertTodoUseCase = upsertTodoUseCase
self.deleteTodoUseCase = deleteTodoUseCase
self.undoDeleteTodoUseCase = undoDeleteTodoUseCase
self.trackAnalyticsEventUseCase = trackAnalyticsEventUseCase
self.category = category
self.state = State(
query: TodoQuery(categoryId: category.storageValue)
Expand Down Expand Up @@ -209,6 +212,7 @@ final class TodoListViewModel: Store {
do {
defer { endLoading(.delayed) }
try await upsertTodoUseCase.execute(item)
trackAnalyticsEventUseCase.execute(.todoCreate)
Comment thread
opficdev marked this conversation as resolved.
send(.refresh)
} catch {
send(.setAlert(true))
Expand All @@ -225,6 +229,9 @@ final class TodoListViewModel: Store {
todo.completedAt = todo.isCompleted ? now : nil
todo.updatedAt = now
try await upsertTodoUseCase.execute(todo)
if todo.isCompleted {
trackAnalyticsEventUseCase.execute(.todoComplete)
}
guard let todoListItem = TodoListItem(from: todo) else {
send(.setAlert(true))
return
Expand Down
Loading