Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SCRUM-59] 잠금화면 타이머 #60

Merged
merged 10 commits into from
Nov 26, 2024
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
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 @@ -17,4 +17,5 @@ public enum Core: String, Modulable {
case FeedbackGeneratorClient
case NetworkTracking
case StreamListener
case LiveActivityClient
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ public enum Feature: String, Modulable {
case PomodoroFeature
case CatFeature
case ErrorFeature
case LAPomodoroFeature
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
11 changes: 11 additions & 0 deletions Projects/App/ApplicationExtension/WidgetExtension/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// WidgetExtensionBundle.swift
// WidgetExtension
//
// Created by devMinseok on 11/19/24.
// Copyright © 2024 PomoNyang. All rights reserved.
//

import WidgetKit
import SwiftUI

import LAPomodoroFeature

@main
struct WidgetExtensionBundle: WidgetBundle {
var body: some Widget {
PomodoroLiveActivityWidget()
}
}
34 changes: 31 additions & 3 deletions Projects/App/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ import ProjectDescriptionHelpers
@_spi(Shared)
import DependencyPlugin

// MARK: - ApplicationExtension

let widgetExtensionTargetName = "WidgetExtension"
let widgetExtensionTarget: Target = .target(
name: widgetExtensionTargetName,
destinations: AppEnv.platform,
product: .appExtension,
bundleId: AppEnv.bundleId + ".widgetextension",
deploymentTargets: AppEnv.deploymentTarget,
infoPlist: InfoPlist.Mohanyang.widgetExtension,
sources: [
"ApplicationExtension/WidgetExtension/**"
],
entitlements: Entitlements.Mohanyang.widgetExtension,
dependencies: [
.dependency(module: Feature.LAPomodoroFeature)
],
settings: .targetSettings(product: .appExtension)
)


// MARK: - Target

let scripts: [TargetScript] = if currentConfig == .dev {
Expand Down Expand Up @@ -41,7 +62,8 @@ let appTarget: Target = .target(
entitlements: Entitlements.Mohanyang.app,
scripts: scripts,
dependencies: [
.dependency(rootModule: Feature.self)
.dependency(rootModule: Feature.self),
.target(widgetExtensionTarget)
],
settings: .targetSettings(product: .app)
)
Expand All @@ -53,6 +75,12 @@ let appScheme = Scheme.makeAppScheme(
config: currentConfig
)

let widgetExtensionScheme = Scheme.makeExtensionScheme(
extensionTarget: TargetReference(stringLiteral: widgetExtensionTargetName),
appTarget: TargetReference(stringLiteral: appTargetName),
config: currentConfig
)


// MARK: - Project

Expand All @@ -61,6 +89,6 @@ let project: Project = .init(
organizationName: AppEnv.organizationName,
options: .options(automaticSchemesOptions: .disabled, disableSynthesizedResourceAccessors: true),
settings: .projectSettings(xcconfig: .mohanyangAppXCConfig),
targets: [appTarget],
schemes: [appScheme]
targets: [appTarget, widgetExtensionTarget],
schemes: [appScheme, widgetExtensionScheme]
)
4 changes: 4 additions & 0 deletions Projects/App/Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
) {
self.store.send(.appDelegate(.didRegisterForRemoteNotifications(.failure(error))))
}

func applicationWillTerminate(_ application: UIApplication) {
self.store.send(.appDelegate(.willTerminate))
}
}

@main
Expand Down
5 changes: 1 addition & 4 deletions Projects/Core/APIClient/Interface/APIClientInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@

@DependencyClient
public struct APIClient {
public var apiRequest: @Sendable (
_ request: APIBaseRequest,
_ isWithInterceptor: Bool
) async throws -> (Data, URLResponse)
public var apiRequest: @Sendable (_ request: APIBaseRequest, _ isWithInterceptor: Bool) async throws -> (Data, URLResponse)

public func apiRequest<T: Decodable>(
request: APIBaseRequest,
Expand All @@ -26,7 +23,7 @@
let (data, _) = try await self.apiRequest(request, isWithInterceptor)

if T.self == EmptyResponse.self {
return EmptyResponse() as! T

Check warning on line 26 in Projects/Core/APIClient/Interface/APIClientInterface.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Force casts should be avoided (force_cast)
}

do {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// LiveActivityClientInterface.swift
// LiveActivityClient
//
// Created by MinseokKang on 11/23/24.
//

import Foundation
import ActivityKit

import Dependencies

public struct LiveActivityClient {
public var protocolAdapter: LiveActivityClientProtocol

public init(protocolAdapter: LiveActivityClientProtocol) {
self.protocolAdapter = protocolAdapter
}
}


// MARK: - Protocol 추상화

public protocol LiveActivityClientProtocol {
func isLiveActivityAllowed() -> Bool

func startActivity<T: ActivityAttributes>(
attributes: T,
content: ActivityContent<Activity<T>.ContentState>,
pushType: PushType?
) throws -> Activity<T>?

func updateActivity<T: ActivityAttributes>(
_ activity: T.Type,
id: String,
content: ActivityContent<Activity<T>.ContentState>
) async

func endActivity<T: ActivityAttributes>(
_ activity: T.Type,
id: String,
content: ActivityContent<Activity<T>.ContentState>?,
dismissalPolicy: ActivityUIDismissalPolicy
) async

func endAllActivityImmediately<T: ActivityAttributes>(
type: T.Type
) async
}
47 changes: 47 additions & 0 deletions Projects/Core/LiveActivityClient/Interface/TestKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// TestKey.swift
// LiveActivityClientInterface
//
// Created by devMinseok on 11/27/24.
// Copyright © 2024 PomoNyang. All rights reserved.
//

import ActivityKit

import Dependencies

extension LiveActivityClient: TestDependencyKey {
public static let previewValue = Self(protocolAdapter: LiveActivityClientImplTest())
public static let testValue = Self(protocolAdapter: LiveActivityClientImplTest())
}

class LiveActivityClientImplTest: LiveActivityClientProtocol {
func isLiveActivityAllowed() -> Bool {
return false
}

func startActivity<T: ActivityAttributes>(
attributes: T,
content: ActivityContent<Activity<T>.ContentState>,
pushType: PushType?
) throws -> Activity<T>? {
return nil
}

func updateActivity<T: ActivityAttributes>(
_ activity: T.Type,
id: String,
content: ActivityContent<Activity<T>.ContentState>
) async {}

func endActivity<T: ActivityAttributes>(
_ activity: T.Type,
id: String,
content: ActivityContent<Activity<T>.ContentState>?,
dismissalPolicy: ActivityUIDismissalPolicy
) async {}

func endAllActivityImmediately<T: ActivityAttributes>(
type: T.Type
) async {}
}
27 changes: 27 additions & 0 deletions Projects/Core/LiveActivityClient/Project.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// Project.swift
// LiveActivityClientManifests
//
// Created by MinseokKang on 11/23/24.
//

import ProjectDescription
import ProjectDescriptionHelpers

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

let project: Project = .makeTMABasedProject(
module: Core.LiveActivityClient,
scripts: [],
targets: [
.sources,
.interface
],
dependencies: [
.interface: [
.dependency(rootModule: Shared.self)
]
]
)
69 changes: 69 additions & 0 deletions Projects/Core/LiveActivityClient/Sources/LiveActivityClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// LiveActivityClient.swift
// LiveActivityClient
//
// Created by MinseokKang on 11/23/24.
//

import ActivityKit

import LiveActivityClientInterface

import Dependencies

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

public static func live() -> LiveActivityClient {
return .init(protocolAdapter: LiveActivityClientImpl())
}
}

// MARK: - Protocol 추상화 구현

final class LiveActivityClientImpl: LiveActivityClientProtocol {
public func isLiveActivityAllowed() -> Bool {
return ActivityAuthorizationInfo().areActivitiesEnabled
}

public func startActivity<T: ActivityAttributes>(
attributes: T,
content: ActivityContent<Activity<T>.ContentState>,
pushType: PushType?
) throws -> Activity<T>? {
guard ActivityAuthorizationInfo().areActivitiesEnabled else { return nil }

return try Activity.request(
attributes: attributes,
content: content,
pushType: pushType
)
}

public func updateActivity<T: ActivityAttributes>(
_ activity: T.Type,
id: String,
content: ActivityContent<Activity<T>.ContentState>
) async {
let activity = Activity<T>.activities.first { $0.id == id }
await activity?.update(content, alertConfiguration: nil)
}

public func endActivity<T: ActivityAttributes>(
_ activity: T.Type,
id: String,
content: ActivityContent<Activity<T>.ContentState>?,
dismissalPolicy: ActivityUIDismissalPolicy
) async {
let activity = Activity<T>.activities.first { $0.id == id }
await activity?.end(content, dismissalPolicy: dismissalPolicy)
}

public func endAllActivityImmediately<T: ActivityAttributes>(
type: T.Type
) async {
for activity in Activity<T>.activities {
await activity.end(nil, dismissalPolicy: .immediate)
}
}
}
16 changes: 16 additions & 0 deletions Projects/Domain/AppService/Sources/AppService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,19 @@ public func getTimerAlarm(
) -> Bool {
return userDefaultsClient.boolForKey(isTimerAlarmOnKey)
}


let isLiveActivityOnKey = "mohanyang_userdefaults_isLiveActivityOnKey"

public func setLiveActivityState(
userDefaultsClient: UserDefaultsClient,
isEnabled: Bool
) async -> Void {
await userDefaultsClient.setBool(isEnabled, isLiveActivityOnKey)
}

public func getLiveActivityState(
userDefaultsClient: UserDefaultsClient
) -> Bool {
return userDefaultsClient.boolForKey(isLiveActivityOnKey)
}
Loading
Loading