From 9851e524bd0fae99712a44a8c6486dec0f984ded Mon Sep 17 00:00:00 2001 From: ParkSeongGeun Date: Tue, 25 Nov 2025 16:22:51 +0900 Subject: [PATCH] [Refact] Refactor alert handling for Bluetooth notifications --- .../Core/Manager/AlertManager.swift | 32 +++++- .../Manager/Bluetooth/BluetoothManager.swift | 4 +- .../Core/Presentation/Home/HomeView.swift | 105 ++++++++++-------- 3 files changed, 87 insertions(+), 54 deletions(-) diff --git a/ComfortableMove/ComfortableMove/Core/Manager/AlertManager.swift b/ComfortableMove/ComfortableMove/Core/Manager/AlertManager.swift index 07b5d70..b50076c 100644 --- a/ComfortableMove/ComfortableMove/Core/Manager/AlertManager.swift +++ b/ComfortableMove/ComfortableMove/Core/Manager/AlertManager.swift @@ -15,6 +15,9 @@ enum AlertType: Identifiable { case bluetoothUnsupported case bluetoothUnauthorized case locationUnauthorized + case bluetoothConfirm(busName: String, onConfirm: () -> Void, onCancel: () -> Void) + case bluetoothSuccess + case bluetoothFailure var id: String { switch self { @@ -23,6 +26,9 @@ enum AlertType: Identifiable { case .bluetoothUnsupported: return "bluetoothUnsupported" case .bluetoothUnauthorized: return "bluetoothUnauthorized" case .locationUnauthorized: return "locationUnauthorized" + case .bluetoothConfirm: return "bluetoothConfirm" + case .bluetoothSuccess: return "bluetoothSuccess" + case .bluetoothFailure: return "bluetoothFailure" } } @@ -30,6 +36,7 @@ enum AlertType: Identifiable { switch self { case .locationUnauthorized: return 3 case .bluetoothUnsupported, .bluetoothUnauthorized: return 2 + case .bluetoothConfirm, .bluetoothSuccess, .bluetoothFailure: return 1 case .noBusInfo: return 1 case .apiError: return 0 } @@ -47,6 +54,12 @@ enum AlertType: Identifiable { return "블루투스 권한 필요" case .locationUnauthorized: return "위치 권한 필요" + case .bluetoothConfirm(let busName, _, _): + return "\(busName)버스에 배려석 알림을 전송하시겠습니까?" + case .bluetoothSuccess: + return "알림 전송 완료" + case .bluetoothFailure: + return "버스 배려석 알림 전송에 실패하였습니다." } } @@ -62,6 +75,12 @@ enum AlertType: Identifiable { return "블루투스 권한이 필요합니다.\n설정에서 블루투스 권한을 허용해주세요." case .locationUnauthorized: return "위치 권한이 필요합니다.\n설정에서 위치 권한을 허용해주세요." + case .bluetoothConfirm: + return "" + case .bluetoothSuccess: + return "" + case .bluetoothFailure: + return "다시 한번 시도해주세요." } } @@ -69,7 +88,7 @@ enum AlertType: Identifiable { switch self { case .bluetoothUnsupported, .bluetoothUnauthorized, .locationUnauthorized: return true - case .noBusInfo, .apiError: + case .noBusInfo, .apiError, .bluetoothConfirm, .bluetoothSuccess, .bluetoothFailure: return false } } @@ -77,6 +96,13 @@ enum AlertType: Identifiable { var primaryButtonText: String { shouldBlockApp ? "설정으로 이동" : "확인" } + + var isConfirmAlert: Bool { + if case .bluetoothConfirm = self { + return true + } + return false + } } // MARK: - Alert Manager @@ -84,9 +110,9 @@ class AlertManager: ObservableObject { @Published var currentAlert: AlertType? func showAlert(_ type: AlertType) { - // 현재 alert가 없거나, 새로운 alert의 우선순위가 더 높은 경우에만 표시 + // 현재 alert가 없거나, 새로운 alert의 우선순위가 더 높거나 같은 경우 표시 if let current = currentAlert { - if type.priority > current.priority { + if type.priority >= current.priority { currentAlert = type } } else { diff --git a/ComfortableMove/ComfortableMove/Core/Manager/Bluetooth/BluetoothManager.swift b/ComfortableMove/ComfortableMove/Core/Manager/Bluetooth/BluetoothManager.swift index bf7414f..2566c79 100644 --- a/ComfortableMove/ComfortableMove/Core/Manager/Bluetooth/BluetoothManager.swift +++ b/ComfortableMove/ComfortableMove/Core/Manager/Bluetooth/BluetoothManager.swift @@ -103,9 +103,9 @@ extension BluetoothManager: CBCentralManagerDelegate { return } - // 버스 번호로 필터링 + // 버스 번호로 필터링 (대소문자 무시) guard let busNumber = BluetoothConfig.busNumber(from: finalDeviceName), - busNumber == targetBusNumber else { + busNumber.lowercased() == targetBusNumber?.lowercased() else { Logger.log(message: "⚠️ 다른 버스(\(BluetoothConfig.busNumber(from: finalDeviceName) ?? "알 수 없음")번) - 무시") return } diff --git a/ComfortableMove/ComfortableMove/Core/Presentation/Home/HomeView.swift b/ComfortableMove/ComfortableMove/Core/Presentation/Home/HomeView.swift index deb130c..9ada47a 100644 --- a/ComfortableMove/ComfortableMove/Core/Presentation/Home/HomeView.swift +++ b/ComfortableMove/ComfortableMove/Core/Presentation/Home/HomeView.swift @@ -19,10 +19,6 @@ struct HomeView: View { @State private var nearestStation: StationItem? // 가장 가까운 정류소 @State private var isLoadingStation = false - // Alert 상태 - @State private var showConfirmAlert = false - @State private var showSuccessAlert = false - @State private var showFailureAlert = false // 화면 표시 상태 @State private var showHelpPage = false @@ -74,8 +70,14 @@ struct HomeView: View { VStack(spacing: 30) { // 중앙 버튼 Button(action: { + Logger.log(message: "🔘 중앙 버튼 클릭 - selectedRouteName: \(selectedRouteName ?? "nil"), isButtonTapped: \(isButtonTapped)") if selectedRouteName != nil && isButtonTapped { - showConfirmAlert = true + Logger.log(message: "✅ 확인 Alert 표시") + alertManager.showAlert(.bluetoothConfirm( + busName: selectedBusName, + onConfirm: { sendCourtesySeatNotification() }, + onCancel: { resetButtonState() } + )) } }) { ZStack { @@ -225,44 +227,8 @@ struct HomeView: View { refreshBusArrivals() } } - .alert(isPresented: $showConfirmAlert) { - Alert( - title: Text("\(selectedBusName)버스에 배려석 알림을 전송하시겠습니까?"), - primaryButton: .destructive(Text("취소")) { - resetButtonState() - }, - secondaryButton: .default(Text("확인")) { - sendCourtesySeatNotification() - } - ) - } - .alert("알림 전송 완료", isPresented: $showSuccessAlert) { - Button("확인", role: .cancel) { } - } - .alert("버스 배려석 알림 전송에 실패하였습니다.", isPresented: $showFailureAlert) { - Button("확인", role: .cancel) { } - } message: { - Text("다시 한번 시도해주세요.") - } .alert(item: $alertManager.currentAlert) { alertType in - if alertType.shouldBlockApp { - return Alert( - title: Text(alertType.title), - message: Text(alertType.message), - primaryButton: .default(Text(alertType.primaryButtonText)) { - alertManager.openSettings() - }, - secondaryButton: .cancel(Text("취소")) - ) - } else { - return Alert( - title: Text(alertType.title), - message: Text(alertType.message), - dismissButton: .default(Text(alertType.primaryButtonText)) { - alertManager.dismissAlert() - } - ) - } + createAlert(for: alertType) } .overlay( showHelpPage ? HelpPageView(isPresented: $showHelpPage) : nil @@ -271,6 +237,43 @@ struct HomeView: View { } } + // MARK: - Create Alert + private func createAlert(for alertType: AlertType) -> Alert { + if case .bluetoothConfirm(_, let onConfirm, let onCancel) = alertType { + return Alert( + title: Text(alertType.title), + primaryButton: .default(Text("확인")) { + alertManager.dismissAlert() + onConfirm() + }, + secondaryButton: .cancel(Text("취소")) { + alertManager.dismissAlert() + onCancel() + } + ) + } + + if alertType.shouldBlockApp { + return Alert( + title: Text(alertType.title), + message: Text(alertType.message), + primaryButton: .default(Text(alertType.primaryButtonText)) { + alertManager.openSettings() + }, + secondaryButton: .cancel(Text("취소")) + ) + } + + let messageText: Text? = alertType.message.isEmpty ? nil : Text(alertType.message) + return Alert( + title: Text(alertType.title), + message: messageText, + dismissButton: .default(Text(alertType.primaryButtonText)) { + alertManager.dismissAlert() + } + ) + } + // MARK: - Setup Alert Callbacks private func setupAlertCallbacks() { bluetoothManager.onBluetoothUnsupported = { @@ -289,14 +292,18 @@ struct HomeView: View { // MARK: - 배려석 알림 전송 private func sendCourtesySeatNotification() { - bluetoothManager.sendCourtesySeatNotification(busNumber: selectedBusName) { success in - if success { - showSuccessAlert = true - } else { - showFailureAlert = true + Logger.log(message: "📲 sendCourtesySeatNotification 호출됨 - 버스: \(selectedBusName)") + bluetoothManager.sendCourtesySeatNotification(busNumber: selectedBusName) { success in + DispatchQueue.main.async { + Logger.log(message: "📲 Bluetooth 전송 완료 - success: \(success)") + if success { + alertManager.showAlert(.bluetoothSuccess) + } else { + alertManager.showAlert(.bluetoothFailure) + } + // 알림 전송 완료/실패 후 초기화 + resetButtonState() } - // 알림 전송 완료/실패 후 초기화 - resetButtonState() } }