Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/developeracademy-postech/2024-nc2-m41-homekit


https://github.com/developeracademy-postech/2024-nc2-m41-homekit

Last synced: about 11 hours ago
JSON representation

Awesome Lists containing this project

README

        

# 2024-NC2-M41-HomeKit
## πŸŽ₯ Youtube Link
(μΆ”ν›„ λ§Œλ“€μ–΄μ§„ 유튜브 링크 μΆ”κ°€)

## πŸ’‘ About Augmented Reality
(ν•΄λ‹Ή κΈ°μˆ μ— λŒ€ν•œ 쑰사 λ‚΄μš© 정리)
> ν™ˆν‚·μ„ λ°”νƒ•μœΌλ‘œ νœ΄λŒ€ν°μ„ μ΄μš©ν•΄ 집에 μžˆλŠ” 전ꡬλ₯Ό μ‘°μž‘ν•  수 μžˆλ‹€

## 🎯 What we focus on?
> ν™ˆ 앱에 μΆ”κ°€λœ κΈ°κΈ°λ₯Ό λΆˆλŸ¬μ™€μ„œ μ•± λ‚΄μ—μ„œ 타이머λ₯Ό μ„€μ •ν•˜λ©΄ 타이머 μ’…λ£Œμ‹œκ°„μ— λ§žμΆ”μ–΄ κΈ°κΈ°(전ꡬ)의 점멸을 톡해 μ‚¬μš©μžμ—κ²Œ μ•Œλ¦½λ‹ˆλ‹€.
> 청각μž₯애인을 μ£Όμš” μ•± μ‚¬μš©μžλ‘œ μ„€μ •ν–ˆμŠ΅λ‹ˆλ‹€. μ†Œλ¦¬λ₯Ό 인지할 수 μ—†λŠ” λŒ€μƒμ„ μœ„ν•΄ μ†Œλ¦¬ λŒ€μ‹  μ „κ΅¬μ˜ 점멸둜 μ–΄λ– ν•œ 상황을 μ•Œλ¦¬μžλŠ” λͺ©ν‘œλ₯Ό μ„Έμ› κ³ , κ·Έ μ€‘μ—μ„œλ„ μ‹œκ°„μ„ 재고 μ’…λ£Œ μ‹œμ μ„ ν™•μ‹€νžˆ 인지해야 ν•˜λŠ” 일에 μ΄ˆμ μ„ λ‘μ—ˆμŠ΅λ‹ˆλ‹€.

## πŸ’Ό Use Case
>- 세탁기λ₯Ό μž‘λ™ν•œ 후에 μ’…λ£Œ μ‹œμ μ— λ§žμΆ”μ–΄ 타이머λ₯Ό μ„€μ •ν•˜κ³  μ‚¬μš©ν•œλ‹€.
>- λ°₯μ†₯으둜 λ°₯을 짓고 타이머λ₯Ό μ„€μ •ν•œλ‹€.
>- μˆ˜μœ‘μ„ μ‚ΆλŠ”λ°μ— μ‹œκ°„μ΄ 였래 κ±Έλ¦¬λŠ”λ° κ³„μ†ν•΄μ„œ λ³΄κ³ μžˆμ„ 수 μ—†κΈ° λ•Œλ¬Έμ— 타이머λ₯Ό μ„€μ •ν•œλ‹€.

## πŸ–ΌοΈ Prototype
https://github.com/DeveloperAcademy-POSTECH/2024-NC2--M-41-HomeKit/assets/167425685/bff8b5bb-4130-4d9f-aca5-3817100ed49d
>- μ§‘μ—μ„œ 방으둜 μ ‘κ·Όν•΄ 타이머와 μ‘°λͺ…을 μ„€μ •ν•˜κ³ , 타이머λ₯Ό μž‘λ™μ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.
>- μ•…μ„Έμ„œλ¦¬μ—μ„œ μ•…μ„Έμ„œλ¦¬ 각각에 λŒ€ν•œ μƒνƒœλ₯Ό μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
>- 졜근 μ‚¬μš©ν•œ νƒ€μ΄λ¨Έμ—μ„œ κ°€μž₯ μ΅œκ·Όμ— μ‚¬μš©ν•œ 타이머λ₯Ό λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. 자주 μ‚¬μš©ν•˜λŠ” κΈ°λŠ₯이라면 졜근 μ‚¬μš©ν•œ νƒ€μ΄λ¨Έμ—μ„œ 계속 λ³Ό 수 μžˆμ–΄ μ‚¬μš©μžν™”μ— 도움이 λ©λ‹ˆλ‹€.

