diff --git a/hearo/hearo.xcodeproj/project.pbxproj b/hearo/hearo.xcodeproj/project.pbxproj index ceb46d2..8055e5b 100644 --- a/hearo/hearo.xcodeproj/project.pbxproj +++ b/hearo/hearo.xcodeproj/project.pbxproj @@ -1016,7 +1016,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"hearo/Preview Content\""; - DEVELOPMENT_TEAM = J5N8Y9F8Z8; + DEVELOPMENT_TEAM = GT56H2MYWV; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = hearo/Info.plist; @@ -1050,7 +1050,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"hearo/Preview Content\""; - DEVELOPMENT_TEAM = J5N8Y9F8Z8; + DEVELOPMENT_TEAM = GT56H2MYWV; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = hearo/Info.plist; @@ -1156,7 +1156,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"HearoadWatch Watch App/Preview Content\""; - DEVELOPMENT_TEAM = J5N8Y9F8Z8; + DEVELOPMENT_TEAM = GT56H2MYWV; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "HearoadWatch-Watch-App-Info.plist"; @@ -1187,7 +1187,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"HearoadWatch Watch App/Preview Content\""; - DEVELOPMENT_TEAM = J5N8Y9F8Z8; + DEVELOPMENT_TEAM = GT56H2MYWV; 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 = J5N8Y9F8Z8; + DEVELOPMENT_TEAM = GT56H2MYWV; 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 = J5N8Y9F8Z8; + DEVELOPMENT_TEAM = GT56H2MYWV; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = LiveActivity/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = LiveActivity; diff --git a/hearo/hearo/Application/hearoApp.swift b/hearo/hearo/Application/hearoApp.swift index efd857a..42ab9b3 100644 --- a/hearo/hearo/Application/hearoApp.swift +++ b/hearo/hearo/Application/hearoApp.swift @@ -14,7 +14,7 @@ import WatchConnectivity struct hearoApp: App { @StateObject var appRootManager = AppRootManager() // 앱 전역에서 사용되는 상태 관리 @Environment(\.scenePhase) private var scenePhase // 앱 상태 감지 - + var body: some Scene { WindowGroup { ZStack { @@ -29,10 +29,19 @@ struct hearoApp: App { } private func handleScenePhaseChange(_ newPhase: ScenePhase) { - if newPhase == .background && appRootManager.currentRoot == .working { - appRootManager.startLiveActivity(isWarning: false) - } else if newPhase == .inactive || newPhase == .active { - appRootManager.stopLiveActivity() + switch newPhase { + case .background: + // 백그라운드로 전환되면 모든 오디오 및 ML 작업 중지 + appRootManager.stopAudioAndMLTasks() + // WorkingView일 경우 라이브 액티비티 시작 + if appRootManager.currentRoot == .working { + appRootManager.startLiveActivity(isWarning: false) + } + case .active: + // 앱이 포그라운드로 돌아올 때 오직 WorkingView 상태에서만 오디오 수집 재개 + appRootManager.resumeAudioTasksIfWorking() + default: + break } } } @@ -47,12 +56,25 @@ struct LiveActivityAttributes: ActivityAttributes { } final class AppRootManager: ObservableObject { - @Published var currentRoot: AppRoot = .splash // 기본값: splash + @Published var currentRoot: AppRoot = .splash { + didSet { + // working 상태로 전환될 때 오디오 수집 자동 시작 + if currentRoot == .working { + hornSoundDetector?.startRecording() + print("오디오 수집 시작됨") + } else { + hornSoundDetector?.stopRecording() + print("오디오 수집 중지됨") + } + } + } // 기본값: splash @Published var detectedSound: String? = nil // 감지된 소리 저장 private var isActivityActive = false // 라이브 액티비티 활성 상태 추적 변수 private var isWarning = false // isWarning 상태 저장 private var currentWarningState: Bool = false // 현재 isWarning 상태 저장 - + private var hornSoundDetector: HornSoundDetector? // HornSoundDetector 인스턴스 + + // 루트 뷰 상태를 나타내는 열거형 enum AppRoot { case splash @@ -64,17 +86,21 @@ final class AppRootManager: ObservableObject { case warning } + init() { + self.hornSoundDetector = HornSoundDetector(appRootManager: self) + } + // 스플래시 끝났을 때 호출 func determineNextRoot() { let hasSeenOnboarding = UserDefaults.standard.bool(forKey: "hasSeenOnboarding") self.currentRoot = hasSeenOnboarding ? .home : .startOnboarding } - + // 라이브 액티비티 시작 메서드 func startLiveActivity(isWarning: Bool) { // 항상 현재 `Live Activity` 상태를 확인하고 초기화 isActivityActive = Activity.activities.isEmpty == false - + guard !isActivityActive else { print("라이브 액티비티가 이미 활성화되어 있습니다.") return @@ -84,7 +110,7 @@ final class AppRootManager: ObservableObject { print("라이브 액티비티가 지원되지 않거나 비활성화되었습니다.") return } - + // attributes와 initialContentState를 선언 let attributes = LiveActivityAttributes(name: "주행") let initialContentState = LiveActivityAttributes.ContentState(isWarning: isWarning) @@ -104,29 +130,30 @@ final class AppRootManager: ObservableObject { print("라이브 액티비티 시작 실패: \(error)") } } - - + + // 라이브 액티비티 종료 메서드 func stopLiveActivity() { guard !Activity.activities.isEmpty else { print("라이브 액티비티가 이미 중지 상태입니다.") return } - + Task { for activity in Activity.activities { await activity.end(nil, dismissalPolicy: .immediate) print("라이브 액티비티가 중지되었습니다: \(activity.id)") } + isActivityActive = false print("모든 라이브 액티비티가 성공적으로 중지되었습니다.") } } - + // 라이브 액티비티 업데이트 메서드 func updateLiveActivity(isWarning: Bool) { let initialContentState = LiveActivityAttributes.ContentState(isWarning: isWarning) let content = ActivityContent(state: initialContentState, staleDate: Date().addingTimeInterval(6)) // 전체 6초 유지 - + guard isActivityActive else { print("활성화된 라이브 액티비티가 없어 업데이트할 수 없습니다.") return @@ -148,6 +175,25 @@ final class AppRootManager: ObservableObject { print("라이브 액티비티 상태 업데이트: \(isWarning ? "경고" : "주행 중")") } } + + + // 오디오 및 ML 작업 중지 메서드 + func stopAudioAndMLTasks() { + hornSoundDetector?.stopRecording() + print("오디오 수집 및 ML 예측 중지됨") + } + + // 오디오 작업을 working 상태에서만 재개하는 메서드 + func resumeAudioTasksIfWorking() { + if currentRoot == .working { + hornSoundDetector?.startRecording() + print("오디오 수집 및 ML 예측 재개됨 (working 상태에서만)") + } else { + print("오디오 수집은 working 상태에서만 재개됩니다.") + } + + } + } // ContentView 정의 (AppRootManager 클래스 외부에 위치) @@ -171,7 +217,7 @@ struct ContentView: View { FinishView(viewModel: FinishViewModel(appRootManager: appRootManager)) case .warning: WarningView(viewModel: WarningViewModel(appRootManager: appRootManager)) - } + } } } } diff --git a/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index eff8e7a..6ae9a5c 100644 --- a/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Icon 오후 9.05.06.png", + "filename" : "Icon.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" @@ -13,7 +13,7 @@ "value" : "dark" } ], - "filename" : "Icon 오후 9.05.06 1.png", + "filename" : "Icon 1.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" @@ -25,7 +25,7 @@ "value" : "tinted" } ], - "filename" : "Icon 오후 9.05.06 2.png", + "filename" : "Icon 2.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon 1.png b/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon 1.png new file mode 100644 index 0000000..9f2a65a Binary files /dev/null and b/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon 1.png differ diff --git a/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon 2.png b/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon 2.png new file mode 100644 index 0000000..9f2a65a Binary files /dev/null and b/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon 2.png differ diff --git "a/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon \354\230\244\355\233\204 9.05.06 1.png" "b/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon \354\230\244\355\233\204 9.05.06 1.png" deleted file mode 100644 index a874d0e..0000000 Binary files "a/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon \354\230\244\355\233\204 9.05.06 1.png" and /dev/null differ diff --git "a/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon \354\230\244\355\233\204 9.05.06 2.png" "b/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon \354\230\244\355\233\204 9.05.06 2.png" deleted file mode 100644 index a874d0e..0000000 Binary files "a/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon \354\230\244\355\233\204 9.05.06 2.png" and /dev/null differ diff --git a/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png b/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png new file mode 100644 index 0000000..9f2a65a Binary files /dev/null and b/hearo/hearo/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png differ diff --git a/hearo/hearo/Sources/Helper/HornSoundDetector.swift b/hearo/hearo/Sources/Helper/HornSoundDetector.swift index b50c9ff..7b86afd 100644 --- a/hearo/hearo/Sources/Helper/HornSoundDetector.swift +++ b/hearo/hearo/Sources/Helper/HornSoundDetector.swift @@ -14,7 +14,7 @@ import Combine import WatchConnectivity class HornSoundDetector: NSObject, ObservableObject, WCSessionDelegate, SNResultsObserving { - + private var audioEngine: AVAudioEngine! private var inputNode: AVAudioInputNode! private var soundClassifier: HornSoundClassifier_V8? @@ -141,8 +141,9 @@ class HornSoundDetector: NSObject, ObservableObject, WCSessionDelegate, SNResult // MARK: - Trigger Warning Actions private func triggerWarningActions(for sound: String) { - self.appRootManager.currentRoot = .warning self.appRootManager.detectedSound = sound + self.appRootManager.currentRoot = .warning + self.appRootManager.updateLiveActivity(isWarning: true) // 라이브 액티비티 상태 업데이트 sendWarningToWatch(alert: sound) print("⚠️ 경고 알림 처리 완료: \(sound)") } @@ -186,5 +187,5 @@ class HornSoundDetector: NSObject, ObservableObject, WCSessionDelegate, SNResult print("WCSession 비활성화됨. 다시 활성화 준비") WCSession.default.activate() } - + }