diff --git a/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.appiconset/Contents.json b/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.appiconset/Contents.json
deleted file mode 100644
index 2698331..0000000
--- a/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.appiconset/Contents.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "images" : [
- {
- "filename" : "Icon 오후 9.05.06.png",
- "idiom" : "universal",
- "platform" : "watchos",
- "size" : "1024x1024"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git "a/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.appiconset/Icon \354\230\244\355\233\204 9.05.06.png" "b/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.appiconset/Icon \354\230\244\355\233\204 9.05.06.png"
deleted file mode 100644
index a874d0e..0000000
Binary files "a/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.appiconset/Icon \354\230\244\355\233\204 9.05.06.png" and /dev/null differ
diff --git a/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.imageset/Contents.json b/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.imageset/Contents.json
new file mode 100644
index 0000000..42a22c1
--- /dev/null
+++ b/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "MainCircle.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "MainCircle 1.svg",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "MainCircle 2.svg",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.imageset/MainCircle 1.svg b/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.imageset/MainCircle 1.svg
new file mode 100644
index 0000000..a296ccc
--- /dev/null
+++ b/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.imageset/MainCircle 1.svg
@@ -0,0 +1,37 @@
+
diff --git a/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.imageset/MainCircle 2.svg b/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.imageset/MainCircle 2.svg
new file mode 100644
index 0000000..a296ccc
--- /dev/null
+++ b/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.imageset/MainCircle 2.svg
@@ -0,0 +1,37 @@
+
diff --git a/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.imageset/MainCircle.svg b/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.imageset/MainCircle.svg
new file mode 100644
index 0000000..a296ccc
--- /dev/null
+++ b/hearo/HearoadWatch Watch App/Assets.xcassets/MainCircle.imageset/MainCircle.svg
@@ -0,0 +1,37 @@
+
diff --git a/hearo/HearoadWatch Watch App/ContentView.swift b/hearo/HearoadWatch Watch App/ContentView.swift
index a5eb1cb..a4f73a7 100644
--- a/hearo/HearoadWatch Watch App/ContentView.swift
+++ b/hearo/HearoadWatch Watch App/ContentView.swift
@@ -12,24 +12,28 @@ struct ContentView: View {
@ObservedObject var sessionManager = WatchSessionManager.shared
var body: some View {
+
+
+
ZStack {
// 배경색을 alert 상태에 따라 변경
sessionManager.isAlerting ? Color.red.edgesIgnoringSafeArea(.all) : Color.black.edgesIgnoringSafeArea(.all)
-
+
+
// 알림 상태에 따른 아이콘 표시
- if sessionManager.isAlerting {
+ if sessionManager.isAlerting {
Image(sessionManager.alertImageName())
.resizable()
.scaledToFit()
.frame(width: 200, height: 200)
.padding()
- } else {
- // 알림이 아닐 때는 MainCircle 이미지를 표시
- Image("MainCircle")
- .resizable()
- .frame(width: 200, height: 200)
- .padding()
- }
+ }
+ else{
+ Image("MainCircle")
+ .resizable()
+ .frame(width: 200, height: 200)
+ .padding()
+ }
}
.onAppear {
// 초기화
diff --git a/hearo/HearoadWatch Watch App/WatchSessionManager.swift b/hearo/HearoadWatch Watch App/WatchSessionManager.swift
index cef3ba5..e085b83 100644
--- a/hearo/HearoadWatch Watch App/WatchSessionManager.swift
+++ b/hearo/HearoadWatch Watch App/WatchSessionManager.swift
@@ -43,18 +43,29 @@ class WatchSessionManager: NSObject, ObservableObject, WCSessionDelegate {
case "Bicyclebell":
return "Bicycle" // bicycle 이미지 이름
default:
- return "exclamationmark.triangle.fill" // 기본 알림 아이콘
+ return "Car" // 기본 알림 아이콘
}
}
// 3초 동안 알림 표시 후 기본 상태로 복구
func showAlert(with message: String) {
+ // "녹음 시작 전" 메시지는 무시
+ guard message != "녹음 시작 전" else {
+ print("메시지 무시: \(message)")
+ return
+ }
+
alertMessage = message
isAlerting = true
// 강한 진동 알림 발생
playUrgentHapticPattern()
+ // UI 업데이트: 빨간 배경, 아이콘 표시
+ DispatchQueue.main.async {
+ print("UI 업데이트 - 배경색: 빨간색, 메시지: \(message)")
+ }
+
// 3초 후에 기본 상태로 복구
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.resetAlert()
@@ -79,6 +90,7 @@ class WatchSessionManager: NSObject, ObservableObject, WCSessionDelegate {
func resetAlert() {
alertMessage = "인식중"
isAlerting = false
+ print("UI 상태 복구 - 배경색: 검정, 메시지: 기본 상태")
}
// WCSession 활성화 완료 시 호출되는 메서드
diff --git a/hearo/hearo.xcodeproj/project.pbxproj b/hearo/hearo.xcodeproj/project.pbxproj
index 8055e5b..d1969d9 100644
--- a/hearo/hearo.xcodeproj/project.pbxproj
+++ b/hearo/hearo.xcodeproj/project.pbxproj
@@ -1015,8 +1015,8 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_ASSET_PATHS = "\"hearo/Preview Content\"";
- DEVELOPMENT_TEAM = GT56H2MYWV;
+ DEVELOPMENT_ASSET_PATHS = "hearo/Preview\\ Content HearoadWatch\\ Watch\\ App";
+ DEVELOPMENT_TEAM = J5N8Y9F8Z8;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = hearo/Info.plist;
@@ -1049,8 +1049,8 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_ASSET_PATHS = "\"hearo/Preview Content\"";
- DEVELOPMENT_TEAM = GT56H2MYWV;
+ DEVELOPMENT_ASSET_PATHS = "hearo/Preview\\ Content HearoadWatch\\ Watch\\ App";
+ DEVELOPMENT_TEAM = J5N8Y9F8Z8;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = hearo/Info.plist;
@@ -1155,8 +1155,8 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_ASSET_PATHS = "\"HearoadWatch Watch App/Preview Content\"";
- DEVELOPMENT_TEAM = GT56H2MYWV;
+ DEVELOPMENT_ASSET_PATHS = "hearo HearoadWatch\\ Watch\\ App/Preview\\ Content";
+ DEVELOPMENT_TEAM = J5N8Y9F8Z8;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "HearoadWatch-Watch-App-Info.plist";
@@ -1186,8 +1186,8 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_ASSET_PATHS = "\"HearoadWatch Watch App/Preview Content\"";
- DEVELOPMENT_TEAM = GT56H2MYWV;
+ DEVELOPMENT_ASSET_PATHS = "hearo HearoadWatch\\ Watch\\ App/Preview\\ Content";
+ DEVELOPMENT_TEAM = J5N8Y9F8Z8;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "HearoadWatch-Watch-App-Info.plist";
@@ -1295,7 +1295,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = GT56H2MYWV;
+ DEVELOPMENT_TEAM = J5N8Y9F8Z8;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = LiveActivity/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = LiveActivity;
@@ -1323,7 +1323,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = GT56H2MYWV;
+ DEVELOPMENT_TEAM = J5N8Y9F8Z8;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = LiveActivity/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = LiveActivity;
diff --git a/hearo/hearo.xcodeproj/xcuserdata/pil_gaaang.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/hearo/hearo.xcodeproj/xcuserdata/pil_gaaang.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
index 635ce19..587da36 100644
--- a/hearo/hearo.xcodeproj/xcuserdata/pil_gaaang.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
+++ b/hearo/hearo.xcodeproj/xcuserdata/pil_gaaang.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
@@ -3,41 +3,4 @@
uuid = "E612D0C1-8F2A-42B7-B849-5689FA63B147"
type = "1"
version = "2.0">
-
-
-
-
-
-<<<<<<< Updated upstream
-=======
-
-
-
-
->>>>>>> Stashed changes
-
diff --git a/hearo/hearo/Application/hearoApp.swift b/hearo/hearo/Application/hearoApp.swift
index 7b8085a..097e6af 100644
--- a/hearo/hearo/Application/hearoApp.swift
+++ b/hearo/hearo/Application/hearoApp.swift
@@ -57,6 +57,8 @@ struct hearoApp: App {
}
final class AppRootManager: ObservableObject {
+ static let shared = AppRootManager() // 싱글톤 객체
+
@Published var currentRoot: AppRoot = .splash {
didSet {
// working 상태로 전환될 때 오디오 수집 자동 시작
@@ -87,10 +89,7 @@ final class AppRootManager: ObservableObject {
case warning
}
- init() {
- self.hornSoundDetector = HornSoundDetector(appRootManager: self)
- }
-
+
// 스플래시 끝났을 때 호출
func determineNextRoot() {
let hasSeenOnboarding = UserDefaults.standard.bool(forKey: "hasSeenOnboarding")
diff --git a/hearo/hearo/Info.plist b/hearo/hearo/Info.plist
index 8bdc516..c815fc7 100644
--- a/hearo/hearo/Info.plist
+++ b/hearo/hearo/Info.plist
@@ -2,16 +2,13 @@
+ WKCompanionAppBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
UIAppFonts
Pretendard-Light.otf
Pretendard-Regular.otf
Pretendard-Medium.otf
- UIBackgroundModes
-
- audio
- Audio
-
diff --git a/hearo/hearo/Resources/Assets.xcassets/SVGIcon/Icon.imageset/Contents.json b/hearo/hearo/Resources/Assets.xcassets/SVGIcon/Icon.imageset/Contents.json
index b95e909..12bd45c 100644
--- a/hearo/hearo/Resources/Assets.xcassets/SVGIcon/Icon.imageset/Contents.json
+++ b/hearo/hearo/Resources/Assets.xcassets/SVGIcon/Icon.imageset/Contents.json
@@ -1,12 +1,12 @@
{
"images" : [
{
- "filename" : "Icon 오후 9.05.06 1.png",
+ "filename" : "Icon 오후 9.05.06 1.png",
"idiom" : "universal",
"scale" : "1x"
},
{
- "filename" : "Icon 오후 9.05.06.png",
+ "filename" : "Icon 오후 9.05.06.png",
"idiom" : "universal",
"scale" : "2x"
},
diff --git a/hearo/hearo/Resources/Assets.xcassets/SVGIcon/StartButton.imageset/Contents.json b/hearo/hearo/Resources/Assets.xcassets/SVGIcon/StartButton.imageset/Contents.json
index 4c2be32..f5e64f7 100644
--- a/hearo/hearo/Resources/Assets.xcassets/SVGIcon/StartButton.imageset/Contents.json
+++ b/hearo/hearo/Resources/Assets.xcassets/SVGIcon/StartButton.imageset/Contents.json
@@ -1,7 +1,7 @@
{
"images" : [
{
- "filename" : "시작하기.svg",
+ "filename" : "시작하기.svg",
"idiom" : "universal",
"scale" : "1x"
},
diff --git a/hearo/hearo/Sources/Helper/HornSoundDetector.swift b/hearo/hearo/Sources/Helper/HornSoundDetector.swift
index 27ce1c3..1e043dc 100644
--- a/hearo/hearo/Sources/Helper/HornSoundDetector.swift
+++ b/hearo/hearo/Sources/Helper/HornSoundDetector.swift
@@ -13,7 +13,7 @@ import UserNotifications
import Combine
import WatchConnectivity
-class HornSoundDetector: NSObject, ObservableObject, WCSessionDelegate, SNResultsObserving {
+class HornSoundDetector: NSObject, ObservableObject, SNResultsObserving {
private var audioEngine: AVAudioEngine!
private var inputNode: AVAudioInputNode!
@@ -34,7 +34,6 @@ class HornSoundDetector: NSObject, ObservableObject, WCSessionDelegate, SNResult
setupAudioSession()
setupAudioEngine()
setupSoundClassifier()
- activateWCSession()
}
// MARK: - Audio Session Setup
@@ -73,17 +72,6 @@ class HornSoundDetector: NSObject, ObservableObject, WCSessionDelegate, SNResult
}
}
- // MARK: - WCSession Activation
- private func activateWCSession() {
- guard WCSession.isSupported() else {
- print("WCSession이 지원되지 않음")
- return
- }
- let session = WCSession.default
- session.delegate = self
- session.activate()
- print("WCSession 활성화 요청 완료")
- }
// MARK: - Start Recording
func startRecording() {
@@ -123,14 +111,17 @@ class HornSoundDetector: NSObject, ObservableObject, WCSessionDelegate, SNResult
}
// MARK: - SNResultsObserving
- @objc(request:didProduceResult:) func request(_ request: SNRequest, didProduce result: SNResult) {
+ @objc(request:didProduceResult:)
+ func request(_ request: SNRequest, didProduce result: SNResult) {
guard let result = result as? SNClassificationResult else { return }
DispatchQueue.main.async {
if let topClassification = result.classifications.first {
print("최상위 분류: \(topClassification.identifier), 신뢰도: \(topClassification.confidence)")
-
- // 관심 있는 소리만 처리
- if self.relevantSounds.contains(topClassification.identifier), topClassification.confidence >= 0.99 {
+
+ // 관심 있는 소리와 신뢰도 기준 체크
+ if self.relevantSounds.contains(topClassification.identifier),
+ topClassification.confidence >= 0.99 {
+ self.classificationResult = topClassification.identifier // 바로 업데이트
self.triggerWarningActions(for: topClassification.identifier)
} else {
print("관련 없는 소리 무시: \(topClassification.identifier)")
@@ -138,54 +129,18 @@ class HornSoundDetector: NSObject, ObservableObject, WCSessionDelegate, SNResult
}
}
}
-
// MARK: - Trigger Warning Actions
private func triggerWarningActions(for sound: String) {
- self.appRootManager.detectedSound = sound
- self.appRootManager.currentRoot = .warning
-
- sendWarningToWatch(alert: sound)
+ appRootManager.detectedSound = sound
+ appRootManager.currentRoot = .warning
print("⚠️ 경고 알림 처리 완료: \(sound)")
+
+ // Watch로 바로 데이터 전달
+ NotificationCenter.default.post(name: .detectedSoundNotification, object: nil, userInfo: ["sound": sound])
}
+
+}
- private func sendWarningToWatch(alert: String) {
- guard WCSession.isSupported() else {
- print("WCSession이 지원되지 않음")
- return
- }
-
- guard WCSession.default.activationState == .activated else {
- print("WCSession이 활성화되지 않음, 재활성화 시도")
- WCSession.default.activate()
- return
- }
-
- guard WCSession.default.isReachable else {
- print("애플워치가 연결되지 않음")
- return
- }
-
- let message = ["alert": alert]
- WCSession.default.sendMessage(message, replyHandler: { response in
- print("✅ 애플워치로 경고 메시지 전송 성공: \(alert)")
- }) { error in
- print("❌ 애플워치로 경고 메시지 전송 실패: \(error.localizedDescription)")
- }
- }
-
- // MARK: - WCSessionDelegate
- func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
- print("WCSession 활성화 완료: \(activationState.rawValue)")
- }
- func sessionDidBecomeInactive(_ session: WCSession) {
- // 세션이 비활성화될 때 호출됩니다.
- print("WCSession 비활성화됨")
- }
-
- func sessionDidDeactivate(_ session: WCSession) {
- // 세션이 비활성화된 후 다시 활성화를 준비합니다.
- print("WCSession 비활성화됨. 다시 활성화 준비")
- WCSession.default.activate()
- }
-
+extension Notification.Name {
+ static let detectedSoundNotification = Notification.Name("detectedSoundNotification")
}
diff --git a/hearo/hearo/Sources/Helper/SoundDetectorViewModel.swift b/hearo/hearo/Sources/Helper/SoundDetectorViewModel.swift
index af9a276..8a02faf 100644
--- a/hearo/hearo/Sources/Helper/SoundDetectorViewModel.swift
+++ b/hearo/hearo/Sources/Helper/SoundDetectorViewModel.swift
@@ -8,41 +8,123 @@ import Foundation
import Combine
import WatchConnectivity
-class SoundDetectorViewModel: NSObject, ObservableObject {
+class SoundDetectorViewModel: NSObject, ObservableObject, WCSessionDelegate {
@Published var isRecording = false
@Published var classificationResult: String = "녹음 시작 전"
@Published var confidence: Double = 0.0
-
+
private var hornSoundDetector: HornSoundDetector
private var appRootManager: AppRootManager
private var cancellables = Set()
-
+
init(appRootManager: AppRootManager) {
self.appRootManager = appRootManager
self.hornSoundDetector = HornSoundDetector(appRootManager: appRootManager)
super.init()
setupBindings()
- }
+ setupWCSession() // WCSession 설정
+ // NotificationCenter를 통해 detectedSoundNotification 노티피케이션 수신 등록
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(handleDetectedSoundNotification(_:)),
+ name: .detectedSoundNotification,
+ object: nil
+ )
+ }
+
+ @objc private func handleDetectedSoundNotification(_ notification: Notification) {
+ if let sound = notification.userInfo?["sound"] as? String {
+ print("SoundDetectorViewModel에서 전달받은 sound: \(sound)")
+ sendWarningToWatch(alert: sound) // 애플워치로 알림 전송
+ }
+ }
+ deinit {
+ NotificationCenter.default.removeObserver(self, name: .detectedSoundNotification, object: nil)
+ }
+
private func setupBindings() {
hornSoundDetector.$isRecording
.assign(to: \.isRecording, on: self)
.store(in: &cancellables)
-
+
+ // HornSoundDetector에서 Warning 발생 시 직접 Watch로 전달
hornSoundDetector.$classificationResult
- .assign(to: \.classificationResult, on: self)
+ .sink { [weak self] result in
+ guard let self = self else { return }
+ print("SoundDetectorViewModel에서 전달받은 classificationResult: \(result)")
+ self.sendWarningToWatch(alert: result) // Watch에 전달
+ }
.store(in: &cancellables)
-
+
hornSoundDetector.$confidence
.assign(to: \.confidence, on: self)
.store(in: &cancellables)
}
-
+
+ private func setupWCSession() {
+ if WCSession.isSupported() {
+ let session = WCSession.default
+ session.delegate = self
+ session.activate()
+ }
+ }
+
func startRecording() {
hornSoundDetector.startRecording()
}
-
+
func stopRecording() {
hornSoundDetector.stopRecording()
}
+
+ private func sendWarningToWatch(alert: String) {
+ guard WCSession.default.activationState == .activated else {
+ print("WCSession이 활성화되지 않아 전송 보류: \(alert)")
+ return
+ }
+
+ if WCSession.default.isReachable {
+ let message = ["alert": alert]
+ WCSession.default.sendMessage(message, replyHandler: nil) { error in
+ print("애플워치로 메시지 전송 실패: \(error.localizedDescription)")
+ }
+ } else {
+ do {
+ try WCSession.default.updateApplicationContext(["alert": alert])
+ print("애플워치에 ApplicationContext로 데이터 전달 성공: \(alert)")
+ } catch {
+ print("애플워치 ApplicationContext 데이터 전달 실패: \(error.localizedDescription)")
+ }
+ }
+ }
+
+ // WCSessionDelegate 메서드
+ func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
+ switch activationState {
+ case .notActivated:
+ print("WCSession이 활성화되지 않음.")
+ case .inactive:
+ print("WCSession이 비활성 상태.")
+ case .activated:
+ print("WCSession 활성화 성공.")
+ @unknown default:
+ print("알 수 없는 WCSession 상태.")
+ }
+
+ if let error = error {
+ print("WCSession 활성화 실패: \(error.localizedDescription)")
+ }
+ }
+ func sessionDidBecomeInactive(_ session: WCSession) {
+ // 세션이 비활성화되었을 때 로그를 남기거나 필요한 작업을 수행
+ print("WCSession이 비활성화되었습니다. 세션 상태: \(session.activationState.rawValue)")
+ }
+
+ func sessionDidDeactivate(_ session: WCSession) {
+ // 세션이 비활성화된 후 다시 활성화를 준비
+ print("WCSession이 비활성화되었습니다. 새로운 세션을 활성화합니다.")
+ WCSession.default.activate()
+ }
+
}