Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions ComfortableMove/ComfortableMove/Core/Manager/AlertManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//
// AlertManager.swift
// ComfortableMove
//
// Created by Claude on 11/15/25.
//

import Foundation
import SwiftUI

// MARK: - Alert Types
enum AlertType: Identifiable {
case outOfSeoul
case apiError
case bluetoothUnsupported
case bluetoothUnauthorized
case locationUnauthorized

var id: String {
switch self {
case .outOfSeoul: return "outOfSeoul"
case .apiError: return "apiError"
case .bluetoothUnsupported: return "bluetoothUnsupported"
case .bluetoothUnauthorized: return "bluetoothUnauthorized"
case .locationUnauthorized: return "locationUnauthorized"
}
}

var priority: Int {
switch self {
case .locationUnauthorized: return 3
case .bluetoothUnsupported, .bluetoothUnauthorized: return 2
case .outOfSeoul: return 1
case .apiError: return 0
}
}

var title: String {
switch self {
case .outOfSeoul:
return "서울 외 지역"
case .apiError:
return "서버 오류"
case .bluetoothUnsupported:
return "블루투스 미지원"
case .bluetoothUnauthorized:
return "블루투스 권한 필요"
case .locationUnauthorized:
return "위치 권한 필요"
}
}

var message: String {
switch self {
case .outOfSeoul:
return "현재 서울 지역에서만 서비스를 이용할 수 있습니다."
case .apiError:
return "서버에 문제가 발생했습니다.\n잠시 후 다시 시도해주세요."
case .bluetoothUnsupported:
return "이 기기는 블루투스를 지원하지 않습니다.\n배려석 알림 기능을 사용할 수 없습니다."
case .bluetoothUnauthorized:
return "블루투스 권한이 필요합니다.\n설정에서 블루투스 권한을 허용해주세요."
case .locationUnauthorized:
return "위치 권한이 필요합니다.\n설정에서 위치 권한을 허용해주세요."
}
}

var shouldBlockApp: Bool {
switch self {
case .bluetoothUnsupported, .bluetoothUnauthorized, .locationUnauthorized:
return true
case .outOfSeoul, .apiError:
return false
}
}

var primaryButtonText: String {
shouldBlockApp ? "설정으로 이동" : "확인"
}
}

// MARK: - Alert Manager
class AlertManager: ObservableObject {
@Published var currentAlert: AlertType?

func showAlert(_ type: AlertType) {
// 현재 alert가 없거나, 새로운 alert의 우선순위가 더 높은 경우에만 표시
if let current = currentAlert {
if type.priority > current.priority {
currentAlert = type
}
} else {
currentAlert = type
}
}

func dismissAlert() {
currentAlert = nil
}

func openSettings() {
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class BluetoothManager: NSObject, ObservableObject {
private var onTransmitComplete: ((Bool) -> Void)?
private var targetBusNumber: String?

var onBluetoothUnsupported: (() -> Void)?
var onBluetoothUnauthorized: (() -> Void)?

override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
Expand Down Expand Up @@ -74,8 +77,10 @@ extension BluetoothManager: CBCentralManagerDelegate {
Logger.log(message: "블루투스가 켜져있습니다.")
case .unauthorized:
Logger.log(message: "블루투스 권한이 없습니다.")
onBluetoothUnauthorized?()
case .unsupported:
Logger.log(message: "이 기기는 블루투스를 지원하지 않습니다.")
onBluetoothUnsupported?()
default:
break
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class BusArrivalService {

guard result.msgHeader.isSuccess else {
Logger.log(message: "❌ [API] API Error: \(result.msgHeader.headerMsg)")
return []
throw NSError(domain: "APIError", code: -1, userInfo: [NSLocalizedDescriptionKey: result.msgHeader.headerMsg])
}

let items = result.msgBody.itemList ?? []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,15 @@ class BusStopService {
Logger.log(message: "🚏 [API] Header Message: \(result.msgHeader.headerMsg)")
Logger.log(message: "🚏 [API] Item Count: \(result.msgHeader.itemCount)")

// 서울 외 지역 체크
if result.msgHeader.isOutOfSeoul {
Logger.log(message: "❌ [API] Out of Seoul: \(result.msgHeader.headerMsg)")
throw NSError(domain: "OutOfSeoul", code: 4, userInfo: [NSLocalizedDescriptionKey: "서울 외 지역"])
}

guard result.msgHeader.isSuccess else {
Logger.log(message: "❌ [API] API Error: \(result.msgHeader.headerMsg)")
return []
throw NSError(domain: "APIError", code: -1, userInfo: [NSLocalizedDescriptionKey: result.msgHeader.headerMsg])
}

let stations = result.msgBody.itemList ?? []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
}

func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if manager.authorizationStatus == .authorizedWhenInUse ||
manager.authorizationStatus == .authorizedAlways {
switch manager.authorizationStatus {
case .authorizedWhenInUse, .authorizedAlways:
manager.startUpdatingLocation()
case .denied, .restricted:
showPermissionAlert = true
default:
break
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ struct MsgHeader: Codable {
var isSuccess: Bool {
return headerCd == "0"
}

var isOutOfSeoul: Bool {
return headerCd == "4" && headerMsg == "결과가 없습니다."
}
}

// MARK: - 메시지 본문
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,22 @@
import SwiftUI

struct InfoView: View {
@Environment(\.dismiss) private var dismiss
private let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"


@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

var backButton : some View { // <-- 👀 커스텀 버튼
Button{
self.presentationMode.wrappedValue.dismiss()
} label: {
HStack {
Image(systemName: "chevron.left") // 화살표 Image
.aspectRatio(contentMode: .fit)
.foregroundStyle(Color.white)
}
}
}

var body: some View {
VStack(spacing: 0) {
Spacer()
Expand All @@ -21,7 +34,7 @@ struct InfoView: View {
.scaledToFit()
.frame(width: 100, height: 170)
.padding(.bottom, 100)

// 하단 리스트 영역
VStack(spacing: 0) {
// 버전 정보
Expand All @@ -37,10 +50,10 @@ struct InfoView: View {
.padding(.horizontal, 20)
.padding(.vertical, 16)
.background(Color.white)

Divider()
.padding(.leading, 20)

// 앱 문의
Button(action: {
if let url = URL(string: "https://forms.gle/rnSD44sUEuy1nLaH6") {
Expand All @@ -60,10 +73,10 @@ struct InfoView: View {
.padding(.vertical, 16)
.background(Color.white)
}

Divider()
.padding(.leading, 20)

// 개인정보 처리 방침 및 이용약관
Button(action: {
if let url = URL(string: "https://important-hisser-903.notion.site/10-22-ver-29a65f12c44480b6b591e726c5c80f89?source=copy_link") {
Expand All @@ -87,12 +100,13 @@ struct InfoView: View {
.background(Color.white)
.cornerRadius(20)
.padding(.horizontal, 20)

Spacer()
}
.background(Color("BFPrimaryColor"))
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(false)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: backButton)
.navigationTitle("앱정보")
.toolbarBackground(Color("BFPrimaryColor"), for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
Expand Down
Loading