Skip to content

Commit f2200aa

Browse files
authored
[#415] 위젯 싱크 트리거를 수정한다 (#511)
* fix: Todo 변경 시 위젯 동기화 트리거 추가 * chore: DevLogData 프로젝트 포맷 갱신 * fix: 위젯 설정 변경 시 동기화 트리거 추가 * fix: 세션 복구 시 위젯 동기화 트리거 추가 * refactor: 푸시 알림 핸들러 서비스 의존성 적용 * fix: 위젯 세션 동기화 메인 스레드 처리
1 parent d700ad9 commit f2200aa

11 files changed

Lines changed: 448 additions & 11 deletions

File tree

Application/DevLogApp/Sources/App/Assembler/AppLayerAssembler.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ final class AppLayerAssembler: Assembler {
2121
snapshotUpdater: container.resolve(WidgetSnapshotUpdater.self)
2222
)
2323
}
24+
container.register(WidgetSessionSyncHandler.self) {
25+
WidgetSessionSyncHandler(
26+
authService: container.resolve(AuthService.self),
27+
widgetSyncEventBus: container.resolve(WidgetSyncEventBus.self)
28+
)
29+
}
2430
container.register(FCMTokenSyncHandler.self) {
2531
FCMTokenSyncHandler(
2632
userService: container.resolve(UserService.self)
@@ -33,7 +39,7 @@ final class AppLayerAssembler: Assembler {
3339
}
3440
container.register(PushNotificationOpenHandler.self) {
3541
PushNotificationOpenHandler(
36-
trackAnalyticsEventUseCase: container.resolve(TrackAnalyticsEventUseCase.self)
42+
analyticsService: container.resolve(AnalyticsService.self)
3743
)
3844
}
3945
}

Application/DevLogApp/Sources/App/Delegate/AppDelegate.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
3030
_ = container.resolve(FCMTokenSyncHandler.self)
3131
_ = container.resolve(UserTimeZoneSyncHandler.self)
3232
_ = container.resolve(WidgetSyncEventHandler.self)
33+
_ = container.resolve(WidgetSessionSyncHandler.self)
3334

3435
// 알림 권한 요청
3536
UNUserNotificationCenter.current().delegate = self

Application/DevLogApp/Sources/App/Handler/PushNotificationOpenHandler.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@
66
//
77

88
import Foundation
9-
import DevLogDomain
9+
import DevLogData
1010

1111
final class PushNotificationOpenHandler {
12-
private let trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase
12+
private let analyticsService: AnalyticsService
1313

14-
init(trackAnalyticsEventUseCase: TrackAnalyticsEventUseCase) {
15-
self.trackAnalyticsEventUseCase = trackAnalyticsEventUseCase
14+
init(analyticsService: AnalyticsService) {
15+
self.analyticsService = analyticsService
1616
}
1717

1818
func handlePushOpen(userInfo: [AnyHashable: Any]) {
19-
trackAnalyticsEventUseCase.execute(.pushOpen)
19+
analyticsService.trackPushOpen()
2020
PushNotificationRoute.shared.handlePushTap(userInfo: userInfo)
2121
}
2222
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// WidgetSessionSyncHandler.swift
3+
// DevLog
4+
//
5+
// Created by opfic on 6/1/26.
6+
//
7+
8+
import Combine
9+
import Foundation
10+
import DevLogData
11+
12+
final class WidgetSessionSyncHandler {
13+
private let authService: AuthService
14+
private let widgetSyncEventBus: WidgetSyncEventBus
15+
private var hasRequestedWidgetSync = false
16+
private var cancellables = Set<AnyCancellable>()
17+
18+
init(
19+
authService: AuthService,
20+
widgetSyncEventBus: WidgetSyncEventBus
21+
) {
22+
self.authService = authService
23+
self.widgetSyncEventBus = widgetSyncEventBus
24+
25+
authService.observeSignedIn()
26+
.removeDuplicates()
27+
.receive(on: DispatchQueue.main)
28+
.sink { [weak self] isSignedIn in
29+
self?.handleSessionUpdate(isSignedIn: isSignedIn)
30+
}
31+
.store(in: &cancellables)
32+
}
33+
}
34+
35+
private extension WidgetSessionSyncHandler {
36+
func handleSessionUpdate(isSignedIn: Bool) {
37+
guard isSignedIn else {
38+
hasRequestedWidgetSync = false
39+
return
40+
}
41+
42+
guard hasRequestedWidgetSync == false else {
43+
return
44+
}
45+
46+
hasRequestedWidgetSync = true
47+
widgetSyncEventBus.publish(.syncRequested)
48+
}
49+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//
2+
// WidgetSessionSyncHandlerTests.swift
3+
// DevLogAppTests
4+
//
5+
// Created by opfic on 6/1/26.
6+
//
7+
8+
import Combine
9+
import Foundation
10+
import Testing
11+
import DevLogData
12+
@testable import DevLog
13+
14+
struct WidgetSessionSyncHandlerTests {
15+
@Test("로그인 세션 true 첫 진입에서만 위젯 초기 동기화를 요청한다")
16+
func 로그인_세션_true_첫_진입에서만_위젯_초기_동기화를_요청한다() async {
17+
let authServiceSpy = AuthServiceSpy()
18+
let widgetSyncEventBusSpy = WidgetSyncEventBusSpy()
19+
let widgetSessionSyncHandler = WidgetSessionSyncHandler(
20+
authService: authServiceSpy,
21+
widgetSyncEventBus: widgetSyncEventBusSpy
22+
)
23+
24+
authServiceSpy.send(true)
25+
authServiceSpy.send(true)
26+
27+
await withCheckedContinuation { continuation in
28+
DispatchQueue.main.async {
29+
continuation.resume()
30+
}
31+
}
32+
#expect(widgetSyncEventBusSpy.events == [.syncRequested])
33+
_ = widgetSessionSyncHandler
34+
}
35+
36+
@Test("로그아웃 이후 재로그인 시 위젯 초기 동기화를 다시 요청한다")
37+
func 로그아웃_이후_재로그인_시_위젯_초기_동기화를_다시_요청한다() async {
38+
let authServiceSpy = AuthServiceSpy()
39+
let widgetSyncEventBusSpy = WidgetSyncEventBusSpy()
40+
let widgetSessionSyncHandler = WidgetSessionSyncHandler(
41+
authService: authServiceSpy,
42+
widgetSyncEventBus: widgetSyncEventBusSpy
43+
)
44+
45+
authServiceSpy.send(true)
46+
authServiceSpy.send(false)
47+
authServiceSpy.send(true)
48+
49+
await withCheckedContinuation { continuation in
50+
DispatchQueue.main.async {
51+
continuation.resume()
52+
}
53+
}
54+
#expect(widgetSyncEventBusSpy.events == [.syncRequested, .syncRequested])
55+
_ = widgetSessionSyncHandler
56+
}
57+
}
58+
59+
private final class AuthServiceSpy: AuthService {
60+
private let currentValueSubject = CurrentValueSubject<Bool, Never>(false)
61+
62+
var uid: String? { nil }
63+
var providerIDs: [String] { [] }
64+
var currentUserEmail: String? { nil }
65+
var providerCount: Int { 0 }
66+
67+
func observeSignedIn() -> AnyPublisher<Bool, Never> {
68+
currentValueSubject.eraseToAnyPublisher()
69+
}
70+
71+
func beginSignIn() { }
72+
func completeSignIn() { }
73+
func cancelSignIn() { }
74+
func getProviderID() async throws -> String? { nil }
75+
func deleteCurrentUser() async throws { }
76+
func clearCurrentSession() async throws { }
77+
78+
func send(_ isSignedIn: Bool) {
79+
currentValueSubject.send(isSignedIn)
80+
}
81+
}
82+
83+
private final class WidgetSyncEventBusSpy: WidgetSyncEventBus {
84+
private(set) var events = [WidgetSyncEvent]()
85+
86+
func publish(_ event: WidgetSyncEvent) {
87+
events.append(event)
88+
}
89+
90+
func observe() -> AnyPublisher<WidgetSyncEvent, Never> {
91+
Empty().eraseToAnyPublisher()
92+
}
93+
}

Application/DevLogData/DevLogData.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 70;
6+
objectVersion = 71;
77
objects = {
88

99
/* Begin PBXBuildFile section */

Application/DevLogData/Sources/DataAssembler.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public final class DataAssembler: Assembler {
3535
container.register(TodoRepository.self) {
3636
TodoRepositoryImpl(
3737
todoService: container.resolve(TodoService.self),
38-
todoCategoryService: container.resolve(TodoCategoryService.self)
38+
todoCategoryService: container.resolve(TodoCategoryService.self),
39+
widgetSyncEventBus: container.resolve(WidgetSyncEventBus.self)
3940
)
4041
}
4142

@@ -109,7 +110,8 @@ public final class DataAssembler: Assembler {
109110
UserPreferencesRepositoryImpl(
110111
store: container.resolve(UserDefaultsStore.self),
111112
themeStore: container.resolve(ThemeStore.self),
112-
widgetSnapshotPreferenceStore: container.resolve(WidgetSnapshotPreferenceStore.self)
113+
widgetSnapshotPreferenceStore: container.resolve(WidgetSnapshotPreferenceStore.self),
114+
widgetSyncEventBus: container.resolve(WidgetSyncEventBus.self)
113115
)
114116
}
115117
}

Application/DevLogData/Sources/Repository/TodoRepositoryImpl.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ import DevLogDomain
1212
final class TodoRepositoryImpl: TodoRepository {
1313
private let todoService: TodoService
1414
private let todoCategoryService: TodoCategoryService
15+
private let widgetSyncEventBus: WidgetSyncEventBus
1516

1617
init(
1718
todoService: TodoService,
18-
todoCategoryService: TodoCategoryService
19+
todoCategoryService: TodoCategoryService,
20+
widgetSyncEventBus: WidgetSyncEventBus
1921
) {
2022
self.todoService = todoService
2123
self.todoCategoryService = todoCategoryService
24+
self.widgetSyncEventBus = widgetSyncEventBus
2225
}
2326

2427
func fetchTodos(_ query: TodoQuery, cursor: TodoCursor?) async throws -> TodoPage {
@@ -105,6 +108,7 @@ final class TodoRepositoryImpl: TodoRepository {
105108
let request = TodoRequest.fromDomain(todo)
106109
do {
107110
try await todoService.upsertTodo(request: request)
111+
widgetSyncEventBus.publish(.syncRequested)
108112
} catch {
109113
throw error.toDomain()
110114
}
@@ -113,6 +117,7 @@ final class TodoRepositoryImpl: TodoRepository {
113117
func deleteTodo(_ todoId: String) async throws {
114118
do {
115119
try await todoService.deleteTodo(todoId: todoId)
120+
widgetSyncEventBus.publish(.syncRequested)
116121
} catch {
117122
throw error.toDomain()
118123
}
@@ -121,6 +126,7 @@ final class TodoRepositoryImpl: TodoRepository {
121126
func undoDeleteTodo(_ todoId: String) async throws {
122127
do {
123128
try await todoService.undoDeleteTodo(todoId: todoId)
129+
widgetSyncEventBus.publish(.syncRequested)
124130
} catch {
125131
throw error.toDomain()
126132
}

Application/DevLogData/Sources/Repository/UserPreferencesRepositoryImpl.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,18 @@ final class UserPreferencesRepositoryImpl: UserPreferencesRepository {
2222
private let store: UserDefaultsStore
2323
private let themeStore: ThemeStore
2424
private let widgetSnapshotPreferenceStore: WidgetSnapshotPreferenceStore
25+
private let widgetSyncEventBus: WidgetSyncEventBus
2526

2627
init(
2728
store: UserDefaultsStore,
2829
themeStore: ThemeStore,
29-
widgetSnapshotPreferenceStore: WidgetSnapshotPreferenceStore
30+
widgetSnapshotPreferenceStore: WidgetSnapshotPreferenceStore,
31+
widgetSyncEventBus: WidgetSyncEventBus
3032
) {
3133
self.store = store
3234
self.themeStore = themeStore
3335
self.widgetSnapshotPreferenceStore = widgetSnapshotPreferenceStore
36+
self.widgetSyncEventBus = widgetSyncEventBus
3437
themeStore.send(systemTheme())
3538
}
3639

@@ -92,6 +95,7 @@ final class UserPreferencesRepositoryImpl: UserPreferencesRepository {
9295

9396
func setHeatmapActivityTypes(_ activityTypes: [String]) {
9497
widgetSnapshotPreferenceStore.setHeatmapActivityTypes(activityTypes)
98+
widgetSyncEventBus.publish(.syncRequested)
9599
}
96100

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

101105
func setTodayDisplayOptions(_ options: TodayDisplayOptions) {
102106
widgetSnapshotPreferenceStore.setTodayDisplayOptions(options)
107+
widgetSyncEventBus.publish(.syncRequested)
103108
}
104109
}

0 commit comments

Comments
 (0)