diff --git a/hearo/HearoadWatch Watch App/ContentView.swift b/hearo/HearoadWatch Watch App/ContentView.swift index 04293a7..9306915 100644 --- a/hearo/HearoadWatch Watch App/ContentView.swift +++ b/hearo/HearoadWatch Watch App/ContentView.swift @@ -9,18 +9,54 @@ import SwiftUI import WatchKit struct ContentView: View { - @ObservedObject private var sessionManager = WatchSessionManager.shared - + @ObservedObject var sessionManager = WatchSessionManager.shared + var body: some View { - VStack { - Text("소리 감지: \(sessionManager.alertMessage)") - .font(.headline) - .foregroundColor(.red) - .padding() + ZStack { + // 배경색을 alert 상태에 따라 변경 + sessionManager.isAlerting ? Color.red.edgesIgnoringSafeArea(.all) : Color.black.edgesIgnoringSafeArea(.all) - Button("진동 테스트") { - WKInterfaceDevice.current().play(.notification) // 수동으로 진동 테스트 + VStack { + // 상태에 따른 텍스트 표시 + Text(sessionManager.isAlerting ? sessionManager.alertMessage : "인식중") + .foregroundColor(.white) + .font(.title) + .padding() + + + // 알림 아이콘을 표시하고, 알림이 아닐 때는 숨김 처리 + if sessionManager.isAlerting { + Image(systemName: "exclamationmark.triangle.fill") + .resizable() + .frame(width: 50, height: 50) + .foregroundColor(.yellow) + .padding() + } } } + .onAppear { + // 초기화 + sessionManager.resetAlert() + } } } +//struct ContentView: View { +// @ObservedObject var viewManager = WatchViewManager() +// +// var body: some View { +// VStack { +// if viewManager.currentView == "working" { +// WatchWorkingView() +// } else if viewManager.currentView == "warning" { +// WatchWarningView() +// } else if viewManager.currentView == "finish" { +// WatchFinishView() +// } else { +// WatchHomeView() +// } +// } +// .onAppear { +// print("현재 Watch 뷰: \(viewManager.currentView)") +// } +// } +//} diff --git a/hearo/HearoadWatch Watch App/HearoadWatch.entitlements b/hearo/HearoadWatch Watch App/HearoadWatch.entitlements new file mode 100644 index 0000000..3cd3197 --- /dev/null +++ b/hearo/HearoadWatch Watch App/HearoadWatch.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.developer.watchkit.extended-runtime-session + + + diff --git a/hearo/HearoadWatch Watch App/WatchSessionManager.swift b/hearo/HearoadWatch Watch App/WatchSessionManager.swift index 6a4ae66..6934e4b 100644 --- a/hearo/HearoadWatch Watch App/WatchSessionManager.swift +++ b/hearo/HearoadWatch Watch App/WatchSessionManager.swift @@ -8,27 +8,12 @@ import Foundation import WatchKit import WatchConnectivity -class WatchSessionManager: NSObject, ObservableObject, WCSessionDelegate, WKExtendedRuntimeSessionDelegate { - func extendedRuntimeSession(_ extendedRuntimeSession: WKExtendedRuntimeSession, didInvalidateWith reason: WKExtendedRuntimeSessionInvalidationReason, error: Error?) { - switch reason { - case .expired: - print("세션이 만료되었습니다.") - default: - print("알 수 없는 이유로 세션이 만료되었습니다.") - } - - if let error = error { - print("세션 만료 오류: \(error.localizedDescription)") - } - - // 세션 만료 후 백그라운드 세션을 다시 시작 - startBackgroundSession() - } +class WatchSessionManager: NSObject, ObservableObject, WCSessionDelegate { static let shared = WatchSessionManager() // 싱글톤 인스턴스 생성 - @Published var alertMessage: String = "대기 중" - private var backgroundSession: WKExtendedRuntimeSession? - + @Published var alertMessage: String = "인식중" // 기본 메시지 + @Published var isAlerting: Bool = false // 알림 상태 확인 + private override init() { super.init() @@ -36,55 +21,51 @@ class WatchSessionManager: NSObject, ObservableObject, WCSessionDelegate, WKExte WCSession.default.delegate = self WCSession.default.activate() } - - startBackgroundSession() // 백그라운드 세션 시작 } - - // 백그라운드 실행을 위한 세션 시작 - private func startBackgroundSession() { - backgroundSession = WKExtendedRuntimeSession() - backgroundSession?.delegate = self - backgroundSession?.start() - } - + // iOS에서 경고 메시지를 수신하는 메서드 func session(_ session: WCSession, didReceiveMessage message: [String : Any]) { if let alert = message["alert"] as? String { DispatchQueue.main.async { - print("watch alarm 감지:", alert) - self.alertMessage = alert - WKInterfaceDevice.current().play(.notification) // 진동 알림 발생 - self.showAlert(alertMessage: alert) + self.showAlert(with: alert) } } } - - // 알림 팝업을 표시하는 메서드 - private func showAlert(alertMessage: String) { - let action = WKAlertAction(title: "확인", style: .default) { } + + // 3초 동안 알림 표시 후 기본 상태로 복구 + func showAlert(with message: String) { + alertMessage = message + isAlerting = true - // 현재 활성화된 인터페이스 컨트롤러에서 경고를 표시 - if let controller = WKExtension.shared().visibleInterfaceController { - controller.presentAlert(withTitle: "경고", message: alertMessage, preferredStyle: .alert, actions: [action]) + // 강한 진동 알림 발생 + playUrgentHapticPattern() + + // 3초 후에 기본 상태로 복구 + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + self.resetAlert() } } - // WKExtendedRuntimeSessionDelegate - 세션 시작 - func extendedRuntimeSessionDidStart(_ extendedRuntimeSession: WKExtendedRuntimeSession) { - print("배경 세션 시작됨") + // 긴급 상황을 위한 강한 진동 패턴 + func playUrgentHapticPattern() { + // 반복 횟수 및 간격 설정 + let repeatCount = 5 // 진동 반복 횟수 + let interval: TimeInterval = 0.1 // 반복 간격 (0.1초) + + // 반복적으로 강한 진동을 재생하는 패턴 + for i in 0.. - - UIBackgroundModes - - audio - remote-notification - - WKBackgroundModes - - App downloads content from the network - - + diff --git a/hearo/hearo.xcodeproj/project.pbxproj b/hearo/hearo.xcodeproj/project.pbxproj index 6b39af7..206cd6c 100644 --- a/hearo/hearo.xcodeproj/project.pbxproj +++ b/hearo/hearo.xcodeproj/project.pbxproj @@ -26,18 +26,22 @@ 001C9F752CCF7984004EC4D2 /* HearoadWatch_Watch_AppUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001C9F742CCF7984004EC4D2 /* HearoadWatch_Watch_AppUITestsLaunchTests.swift */; }; 001C9F782CCF7984004EC4D2 /* HearoadWatch Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 001C9F552CCF7983004EC4D2 /* HearoadWatch Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 001C9F842CCF81A1004EC4D2 /* WatchSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001C9F832CCF81A1004EC4D2 /* WatchSessionManager.swift */; }; + 001D669B2CDB3E11005746AC /* Haptic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001D669A2CDB3E11005746AC /* Haptic.swift */; }; 0045C6B82CC0295F009B4261 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0045C6B72CC0295F009B4261 /* WarningView.swift */; }; 0045C6BA2CC02969009B4261 /* WarningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0045C6B92CC02969009B4261 /* WarningViewModel.swift */; }; 007BEFCE2CB159AD00C0E9FD /* WorkingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 007BEFCD2CB159AD00C0E9FD /* WorkingView.swift */; }; 007BEFD02CB159BF00C0E9FD /* WorkingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 007BEFCF2CB159BF00C0E9FD /* WorkingViewModel.swift */; }; 00A04EA72CB7CBB20031DECC /* SpoqaHanSansNeo_OTF_original in Resources */ = {isa = PBXBuildFile; fileRef = 00A04EA62CB7CBB20031DECC /* SpoqaHanSansNeo_OTF_original */; }; + 112177742CC283BC000A146F /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 112177732CC283BC000A146F /* WidgetKit.framework */; }; 112177762CC283BC000A146F /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 112177752CC283BC000A146F /* SwiftUI.framework */; }; 112177792CC283BC000A146F /* LiveActivityBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112177782CC283BC000A146F /* LiveActivityBundle.swift */; }; 1121777B2CC283BC000A146F /* LiveActivityLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1121777A2CC283BC000A146F /* LiveActivityLiveActivity.swift */; }; 112177812CC283BD000A146F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 112177802CC283BD000A146F /* Assets.xcassets */; }; 112177852CC283BD000A146F /* LiveActivityExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 112177712CC283BC000A146F /* LiveActivityExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 1159334C2CDB2A7C005902F2 /* HornSoundClassifier_V11.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = 1159334B2CDB2A7C005902F2 /* HornSoundClassifier_V11.mlmodel */; }; + 64F73A252CBAC9F100D2A140 /* HornSoundDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F73A242CBAC9F100D2A140 /* HornSoundDetector.swift */; }; 64F73A272CBAD1C000D2A140 /* SoundDetectorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F73A262CBAD1C000D2A140 /* SoundDetectorViewModel.swift */; }; /* End PBXBuildFile section */ @@ -138,12 +142,14 @@ 001C9F742CCF7984004EC4D2 /* HearoadWatch_Watch_AppUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HearoadWatch_Watch_AppUITestsLaunchTests.swift; sourceTree = ""; }; 001C9F832CCF81A1004EC4D2 /* WatchSessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchSessionManager.swift; sourceTree = ""; }; 001C9F852CD0CA11004EC4D2 /* HearoadWatch-Watch-App-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "HearoadWatch-Watch-App-Info.plist"; sourceTree = SOURCE_ROOT; }; + 001D669A2CDB3E11005746AC /* Haptic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Haptic.swift; sourceTree = ""; }; 0045C6B72CC0295F009B4261 /* WarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningView.swift; sourceTree = ""; }; 0045C6B92CC02969009B4261 /* WarningViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningViewModel.swift; sourceTree = ""; }; 007BEFCD2CB159AD00C0E9FD /* WorkingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkingView.swift; sourceTree = ""; }; 007BEFCF2CB159BF00C0E9FD /* WorkingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkingViewModel.swift; sourceTree = ""; }; 00A04EA62CB7CBB20031DECC /* SpoqaHanSansNeo_OTF_original */ = {isa = PBXFileReference; lastKnownFileType = folder; path = SpoqaHanSansNeo_OTF_original; sourceTree = ""; }; 00A04EA82CB7CC320031DECC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 112177712CC283BC000A146F /* LiveActivityExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = LiveActivityExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 112177732CC283BC000A146F /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 112177752CC283BC000A146F /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; @@ -151,7 +157,9 @@ 1121777A2CC283BC000A146F /* LiveActivityLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityLiveActivity.swift; sourceTree = ""; }; 112177802CC283BD000A146F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 112177822CC283BD000A146F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 1159334B2CDB2A7C005902F2 /* HornSoundClassifier_V11.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = HornSoundClassifier_V11.mlmodel; sourceTree = ""; }; + 64F73A242CBAC9F100D2A140 /* HornSoundDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HornSoundDetector.swift; path = hearo/Sources/Presentations/Working/ViewModel/HornSoundDetector.swift; sourceTree = SOURCE_ROOT; }; 64F73A262CBAD1C000D2A140 /* SoundDetectorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SoundDetectorViewModel.swift; path = hearo/Sources/Presentations/Working/ViewModel/SoundDetectorViewModel.swift; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ @@ -215,6 +223,11 @@ isa = PBXGroup; children = ( 00078BE62CAFC79E00FB3F70 /* OnboardingView.swift */, + 645C1B152CCBB3BF00251157 /* OnboardingWelcomeView.swift */, + 645C1B172CCBB53C00251157 /* OnboardingNotiPermissionView.swift */, + 645C1B192CCBBB4800251157 /* OnboardingPrivacyView.swift */, + 645C1B1B2CCBBE7000251157 /* OnboardingWarningView.swift */, + 645C1B1D2CCBBFAB00251157 /* OnboardingStandRecommendView.swift */, ); path = View; sourceTree = ""; @@ -335,22 +348,11 @@ isa = PBXGroup; children = ( 00A04EA32CB7CA510031DECC /* Helper */, - 00A04EA22CB7CA320031DECC /* Enums */, - 001C6FAC2CAFC44900B03767 /* Delegate */, - 001C6FAF2CAFC46200B03767 /* Extensions */, - 001C6FAE2CAFC45900B03767 /* Data */, 001C6FAD2CAFC45100B03767 /* Presentations */, ); path = Sources; sourceTree = ""; }; - 001C6FAC2CAFC44900B03767 /* Delegate */ = { - isa = PBXGroup; - children = ( - ); - path = Delegate; - sourceTree = ""; - }; 001C6FAD2CAFC45100B03767 /* Presentations */ = { isa = PBXGroup; children = ( @@ -364,20 +366,6 @@ path = Presentations; sourceTree = ""; }; - 001C6FAE2CAFC45900B03767 /* Data */ = { - isa = PBXGroup; - children = ( - ); - path = Data; - sourceTree = ""; - }; - 001C6FAF2CAFC46200B03767 /* Extensions */ = { - isa = PBXGroup; - children = ( - ); - path = Extensions; - sourceTree = ""; - }; 001C6FB32CAFC4DF00B03767 /* Home */ = { isa = PBXGroup; children = ( @@ -416,12 +404,13 @@ 001C9F562CCF7983004EC4D2 /* HearoadWatch Watch App */ = { isa = PBXGroup; children = ( + 001C9F5D2CCF7984004EC4D2 /* Preview Content */, + 00AD926C2CD227A200DDA11B /* HearoadWatch.entitlements */, 001C9F852CD0CA11004EC4D2 /* HearoadWatch-Watch-App-Info.plist */, 001C9F572CCF7983004EC4D2 /* HearoadWatchApp.swift */, 001C9F832CCF81A1004EC4D2 /* WatchSessionManager.swift */, 001C9F592CCF7983004EC4D2 /* ContentView.swift */, 001C9F5B2CCF7984004EC4D2 /* Assets.xcassets */, - 001C9F5D2CCF7984004EC4D2 /* Preview Content */, ); path = "HearoadWatch Watch App"; sourceTree = ""; @@ -501,19 +490,15 @@ path = View; sourceTree = ""; }; - 00A04EA22CB7CA320031DECC /* Enums */ = { - isa = PBXGroup; - children = ( - ); - path = Enums; - sourceTree = ""; - }; 00A04EA32CB7CA510031DECC /* Helper */ = { isa = PBXGroup; children = ( + 1159334B2CDB2A7C005902F2 /* HornSoundClassifier_V11.mlmodel */, + 64F73A242CBAC9F100D2A140 /* HornSoundDetector.swift */, 64F73A262CBAD1C000D2A140 /* SoundDetectorViewModel.swift */, + 001D669A2CDB3E11005746AC /* Haptic.swift */, ); path = Helper; sourceTree = ""; @@ -792,17 +777,24 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 645C1B1C2CCBBE7000251157 /* OnboardingWarningView.swift in Sources */, 00078BED2CAFC7BA00FB3F70 /* HomeViewModel.swift in Sources */, + 645C1B162CCBB3BF00251157 /* OnboardingWelcomeView.swift in Sources */, + 645C1B1E2CCBBFAB00251157 /* OnboardingStandRecommendView.swift in Sources */, 00078BE12CAFC77000FB3F70 /* FinishViewModel.swift in Sources */, + 00FC15162CDA10B700FBCE0C /* HornSoundClassifier_V9.mlmodel in Sources */, + 001D669B2CDB3E11005746AC /* Haptic.swift in Sources */, 00078BEB2CAFC7B000FB3F70 /* HomeView.swift in Sources */, 0045C6BA2CC02969009B4261 /* WarningViewModel.swift in Sources */, 001C6F7E2CAFBC6900B03767 /* hearoApp.swift in Sources */, + 00078BE92CAFC7A800FB3F70 /* OnboardingViewModel.swift in Sources */, 00078BDF2CAFC75900FB3F70 /* FinishView.swift in Sources */, 64F73A272CBAD1C000D2A140 /* SoundDetectorViewModel.swift in Sources */, 007BEFCE2CB159AD00C0E9FD /* WorkingView.swift in Sources */, 007BEFD02CB159BF00C0E9FD /* WorkingViewModel.swift in Sources */, 64F73A252CBAC9F100D2A140 /* HornSoundDetector.swift in Sources */, + 645C1B1A2CCBBB4800251157 /* OnboardingPrivacyView.swift in Sources */, 0045C6B82CC0295F009B4261 /* WarningView.swift in Sources */, 1159334C2CDB2A7C005902F2 /* HornSoundClassifier_V11.mlmodel in Sources */, 00078BE72CAFC79E00FB3F70 /* OnboardingView.swift in Sources */, @@ -1024,7 +1016,9 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"hearo/Preview Content\""; + DEVELOPMENT_TEAM = GT56H2MYWV; + ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = hearo/Info.plist; @@ -1059,7 +1053,9 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"hearo/Preview Content\""; + DEVELOPMENT_TEAM = GT56H2MYWV; + ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = hearo/Info.plist; @@ -1169,7 +1165,9 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"HearoadWatch Watch App/Preview Content\""; + DEVELOPMENT_TEAM = GT56H2MYWV; + ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "HearoadWatch-Watch-App-Info.plist"; @@ -1200,7 +1198,9 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"HearoadWatch Watch App/Preview Content\""; + DEVELOPMENT_TEAM = GT56H2MYWV; + ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "HearoadWatch-Watch-App-Info.plist"; @@ -1312,7 +1312,9 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = GT56H2MYWV; + GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = LiveActivity/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = LiveActivity; @@ -1340,7 +1342,9 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = GT56H2MYWV; + GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = LiveActivity/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = LiveActivity; diff --git a/hearo/hearo.xcodeproj/xcshareddata/xcschemes/HearoadWatch Watch App.xcscheme b/hearo/hearo.xcodeproj/xcshareddata/xcschemes/HearoadWatch Watch App.xcscheme index 6aad58a..bc759b8 100644 --- a/hearo/hearo.xcodeproj/xcshareddata/xcschemes/HearoadWatch Watch App.xcscheme +++ b/hearo/hearo.xcodeproj/xcshareddata/xcschemes/HearoadWatch Watch App.xcscheme @@ -88,6 +88,13 @@ ReferencedContainer = "container:hearo.xcodeproj"> + + + + + + + + .request( - attributes: attributes, - contentState: initialContentState, - pushType: nil - ) - print("라이브 액티비티가 시작되었습니다: \(activity.id)") - } catch { - print("라이브 액티비티 시작 실패: \(error)") + // 라이브 액티비티 시작 메서드 + func startLiveActivity(isWarning: Bool) { + guard ActivityAuthorizationInfo().areActivitiesEnabled else { + print("라이브 액티비티가 지원되지 않거나 비활성화되었습니다.") + return + } + + let attributes = LiveActivityAttributes(name: "주행") + let initialContentState = LiveActivityAttributes.ContentState(isWarning: isWarning) + + do { + let activity = try Activity.request( + attributes: attributes, + contentState: initialContentState, + pushType: nil + ) + print("라이브 액티비티가 시작되었습니다: \(activity.id)") + } catch { + print("라이브 액티비티 시작 실패: \(error)") + } } - } - - // 라이브 액티비티 중지 메서드 - func stopLiveActivity() { - Task { - for activity in Activity.activities { - await activity.end(dismissalPolicy: .immediate) - print("라이브 액티비티가 중지되었습니다: \(activity.id)") - } + + // 라이브 액티비티 중지 메서드 + func stopLiveActivity() { + Task { + for activity in Activity.activities { + await activity.end(dismissalPolicy: .immediate) + print("라이브 액티비티가 중지되었습니다: \(activity.id)") + } + } } - } - - // 라이브 액티비티 업데이트 메서드 - func updateLiveActivity(isWarning: Bool) { - guard let activity = Activity.activities.first else { return } - Task { - // 현재 상태와 새로운 경고 상태가 다를 때만 업데이트 - if activity.contentState.isWarning != isWarning { - let updatedContentState = LiveActivityAttributes.ContentState(isWarning: isWarning) - await activity.update(using: updatedContentState) - print("라이브 액티비티 상태 업데이트: \(isWarning ? "경고" : "주행 중")") - } + // 라이브 액티비티 업데이트 메서드 + func updateLiveActivity(isWarning: Bool) { + guard let activity = Activity.activities.first else { return } + + Task { + if activity.contentState.isWarning != isWarning { + let updatedContentState = LiveActivityAttributes.ContentState(isWarning: isWarning) + await activity.update(using: updatedContentState) + print("라이브 액티비티 상태 업데이트: \(isWarning ? "경고" : "주행 중")") + } + } } - } } +// ContentView 정의 (AppRootManager 클래스 외부에 위치) struct ContentView: View { @ObservedObject var appRootManager: AppRootManager diff --git a/hearo/hearo/Resources/Assets.xcassets/HPrimaryColor.colorset/Contents.json b/hearo/hearo/Resources/Assets.xcassets/HPrimaryColor.colorset/Contents.json index fb54bee..9a696be 100644 --- a/hearo/hearo/Resources/Assets.xcassets/HPrimaryColor.colorset/Contents.json +++ b/hearo/hearo/Resources/Assets.xcassets/HPrimaryColor.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "243", - "green" : "122", - "red" : "10" + "blue" : "89", + "green" : "199", + "red" : "52" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "243", - "green" : "122", - "red" : "10" + "blue" : "89", + "green" : "199", + "red" : "52" } }, "idiom" : "universal" diff --git a/hearo/hearo/Sources/Helper/Haptic.swift b/hearo/hearo/Sources/Helper/Haptic.swift new file mode 100644 index 0000000..d546345 --- /dev/null +++ b/hearo/hearo/Sources/Helper/Haptic.swift @@ -0,0 +1,24 @@ +// +// Haptic.swift +// hearo +// +// Created by Pil_Gaaang on 11/6/24. +// + +import Foundation +import UIKit + +func triggerSuccessHaptic() { + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(.success) +} + +func triggerWarningHaptic() { + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(.warning) +} + +func triggerErrorHaptic() { + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(.error) +} diff --git a/hearo/hearo/Sources/Presentations/Finish/View/FinishView.swift b/hearo/hearo/Sources/Presentations/Finish/View/FinishView.swift index 0a0e029..6bd9ba9 100644 --- a/hearo/hearo/Sources/Presentations/Finish/View/FinishView.swift +++ b/hearo/hearo/Sources/Presentations/Finish/View/FinishView.swift @@ -12,64 +12,47 @@ struct FinishView: View { var body: some View { VStack { - Spacer().frame(height: 84) + Spacer().frame(height: 195) - HStack { - Spacer().frame(width: 16) - + Text("안전 주행 완료!") .font( - Font.custom("Spoqa Han Sans Neo", size: 25) + Font.custom("Spoqa Han Sans Neo", size: 24) .weight(.bold) ) - .foregroundColor(Color("HWhite")) + .foregroundColor(Color.black) - Spacer() - } - - Spacer().frame(height: 19) - HStack { - Spacer().frame(width: 16) - - Text("오늘도 히어로드와 함께 무사히 도착하셨습니다.\n다음에도 안전하게 뵙겠습니다!") - .font(Font.custom("Spoqa Han Sans Neo", size: 16)) - .foregroundColor(Color("HGray2")) - .frame(width: 295, alignment: .topLeading) + Spacer().frame(height: 10) + + Text("오늘도 함께 무사히 도착했습니다.\n다음에도 안전하게 뵙겠습니다.") + .font(Font.custom("Spoqa Han Sans Neo", size: 15)) + .foregroundColor(Color("HGray2")) + .font(Font.custom("Pretendard", size: 15)) + .foregroundColor(Color(red: 0.24, green: 0.26, blue: 0.31)) + .frame(width: 345, alignment: .center) // 가로 중앙 정렬 + .multilineTextAlignment(.center) // 텍스트 줄바꿈 시 가운데 정렬 - Spacer() - } + + - Spacer().frame(height: 104) + Spacer().frame(height: 72) Image(systemName: "checkmark.circle.fill") // SF Symbols에서 원형 아이콘 사용 .resizable() - .frame(width: 160, height: 160) + .frame(width: 121, height: 121) .foregroundColor(Color("HPrimaryColor")) // 아이콘 색상 설정 Spacer().frame(height: 231) - Button(action: { - viewModel.goToHome() // 홈으로 돌아가는 동작 - }) { - ZStack { - Rectangle() - .foregroundColor(.clear) - .frame(width: 361, height: 58) - .background(Color.white) - .cornerRadius(10) - .opacity(0.28) - - Text("시작하기") - .font(Font.custom("Spoqa Han Sans Neo", size: 18).weight(.medium)) - .multilineTextAlignment(.center) - .foregroundColor(.white) - } - } + Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color(red: 28/255, green: 34/255, blue: 46/255, opacity: 1)) + .background(Color(red: 1, green: 1, blue: 1, opacity: 1)) .edgesIgnoringSafeArea(.all) + .onDisappear { + triggerSuccessHaptic() + } } } diff --git a/hearo/hearo/Sources/Presentations/Finish/ViewModel/FinishViewModel.swift b/hearo/hearo/Sources/Presentations/Finish/ViewModel/FinishViewModel.swift index c1a6f67..206c22c 100644 --- a/hearo/hearo/Sources/Presentations/Finish/ViewModel/FinishViewModel.swift +++ b/hearo/hearo/Sources/Presentations/Finish/ViewModel/FinishViewModel.swift @@ -6,15 +6,20 @@ // import Foundation +import UIKit class FinishViewModel: ObservableObject { @Published var appRootManager: AppRootManager init(appRootManager: AppRootManager) { self.appRootManager = appRootManager + goToHomeWithDelay() } - func goToHome() { - appRootManager.currentRoot = .home + private func goToHomeWithDelay() { + + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in + self?.appRootManager.currentRoot = .home + } } } diff --git a/hearo/hearo/Sources/Presentations/Home/View/HomeView.swift b/hearo/hearo/Sources/Presentations/Home/View/HomeView.swift index 8f4799b..41bd7e1 100644 --- a/hearo/hearo/Sources/Presentations/Home/View/HomeView.swift +++ b/hearo/hearo/Sources/Presentations/Home/View/HomeView.swift @@ -77,3 +77,7 @@ struct HomeView: View { } } } + +#Preview { + HomeView(viewModel: HomeViewModel(appRootManager: AppRootManager())) +} diff --git a/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingNotiPermissionView.swift b/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingNotiPermissionView.swift new file mode 100644 index 0000000..3ad5e4b --- /dev/null +++ b/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingNotiPermissionView.swift @@ -0,0 +1,74 @@ +// +// OnboardingNotiPermissionView.swift +// hearo +// +// Created by 규북 on 10/25/24. +// + +import SwiftUI + +struct OnboardingNotiPermissionView: View { + @StateObject var viewModel: OnboardingViewModel + + var body: some View { + VStack { + Spacer().frame(height: 59) + + HStack { + Spacer().frame(width: 16) + + Text("경적 소리 감지시\n안전을 위한 알림을 허용하세요") + .font( + Font.custom("Spoqa Han Sans Neo", size: 25) + .weight(.bold) + ) + // .foregroundColor(Color("HWhite")) + + Spacer() + } + + Spacer().frame(height: 19) + + HStack { + Spacer().frame(width: 16) + + Text("안전한 주행을 위해, 주행 중 경적, 사이렌 소리 등\n중요한 경고 신호를 놓치지 않도록 알림을 허용해 주세요.") + .font(Font.custom("Spoqa Han Sans Neo", size: 13)) + .foregroundColor(Color("HGray2")) + .frame(width: 295, alignment: .topLeading) + + Spacer() + } + + Spacer().frame(height: 139) + + Image(systemName: "bell.fill") + .resizable() + .frame(width: 138.34, height: 161.72) + .foregroundColor(Color("HPrimaryColor")) + + Spacer().frame(height: 121.28) + + Button(action: { + viewModel.moveToNextPage() // 두 번째 페이지로 전환 + }) { + ZStack { + Rectangle() + // .foregroundColor(.clear) + .frame(width: 361, height: 58) + .background(Color.white) + .cornerRadius(10) + .opacity(0.28) + + Text("시작하기") + .font(Font.custom("Spoqa Han Sans Neo", size: 18).weight(.medium)) + .multilineTextAlignment(.center) + } + } + } + } +} + +#Preview { + OnboardingNotiPermissionView(viewModel: OnboardingViewModel(appRootManager: AppRootManager())) +} diff --git a/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingPrivacyView.swift b/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingPrivacyView.swift new file mode 100644 index 0000000..c7b731d --- /dev/null +++ b/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingPrivacyView.swift @@ -0,0 +1,66 @@ +// +// OnboardingPrivacyView.swift +// hearo +// +// Created by 규북 on 10/25/24. +// + +import SwiftUI + +struct OnboardingPrivacyView: View { + @StateObject var viewModel: OnboardingViewModel + var body: some View { + VStack { + Spacer().frame(height: 59) + + HStack { + Spacer().frame(width: 16) + + Text("프라이버시 보호관련 안내") + .font( + Font.custom("Spoqa Han Sans Neo", size: 25) + .weight(.bold) + ) + + Spacer() + } + + Spacer().frame(height: 19) + + Text("· 녹음된 오디오는 오직 경적 소리 인식 목적으로만 사용됩니다.\n\n"+"· 녹음 데이터는 저장되지 않으며, 실시간으로 분석 후 즉시 삭제됩니다.\n\n"+"· 사용자의 다른 행동이나 대화는 절대 녹음되지 않습니다.") + .foregroundStyle(Color("HGray2")) + .padding(.leading,44) + + + + Spacer().frame(height: 139) + + Image(systemName: "lock.shield.fill") + .resizable() + .frame(width: 138.34, height: 161.72) + .foregroundColor(Color("HPrimaryColor")) + + Spacer().frame(height: 121.28) + + Button(action: { + viewModel.moveToNextPage() // 다음 페이지로 전환 + }) { + ZStack { + Rectangle() + .frame(width: 361, height: 58) + .background(Color.white) + .cornerRadius(10) + .opacity(0.28) + + Text("다음") + .font(Font.custom("Spoqa Han Sans Neo", size: 18).weight(.medium)) + .multilineTextAlignment(.center) + } + } + } + } +} + +#Preview { + OnboardingPrivacyView(viewModel: OnboardingViewModel(appRootManager: AppRootManager())) +} diff --git a/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingStandRecommendView.swift b/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingStandRecommendView.swift new file mode 100644 index 0000000..78f63d4 --- /dev/null +++ b/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingStandRecommendView.swift @@ -0,0 +1,62 @@ +// +// OnboardingStandRecommendView.swift +// hearo +// +// Created by 규북 on 10/25/24. +// + +import SwiftUI + +struct OnboardingStandRecommendView: View { + @StateObject var viewModel: OnboardingViewModel + var body: some View { + VStack { + Text("정확한 위치 확인을 위해서\n거치대를 확인해주세요") + .font( + Font.custom("Spoqa Han Sans Neo", size: 25) + .weight(.bold) + ) + .frame(width: 393, alignment: .leading) + .padding() + + + + Text("더 안전하고 정확한 경고 알림을 받기 위해\n스마트폰을 거치대에 고정해 주행해 주세요.") + .font(Font.custom("Spoqa Han Sans Neo", size: 14)) + .foregroundStyle(Color("HGray2")) + .frame(width: 345, alignment: .topLeading) + + + Spacer().frame(height: 139) + + Image(systemName: "iphone.gen1.radiowaves.left.and.right") + .resizable() + .frame(width: 135.84, height: 180) + .foregroundColor(Color("HPrimaryColor")) + + Spacer().frame(height: 162) + + Button(action: { + viewModel.moveToHome() // 홈 화면으로 전환 + }) { + ZStack { + Rectangle() + // .foregroundColor(.clear) + .frame(width: 361, height: 58) + .background(Color.white) + .cornerRadius(10) + .opacity(0.28) + + Text("확인") + .font(Font.custom("Spoqa Han Sans Neo", size: 18).weight(.medium)) + .multilineTextAlignment(.center) + .foregroundColor(.white) + } + } + } + } +} + +#Preview { + OnboardingStandRecommendView(viewModel: OnboardingViewModel(appRootManager: AppRootManager())) +} diff --git a/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingView.swift b/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingView.swift index 26cc6b7..6828c75 100644 --- a/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingView.swift +++ b/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingView.swift @@ -13,193 +13,27 @@ struct OnboardingView: View { var body: some View { TabView(selection: $viewModel.currentPage) { // 첫 번째 온보딩 페이지 - VStack { - Spacer().frame(height: 59) - - HStack { - Spacer().frame(width: 16) - - Text("당신을 위한 소리 감지 앱\n히어로드에 오신 걸 환영합니다!") - .font( - Font.custom("Spoqa Han Sans Neo", size: 25) - .weight(.bold) - ) - .foregroundColor(Color("HWhite")) - - Spacer() - } - - Spacer().frame(height: 19) - - HStack { - Spacer().frame(width: 16) - - Text("히여로는 주행 중 위험 신호를 실시간으로 감지해\n시각과 진동으로 알려드립니다.") - .font(Font.custom("Spoqa Han Sans Neo", size: 13)) - .foregroundColor(Color("HGray2")) - .frame(width: 295, alignment: .topLeading) - - Spacer() - } - - Spacer().frame(height: 102) - - Image(systemName: "circle.fill") - .resizable() - .frame(width: 189, height: 189) - .foregroundColor(Color("HPrimaryColor")) - - Spacer().frame(height: 120) - - Button(action: { - viewModel.moveToNextPage() // 두 번째 페이지로 전환 - }) { - ZStack { - Rectangle() - .foregroundColor(.clear) - .frame(width: 361, height: 58) - .background(Color.white) - .cornerRadius(10) - .opacity(0.28) - - Text("시작하기") - .font(Font.custom("Spoqa Han Sans Neo", size: 18).weight(.medium)) - .multilineTextAlignment(.center) - .foregroundColor(.white) - } - } - - Spacer() - } + OnboardingNotiPermissionView(viewModel: viewModel) .tag(0) // 두 번째 온보딩 페이지 - VStack { - Spacer().frame(height: 59) - - HStack { - Spacer().frame(width: 16) - - Text("경적 소리 감지시\n안전을 위한 알림을 허용하세요") - .font( - Font.custom("Spoqa Han Sans Neo", size: 25) - .weight(.bold) - ) - .foregroundColor(Color("HWhite")) - - Spacer() - } - - Spacer().frame(height: 19) - - HStack { - Spacer().frame(width: 16) - - Text("안전한 주행을 위해, 주행 중 경적, 사이렌 소리 등\n중요한 경고 신호를 놓치지 않도록 알림을 허용해 주세요.") - .font(Font.custom("Spoqa Han Sans Neo", size: 13)) - .foregroundColor(Color("HGray2")) - .frame(width: 295, alignment: .topLeading) - - Spacer() - } - - Spacer().frame(height: 139) - - Image(systemName: "bell.fill") - .resizable() - .frame(width: 138.34, height: 161.72) - .foregroundColor(Color("HPrimaryColor")) - - Spacer().frame(height: 121.28) - - Button(action: { - viewModel.moveToNextPage() // 세 번째 페이지로 전환 - }) { - ZStack { - Rectangle() - .foregroundColor(.clear) - .frame(width: 361, height: 58) - .background(Color.white) - .cornerRadius(10) - .opacity(0.28) - - Text("확인") - .font(Font.custom("Spoqa Han Sans Neo", size: 18).weight(.medium)) - .multilineTextAlignment(.center) - .foregroundColor(.white) - } - } - - Spacer() - } + OnboardingPrivacyView(viewModel:viewModel) .tag(1) - // 추가 페이지들도 동일한 방식으로 추가... + + OnboardingWarningView(viewModel:viewModel) + .tag(2) // 마지막 온보딩 페이지 - VStack { - Spacer().frame(height: 59) - - HStack { - Spacer().frame(width: 19) - - Text("정확한 위치 확인을 위해서\n거치대를 활용해주세요") - .font( - Font.custom("Spoqa Han Sans Neo", size: 25) - .weight(.bold) - ) - .foregroundColor(Color("HWhite")) - - Spacer() - } - - Spacer().frame(height: 21) - - HStack { - Spacer().frame(width: 16) - - Text("더 안전하고 정확한 경고 알림을 받기 위해\n스마트폰을 거치대에 고정해 주행해 주세요.") - .font(Font.custom("Spoqa Han Sans Neo", size: 14)) - .foregroundColor(Color(red: 0.7, green: 0.7, blue: 0.7)) - .frame(width: 345, alignment: .topLeading) - - Spacer() - } - - Spacer().frame(height: 139) - - Image(systemName: "iphone.gen1.radiowaves.left.and.right") - .resizable() - .frame(width: 135.84, height: 180) - .foregroundColor(Color("HPrimaryColor")) - - Spacer().frame(height: 162) - - Button(action: { - viewModel.moveToHome() // 홈 화면으로 전환 - }) { - ZStack { - Rectangle() - .foregroundColor(.clear) - .frame(width: 361, height: 58) - .background(Color.white) - .cornerRadius(10) - .opacity(0.28) - - Text("확인") - .font(Font.custom("Spoqa Han Sans Neo", size: 18).weight(.medium)) - .multilineTextAlignment(.center) - .foregroundColor(.white) - } - } - - Spacer() - } - .tag(4) + OnboardingStandRecommendView(viewModel: viewModel) + .tag(3) } .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color(red: 28/255, green: 34/255, blue: 46/255, opacity: 1)) .edgesIgnoringSafeArea(.all) } } + +#Preview { + OnboardingView(viewModel: OnboardingViewModel(appRootManager: AppRootManager())) +} diff --git a/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingWarningView.swift b/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingWarningView.swift new file mode 100644 index 0000000..bbfe3e1 --- /dev/null +++ b/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingWarningView.swift @@ -0,0 +1,66 @@ +// +// OnboardingWarningView.swift +// hearo +// +// Created by 규북 on 10/25/24. +// + +import SwiftUI + +struct OnboardingWarningView: View { + @StateObject var viewModel: OnboardingViewModel + var body: some View { + VStack { + Spacer().frame(height: 59) + + HStack { + Spacer().frame(width: 16) + + Text("우리의 경고 알림은 \n \"보조수단\"일 뿐입니다.") + .font( + Font.custom("Spoqa Han Sans Neo", size: 25) + .weight(.bold) + ) + + Spacer() + } + + Spacer().frame(height: 19) + + Text("· 이 앱은 위험 신호를 보조적으로 알리는 도구입니다.\n\n"+"· 사용자의 안전 주의는 가장 중요한 요소입니다.\n 항상 주변을 확인해주세요.") + .foregroundStyle(Color("HGray2")) + .padding(.leading,40) + .padding(.trailing) + + + Spacer().frame(height: 139) + + Image(systemName: "exclamationmark.triangle.fill") + .resizable() + .frame(width: 138.34, height: 161.72) + .foregroundColor(Color(red: 255/255, green: 216/255, blue: 19/255, opacity: 1)) + + Spacer().frame(height: 121.28) + + Button(action: { + viewModel.moveToNextPage() // 다음 페이지로 전환 + }) { + ZStack { + Rectangle() + .frame(width: 361, height: 58) + .background(Color.white) + .cornerRadius(10) + .opacity(0.28) + + Text("다음") + .font(Font.custom("Spoqa Han Sans Neo", size: 18).weight(.medium)) + .multilineTextAlignment(.center) + } + } + } + } +} + +#Preview { + OnboardingWarningView(viewModel: OnboardingViewModel(appRootManager: AppRootManager())) +} diff --git a/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingWelcomeView.swift b/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingWelcomeView.swift new file mode 100644 index 0000000..f71a563 --- /dev/null +++ b/hearo/hearo/Sources/Presentations/OnBoarding/View/OnboardingWelcomeView.swift @@ -0,0 +1,55 @@ +// +// OnboardingWelcomeView.swift +// hearo +// +// Created by 규북 on 10/25/24. +// + +import SwiftUI + +struct OnboardingWelcomeView: View { + var body: some View { + VStack { + Spacer().frame(height: 59) + + HStack { + Spacer().frame(width: 16) + + Text("당신을 위한 소리 감지 앱\n히어로드에 오신 걸 환영합니다!") + .font( + Font.custom("Spoqa Han Sans Neo", size: 25) + .weight(.bold) + ) + + Spacer() + } + + Spacer().frame(height: 19) + + HStack { + Spacer().frame(width: 16) + + Text("히어로드는 주행 중 위험 신호를 실시간으로 감지해\n시각과 진동으로 알려드립니다.") + .font(Font.custom("Spoqa Han Sans Neo", size: 13)) + .foregroundColor(Color("HGray2")) + .frame(width: 295, alignment: .topLeading) + + Spacer() + } + + Spacer().frame(height: 102) + + VStack(alignment: .leading) { + Text("서비스 이용 약관에 동의합니다.") + Text("히어로 이용 약관에 동의하시면 '앱 시작하기'를 눌러주세요.") + } + + Spacer().frame(height: 120) + } + } +} + + +#Preview { + OnboardingWelcomeView() +} diff --git a/hearo/hearo/Sources/Presentations/OnBoarding/ViewModel/OnboardingViewModel.swift b/hearo/hearo/Sources/Presentations/OnBoarding/ViewModel/OnboardingViewModel.swift index 7ca3745..03bc959 100644 --- a/hearo/hearo/Sources/Presentations/OnBoarding/ViewModel/OnboardingViewModel.swift +++ b/hearo/hearo/Sources/Presentations/OnBoarding/ViewModel/OnboardingViewModel.swift @@ -20,6 +20,7 @@ class OnboardingViewModel: ObservableObject { } func moveToHome() { + UserDefaults.standard.set(true, forKey: "hasSeenOnboarding") appRootManager.currentRoot = .home } } diff --git a/hearo/hearo/Sources/Presentations/Splash/View/SplashView.swift b/hearo/hearo/Sources/Presentations/Splash/View/SplashView.swift index ecff404..45d9c39 100644 --- a/hearo/hearo/Sources/Presentations/Splash/View/SplashView.swift +++ b/hearo/hearo/Sources/Presentations/Splash/View/SplashView.swift @@ -7,48 +7,53 @@ import SwiftUI struct SplashView: View { - var appRootManager: AppRootManager // 인자로 받기 - @State private var isActive = false // 스플래시 화면이 끝났는지 여부를 관리하는 상태 - - var body: some View { - ZStack { - VStack { - if isActive { - // 상태를 즉시 변경하여 OnboardingView로 전환 - // EmptyView를 사용하지 않고 상태만 변경 - Color.clear - .onAppear { - appRootManager.currentRoot = .onboarding // 상태 변경 - } - } else { - - Spacer().frame(height: 217) - - Text("Hearoad") - .font(Font.custom("Inter", size: 34.80519) - .weight(.medium) - ) - .multilineTextAlignment(.center) - .foregroundColor(Color("HWhite")) - - Spacer() - - Text("Copyright 2024. DaQman2ni in all rights reserved.") - .font(Font.custom("Spoqa Han Sans Neo", size: 12)) - .multilineTextAlignment(.center) - .foregroundColor(Color("HWhite")) - - Spacer().frame(height: 44) - .onAppear { - // 2초 후 isActive 상태를 true로 변경 - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { - self.isActive = true - } - } + var appRootManager: AppRootManager // 인자로 받기 + @State private var isActive = false // 스플래시 화면이 끝났는지 여부를 관리하는 상태 + + var body: some View { + ZStack { + + Color("HPrimaryColor") + .ignoresSafeArea(.all) + + VStack { + if isActive { + Color.clear + .onAppear { + appRootManager.determineNextRoot() // splash 후 다음 화면 결정 + } + } else { + + Spacer().frame(height: 217) + + Text("Hearoad") + .font(Font.custom("Inter", size: 34.80519) + .weight(.medium) + ) + .multilineTextAlignment(.center) + .foregroundColor(Color("HWhite")) + + Spacer() + + Text("Copyright 2024. DaQman2ni in all rights reserved.") + .font(Font.custom("Spoqa Han Sans Neo", size: 12)) + .multilineTextAlignment(.center) + .foregroundColor(Color("HWhite")) + + Spacer().frame(height: 44) + .onAppear { + // 2초 후 isActive 상태를 true로 변경 + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + self.isActive = true + } } } - } - .frame(width: 393, height: 852) - .background(Color(red: 28/255, green: 34/255, blue: 46/255, opacity: 1)) + } } + + } +} + +#Preview { + SplashView(appRootManager: AppRootManager()) } diff --git a/hearo/hearo/Sources/Presentations/Working/ViewModel/HornSoundDetector.swift b/hearo/hearo/Sources/Presentations/Working/ViewModel/HornSoundDetector.swift index b14000f..3bdb498 100644 --- a/hearo/hearo/Sources/Presentations/Working/ViewModel/HornSoundDetector.swift +++ b/hearo/hearo/Sources/Presentations/Working/ViewModel/HornSoundDetector.swift @@ -178,31 +178,31 @@ class HornSoundDetector: NSObject, ObservableObject { } extension HornSoundDetector: SNResultsObserving { - func request(_ request: SNRequest, didProduce result: SNResult) { - guard let result = result as? SNClassificationResult else { return } - - let topClassifications = result.classifications.prefix(3) - - DispatchQueue.main.async { - // 첫 번째 분류를 가장 신뢰도 높은 것으로 설정 - if let topClassification = topClassifications.first(where: { classification in - return classification.identifier == "Bicyclebell" || classification.identifier == "Carhorn" || classification.identifier == "Siren" - }) { - self.topClassification = topClassification // 가장 높은 분류 저장 - self.classificationResult = "소리: \(topClassification.identifier), 신뢰도: \(topClassification.confidence)" - } - - for (index, classification) in topClassifications.enumerated() { - if classification.identifier == "Bicyclebell" || classification.identifier == "Carhorn" || classification.identifier == "Siren" { - print("소리: \(classification.identifier), 신뢰도: \(classification.confidence)") - - // 경적 및 사이렌 소리 감지 - if classification.confidence >= 1.0 { - self.mlConfidences[index] = classification.confidence - self.checkAllChannelsConfidence() // 모든 채널 신뢰도 확인 - } + func request(_ request: SNRequest, didProduce result: SNResult) { + guard let result = result as? SNClassificationResult else { return } + + let topClassifications = result.classifications.prefix(3) + + DispatchQueue.main.async { + // 첫 번째 분류를 가장 신뢰도 높은 것으로 설정 + if let topClassification = topClassifications.first(where: { classification in + return classification.identifier == "Bicyclebell" || classification.identifier == "Carhorn" || classification.identifier == "Siren" + }) { + self.topClassification = topClassification // 가장 높은 분류 저장 + self.classificationResult = topClassification.identifier // 소리 종류만 저장 + } + + for (index, classification) in topClassifications.enumerated() { + if classification.identifier == "Bicyclebell" || classification.identifier == "Carhorn" || classification.identifier == "Siren" { + // 경적 및 사이렌 소리 감지 + if classification.confidence >= 1.0 { + self.mlConfidences[index] = classification.confidence + self.checkAllChannelsConfidence() // 모든 채널 신뢰도 확인 + } + } + } } } } - } -} + + diff --git a/hearo/hearo/Sources/Presentations/Working/ViewModel/SoundDetectorViewModel.swift b/hearo/hearo/Sources/Presentations/Working/ViewModel/SoundDetectorViewModel.swift index 6eb4bc5..37d1479 100644 --- a/hearo/hearo/Sources/Presentations/Working/ViewModel/SoundDetectorViewModel.swift +++ b/hearo/hearo/Sources/Presentations/Working/ViewModel/SoundDetectorViewModel.swift @@ -86,7 +86,9 @@ class SoundDetectorViewModel: NSObject, ObservableObject, WCSessionDelegate { highestConfidenceIndex < classificationResults.count { let highestConfidenceSound = classificationResults[highestConfidenceIndex] - let message = ["alert": "경고: \(highestConfidenceSound) 소리 감지됨"] + + // **신뢰도 없이 소리 종류만 전송** + let message = ["alert": highestConfidenceSound] WCSession.default.sendMessage(message, replyHandler: nil) { error in print("애플워치로 경고 메시지 전송 오류: \(error.localizedDescription)") @@ -110,15 +112,15 @@ class SoundDetectorViewModel: NSObject, ObservableObject, WCSessionDelegate { // 필수 WCSessionDelegate 메서드 구현 func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { - print("WCSession 활성화 완료. 상태: \(activationState)") + print("MLWCSession 활성화 완료. 상태: \(activationState)") } func sessionDidBecomeInactive(_ session: WCSession) { - print("WCSession 비활성화됨") + print("MLWCSession 비활성화됨") } func sessionDidDeactivate(_ session: WCSession) { - print("WCSession 비활성화됨. 다시 활성화") + print("MLWCSession 비활성화됨. 다시 활성화") WCSession.default.activate() } } diff --git a/hearo/hearo/Sources/Presentations/Working/ViewModel/WorkingViewModel.swift b/hearo/hearo/Sources/Presentations/Working/ViewModel/WorkingViewModel.swift index 93ed8a2..0fe573e 100644 --- a/hearo/hearo/Sources/Presentations/Working/ViewModel/WorkingViewModel.swift +++ b/hearo/hearo/Sources/Presentations/Working/ViewModel/WorkingViewModel.swift @@ -8,6 +8,7 @@ import Foundation import Combine import SwiftUI import AVFoundation +import AudioToolbox @@ -64,5 +65,8 @@ class WorkingViewModel: ObservableObject { func finishRecording() { appRootManager.currentRoot = .finish stopRecording() + + triggerErrorHaptic() } } +