Skip to content

Commit

Permalink
[SCRUM-103] LiveActivity 개선 (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
devMinseok authored Dec 25, 2024
1 parent 7a8cf0a commit 2db5d87
Show file tree
Hide file tree
Showing 34 changed files with 532 additions and 155 deletions.
Binary file modified DependencyGraph/mohanyang_dev_graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified DependencyGraph/mohanyang_prod_graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ public enum Core: String, Modulable {
case StreamListener
case LiveActivityClient
case AudioClient
case BackgroundTaskClient
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// BackgroundTaskClientInterface.swift
// BackgroundTaskClient
//
// Created by MinseokKang on 12/5/24.
//

import Foundation
import BackgroundTasks

import Dependencies
import DependenciesMacros

@DependencyClient
public struct BackgroundTaskClient {
public var registerTask: @Sendable (
_ identifier: String,
_ queue: DispatchQueue?,
_ launchHandler: @escaping (BGTask) -> Void
) -> Bool = { _, _, _ in false }

public var submit: @Sendable (_ taskRequest: BGTaskRequest) throws -> Void

public var cancel: @Sendable (_ identifier: String) -> Void

public var cancelAllTaskRequests: @Sendable () -> Void

public var pendingTaskRequests: () async -> [BGTaskRequest] = { [] }
}
14 changes: 14 additions & 0 deletions Projects/Core/BackgroundTaskClient/Interface/TestKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// TestKey.swift
// BackgroundTaskClientInterface
//
// Created by devMinseok on 12/5/24.
// Copyright © 2024 PomoNyang. All rights reserved.
//

import Dependencies

extension BackgroundTaskClient: TestDependencyKey {
public static let previewValue = Self()
public static let testValue = Self()
}
27 changes: 27 additions & 0 deletions Projects/Core/BackgroundTaskClient/Project.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// Project.swift
// BackgroundTaskClientManifests
//
// Created by MinseokKang on 12/5/24.
//

import ProjectDescription
import ProjectDescriptionHelpers

@_spi(Core)
@_spi(Shared)
import DependencyPlugin

let project: Project = .makeTMABasedProject(
module: Core.BackgroundTaskClient,
scripts: [],
targets: [
.sources,
.interface
],
dependencies: [
.interface: [
.dependency(rootModule: Shared.self)
]
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// BackgroundTaskClient.swift
// BackgroundTaskClient
//
// Created by MinseokKang on 12/5/24.
//

import Foundation
import BackgroundTasks

import BackgroundTaskClientInterface

import Dependencies

extension BackgroundTaskClient: DependencyKey {
public static let liveValue: BackgroundTaskClient = .live()

public static func live() -> BackgroundTaskClient {
let backgroundTaskScheduler = BGTaskScheduler.shared

return .init(
registerTask: { identifier, queue, handler in
return backgroundTaskScheduler.register(forTaskWithIdentifier: identifier, using: queue, launchHandler: handler)
},
submit: { request in
try backgroundTaskScheduler.submit(request)
},
cancel: { identifier in
backgroundTaskScheduler.cancel(taskRequestWithIdentifier: identifier)
},
cancelAllTaskRequests: {
backgroundTaskScheduler.cancelAllTaskRequests()
},
pendingTaskRequests: {
await backgroundTaskScheduler.pendingTaskRequests()
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@ public protocol LiveActivityClientProtocol {
func endAllActivityImmediately<T: ActivityAttributes>(
type: T.Type
) async

func getActivities<T: ActivityAttributes>(type: T.Type) -> [Activity<T>]
}
4 changes: 4 additions & 0 deletions Projects/Core/LiveActivityClient/Interface/TestKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ class LiveActivityClientImplTest: LiveActivityClientProtocol {
func endAllActivityImmediately<T: ActivityAttributes>(
type: T.Type
) async {}

func getActivities<T: ActivityAttributes>(type: T.Type) -> [Activity<T>] {
return []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,8 @@ final class LiveActivityClientImpl: LiveActivityClientProtocol {
await activity.end(nil, dismissalPolicy: .immediate)
}
}

func getActivities<T: ActivityAttributes>(type: T.Type) -> [Activity<T>] {
return Activity<T>.activities
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ public struct UserNotificationClient {
}
public var removeDeliveredNotificationsWithIdentifiers: @Sendable ([String]) async -> Void
public var removePendingNotificationRequestsWithIdentifiers: @Sendable ([String]) async -> Void
public var removeAllPendingNotificationRequests: @Sendable () async -> Void
public var requestAuthorization: @Sendable (UNAuthorizationOptions) async throws -> Bool
public var setBadgeCount: @Sendable (Int) async throws -> Void

@CasePathable
public enum DelegateEvent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,14 @@ extension UserNotificationClient: DependencyKey {
removePendingNotificationRequestsWithIdentifiers: {
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: $0)
},
removeAllPendingNotificationRequests: {
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
},
requestAuthorization: {
try await UNUserNotificationCenter.current().requestAuthorization(options: $0)
},
setBadgeCount: { badgeCount in
try await UNUserNotificationCenter.current().setBadgeCount(badgeCount)
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public struct PomodoroActivityAttributes: Equatable, ActivityAttributes {
public var goalDatetime: Date
public var isRest: Bool

public var dateRange: ClosedRange<Date> {
return Date.now...goalDatetime
public func isTimerOver() -> Bool {
return Date() >= goalDatetime
}

public init(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import UserDefaultsClientInterface
import DatabaseClientInterface
import APIClientInterface
import BackgroundTaskClientInterface
import LiveActivityClientInterface

import Dependencies
import DependenciesMacros
Expand All @@ -21,6 +23,8 @@ public struct PomodoroService {
public var changeCategoryTime: @Sendable (_ apiClient: APIClient, _ categoryID: Int, _ request: EditCategoryRequest) async throws -> Void
public var saveFocusTimeHistory: @Sendable (_ apiClient: APIClient, _ databaseClient: DatabaseClient, _ request: [FocusTimeHistory]) async throws -> Void
public var getFocusTimeSummaries: @Sendable (_ apiClient: APIClient) async throws -> FocusTimeSummary

public var registerBGTaskToUpdateTimer: @Sendable (_ bgTaskClient: BackgroundTaskClient, _ liveActivityClient: LiveActivityClient) -> Bool = { _, _ in false }
}

extension PomodoroService: TestDependencyKey {
Expand Down
21 changes: 20 additions & 1 deletion Projects/Domain/PomodoroService/Sources/PomodoroService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
@_spi(Internal)
import PomodoroServiceInterface
import APIClientInterface
import Foundation

import Dependencies

Expand All @@ -18,7 +19,8 @@ extension PomodoroService: DependencyKey {

private static func live() -> PomodoroService {
return .init(
syncCategoryList: { apiClient, databaseClient in
syncCategoryList: {
apiClient, databaseClient in
let api = CategoryAPI.getCategoryList
let categoryList = try await apiClient.apiRequest(request: api, as: [PomodoroCategory].self)
for category in categoryList {
Expand Down Expand Up @@ -50,6 +52,23 @@ extension PomodoroService: DependencyKey {
getFocusTimeSummaries: { apiClient in
let api = FocusTimeAPI.getSummaries
return try await apiClient.apiRequest(request: api, as: FocusTimeSummary.self)
},
registerBGTaskToUpdateTimer: { bgTaskClient, liveActivityClient in
bgTaskClient.registerTask(
identifier: "com.pomonyang.mohanyang.update_LiveActivity",
queue: nil
) { task in
task.expirationHandler = {
task.setTaskCompleted(success: false)
}
let pomodoroActivities = liveActivityClient.protocolAdapter.getActivities(type: PomodoroActivityAttributes.self)
Task {
if let firstActivity = pomodoroActivities.first {
await firstActivity.update(firstActivity.content)
}
task.setTaskCompleted(success: true)
}
}
}
)
}
Expand Down
65 changes: 51 additions & 14 deletions Projects/Feature/Feature/Sources/AppCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ import UserDefaultsClientInterface
import UserNotificationClientInterface
import CatServiceInterface
import UserServiceInterface
import PomodoroServiceInterface
import DatabaseClientInterface
import StreamListenerInterface
import BackgroundTaskClientInterface
import LiveActivityClientInterface

import ComposableArchitecture

import BackgroundTasks

@Reducer
public struct AppCore {
@ObservableState
Expand All @@ -34,9 +39,9 @@ public struct AppCore {
var onboarding: OnboardingCore.State?
@Presents var networkError: NetworkErrorCore.State?
@Presents var requestError: RequestErrorCore.State?

var isLoading: Bool = false

public init() {}
}

Expand All @@ -58,7 +63,9 @@ public struct AppCore {
@Dependency(UserService.self) var userService
@Dependency(DatabaseClient.self) var databaseClient
@Dependency(StreamListener.self) var streamListener

@Dependency(BackgroundTaskClient.self) var backgroundTaskClient
@Dependency(LiveActivityClient.self) var liveActivityClient

public init() {}

public var body: some ReducerOf<Self> {
Expand Down Expand Up @@ -97,51 +104,73 @@ public struct AppCore {
await send(.serverState(serverState))
}
}

case .appDelegate:
return .none

case .didChangeScenePhase(.background):
return .run { send in
let pomodoroActivity = liveActivityClient.protocolAdapter.getActivities(type: PomodoroActivityAttributes.self).first
let pendingBGTaskRequest = await backgroundTaskClient.pendingTaskRequests().first

if let pomodoroActivity {
if let pendingBGTaskRequest {
if pendingBGTaskRequest.earliestBeginDate != pomodoroActivity.content.state.goalDatetime {
backgroundTaskClient.cancel(identifier: pendingBGTaskRequest.identifier)
await pomodoroActivity.update(pomodoroActivity.content)
try submitUpdateLiveActivityBGTask(earliestBeginDate: pomodoroActivity.content.state.goalDatetime)
}
} else {
try submitUpdateLiveActivityBGTask(earliestBeginDate: pomodoroActivity.content.state.goalDatetime)
}
} else {
if let pendingBGTaskRequest {
backgroundTaskClient.cancel(identifier: pendingBGTaskRequest.identifier)
}
}
}

case .didChangeScenePhase:
return .none

case .splash(.moveToHome):
state.splash = nil
state.home = HomeCore.State()
return .none

case .splash(.moveToOnboarding):
state.splash = nil
state.onboarding = OnboardingCore.State()
return .none

case .splash:
return .none

case .home:
return .none

case .onboarding(.selectCat(.presented(.namingCat(.presented(.moveToHome))))):
state.onboarding = nil
state.home = HomeCore.State()
return .none

case .onboarding:
return .none

case .networkError:
return .none

case .requestError(.presented(.moveToHome)):
if state.onboarding != nil {
state.onboarding = OnboardingCore.State()
} else if state.home != nil {
state.home = HomeCore.State()
}
return .none

case .requestError:
return .none

case .serverState(let serverState):
switch serverState {
case .requestStarted:
Expand All @@ -158,4 +187,12 @@ public struct AppCore {
return .none
}
}

func submitUpdateLiveActivityBGTask(earliestBeginDate: Date) throws {
let request = BGProcessingTaskRequest(identifier: "com.pomonyang.mohanyang.update_LiveActivity")
request.requiresExternalPower = false
request.requiresNetworkConnectivity = false
request.earliestBeginDate = earliestBeginDate
try backgroundTaskClient.submit(taskRequest: request)
}
}
Loading

0 comments on commit 2db5d87

Please sign in to comment.