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 @@ -21,6 +21,12 @@ final class AppLayerAssembler: Assembler {
snapshotUpdater: container.resolve(WidgetSnapshotUpdater.self)
)
}
container.register(WidgetSessionSyncHandler.self) {
WidgetSessionSyncHandler(
authService: container.resolve(AuthService.self),
widgetSyncEventBus: container.resolve(WidgetSyncEventBus.self)
)
}
container.register(FCMTokenSyncHandler.self) {
FCMTokenSyncHandler(
userService: container.resolve(UserService.self)
Expand All @@ -33,7 +39,7 @@ final class AppLayerAssembler: Assembler {
}
container.register(PushNotificationOpenHandler.self) {
PushNotificationOpenHandler(
trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self)
analyticsService: container.resolve(AnalyticsService.self)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
_ = container.resolve(FCMTokenSyncHandler.self)
_ = container.resolve(UserTimeZoneSyncHandler.self)
_ = container.resolve(WidgetSyncEventHandler.self)
_ = container.resolve(WidgetSessionSyncHandler.self)

// 알림 권한 요청
UNUserNotificationCenter.current().delegate = self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@
//

import Foundation
import DevLogDomain
import DevLogData

final class PushNotificationOpenHandler {
private let trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase
private let analyticsService: AnalyticsService

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

func handlePushOpen(userInfo: [AnyHashable: Any]) {
trackAnalyticsEventUseCase.execute(.pushOpen)
analyticsService.trackPushOpen()
PushNotificationRoute.shared.handlePushTap(userInfo: userInfo)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// WidgetSessionSyncHandler.swift
// DevLog
//
// Created by opfic on 6/1/26.
//

import Combine
import Foundation
import DevLogData

final class WidgetSessionSyncHandler {
private let authService: AuthService
private let widgetSyncEventBus: WidgetSyncEventBus
private var hasRequestedWidgetSync = false
private var cancellables = Set<AnyCancellable>()

init(
authService: AuthService,
widgetSyncEventBus: WidgetSyncEventBus
) {
self.authService = authService
self.widgetSyncEventBus = widgetSyncEventBus

authService.observeSignedIn()
.removeDuplicates()
.receive(on: DispatchQueue.main)
.sink { [weak self] isSignedIn in
self?.handleSessionUpdate(isSignedIn: isSignedIn)
}
.store(in: &cancellables)
Comment thread
opficdev marked this conversation as resolved.
}
}

private extension WidgetSessionSyncHandler {
func handleSessionUpdate(isSignedIn: Bool) {
guard isSignedIn else {
hasRequestedWidgetSync = false
return
}

guard hasRequestedWidgetSync == false else {
return
}

hasRequestedWidgetSync = true
widgetSyncEventBus.publish(.syncRequested)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// WidgetSessionSyncHandlerTests.swift
// DevLogAppTests
//
// Created by opfic on 6/1/26.
//

import Combine
import Foundation
import Testing
import DevLogData
@testable import DevLog

struct WidgetSessionSyncHandlerTests {
@Test("로그인 세션 true 첫 진입에서만 위젯 초기 동기화를 요청한다")
func 로그인_세션_true_첫_진입에서만_위젯_초기_동기화를_요청한다() async {
let authServiceSpy = AuthServiceSpy()
let widgetSyncEventBusSpy = WidgetSyncEventBusSpy()
let widgetSessionSyncHandler = WidgetSessionSyncHandler(
authService: authServiceSpy,
widgetSyncEventBus: widgetSyncEventBusSpy
)

authServiceSpy.send(true)
authServiceSpy.send(true)

await withCheckedContinuation { continuation in
DispatchQueue.main.async {
continuation.resume()
}
}
#expect(widgetSyncEventBusSpy.events == [.syncRequested])
_ = widgetSessionSyncHandler
}

@Test("로그아웃 이후 재로그인 시 위젯 초기 동기화를 다시 요청한다")
func 로그아웃_이후_재로그인_시_위젯_초기_동기화를_다시_요청한다() async {
let authServiceSpy = AuthServiceSpy()
let widgetSyncEventBusSpy = WidgetSyncEventBusSpy()
let widgetSessionSyncHandler = WidgetSessionSyncHandler(
authService: authServiceSpy,
widgetSyncEventBus: widgetSyncEventBusSpy
)

authServiceSpy.send(true)
authServiceSpy.send(false)
authServiceSpy.send(true)

await withCheckedContinuation { continuation in
DispatchQueue.main.async {
continuation.resume()
}
}
#expect(widgetSyncEventBusSpy.events == [.syncRequested, .syncRequested])
_ = widgetSessionSyncHandler
}
}

private final class AuthServiceSpy: AuthService {
private let currentValueSubject = CurrentValueSubject<Bool, Never>(false)

var uid: String? { nil }
var providerIDs: [String] { [] }
var currentUserEmail: String? { nil }
var providerCount: Int { 0 }

func observeSignedIn() -> AnyPublisher<Bool, Never> {
currentValueSubject.eraseToAnyPublisher()
}

func beginSignIn() { }
func completeSignIn() { }
func cancelSignIn() { }
func getProviderID() async throws -> String? { nil }
func deleteCurrentUser() async throws { }
func clearCurrentSession() async throws { }

func send(_ isSignedIn: Bool) {
currentValueSubject.send(isSignedIn)
}
}

private final class WidgetSyncEventBusSpy: WidgetSyncEventBus {
private(set) var events = [WidgetSyncEvent]()

func publish(_ event: WidgetSyncEvent) {
events.append(event)
}

func observe() -> AnyPublisher<WidgetSyncEvent, Never> {
Empty().eraseToAnyPublisher()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 70;
objectVersion = 71;
Comment thread
opficdev marked this conversation as resolved.
objects = {

/* Begin PBXBuildFile section */
Expand Down
6 changes: 4 additions & 2 deletions Application/DevLogData/Sources/DataAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public final class DataAssembler: Assembler {
container.register(TodoRepository.self) {
TodoRepositoryImpl(
todoService: container.resolve(TodoService.self),
todoCategoryService: container.resolve(TodoCategoryService.self)
todoCategoryService: container.resolve(TodoCategoryService.self),
widgetSyncEventBus: container.resolve(WidgetSyncEventBus.self)
)
}

Expand Down Expand Up @@ -109,7 +110,8 @@ public final class DataAssembler: Assembler {
UserPreferencesRepositoryImpl(
store: container.resolve(UserDefaultsStore.self),
themeStore: container.resolve(ThemeStore.self),
widgetSnapshotPreferenceStore: container.resolve(WidgetSnapshotPreferenceStore.self)
widgetSnapshotPreferenceStore: container.resolve(WidgetSnapshotPreferenceStore.self),
widgetSyncEventBus: container.resolve(WidgetSyncEventBus.self)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ import DevLogDomain
final class TodoRepositoryImpl: TodoRepository {
private let todoService: TodoService
private let todoCategoryService: TodoCategoryService
private let widgetSyncEventBus: WidgetSyncEventBus

init(
todoService: TodoService,
todoCategoryService: TodoCategoryService
todoCategoryService: TodoCategoryService,
widgetSyncEventBus: WidgetSyncEventBus
) {
self.todoService = todoService
self.todoCategoryService = todoCategoryService
self.widgetSyncEventBus = widgetSyncEventBus
}

func fetchTodos(_ query: TodoQuery, cursor: TodoCursor?) async throws -> TodoPage {
Expand Down Expand Up @@ -105,6 +108,7 @@ final class TodoRepositoryImpl: TodoRepository {
let request = TodoRequest.fromDomain(todo)
do {
try await todoService.upsertTodo(request: request)
widgetSyncEventBus.publish(.syncRequested)
} catch {
throw error.toDomain()
}
Expand All @@ -113,6 +117,7 @@ final class TodoRepositoryImpl: TodoRepository {
func deleteTodo(_ todoId: String) async throws {
do {
try await todoService.deleteTodo(todoId: todoId)
widgetSyncEventBus.publish(.syncRequested)
} catch {
throw error.toDomain()
}
Expand All @@ -121,6 +126,7 @@ final class TodoRepositoryImpl: TodoRepository {
func undoDeleteTodo(_ todoId: String) async throws {
do {
try await todoService.undoDeleteTodo(todoId: todoId)
widgetSyncEventBus.publish(.syncRequested)
} catch {
throw error.toDomain()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@ final class UserPreferencesRepositoryImpl: UserPreferencesRepository {
private let store: UserDefaultsStore
private let themeStore: ThemeStore
private let widgetSnapshotPreferenceStore: WidgetSnapshotPreferenceStore
private let widgetSyncEventBus: WidgetSyncEventBus

init(
store: UserDefaultsStore,
themeStore: ThemeStore,
widgetSnapshotPreferenceStore: WidgetSnapshotPreferenceStore
widgetSnapshotPreferenceStore: WidgetSnapshotPreferenceStore,
widgetSyncEventBus: WidgetSyncEventBus
) {
self.store = store
self.themeStore = themeStore
self.widgetSnapshotPreferenceStore = widgetSnapshotPreferenceStore
self.widgetSyncEventBus = widgetSyncEventBus
themeStore.send(systemTheme())
}

Expand Down Expand Up @@ -92,6 +95,7 @@ final class UserPreferencesRepositoryImpl: UserPreferencesRepository {

func setHeatmapActivityTypes(_ activityTypes: [String]) {
widgetSnapshotPreferenceStore.setHeatmapActivityTypes(activityTypes)
widgetSyncEventBus.publish(.syncRequested)
}

func todayDisplayOptions() -> TodayDisplayOptions {
Expand All @@ -100,5 +104,6 @@ final class UserPreferencesRepositoryImpl: UserPreferencesRepository {

func setTodayDisplayOptions(_ options: TodayDisplayOptions) {
widgetSnapshotPreferenceStore.setTodayDisplayOptions(options)
widgetSyncEventBus.publish(.syncRequested)
}
}
Loading