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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ jobs:
schemes: "DevLogDomain DevLogData"
- name: Persistence-Presentation
schemes: "DevLogPersistence DevLogPresentation"
- name: WidgetCore
schemes: "DevLogWidgetCore"
- name: Widget
schemes: "DevLogWidget DevLogWidgetCore"
steps:
- uses: actions/checkout@v5

Expand Down
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ excluded:
- Application/DevLogInfra/Project.swift
- Application/DevLogPersistence/Project.swift
- Application/DevLogPresentation/Project.swift
- Application/DevLogWidget/Project.swift
- Widget/DevLogWidgetCore/Project.swift
- Widget/DevLogWidgetExtension/Project.swift

Expand Down
1 change: 1 addition & 0 deletions Application/DevLogApp/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ let project = Project(
.project(target: "DevLogPresentation", path: "../DevLogPresentation"),
.project(target: "DevLogPersistence", path: "../DevLogPersistence"),
.project(target: "DevLogInfra", path: "../DevLogInfra"),
.project(target: "DevLogWidget", path: "../DevLogWidget"),
.project(target: "DevLogData", path: "../DevLogData"),
.project(target: "DevLogDomain", path: "../DevLogDomain"),
.project(target: "DevLogCore", path: "../DevLogCore"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import DevLogData
import DevLogDomain
import DevLogInfra
import DevLogPersistence
import DevLogWidget

final class AppAssembler: Assembler {
private let assemblers: [Assembler] = [
PersistenceAssembler(),
InfraAssembler(),
WidgetAssembler(),
DataAssembler(),
DomainAssembler(),
AppLayerAssembler()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,9 @@

import DevLogCore
import DevLogData
import DevLogDomain

final class AppLayerAssembler: Assembler {
func assemble(_ container: any DIContainer) {
container.register(WidgetSyncEventBus.self) {
WidgetSyncEventBusImpl()
}
container.register(WidgetSyncEventHandler.self) {
WidgetSyncEventHandler(
eventBus: container.resolve(WidgetSyncEventBus.self),
repository: container.resolve(TodoRepository.self),
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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import UIKit
import DevLogCore
import DevLogData
import DevLogInfra
import DevLogWidget

class AppDelegate: UIResponder, UIApplicationDelegate {
private let logger = Logger(category: "AppDelegate")
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 @@ -10,6 +10,7 @@ import DevLogCore
import DevLogData
import DevLogDomain
import DevLogPresentation
import DevLogWidget

@main
struct DevLogApp: App {
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 @@ -45,6 +45,12 @@ public final class DataAssembler: Assembler {
)
}

container.register(WidgetTodoSnapshotRepository.self) {
WidgetTodoSnapshotRepositoryImpl(
repository: container.resolve(TodoRepository.self)
)
}

container.register(TodoCategoryRepository.self) {
TodoCategoryRepositoryImpl(
todoCategoryService: container.resolve(TodoCategoryService.self)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// WidgetTodoSnapshotRepository.swift
// DevLogData
//
// Created by opfic on 6/8/26.
//

import Foundation
import DevLogCore

public protocol WidgetTodoSnapshotRepository {
func fetchTodayTodos(
dueDateFilter: TodoQuery.DueDateFilter,
sortTarget: TodoQuery.SortTarget,
sortOrder: TodoQuery.SortOrder,
pageSize: Int
) async throws -> [WidgetTodoSnapshot]

func fetchHeatmapTodos(
sortTarget: TodoQuery.SortTarget,
quarterStart: Date,
nextQuarterStart: Date,
pageSize: Int
) async throws -> [WidgetTodoSnapshot]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// WidgetTodoSnapshotRepositoryImpl.swift
// DevLogData
//
// Created by opfic on 6/8/26.
//

import Foundation
import DevLogCore
import DevLogDomain

final class WidgetTodoSnapshotRepositoryImpl: WidgetTodoSnapshotRepository {
private let repository: TodoRepository

init(repository: TodoRepository) {
self.repository = repository
}

func fetchTodayTodos(
dueDateFilter: TodoQuery.DueDateFilter,
sortTarget: TodoQuery.SortTarget,
sortOrder: TodoQuery.SortOrder,
pageSize: Int
) async throws -> [WidgetTodoSnapshot] {
let todoPage = try await repository.fetchTodos(
TodoQuery(
completionFilter: .incomplete,
dueDateFilter: dueDateFilter,
sortTarget: sortTarget,
sortOrder: sortOrder,
pageSize: pageSize,
fetchAllPages: true
),
cursor: nil
)

return todoPage.items.map(WidgetTodoSnapshot.fromDomain)
}

func fetchHeatmapTodos(
sortTarget: TodoQuery.SortTarget,
quarterStart: Date,
nextQuarterStart: Date,
pageSize: Int
) async throws -> [WidgetTodoSnapshot] {
let todoPage = try await repository.fetchTodos(
TodoQuery(
sortDateFrom: quarterStart,
sortDateTo: nextQuarterStart,
includesDeleted: true,
sortTarget: sortTarget,
pageSize: pageSize,
fetchAllPages: true
),
cursor: nil
)

return todoPage.items.map(WidgetTodoSnapshot.fromDomain)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
//
// WidgetTodoSnapshotRepositoryImplTests.swift
// DevLogDataTests
//
// Created by opfic on 6/8/26.
//

import Foundation
import Testing
import DevLogCore
import DevLogDomain
@testable import DevLogData

struct WidgetTodoSnapshotRepositoryImplTests {
@Test("Today 위젯 Todo 조회는 기존 TodoRepository query와 snapshot 매핑을 사용한다")
func today_위젯_todo_조회는_기존_todorepository_query와_snapshot_매핑을_사용한다() async throws {
let repositorySpy = TodoRepositorySpy()
let repository = WidgetTodoSnapshotRepositoryImpl(repository: repositorySpy)
let now = Date(timeIntervalSince1970: 100)
let todo = makeTodo(id: "today", createdAt: now, dueDate: now)

await repositorySpy.setTodos([todo], for: .dueDate)

let snapshots = try await repository.fetchTodayTodos(
dueDateFilter: .withDueDate,
sortTarget: .dueDate,
sortOrder: .oldest,
pageSize: 100
)
let queries = await repositorySpy.calledQueries()

#expect(snapshots == [makeSnapshot(id: "today", createdAt: now, dueDate: now)])
#expect(queries == [
TodoQuery(
completionFilter: .incomplete,
dueDateFilter: .withDueDate,
sortTarget: .dueDate,
sortOrder: .oldest,
pageSize: 100,
fetchAllPages: true
)
])
}

@Test("Heatmap 위젯 Todo 조회는 기존 TodoRepository query와 snapshot 매핑을 사용한다")
func heatmap_위젯_todo_조회는_기존_todorepository_query와_snapshot_매핑을_사용한다() async throws {
let repositorySpy = TodoRepositorySpy()
let repository = WidgetTodoSnapshotRepositoryImpl(repository: repositorySpy)
let quarterStart = Date(timeIntervalSince1970: 100)
let nextQuarterStart = Date(timeIntervalSince1970: 200)
let todo = makeTodo(id: "created", createdAt: quarterStart)

await repositorySpy.setTodos([todo], for: .createdAt)

let snapshots = try await repository.fetchHeatmapTodos(
sortTarget: .createdAt,
quarterStart: quarterStart,
nextQuarterStart: nextQuarterStart,
pageSize: 100
)
let queries = await repositorySpy.calledQueries()

#expect(snapshots == [makeSnapshot(id: "created", createdAt: quarterStart)])
#expect(queries == [
TodoQuery(
sortDateFrom: quarterStart,
sortDateTo: nextQuarterStart,
includesDeleted: true,
sortTarget: .createdAt,
pageSize: 100,
fetchAllPages: true
)
])
}

private func makeTodo(
id: String,
createdAt: Date,
completedAt: Date? = nil,
deletedAt: Date? = nil,
dueDate: Date? = nil
) -> Todo {
Todo(
id: id,
isPinned: false,
isCompleted: completedAt != nil,
isChecked: false,
number: 1,
title: id,
content: "",
createdAt: createdAt,
updatedAt: createdAt,
completedAt: completedAt,
deletedAt: deletedAt,
dueDate: dueDate,
tags: [],
category: .system(.feature)
)
}

private func makeSnapshot(
id: String,
createdAt: Date,
completedAt: Date? = nil,
deletedAt: Date? = nil,
dueDate: Date? = nil
) -> WidgetTodoSnapshot {
WidgetTodoSnapshot(
id: id,
number: 1,
title: id,
isPinned: false,
createdAt: createdAt,
completedAt: completedAt,
deletedAt: deletedAt,
dueDate: dueDate
)
}
}

private actor TodoRepositorySpy: TodoRepository {
private var queries = [TodoQuery]()
private var todosBySortTarget = [TodoQuery.SortTarget: [Todo]]()

func setTodos(_ todos: [Todo], for sortTarget: TodoQuery.SortTarget) {
todosBySortTarget[sortTarget] = todos
}

func fetchTodos(_ query: TodoQuery, cursor: TodoCursor?) async throws -> TodoPage {
queries.append(query)

return TodoPage(
items: todosBySortTarget[query.sortTarget] ?? [],
nextCursor: nil
)
}

func fetchTodo(_ todoId: String) async throws -> Todo {
throw TodoRepositorySpyError.unexpectedCall
}

func fetchReferences(_ numbers: [Int]) async throws -> [Int: TodoReference] {
throw TodoRepositorySpyError.unexpectedCall
}

func upsertTodo(_ todo: Todo) async throws {
throw TodoRepositorySpyError.unexpectedCall
}

func upsertTodo(_ todoDraft: TodoDraft) async throws {
throw TodoRepositorySpyError.unexpectedCall
}

func deleteTodo(_ todoId: String) async throws {
throw TodoRepositorySpyError.unexpectedCall
}

func undoDeleteTodo(_ todoId: String) async throws {
throw TodoRepositorySpyError.unexpectedCall
}

func calledQueries() -> [TodoQuery] {
queries
}
}

private enum TodoRepositorySpyError: Error {
case unexpectedCall
}
16 changes: 16 additions & 0 deletions Application/DevLogWidget/Project.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import ProjectDescription
import ProjectDescriptionHelpers

let project = Project.devlogFramework(
name: "DevLogWidget",
bundleId: "com.opfic.DevLog.DevLogWidget",
versionXcconfigPath: "../Shared/Version.xcconfig",
frameworkInfoPlistPath: "../Shared/InfoPlists/Framework-Info.plist",
testsInfoPlistPath: "../Shared/InfoPlists/UnitTests-Info.plist",
packages: DevLogPackages.defaultPackages,
dependencies: [
.project(target: "DevLogData", path: "../DevLogData"),
.project(target: "DevLogCore", path: "../DevLogCore"),
],
hasTests: true
)
1 change: 1 addition & 0 deletions Application/DevLogWidget/Sources/.swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
parent_config: ../../../.swiftlint.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// WidgetSessionSyncHandler.swift
// DevLog
// DevLogWidget
//
// Created by opfic on 6/1/26.
//
Expand All @@ -9,13 +9,13 @@ import Combine
import Foundation
import DevLogData

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

init(
public init(
authService: AuthService,
widgetSyncEventBus: WidgetSyncEventBus
) {
Expand Down
Loading