## πŸ› οΈ About Code
타이머λ₯Ό μ„€μ •ν•œ λ’€ μ’…λ£Œλ˜λ©΄ 전ꡬ가 μ λ©Έν•œλ‹€
```swift
import SwiftUI
import Combine
import HomeKit

struct TimerView: View {
@ObservedObject var homeKitManager: HomeKitManager
@StateObject private var timerViewModel = TimerViewModel()
@Binding var timerFinished: Bool
@Binding var selectedTime: String
@Binding var showPickerSection: Bool
let timerTitle: String
private let lightController: LightController
let accessory: HMAccessory

// 이닛 λΆ€λΆ„
init(accessory: HMAccessory, homeKitManager: HomeKitManager, lightController: LightController, timerFinished: Binding, selectedTime: Binding, showPickerSection: Binding, timerTitle: String) {
self.accessory = accessory
self.homeKitManager = homeKitManager
self.lightController = lightController
self._timerFinished = timerFinished
self._selectedTime = selectedTime
self._showPickerSection = showPickerSection
self.timerTitle = timerTitle
}

var body: some View {
VStack {
// 타이머 제λͺ© (Ex. 빨래 ν›„ 건쑰)
HStack{
Text(timerTitle)
.font(.title)
.padding(.leading)
.bold()
.foregroundStyle(Color.white)
Spacer()
}
.padding(.vertical, 20)

// 타이머 λŒμ•„κ°€λŠ” 숫자 λΆ€λΆ„
ZStack{
// 타이머 숫자
Text(timerViewModel.timeString)
.font(.system(size: 40))
.padding()
.foregroundStyle(Color.white)
// μ›ν˜• ν”„λ‘œκ·Έλ ˆμŠ€λ·°
RadialProgressView(progress: timerViewModel.progress)
.frame(width: 250, height: 250)
// μ‹œκ³„
}
.padding(.bottom, 20)

// μ·¨μ†Œ, μΌμ‹œμ •μ§€/μ‹œμž‘ λ²„νŠΌ
HStack {
// μ·¨μ†Œλ²„νŠΌ
Button(action: timerViewModel.reset) {
Text("μ·¨μ†Œ")
.foregroundColor(.white)
.frame(width: 92, height: 92)
.background(Color.gray.opacity(0.3))
.clipShape(Circle())
}
Spacer()
// μΌμ‹œμ •μ§€/μ‹œμž‘ λ²„νŠΌ
Button(action: timerViewModel.toggleTimer) {
Text(timerViewModel.isRunning ? "μΌμ‹œμ •μ§€" : "μ‹œμž‘")
.foregroundColor(timerViewModel.isRunning ? Color.yellow : Color.green)
.frame(width: 92, height: 92)
.background(timerViewModel.isRunning ? Color.yellow.opacity(0.3) : Color.green.opacity(0.3))
.clipShape(Circle())
}
}
.padding(.horizontal)
Spacer()
}
.background(Color.bgPrimaryDarkBase)
// 화면이 λ‚˜νƒ€λ‚¬μ„λ•Œ
.onAppear {
let timeComponents = selectedTime.split(separator: ":").map { String($0) }
if timeComponents.count == 3 {
if let hours = Int(timeComponents[0]), let minutes = Int(timeComponents[1]), let seconds = Int(timeComponents[2]) {
timerViewModel.selectedHours = hours
timerViewModel.selectedMinutes = minutes
timerViewModel.selectedSeconds = seconds
timerViewModel.reset()
}
}
}
// 타이머가 μ’…λ£Œλ˜μ—ˆμ„ λ•Œ
.onChange(of: timerViewModel.timerFinished, initial: false) { oldValue, newValue in
if newValue {
lightController.startBlinkingBulb(for: accessory)
}
else {
lightController.stopBlinkingBulb()
}
}
//툴 λ°” μ’…λ£Œλ²„νŠΌ
.toolbar {
Button("μ’…λ£Œ"){
lightController.stopBlinkingBulb()
}
.foregroundStyle(Color.appYellow)
}
}
}

// 타이머 μ„€μ •ν•˜λŠ” λΆ€λΆ„
struct PickerSection: View {
@Binding var selectedTime: String
@Binding var showPickerSection: Bool
@ObservedObject var timerViewModel: TimerViewModel

var body: some View {
VStack{
// μ‹œκ°„, λΆ„, 초 μ„€μ •
HStack {
// μ‹œκ°„ μ„€μ •
Picker("Hours", selection: $timerViewModel.selectedHours) {
ForEach(0..<24) { hour in
Text("\(hour) h").tag(hour)
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: 90)
.clipped()

// λΆ„ μ„€μ •
Picker("Minutes", selection: $timerViewModel.selectedMinutes) {
ForEach(0..<60) { minute in
Text("\(minute) m").tag(minute)
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: 90)
.clipped()

// 초 μ„€μ •
Picker("Seconds", selection: $timerViewModel.selectedSeconds) {
ForEach(0..<60) { second in
Text("\(second) s").tag(second)
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: 90)
.clipped()
}
.padding()
// μ…‹νŒ… μ™„λ£Œ ν›„ λ²„νŠΌμ„ λˆ„λ₯΄λ©΄ μœ„μ˜ μˆ«μžκ°€ λ³€κ²½λœλ‹€
Button("set time") {
selectedTime = String(format: "%02d:%02d:%02d", timerViewModel.selectedHours, timerViewModel.selectedMinutes, timerViewModel.selectedSeconds)
showPickerSection = false
}
}
}
}

// μ›ν˜• ν”„λ‘œκ·Έλ ˆμŠ€λ·°
struct RadialProgressView: View {
var progress: Double

var body: some View {
ZStack {
Circle()
.stroke(lineWidth: 20.0)
.opacity(0.3)
.foregroundColor(Color.appYellow)

Circle()
.trim(from: 0.0, to: CGFloat(min(progress, 1.0)))
.stroke(style: StrokeStyle(lineWidth: 20.0, lineCap: .round, lineJoin: .round))
.foregroundColor(Color.appYellow)
.rotationEffect(Angle(degrees: 270.0))
.opacity(0.8)
.animation(.linear, value: progress)
}
}
}

// 타이머 λͺ¨λΈ.. ꡉμž₯히 볡작..
class TimerViewModel: ObservableObject {
@Published var timeString: String = "00:00:00"
@Published var progress: Double = 1.0
@Published var selectedHours: Int = 0
@Published var selectedMinutes: Int = 0
@Published var selectedSeconds: Int = 0
@Published var savedTimes: [String] = []
@Published var isRunning: Bool = false
@Published var timerFinished: Bool = false

private var cancellable: AnyCancellable?
private var totalTime: TimeInterval = 0
private var remainingTime: TimeInterval = 0

// 타이머 on/off
func toggleTimer() {
if isRunning {
stop()
} else {
start()
}
}

// 타이머가 μ‹œμž‘λœλ‹€
func start() {
stop()
totalTime = TimeInterval(selectedHours * 3600 + selectedMinutes * 60 + selectedSeconds)
remainingTime = totalTime

isRunning = true
timerFinished = false

cancellable = Timer.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
.sink { _ in
self.updateTime()
}
}

// 타이머 정지
func stop() {
cancellable?.cancel()
cancellable = nil
isRunning = false
}

// 타이머 리셋
func reset() {
stop()
remainingTime = totalTime
progress = 1.0
timeString = formatTime(time: remainingTime)
timerFinished = false
}

// μ‹œκ°„ μ €μž₯인데 CRUD μ‹€νŒ¨λ‘œ μ‚¬μš©ν•˜μ§€ λͺ»ν•œ 아이..
func saveTime() {
let savedTime = String(format: "%02d:%02d:%02d", selectedHours, selectedMinutes, selectedSeconds)
savedTimes.append(savedTime)
}

// μ‹œκ°„ μ—…λŽƒ
private func updateTime() {
if remainingTime > 0 {
remainingTime -= 1
}
timeString = formatTime(time: remainingTime)
progress = totalTime > 0 ? remainingTime / totalTime : 1.0
if remainingTime == 0 {
stop()
timerFinished = true
}
}
// μ‹œκ°„μ„ λ¬Έμžμ—΄λ‘œ ν¬λ§·νŒ…
private func formatTime(time: TimeInterval) -> String {
let hours = Int(time) / 3600
let minutes = (Int(time) % 3600) / 60
let seconds = Int(time) % 60
return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
}
}

```