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

[Feat/#122] watchOS 기능 구현 #126

Merged
merged 15 commits into from
Jul 7, 2023
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 @@ -83,6 +83,7 @@ public extension ModulePath {

public extension ModulePath {
enum WatchShared: String, CaseIterable {
case Util
case DesignSystem
case ThirdPartyLib

Expand Down
33 changes: 8 additions & 25 deletions Projects/App/WatchExtension/Sources/App/RootApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,24 @@
//

import SwiftUI

import ComposableArchitecture

import WatchSharedDesignSystem

@main
struct RootApp: App {
@StateObject private var workoutDelegate = WorkoutDelegate()
@StateObject private var watchConnectivityDelegate = WatchConnectivityDelegate()
private var workoutDelegate = WorkoutDelegate()
private var watchConnectivityDelegate = WatchConnectivityDelegate()

@SceneBuilder var body: some Scene {
WindowGroup {
RootView(store: .init(initialState: RootStore.State(), reducer: RootStore()._printChanges()))
.environmentObject(workoutDelegate)
.environmentObject(watchConnectivityDelegate)
.onAppear {
WatchSharedDesignSystemFontFamily.registerAllCustomFonts()
}
}
}
}

//TODO: workout session 으로 마이그레이션 후 주석 제거

//@main
//struct RootApp: App {
// @StateObject private var workoutManager = WorkoutManager()
//
// @SceneBuilder var body: some Scene {
// WindowGroup {
// NavigationStack {
// StartView()
// }
// .onAppear {
// workoutManager.requestAuthorization()
// }
// .sheet(isPresented: $workoutManager.showingSummaryView) {
// SummaryView()
// }
// .environmentObject(workoutManager)
// }
// }
//}
//
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import ComposableArchitecture
public class WatchConnectivityDelegate: NSObject, ObservableObject, WCSessionDelegate {
public weak var session: WCSession?

@Published public var pumpingTimerData: PumpingTimerData = .init(timers: [], updatedTime: Date().timeIntervalSince1970)

init(session: WCSession = .default) {
self.session = session

Expand All @@ -23,14 +25,34 @@ public class WatchConnectivityDelegate: NSObject, ObservableObject, WCSessionDel
}

public func sendMessage(key: String, value: Double) {
self.session?.sendMessage([key: value], replyHandler: nil)
DispatchQueue.main.async {
self.session?.sendMessage([key: value], replyHandler: nil)
debugPrint("watchOS send key: \(key) value: \(value)")
}
}

public func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
public func sendPumpingTimerData() {

}

public func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}

public func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
debugPrint("watchOS recieved \(message)")
DispatchQueue.main.async {
debugPrint("watchOS recieved \(message)")
}
}

public func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
DispatchQueue.main.async {
do {
let pumpingTimerData = try JSONDecoder().decode(PumpingTimerData.self, from: messageData)
self.pumpingTimerData = pumpingTimerData
debugPrint("watchOS recieved \(pumpingTimerData)")
} catch {
debugPrint(error)
}
}
}
}
17 changes: 2 additions & 15 deletions Projects/App/WatchExtension/Sources/App/WorkoutDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,30 +62,17 @@ public class WorkoutDelegate: NSObject, ObservableObject {
}
}

func requestAuth() {
func requestAuthorization(completion: @escaping (Bool, Error?) -> Void) {
let typesToShare: Set = [
HKQuantityType.workoutType()
]

let typesToRead: Set = [
HKQuantityType.quantityType(forIdentifier: .heartRate)!,
HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)!,
HKObjectType.activitySummaryType()
]

healthStore.requestAuthorization(toShare: typesToShare, read: typesToRead) { (success, error) in
DispatchQueue.main.async {
if error != nil {
print(error.debugDescription)
} else {
if success {
print("권한이 허락되었습니다")
} else {
print("권한이 없습니다")
}
}
}
}
healthStore.requestAuthorization(toShare: typesToShare, read: typesToRead, completion: completion)
}
}

Expand Down
9 changes: 0 additions & 9 deletions Projects/App/WatchExtension/Sources/Domain/HealthClient.swift

This file was deleted.

41 changes: 41 additions & 0 deletions Projects/App/WatchExtension/Sources/Domain/PumpingTimer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// PumpingTimer.swift
// AppWatchExtension
//
// Created by 송영모 on 2023/07/04.
//

