Merge pull request 'main' (#2) from seonkyu.kim/AcaMate_iOS:main into main
Reviewed-on: https://git.ipstein.myds.me/AcaMate/AcaMate_iOS/pulls/2
This commit is contained in:
commit
584ab61cdc
Binary file not shown.
|
@ -44,8 +44,8 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
||||||
}
|
}
|
||||||
center.delegate = self
|
center.delegate = self
|
||||||
|
|
||||||
//MARK: - 네트워크 모니터 초기화
|
//MARK: - 네트워크 모니터 초기화
|
||||||
// _ = NetworkMonitor.shared
|
_ = NetworkMonitor.shared
|
||||||
|
|
||||||
printLog("End Set AppDelegate")
|
printLog("End Set AppDelegate")
|
||||||
return true
|
return true
|
||||||
|
|
87
AcaMate/1. View/0. Common/NavigationView.swift
Normal file
87
AcaMate/1. View/0. Common/NavigationView.swift
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
//
|
||||||
|
// NavigationView.swift
|
||||||
|
// AcaMate
|
||||||
|
//
|
||||||
|
// Created by Sean Kim on 12/12/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct NavigationView: View {
|
||||||
|
@State private var naviState : NaviState = .init(act: .NONE, path: .Intro)
|
||||||
|
@State private var history: [PathName] = [.Intro]
|
||||||
|
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
switch naviState.path {
|
||||||
|
case .NONE:
|
||||||
|
EmptyView()
|
||||||
|
case .Intro:
|
||||||
|
IntroView(naviState: $naviState)
|
||||||
|
case .Login :
|
||||||
|
LoginView(naviState: $naviState)
|
||||||
|
case .Main:
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: naviState) { old, new in
|
||||||
|
switch new.act {
|
||||||
|
case .NONE:
|
||||||
|
break
|
||||||
|
case .ADD:
|
||||||
|
addHistory(path: new.path)
|
||||||
|
case .POP:
|
||||||
|
popHistory()
|
||||||
|
case .RESET:
|
||||||
|
resetHistory(path: new.path)
|
||||||
|
case .MOVE:
|
||||||
|
moveHistory(path: new.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOG
|
||||||
|
printLog("\(old.path) => \(new.path)")
|
||||||
|
showHistory()
|
||||||
|
}
|
||||||
|
.fullView(.Normal.normal)
|
||||||
|
.setAlert()
|
||||||
|
.setNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addHistory(path: PathName) {
|
||||||
|
history.append(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func popHistory() {
|
||||||
|
history.removeLast()
|
||||||
|
naviState.set(act: .NONE, path: history.last ?? .NONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resetHistory(path: PathName) {
|
||||||
|
history.removeAll()
|
||||||
|
addHistory(path: path)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func moveHistory(path: PathName) {
|
||||||
|
if path == .NONE {
|
||||||
|
naviState.set(act: .RESET, path: history.first ?? .Main)
|
||||||
|
}
|
||||||
|
if history.contains(path) {
|
||||||
|
let remove = history.count - history.firstIndex(of: path)! - 1
|
||||||
|
history.removeLast(remove)
|
||||||
|
if remove > 0 {
|
||||||
|
naviState.set(act: .NONE, path: path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
naviState.set(act: .RESET, path: path)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showHistory() {
|
||||||
|
printLog(history)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,67 +9,80 @@ import SwiftUI
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
struct IntroView: View {
|
struct IntroView: View {
|
||||||
|
@EnvironmentObject var alertController: AlertController
|
||||||
@State var cancellables: Set<AnyCancellable> = []
|
@State var cancellables: Set<AnyCancellable> = []
|
||||||
|
@Binding var naviState : NaviState
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
VStack(spacing: 0) {
|
||||||
VStack(spacing: 0) {
|
Spacer()
|
||||||
Spacer()
|
.frame(height: 100)
|
||||||
.frame(height: 100)
|
Image("Team_Icon")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 200, height: 200)
|
||||||
|
.background(.white)
|
||||||
|
.border(.black)
|
||||||
|
.padding()
|
||||||
|
Spacer()
|
||||||
|
HStack(spacing: 4) {
|
||||||
Image("Team_Icon")
|
Image("Team_Icon")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 200, height: 200)
|
.frame(width: 24, height: 24)
|
||||||
.background(.white)
|
Text("STEIN")
|
||||||
.border(.black)
|
.font(.nps(font: .bold, size: 16))
|
||||||
.padding()
|
|
||||||
Spacer()
|
|
||||||
HStack(spacing: 4) {
|
|
||||||
Image("Team_Icon")
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 24, height: 24)
|
|
||||||
Text("STEIN")
|
|
||||||
.font(.nps(font: .bold, size: 16))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
}
|
|
||||||
.padding(.bottom,12)
|
|
||||||
Text("Copyright © Team.Stein")
|
|
||||||
.font(.nps(font: .regular, size: 14))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
.foregroundStyle(Color(.Text.detail))
|
||||||
.padding(.bottom,50)
|
|
||||||
}
|
|
||||||
.fullView(.Normal.normal)
|
|
||||||
.onAppear {
|
|
||||||
printLog("IntroView_onAppear")
|
|
||||||
loadVersion()
|
|
||||||
.sink { completion in
|
|
||||||
switch completion {
|
|
||||||
case .failure(let error):
|
|
||||||
printLog(error)
|
|
||||||
case .finished: break
|
|
||||||
}
|
|
||||||
} receiveValue: { version in
|
|
||||||
switch compareVersion(version.force_ver, currentVersion()){
|
|
||||||
case .bigger:
|
|
||||||
printLog("강제 업데이트")
|
|
||||||
default:
|
|
||||||
switch compareVersion(version.final_ver, currentVersion()) {
|
|
||||||
case .bigger:
|
|
||||||
if version.choice_update_yn {
|
|
||||||
printLog("선택 업데이트")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printLog("정상 동작")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
printLog("선택 업데이트 넘어감")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
.padding(.bottom,12)
|
||||||
|
Text("Copyright © Team.Stein")
|
||||||
|
.font(.nps(font: .regular, size: 14))
|
||||||
|
.foregroundStyle(Color(.Text.detail))
|
||||||
|
.padding(.bottom,50)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onAppear {
|
||||||
|
printLog("IntroView_onAppear")
|
||||||
|
subscribeAlertAction()
|
||||||
|
loadVersion()
|
||||||
|
.sink { completion in
|
||||||
|
switch completion {
|
||||||
|
case .failure(let error):
|
||||||
|
printLog(error)
|
||||||
|
case .finished: break
|
||||||
|
}
|
||||||
|
} receiveValue: { version in
|
||||||
|
let compareForce = compareVersion(version.force_ver, currentVersion())
|
||||||
|
let compareChoice = compareVersion(version.final_ver, currentVersion())
|
||||||
|
if compareForce == .bigger {
|
||||||
|
alertController.alertData = SetAlertData().setForceUpdate(
|
||||||
|
action: alertController.alertAction
|
||||||
|
)
|
||||||
|
alertController.showAlert.toggle()
|
||||||
|
} else if compareChoice == .bigger && version.choice_update_yn {
|
||||||
|
alertController.alertData = SetAlertData().setSelectUpdate(
|
||||||
|
action: alertController.alertAction
|
||||||
|
)
|
||||||
|
alertController.showAlert.toggle()
|
||||||
|
} else {
|
||||||
|
naviState.set(act: .RESET, path: .Login)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func subscribeAlertAction() {
|
||||||
|
alertController.alertAction
|
||||||
|
.compactMap { $0 }
|
||||||
|
.sink { action in
|
||||||
|
if action == "updateNow" {
|
||||||
|
exit(1)
|
||||||
|
//MARK: - TODO (앱스토어 이동 로직 넣을 것)
|
||||||
|
} else {
|
||||||
|
naviState.set(act: .RESET, path: .Login)
|
||||||
|
}
|
||||||
|
}.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -101,15 +114,15 @@ struct IntroView: View {
|
||||||
return currentVer
|
return currentVer
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func versionChange(ver: String) -> [Int] {
|
private func versionChange(ver: String) -> [Int] {
|
||||||
return ver.components(separatedBy: ["."]).map {Int($0) ?? 0}
|
return ver.components(separatedBy: ["."]).map {Int($0) ?? 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
|
||||||
IntroView()
|
|
||||||
}
|
|
||||||
|
//#Preview {
|
||||||
|
// IntroView(path: $NavigationPath())
|
||||||
|
//}
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct LoginView: View {
|
struct LoginView: View {
|
||||||
|
@EnvironmentObject var alertController: AlertController
|
||||||
|
@Binding var naviState : NaviState
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
Image("Team_Icon")
|
Image("Team_Icon")
|
||||||
|
@ -34,12 +37,21 @@ struct LoginView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
alertController.alertData = SetAlertData().setTest()
|
||||||
|
alertController.showAlert.toggle()
|
||||||
|
// naviState.set(act: .MOVE,path: .Intro)
|
||||||
|
|
||||||
|
} label: {
|
||||||
|
Text("111111111")
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
}
|
}
|
||||||
.fullView(.Normal.normal)
|
.fullView(.Normal.normal)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
//#Preview {
|
||||||
LoginView()
|
// LoginView()
|
||||||
}
|
//}
|
||||||
|
|
19
AcaMate/3. Controller/AlertController.swift
Normal file
19
AcaMate/3. Controller/AlertController.swift
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// AlertController.swift
|
||||||
|
// AcaMate
|
||||||
|
//
|
||||||
|
// Created by Sean Kim on 12/13/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
|
||||||
|
class AlertController: ObservableObject {
|
||||||
|
@Published var showAlert: Bool = false
|
||||||
|
var alertData: AlertData = .init(body: "")
|
||||||
|
|
||||||
|
let alertAction = CurrentValueSubject<String?, Never>(nil)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
struct AlertData {
|
struct AlertData {
|
||||||
var title: String
|
var title: String
|
||||||
|
@ -24,3 +25,62 @@ struct ButtonType {
|
||||||
var role: ButtonRole?
|
var role: ButtonRole?
|
||||||
var function: (()->())?
|
var function: (()->())?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct SetAlertData {
|
||||||
|
func setTest() -> AlertData {
|
||||||
|
return AlertData(title: "TEST", body: "TEST용 알럿 입니다.",
|
||||||
|
button: [
|
||||||
|
ButtonType(name: "테스트", role: .cancel,
|
||||||
|
function: {
|
||||||
|
printLog("테스트 중입니다.")
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// 강제 업데이트 안내
|
||||||
|
func setForceUpdate(action: CurrentValueSubject<String?, Never>) -> AlertData {
|
||||||
|
return AlertData(title: "업데이트 안내",
|
||||||
|
body: """
|
||||||
|
앱 최신 버전이 나왔습니다.
|
||||||
|
앱을 종료 후 업데이트를 해주세요.
|
||||||
|
""",
|
||||||
|
button: [
|
||||||
|
ButtonType(name: "업데이트 하기", role: .none,
|
||||||
|
function: {
|
||||||
|
action.send("updateNow")
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 선택 업데이트 안내
|
||||||
|
func setSelectUpdate (action: CurrentValueSubject<String?, Never>) -> AlertData {
|
||||||
|
return AlertData(title: "업데이트 안내",
|
||||||
|
body: """
|
||||||
|
앱 최신 버전이 새로 나왔습니다.
|
||||||
|
지금 업데이트 하시겠어요?
|
||||||
|
""",
|
||||||
|
button: [
|
||||||
|
ButtonType(name: "지금 업데이트", role: .cancel,
|
||||||
|
function: {
|
||||||
|
action.send("updateNow")
|
||||||
|
}),
|
||||||
|
ButtonType(name: "나중에", role: .none,
|
||||||
|
function: {
|
||||||
|
action.send("updateLater")
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 네트워크 문제 발생 시
|
||||||
|
func setErrorNetwork() -> AlertData {
|
||||||
|
return AlertData(title: "네트워크 오류", body: "네트워크가 불안정합니다.\n확인 후 다시 시도해주세요.",
|
||||||
|
button: [
|
||||||
|
ButtonType(name: "확인", role: .cancel, function: nil)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
47
AcaMate/4. Model/Navigation.swift
Normal file
47
AcaMate/4. Model/Navigation.swift
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// Navigation.swift
|
||||||
|
// AcaMate
|
||||||
|
//
|
||||||
|
// Created by Sean Kim on 12/13/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct NaviState: Equatable {
|
||||||
|
var act: NaviAction
|
||||||
|
var path: PathName
|
||||||
|
|
||||||
|
static func == (lhs: NaviState, rhs: NaviState) -> Bool {
|
||||||
|
return lhs.act == rhs.act && lhs.path == rhs.path
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func set(act: NaviAction = .ADD, path: PathName = .NONE) {
|
||||||
|
self.act = act
|
||||||
|
self.path = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NaviAction: Hashable {
|
||||||
|
/// 한 계층 추가
|
||||||
|
case ADD
|
||||||
|
/// 전체 삭제 후 추가
|
||||||
|
case RESET
|
||||||
|
/// 한 계층 빼기
|
||||||
|
case POP
|
||||||
|
/// 위치로 이동 (path == NONE인 경우 최상단)
|
||||||
|
case MOVE
|
||||||
|
/// 아무 동작 안함
|
||||||
|
case NONE
|
||||||
|
|
||||||
|
/// FIRST = 가장 메인, MOVE = 그곳으로
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PathName: Hashable {
|
||||||
|
case Intro
|
||||||
|
case Login
|
||||||
|
case Main
|
||||||
|
|
||||||
|
case NONE
|
||||||
|
}
|
27
AcaMate/5. Modifier/Network.swift
Normal file
27
AcaMate/5. Modifier/Network.swift
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// Network.swift
|
||||||
|
// AcaMate
|
||||||
|
//
|
||||||
|
// Created by Sean Kim on 12/10/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Network
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
class NetworkMonitor: ObservableObject {
|
||||||
|
static let shared = NetworkMonitor()
|
||||||
|
|
||||||
|
private let monitor = NWPathMonitor()
|
||||||
|
private let queue = DispatchQueue.global(qos: .background)
|
||||||
|
|
||||||
|
@Published var isConnected: Bool = true
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
monitor.pathUpdateHandler = { [weak self] path in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self?.isConnected = (path.status == .satisfied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
monitor.start(queue: queue)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,15 +6,66 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
struct NetworkModifier: ViewModifier {
|
||||||
|
@ObservedObject private var networkMonitor = NetworkMonitor.shared
|
||||||
|
@EnvironmentObject var alertController: AlertController
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.onChange(of: networkMonitor.isConnected) { _ , new in
|
||||||
|
if !new {
|
||||||
|
alertController.alertData = SetAlertData().setErrorNetwork()
|
||||||
|
alertController.showAlert.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AlertModifier: ViewModifier {
|
||||||
|
@EnvironmentObject var controller: AlertController
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.alert(controller.alertData.title,
|
||||||
|
isPresented: $controller.showAlert,
|
||||||
|
presenting: $controller.alertData) { data in
|
||||||
|
let btnCount = data.button.count
|
||||||
|
ForEach(0 ..< btnCount, id: \.self) { index in
|
||||||
|
let btn = data.wrappedValue.button[index]
|
||||||
|
Button(role: btn.role) {
|
||||||
|
if let function = btn.function { function() }
|
||||||
|
} label: {
|
||||||
|
Text("\(btn.name)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} message: { data in
|
||||||
|
Text("\(data.body.wrappedValue)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
func fullView(_ backColor: Color) -> some View{
|
func fullView(_ backColor: Color) -> some View {
|
||||||
return self
|
return self
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.background(backColor)
|
.background(backColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setAlert() -> some View {
|
||||||
|
self.modifier(AlertModifier())
|
||||||
|
}
|
||||||
|
func setNetwork() -> some View {
|
||||||
|
self.modifier(NetworkModifier())
|
||||||
|
}
|
||||||
|
|
||||||
func endTextEditing() {
|
func endTextEditing() {
|
||||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setNavigaion() -> some View {
|
||||||
|
self
|
||||||
|
.navigationBarBackButtonHidden(true)
|
||||||
|
.toolbar(.hidden, for: .navigationBar)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import KakaoSDKAuth
|
||||||
struct AcaMateApp: App {
|
struct AcaMateApp: App {
|
||||||
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||||
var cancellables: Set<AnyCancellable> = []
|
var cancellables: Set<AnyCancellable> = []
|
||||||
|
var alertController = AlertController()
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
printLog("APP INIT")
|
printLog("APP INIT")
|
||||||
|
@ -22,11 +23,13 @@ struct AcaMateApp: App {
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
IntroView().onOpenURL { url in
|
NavigationView()
|
||||||
if (AuthApi.isKakaoTalkLoginUrl(url)) {
|
.onOpenURL { url in
|
||||||
_ = AuthController.handleOpenUrl(url: url)
|
if (AuthApi.isKakaoTalkLoginUrl(url)) {
|
||||||
|
_ = AuthController.handleOpenUrl(url: url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.environmentObject(self.alertController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user