import Foundation

public struct PumpingTimer: Codable, Equatable {
public let id: UUID
public let workoutCategoryIdentifier: WorkoutCategoryIdentifier
public var time: Int

public var heartRateSum: Double
public var heartRateCount: Int
public var calorie: Double

public var pinTime: Int
public var isActive: Bool

public init(
id: UUID,
workoutCategoryIdentifier: WorkoutCategoryIdentifier,
time: Int = 0,
heartRateSum: Double = 0.0,
heartRateCount: Int = 0,
calorie: Double = 0,
pinTime: Int = 0,
isActive: Bool = false
) {
self.id = id
self.workoutCategoryIdentifier = workoutCategoryIdentifier
self.time = time
self.heartRateSum = heartRateSum
self.heartRateCount = heartRateCount
self.calorie = calorie
self.pinTime = pinTime
self.isActive = isActive
}
}
23 changes: 23 additions & 0 deletions Projects/App/WatchExtension/Sources/Domain/PumpingTimerData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// PumpingTimerData.swift
// AppWatchExtension
//
// Created by 송영모 on 2023/07/04.
//

import Foundation

public struct PumpingTimerData: Codable, Equatable {
public let timers: [PumpingTimer]
public let updatedTime: Double
public let isHardPush: Bool

public init(
timers: [PumpingTimer],
updatedTime: Double,
isHardPush: Bool = false) {
self.timers = timers
self.updatedTime = updatedTime
self.isHardPush = isHardPush
}
}
51 changes: 51 additions & 0 deletions Projects/App/WatchExtension/Sources/Domain/WorkoutCategory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// WorkoutCategory.swift
// AppWatchExtension
//
// Created by 송영모 on 2023/07/04.
//

import Foundation

//MARK: WorkoutCategoryIdentifierType
public enum WorkoutCategoryIdentifierType: String, CaseIterable {
case whole = "전신"
case upper = "상체"
case lower = "하체"
}

public extension WorkoutCategoryIdentifierType {
var identifiers: [WorkoutCategoryIdentifier] {
switch self {
case .whole:
return [.aerobic]
case .upper:
return [.shoulder, .chest, .arms, .back]
case .lower:
return [.butt]
}
}
}

//MARK: WorkoutCategoryIdentifier
public enum WorkoutCategoryIdentifier: String, Codable, CaseIterable, Equatable {
case aerobic = "유산소"
case shoulder = "어깨"
case chest = "가슴"
case arms = "팔"
case back = "등"
case butt = "엉덩이"
}

public extension WorkoutCategoryIdentifier {
var type: WorkoutCategoryIdentifierType {
switch self {
case .aerobic:
return .whole
case .shoulder, .chest, .arms, .back:
return .upper
case .butt:
return .lower
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// TimerCellStore.swift
// Pumping
//
// Created by 송영모 on 2023/07/05.
//

import SwiftUI

import ComposableArchitecture

import WatchSharedDesignSystem
import WatchSharedUtil

public struct TimerCellStore: ReducerProtocol {
public enum ResultType {
case time, heatRate, calorie

public var title: String {
switch self {
case .time: return "총 시간"
case .heatRate: return "심박수"
case .calorie: return "총 칼로리"
}
}

public var image: Image {
switch self {
case .time: return PumpingImages.iconTimer.swiftUIImage
case .heatRate: return PumpingImages.iconHeartbeat.swiftUIImage
case .calorie: return PumpingImages.iconFire.swiftUIImage
}
}

public func toSyntax(value: Double) -> String {
switch self {
case .time:
return DateManager.toClockString(from: Int(value))

case .heatRate:
if value == 0 {
return "-"
} else {
return String(describing: "\(Int(value))bpm")
}

case .calorie:
if value == 0 {
return "-"
} else {
return String(describing: "\(Int(value))Kcal")
}
}
}
}

public struct State: Equatable, Identifiable {
public let id: UUID
public let timer: PumpingTimer

public init(id: UUID, timer: PumpingTimer) {
self.id = id
self.timer = timer
}
}

public enum Action: Equatable {
case tapped
}

public func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
case .tapped:
return .none
}
}
}
Loading