forked from AcaMate/AcaMate_iOS
Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
9036502207 | |||
2aaa179a19 | |||
c8c2aa095a | |||
eef7d01d8c | |||
584ab61cdc |
|
@ -14,7 +14,6 @@
|
||||||
A78774722CF586AF002FE2EE /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = A78774712CF586AF002FE2EE /* Alamofire */; };
|
A78774722CF586AF002FE2EE /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = A78774712CF586AF002FE2EE /* Alamofire */; };
|
||||||
A7A518CF2CF555E200822D0D /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = A7A518CE2CF555E200822D0D /* README.md */; };
|
A7A518CF2CF555E200822D0D /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = A7A518CE2CF555E200822D0D /* README.md */; };
|
||||||
A7A518D12CF5588500822D0D /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = A7A518D02CF5588500822D0D /* .gitignore */; };
|
A7A518D12CF5588500822D0D /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = A7A518D02CF5588500822D0D /* .gitignore */; };
|
||||||
FB0119D32D62EEF000C1FA82 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = FB0119D22D62EEF000C1FA82 /* Starscream */; };
|
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
@ -54,7 +53,6 @@
|
||||||
A73892252D526A9D00659A62 /* FirebaseCrashlytics in Frameworks */,
|
A73892252D526A9D00659A62 /* FirebaseCrashlytics in Frameworks */,
|
||||||
A771FFF22CFB70D100367DA6 /* KakaoSDK in Frameworks */,
|
A771FFF22CFB70D100367DA6 /* KakaoSDK in Frameworks */,
|
||||||
A73892212D526A9D00659A62 /* FirebaseAnalytics in Frameworks */,
|
A73892212D526A9D00659A62 /* FirebaseAnalytics in Frameworks */,
|
||||||
FB0119D32D62EEF000C1FA82 /* Starscream in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -104,7 +102,6 @@
|
||||||
A73892202D526A9D00659A62 /* FirebaseAnalytics */,
|
A73892202D526A9D00659A62 /* FirebaseAnalytics */,
|
||||||
A73892222D526A9D00659A62 /* FirebaseAppCheck */,
|
A73892222D526A9D00659A62 /* FirebaseAppCheck */,
|
||||||
A73892242D526A9D00659A62 /* FirebaseCrashlytics */,
|
A73892242D526A9D00659A62 /* FirebaseCrashlytics */,
|
||||||
FB0119D22D62EEF000C1FA82 /* Starscream */,
|
|
||||||
);
|
);
|
||||||
productName = AcaMate;
|
productName = AcaMate;
|
||||||
productReference = A7A518BB2CF5558B00822D0D /* AcaMate.app */;
|
productReference = A7A518BB2CF5558B00822D0D /* AcaMate.app */;
|
||||||
|
@ -138,7 +135,6 @@
|
||||||
A78774702CF586AF002FE2EE /* XCRemoteSwiftPackageReference "Alamofire" */,
|
A78774702CF586AF002FE2EE /* XCRemoteSwiftPackageReference "Alamofire" */,
|
||||||
A771FFF02CFB70D100367DA6 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */,
|
A771FFF02CFB70D100367DA6 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */,
|
||||||
A738921F2D526A9D00659A62 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
|
A738921F2D526A9D00659A62 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
|
||||||
FB0119D12D62EEF000C1FA82 /* XCRemoteSwiftPackageReference "Starscream" */,
|
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 77;
|
preferredProjectObjectVersion = 77;
|
||||||
productRefGroup = A7A518BC2CF5558B00822D0D /* Products */;
|
productRefGroup = A7A518BC2CF5558B00822D0D /* Products */;
|
||||||
|
@ -317,7 +313,7 @@
|
||||||
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent;
|
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -328,7 +324,7 @@
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited) DEV LOCAL";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited) DEV";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = 1;
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
|
@ -359,7 +355,7 @@
|
||||||
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent;
|
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -424,14 +420,6 @@
|
||||||
minimumVersion = 5.10.2;
|
minimumVersion = 5.10.2;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
FB0119D12D62EEF000C1FA82 /* XCRemoteSwiftPackageReference "Starscream" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/daltoniam/Starscream";
|
|
||||||
requirement = {
|
|
||||||
kind = upToNextMajorVersion;
|
|
||||||
minimumVersion = 4.0.8;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
@ -460,11 +448,6 @@
|
||||||
package = A78774702CF586AF002FE2EE /* XCRemoteSwiftPackageReference "Alamofire" */;
|
package = A78774702CF586AF002FE2EE /* XCRemoteSwiftPackageReference "Alamofire" */;
|
||||||
productName = Alamofire;
|
productName = Alamofire;
|
||||||
};
|
};
|
||||||
FB0119D22D62EEF000C1FA82 /* Starscream */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = FB0119D12D62EEF000C1FA82 /* XCRemoteSwiftPackageReference "Starscream" */;
|
|
||||||
productName = Starscream;
|
|
||||||
};
|
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = A7A518B32CF5558B00822D0D /* Project object */;
|
rootObject = A7A518B32CF5558B00822D0D /* Project object */;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"originHash" : "2aab34be4ec6f8de8e42f37bee06d0f181ea2ab2db742d0c279bace3d5a0bcfb",
|
"originHash" : "3b609245b8d633048f6670834279f82d0601cc0879a2d8c9c86fa0dd25734ea3",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "abseil-cpp-binary",
|
"identity" : "abseil-cpp-binary",
|
||||||
|
@ -127,15 +127,6 @@
|
||||||
"version" : "2.4.0"
|
"version" : "2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"identity" : "starscream",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/daltoniam/Starscream",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "c6bfd1af48efcc9a9ad203665db12375ba6b145a",
|
|
||||||
"version" : "4.0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"identity" : "swift-protobuf",
|
"identity" : "swift-protobuf",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -17,6 +17,9 @@ import FirebaseCore
|
||||||
|
|
||||||
|
|
||||||
class AppDelegate: NSObject, UIApplicationDelegate {
|
class AppDelegate: NSObject, UIApplicationDelegate {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
printLog("Start Set AppDelegate")
|
printLog("Start Set AppDelegate")
|
||||||
|
|
||||||
|
@ -66,6 +69,8 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppDelegate: UNUserNotificationCenterDelegate {
|
extension AppDelegate: UNUserNotificationCenterDelegate {
|
||||||
|
|
||||||
|
//
|
||||||
func registerForRemoteNotifications() {
|
func registerForRemoteNotifications() {
|
||||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
|
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
|
||||||
print("Permission granted: \(granted)")
|
print("Permission granted: \(granted)")
|
||||||
|
@ -83,9 +88,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
|
||||||
// 디바이스 토큰 등록 성공 시
|
// 디바이스 토큰 등록 성공 시
|
||||||
func application(_ application: UIApplication,didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
func application(_ application: UIApplication,didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||||
let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02.2hX", $1)})
|
let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02.2hX", $1)})
|
||||||
@UserDefault(key: "pushToken", defaultValue: "pushToken") var pushToken
|
printLog("APNs 디바이스 토큰: \(deviceTokenString)")
|
||||||
pushToken = deviceTokenString
|
|
||||||
printLog("APNs 디바이스 푸시 토큰: \(deviceTokenString)")
|
|
||||||
// 서버로 디바이스 토큰 전달 로직 추가
|
// 서버로 디바이스 토큰 전달 로직 추가
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +100,6 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
|
||||||
// 앱 켜져있을때 알럿 받으면 직접 로컬로 알림 띄워주는 곳
|
// 앱 켜져있을때 알럿 받으면 직접 로컬로 알림 띄워주는 곳
|
||||||
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
|
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
|
||||||
let userInfo = notification.request.content.userInfo
|
let userInfo = notification.request.content.userInfo
|
||||||
printLog(userInfo)
|
|
||||||
|
|
||||||
if let apsData = userInfo["aps"] as? [AnyHashable: Any],
|
if let apsData = userInfo["aps"] as? [AnyHashable: Any],
|
||||||
let badge = apsData["badge"] as? Int {
|
let badge = apsData["badge"] as? Int {
|
||||||
|
@ -109,18 +111,6 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let bid = userInfo["bid"] as? String {
|
|
||||||
printLog("bid = \(bid)")
|
|
||||||
}
|
|
||||||
|
|
||||||
if let content = userInfo["content"] as? String {
|
|
||||||
printLog("content = \(content)")
|
|
||||||
}
|
|
||||||
|
|
||||||
if let pid = userInfo["pid"] as? String {
|
|
||||||
printLog("pid = \(pid)")
|
|
||||||
}
|
|
||||||
|
|
||||||
if #available(iOS 14.0, *) {
|
if #available(iOS 14.0, *) {
|
||||||
return [[.list,.banner,.sound]]
|
return [[.list,.banner,.sound]]
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -7,41 +7,15 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
// MARK: - ACAMATE
|
// MARK: - ACAMATE
|
||||||
// APPSTORE_URL : https://apps.apple.com/us/app/%EC%95%84%EC%B9%B4%EB%8D%B0%EB%AF%B8%EB%A9%94%EC%9D%B4%ED%8A%B8/id6739448113
|
// APPSTORE_URL : https://apps.apple.com/us/app/%EC%95%84%EC%B9%B4%EB%8D%B0%EB%AF%B8%EB%A9%94%EC%9D%B4%ED%8A%B8/id6739448113
|
||||||
/// 주의사항
|
//#if DEV && LOCAL
|
||||||
/// plist 에서 http 설정 걸려있는거 지워야 함
|
|
||||||
#if LOCAL
|
|
||||||
public let API_URL: String = "http://10.149.217.64:5144"
|
|
||||||
//public let API_URL: String = "http://localhost:5144"
|
//public let API_URL: String = "http://localhost:5144"
|
||||||
//public let API_URL: String = "https://localhost:7086"
|
//#else
|
||||||
public let WS_URL: String = "ws://localhost:5144"
|
#if DEV
|
||||||
|
|
||||||
/// 회사 맥에서 사용할 경우의 URL
|
|
||||||
//public let WS_URL: String = "ws://10.149.217.64:5144"
|
|
||||||
|
|
||||||
/// 집 맥에서 사용할 경우의 URL
|
|
||||||
//public let WS_URL: String = "ws://192.168.0.71:5144"
|
|
||||||
//ipconfig getifaddr en0 이거는 와이파이 주소 알아내는거임
|
|
||||||
|
|
||||||
#elseif DEV
|
|
||||||
public let API_URL: String = "https://devacamate.ipstein.myds.me"
|
public let API_URL: String = "https://devacamate.ipstein.myds.me"
|
||||||
/// 서버용 웹소켓
|
|
||||||
//public let WS_URL: String = "ws://ipstein.myds.me:7004"
|
|
||||||
|
|
||||||
/// 회사 맥에서 사용할 경우의 URL
|
|
||||||
public let WS_URL: String = "ws://10.149.217.64:5144"
|
|
||||||
|
|
||||||
/// 집 맥에서 사용할 경우의 URL
|
|
||||||
//public let WS_URL: String = "ws://192.168.0.71:5144"
|
|
||||||
//ipconfig getifaddr en0 이거는 와이파이 주소 알아내는거임
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
public let API_URL: String = "https://acamate.ipstein.myds.me"
|
public let API_URL: String = "https://acamate.ipstein.myds.me"
|
||||||
public let WS_URL: String = "wss://acamate.ipstein.myds.me"
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public let API_HEADER = "iOS_AM_Connect_Key"
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: - TYPEALIAS
|
// MARK: - TYPEALIAS
|
||||||
typealias VOID_TO_VOID = () -> ()
|
typealias VOID_TO_VOID = () -> ()
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
//
|
|
||||||
// WebView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 3/24/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import WebKit
|
|
||||||
|
|
||||||
struct WebView: UIViewControllerRepresentable {
|
|
||||||
|
|
||||||
|
|
||||||
var url: URL
|
|
||||||
@Binding var showWebView: Bool
|
|
||||||
@Binding var isLoading: Bool
|
|
||||||
var complete: ((Any) -> Void)?
|
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator {
|
|
||||||
return Coordinator(parent: self)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
|
|
||||||
var parent: WebView
|
|
||||||
|
|
||||||
init(parent: WebView) {
|
|
||||||
self.parent = parent
|
|
||||||
}
|
|
||||||
|
|
||||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
||||||
print("웹뷰 로딩 완료")
|
|
||||||
parent.isLoading = false
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func userContentController(_ userContentController: WKUserContentController,
|
|
||||||
didReceive message: WKScriptMessage) {
|
|
||||||
if let data = message.body as? [String: Any]
|
|
||||||
{
|
|
||||||
if let road = data["roadAddress"], let code = data["zonecode"] {
|
|
||||||
printLog("도로명 주소: \(road) // ZIP CODE: \(code)")
|
|
||||||
parent.complete?((road,code))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parent.showWebView.toggle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: WebViewController, context: Context) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> WebViewController {
|
|
||||||
return WebViewController(url: url, isLoading: $isLoading,
|
|
||||||
complete: complete, coordinator: context.coordinator)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class WebViewController: UIViewController, WKUIDelegate {
|
|
||||||
|
|
||||||
var url: URL
|
|
||||||
@Binding var isLoading: Bool
|
|
||||||
var complete: ((Any) -> Void)?
|
|
||||||
var coordinator: WebView.Coordinator
|
|
||||||
|
|
||||||
init(url: URL, isLoading: Binding<Bool>, complete: ((Any) -> Void)?, coordinator: WebView.Coordinator) {
|
|
||||||
self.url = url
|
|
||||||
_isLoading = isLoading
|
|
||||||
self.complete = complete
|
|
||||||
self.coordinator = coordinator
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
webView()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func webView() {
|
|
||||||
let request = URLRequest(url: url)
|
|
||||||
|
|
||||||
let configuration = WKWebViewConfiguration()
|
|
||||||
let contentController = WKUserContentController()
|
|
||||||
|
|
||||||
contentController.add(coordinator, name: "callBackHandler")
|
|
||||||
configuration.userContentController = contentController
|
|
||||||
|
|
||||||
let webview = WKWebView(frame: view.bounds, configuration: configuration)
|
|
||||||
webview.uiDelegate = self
|
|
||||||
webview.navigationDelegate = coordinator
|
|
||||||
webview.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
||||||
|
|
||||||
webview.isUserInteractionEnabled = true
|
|
||||||
webview.scrollView.isUserInteractionEnabled = true
|
|
||||||
webview.scrollView.delaysContentTouches = false
|
|
||||||
|
|
||||||
webview.load(request)
|
|
||||||
view.addSubview(webview)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,6 +20,11 @@ struct BoxBtnView<Content: View>: View {
|
||||||
action()
|
action()
|
||||||
} label: {
|
} label: {
|
||||||
content
|
content
|
||||||
|
// if let image = image {
|
||||||
|
// image
|
||||||
|
// .resizable()
|
||||||
|
// .frame(width: width, height: height)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,17 +35,18 @@ struct CircleBtnView: View {
|
||||||
.foregroundStyle(state.foreColor)
|
.foregroundStyle(state.foreColor)
|
||||||
.frame(width: state.width/2, height: state.height/2)
|
.frame(width: state.width/2, height: state.height/2)
|
||||||
}
|
}
|
||||||
if let title = state.text, let font = state.font {
|
if let title = state.title, let font = state.font {
|
||||||
Text("\(title)")
|
Text("\(title)")
|
||||||
.font(font)
|
.font(font)
|
||||||
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.5)
|
||||||
|
.truncationMode(.tail)
|
||||||
.foregroundStyle(state.foreColor)
|
.foregroundStyle(state.foreColor)
|
||||||
.multilineStyle()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: state.width, height: state.height)
|
.frame(width: state.width, height: state.height)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
endTextEditing()
|
|
||||||
guard let action = state.action else {return}
|
guard let action = state.action else {return}
|
||||||
action()
|
action()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,15 +15,17 @@ struct SimpleBtnView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if let state = vm.btnStates[id] {
|
if let state = vm.btnStates[id] {
|
||||||
if let title = state.text, let font = state.font {
|
if let title = state.title, let font = state.font {
|
||||||
Text("\(title)")
|
Text("\(title)")
|
||||||
.font(font)
|
.font(font)
|
||||||
.multilineStyle(.center)
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.5)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.truncationMode(.tail)
|
||||||
.foregroundStyle(state.textColor)
|
.foregroundStyle(state.textColor)
|
||||||
.frame(width: state.width, height: state.height)
|
.frame(width: state.width, height: state.height)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if state.isUsable {
|
if state.isUsable {
|
||||||
endTextEditing()
|
|
||||||
guard let action = state.action else { return }
|
guard let action = state.action else { return }
|
||||||
action()
|
action()
|
||||||
}
|
}
|
||||||
|
@ -31,7 +33,6 @@ struct SimpleBtnView: View {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Button{
|
Button{
|
||||||
endTextEditing()
|
|
||||||
guard let action = state.action else { return }
|
guard let action = state.action else { return }
|
||||||
action()
|
action()
|
||||||
} label: {
|
} label: {
|
||||||
|
|
|
@ -1,165 +0,0 @@
|
||||||
//
|
|
||||||
// DropDownView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 3/25/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
class DropdownManager: ObservableObject {
|
|
||||||
@Published var isPresented = false
|
|
||||||
@Published var items: [String] = []
|
|
||||||
@Published var anchor: CGRect = .zero
|
|
||||||
@Published var font: Font = .body
|
|
||||||
|
|
||||||
var onSelect: ((Int) -> Void)?
|
|
||||||
|
|
||||||
func show(items: [String], anchor: CGRect,
|
|
||||||
onSelect: @escaping (Int) -> Void) {
|
|
||||||
self.items = items
|
|
||||||
self.anchor = anchor
|
|
||||||
self.onSelect = onSelect
|
|
||||||
isPresented = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func dismiss() {
|
|
||||||
isPresented = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// .coordinateSpace(name: "dropdownArea") 이거 지정을 해야 함
|
|
||||||
/// GlobalDropdownOverlay(manager: dropdownManager) 이거도 ZStack으로 넣어야 함
|
|
||||||
struct DropdownButton: View {
|
|
||||||
@ObservedObject var manager: DropdownManager
|
|
||||||
@State var title: String
|
|
||||||
|
|
||||||
var onSelect: ((Int) -> Void)?
|
|
||||||
let items: [String]
|
|
||||||
|
|
||||||
|
|
||||||
init(manager: DropdownManager, title: String, items: [String],
|
|
||||||
onSelect: @escaping (Int) -> Void) {
|
|
||||||
self.manager = manager
|
|
||||||
self.title = title
|
|
||||||
self.items = items
|
|
||||||
self.onSelect = onSelect
|
|
||||||
}
|
|
||||||
|
|
||||||
@State private var frame: CGRect = .zero
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Button {
|
|
||||||
endTextEditing()
|
|
||||||
manager.show(items: items, anchor: frame) { index in
|
|
||||||
self.onSelect?(index)
|
|
||||||
print("선택한 항목: \(items[index])")
|
|
||||||
title = items[index]
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
HStack(alignment: .center) {
|
|
||||||
Spacer()
|
|
||||||
Text(title)
|
|
||||||
.font(manager.font)
|
|
||||||
.lineLimit(1)
|
|
||||||
.minimumScaleFactor(0.5)
|
|
||||||
Image(systemName: /*manager.isPresented ? "chevron.up" : */ "chevron.down")
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 8, height: 4)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding(4)
|
|
||||||
.background{
|
|
||||||
RoundedRectangle(cornerRadius: 8)
|
|
||||||
.fill(Color.clear)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
.background(
|
|
||||||
GeometryReader { geo in
|
|
||||||
Color.clear
|
|
||||||
.onAppear {
|
|
||||||
frame = geo.frame(in: .named("dropdownArea"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct GlobalDropdownOverlay: View {
|
|
||||||
@ObservedObject var manager: DropdownManager
|
|
||||||
|
|
||||||
@State private var scrollOffset: CGPoint = .zero
|
|
||||||
|
|
||||||
private var maxVisibleItemCount: Int {
|
|
||||||
if manager.items.count > 5 {
|
|
||||||
return 5
|
|
||||||
} else { return manager.items.count }
|
|
||||||
}
|
|
||||||
private let itemHeight: CGFloat = 42
|
|
||||||
|
|
||||||
private var heightForDropBox: CGFloat {
|
|
||||||
CGFloat(maxVisibleItemCount) * itemHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
if manager.isPresented {
|
|
||||||
ZStack(alignment: .topLeading) {
|
|
||||||
Color.black.opacity(0.1)
|
|
||||||
.ignoresSafeArea()
|
|
||||||
.onTapGesture {
|
|
||||||
manager.dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
if manager.items.count > maxVisibleItemCount {
|
|
||||||
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { _ in
|
|
||||||
dropdownContent()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dropdownContent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(width: manager.anchor.width, height: heightForDropBox)
|
|
||||||
.background{
|
|
||||||
RoundedRectangle(cornerRadius: 8)
|
|
||||||
.fill(Color(.Normal.normal))
|
|
||||||
.stroke(Color(.Second.normal))
|
|
||||||
.strokeBorder(lineWidth: 2)
|
|
||||||
}
|
|
||||||
.position(x: manager.anchor.midX,
|
|
||||||
y: manager.anchor.maxY+heightForDropBox/2)
|
|
||||||
.animation(.easeOut(duration: 0.25), value: manager.isPresented)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private func dropdownContent() -> some View {
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
|
||||||
ForEach(Array(manager.items.enumerated()), id: \.offset) { index, item in
|
|
||||||
Button {
|
|
||||||
manager.onSelect?(index)
|
|
||||||
manager.dismiss()
|
|
||||||
} label: {
|
|
||||||
Text(item)
|
|
||||||
.font(manager.font)
|
|
||||||
.lineLimit(1)
|
|
||||||
.minimumScaleFactor(0.4)
|
|
||||||
.padding([.leading, .trailing], 8)
|
|
||||||
.frame(maxWidth: .infinity, minHeight: 40, maxHeight: 40, alignment: .center)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
|
|
||||||
if index < manager.items.count - 1 {
|
|
||||||
Rectangle()
|
|
||||||
.fill(Color(.Second.normal).opacity(0.3))
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,31 +12,26 @@ import Combine
|
||||||
/// 이거 위에 다른 뷰들이 위젯 느낌으로 계속 갈아 끼워지는거
|
/// 이거 위에 다른 뷰들이 위젯 느낌으로 계속 갈아 끼워지는거
|
||||||
struct NavigationView: View {
|
struct NavigationView: View {
|
||||||
@EnvironmentObject var appVM: AppViewModel
|
@EnvironmentObject var appVM: AppViewModel
|
||||||
|
@State private var naviState : NaviState = .init(act: .NONE, path: .Intro)
|
||||||
@State private var history: [PathName] = [.Intro]
|
@State private var history: [PathName] = [.Intro]
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
switch appVM.naviState.path {
|
switch naviState.path {
|
||||||
case .NONE:
|
case .NONE:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
case .Intro:
|
case .Intro:
|
||||||
IntroView(appVM)
|
IntroView(naviState: $naviState)
|
||||||
case .Login :
|
case .Login :
|
||||||
LoginView(appVM)
|
LoginView(naviState: $naviState)
|
||||||
case .Register(let type, let id):
|
|
||||||
RegisterView(appVM, type: type, snsID: id)
|
|
||||||
case .SelectAcademy:
|
|
||||||
SelectAcademyView(appVM)
|
|
||||||
case .Main:
|
case .Main:
|
||||||
MainView()
|
MainView(naviState: $naviState)
|
||||||
case .ChatRoom(let id):
|
|
||||||
ChattingRoomView(roomID: id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: appVM.naviState) { old, new in
|
.onChange(of: naviState) { old, new in
|
||||||
switch new.act {
|
switch new.act {
|
||||||
case .NONE:
|
case .NONE:
|
||||||
break
|
break
|
||||||
|
@ -59,7 +54,6 @@ struct NavigationView: View {
|
||||||
.setAlert()
|
.setAlert()
|
||||||
.setNetwork()
|
.setNetwork()
|
||||||
.loadingView(isLoading: $appVM.isLoading)
|
.loadingView(isLoading: $appVM.isLoading)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 경로에 한 단계 추가
|
/// 경로에 한 단계 추가
|
||||||
|
@ -70,8 +64,7 @@ struct NavigationView: View {
|
||||||
/// 가장 가까운 경로 삭제
|
/// 가장 가까운 경로 삭제
|
||||||
private func popHistory() {
|
private func popHistory() {
|
||||||
history.removeLast()
|
history.removeLast()
|
||||||
appVM.naviState.set(act: .NONE, path: history.last ?? .NONE)
|
naviState.set(act: .NONE, path: history.last ?? .NONE)
|
||||||
// naviState.set(act: .NONE, path: history.last ?? .NONE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 경로 기록 전체 삭제
|
/// 경로 기록 전체 삭제
|
||||||
|
@ -83,20 +76,17 @@ struct NavigationView: View {
|
||||||
/// 경로의 최상단 지우고, 새로 이동하는 경로로 설정
|
/// 경로의 최상단 지우고, 새로 이동하는 경로로 설정
|
||||||
private func moveHistory(path: PathName) {
|
private func moveHistory(path: PathName) {
|
||||||
if path == .NONE {
|
if path == .NONE {
|
||||||
appVM.naviState.set(act: .RESET, path: history.first ?? .Main)
|
naviState.set(act: .RESET, path: history.first ?? .Main)
|
||||||
// naviState.set(act: .RESET, path: history.first ?? .Main)
|
|
||||||
}
|
}
|
||||||
if history.contains(path) {
|
if history.contains(path) {
|
||||||
let remove = history.count - history.firstIndex(of: path)! - 1
|
let remove = history.count - history.firstIndex(of: path)! - 1
|
||||||
history.removeLast(remove)
|
history.removeLast(remove)
|
||||||
if remove > 0 {
|
if remove > 0 {
|
||||||
appVM.naviState.set(act: .NONE, path: path)
|
naviState.set(act: .NONE, path: path)
|
||||||
// naviState.set(act: .NONE, path: path)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
appVM.naviState.set(act: .RESET, path: path)
|
naviState.set(act: .RESET, path: path)
|
||||||
// naviState.set(act: .RESET, path: path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showHistory() {
|
private func showHistory() {
|
||||||
|
|
|
@ -1,117 +1,117 @@
|
||||||
////
|
|
||||||
//// AccountLoginView.swift
|
|
||||||
//// AcaMate
|
|
||||||
////
|
|
||||||
//// Created by Sean Kim on 12/14/24.
|
|
||||||
////
|
|
||||||
//
|
//
|
||||||
//import SwiftUI
|
// AccountLoginView.swift
|
||||||
|
// AcaMate
|
||||||
//
|
//
|
||||||
//struct AccountLoginView: View {
|
// Created by Sean Kim on 12/14/24.
|
||||||
// @ObservedObject var loginVM.: LoginViewModel
|
|
||||||
// @Binding var userId: String
|
|
||||||
// @Binding var password: String
|
|
||||||
// @Binding var isSecure: Bool
|
|
||||||
// @Binding var isSave: Bool
|
|
||||||
// var body: some View {
|
|
||||||
// VStack(spacing: 0) {
|
|
||||||
// ZStack(alignment: .leading) {
|
|
||||||
// if userId.isEmpty {
|
|
||||||
// Text("아이디를 입력하세요.")
|
|
||||||
// .font(.nps(font: .regular, size: 16))
|
|
||||||
// .foregroundStyle(Color(.Text.border))
|
|
||||||
// .padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
|
|
||||||
// }
|
|
||||||
// CustomTextField(placeholder: "", text: $userId)
|
|
||||||
// .frame(maxWidth: .infinity,maxHeight: 24)
|
|
||||||
// .padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
|
|
||||||
// }
|
|
||||||
// .background {
|
|
||||||
// RoundedRectangle(cornerRadius: 24)
|
|
||||||
// .foregroundStyle(.white)
|
|
||||||
// }
|
|
||||||
// .padding(EdgeInsets(top: 0, leading: 12, bottom: 8, trailing: 12))
|
|
||||||
//
|
//
|
||||||
// ZStack(alignment: .leading) {
|
|
||||||
// if password.isEmpty {
|
import SwiftUI
|
||||||
// Text("비밀번호를 입력하세요.")
|
|
||||||
// .font(.nps(font: .regular, size: 16))
|
struct AccountLoginView: View {
|
||||||
// .foregroundStyle(Color(.Text.border))
|
@ObservedObject var viewModel: LoginViewModel
|
||||||
// .padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
|
@Binding var userId: String
|
||||||
// }
|
@Binding var password: String
|
||||||
// CustomTextField(placeholder: "", text: $password, isSecure: $isSecure)
|
@Binding var isSecure: Bool
|
||||||
// .frame(maxWidth: .infinity,maxHeight: 24)
|
@Binding var isSave: Bool
|
||||||
// .padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
|
var body: some View {
|
||||||
//
|
VStack(spacing: 0) {
|
||||||
// HStack {
|
ZStack(alignment: .leading) {
|
||||||
// Spacer()
|
if userId.isEmpty {
|
||||||
// Button {
|
Text("아이디를 입력하세요.")
|
||||||
// isSecure.toggle()
|
.font(.nps(font: .regular, size: 16))
|
||||||
// } label: {
|
.foregroundStyle(Color(.Text.border))
|
||||||
// if password.isEmpty {
|
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
|
||||||
// Rectangle()
|
}
|
||||||
// .frame(width: 16, height: 2)
|
CustomTextField(placeholder: "", text: $userId)
|
||||||
// .foregroundStyle(Color(.Text.border))
|
.frame(maxWidth: .infinity,maxHeight: 24)
|
||||||
// .padding(.trailing,24)
|
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
|
||||||
// }
|
}
|
||||||
// else {
|
.background {
|
||||||
// if isSecure {
|
RoundedRectangle(cornerRadius: 24)
|
||||||
// Image(systemName: "eye")
|
.foregroundStyle(.white)
|
||||||
// .frame(width: 16, height: 16)
|
}
|
||||||
// .foregroundStyle(Color(.Text.detail))
|
.padding(EdgeInsets(top: 0, leading: 12, bottom: 8, trailing: 12))
|
||||||
// .padding(.trailing,24)
|
|
||||||
// }
|
ZStack(alignment: .leading) {
|
||||||
// else {
|
if password.isEmpty {
|
||||||
// Image(systemName: "eye.slash")
|
Text("비밀번호를 입력하세요.")
|
||||||
// .frame(width: 16, height: 16)
|
.font(.nps(font: .regular, size: 16))
|
||||||
// .foregroundStyle(Color(.Text.detail))
|
.foregroundStyle(Color(.Text.border))
|
||||||
// .padding(.trailing,24)
|
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
|
||||||
//
|
}
|
||||||
// }
|
CustomTextField(placeholder: "", text: $password, isSecure: $isSecure)
|
||||||
// }
|
.frame(maxWidth: .infinity,maxHeight: 24)
|
||||||
// }
|
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
|
||||||
// }
|
|
||||||
// }
|
HStack {
|
||||||
// .background {
|
Spacer()
|
||||||
// RoundedRectangle(cornerRadius: 24)
|
Button {
|
||||||
// .foregroundStyle(.white)
|
isSecure.toggle()
|
||||||
// }
|
} label: {
|
||||||
// .padding(EdgeInsets(top: 0, leading: 12, bottom: 8, trailing: 12))
|
if password.isEmpty {
|
||||||
//
|
Rectangle()
|
||||||
// Button {
|
.frame(width: 16, height: 2)
|
||||||
// isSave.toggle()
|
.foregroundStyle(Color(.Text.border))
|
||||||
// } label: {
|
.padding(.trailing,24)
|
||||||
// HStack(alignment: .center, spacing: 4) {
|
}
|
||||||
// Spacer(minLength: 1)
|
else {
|
||||||
// if isSave {
|
if isSecure {
|
||||||
// Image(systemName: "checkmark.square")
|
Image(systemName: "eye")
|
||||||
// .foregroundStyle(Color(.Second.normal))
|
.frame(width: 16, height: 16)
|
||||||
// .frame(width: 24, height: 24)
|
.foregroundStyle(Color(.Text.detail))
|
||||||
// } else {
|
.padding(.trailing,24)
|
||||||
// Image(systemName: "square")
|
}
|
||||||
// .foregroundStyle(Color(.Second.normal))
|
else {
|
||||||
// .frame(width: 24, height: 24)
|
Image(systemName: "eye.slash")
|
||||||
// }
|
.frame(width: 16, height: 16)
|
||||||
//
|
.foregroundStyle(Color(.Text.detail))
|
||||||
// Text("로그인 정보 저장")
|
.padding(.trailing,24)
|
||||||
// .font(.nps(font: .regular, size: 16))
|
|
||||||
// .foregroundStyle(Color(.Text.detail))
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// .padding(EdgeInsets(top: 0, leading: 0, bottom: 24, trailing: 12))
|
}
|
||||||
//
|
}
|
||||||
// Button {
|
.background {
|
||||||
// loginVM..loginAction.send(true)
|
RoundedRectangle(cornerRadius: 24)
|
||||||
// } label: {
|
.foregroundStyle(.white)
|
||||||
// Text("로그인")
|
}
|
||||||
// .font(.nps(font: .bold, size: 24))
|
.padding(EdgeInsets(top: 0, leading: 12, bottom: 8, trailing: 12))
|
||||||
// .foregroundStyle(Color(.Text.white))
|
|
||||||
// .padding(EdgeInsets(top: 8, leading: 48, bottom: 8, trailing: 48))
|
Button {
|
||||||
// .background{
|
isSave.toggle()
|
||||||
// RoundedRectangle(cornerRadius: 12)
|
} label: {
|
||||||
// .foregroundStyle(Color(.Second.normal))
|
HStack(alignment: .center, spacing: 4) {
|
||||||
// }
|
Spacer(minLength: 1)
|
||||||
// }
|
if isSave {
|
||||||
// }
|
Image(systemName: "checkmark.square")
|
||||||
// }
|
.foregroundStyle(Color(.Second.normal))
|
||||||
//}
|
.frame(width: 24, height: 24)
|
||||||
|
} else {
|
||||||
|
Image(systemName: "square")
|
||||||
|
.foregroundStyle(Color(.Second.normal))
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("로그인 정보 저장")
|
||||||
|
.font(.nps(font: .regular, size: 16))
|
||||||
|
.foregroundStyle(Color(.Text.detail))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(EdgeInsets(top: 0, leading: 0, bottom: 24, trailing: 12))
|
||||||
|
|
||||||
|
Button {
|
||||||
|
viewModel.loginAction.send(true)
|
||||||
|
} label: {
|
||||||
|
Text("로그인")
|
||||||
|
.font(.nps(font: .bold, size: 24))
|
||||||
|
.foregroundStyle(Color(.Text.white))
|
||||||
|
.padding(EdgeInsets(top: 8, leading: 48, bottom: 8, trailing: 48))
|
||||||
|
.background{
|
||||||
|
RoundedRectangle(cornerRadius: 12)
|
||||||
|
.foregroundStyle(Color(.Second.normal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,21 +8,17 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
|
|
||||||
struct IntroView: View {
|
struct IntroView: View {
|
||||||
@EnvironmentObject var appVM: AppViewModel
|
@EnvironmentObject var appVM: AppViewModel
|
||||||
@StateObject var introVM: IntroViewModel
|
|
||||||
@State var cancellables: Set<AnyCancellable> = []
|
|
||||||
|
|
||||||
init(_ appVM: AppViewModel) {
|
@State var cancellables: Set<AnyCancellable> = []
|
||||||
_introVM = StateObject(wrappedValue: IntroViewModel(appVM))
|
@Binding var naviState : NaviState
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
Spacer()
|
Spacer()
|
||||||
.frame(height: 100)
|
.frame(height: 100)
|
||||||
Image(.Logo.crystalIcon)
|
Image(.Logo.appIcon)
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 200, height: 200)
|
.frame(width: 200, height: 200)
|
||||||
Spacer()
|
Spacer()
|
||||||
|
@ -43,7 +39,92 @@ struct IntroView: View {
|
||||||
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
printLog("IntroView_onAppear")
|
printLog("IntroView_onAppear")
|
||||||
introVM.appStart()
|
#if LOCAL
|
||||||
|
naviState.set(act: .RESET, path: .Login)
|
||||||
|
#else
|
||||||
|
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 {
|
||||||
|
appVM.alertData = SetAlertData().setForceUpdate(
|
||||||
|
action: appVM.alertAction
|
||||||
|
)
|
||||||
|
appVM.showAlert.toggle()
|
||||||
|
} else if compareChoice == .bigger && version.choice_update_yn {
|
||||||
|
appVM.alertData = SetAlertData().setSelectUpdate(
|
||||||
|
action: appVM.alertAction
|
||||||
|
)
|
||||||
|
appVM.showAlert.toggle()
|
||||||
|
} else {
|
||||||
|
naviState.set(act: .RESET, path: .Login)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func subscribeAlertAction() {
|
||||||
|
appVM.alertAction
|
||||||
|
.compactMap { $0 }
|
||||||
|
.sink { action in
|
||||||
|
if action == "updateNow" {
|
||||||
|
exit(1)
|
||||||
|
//MARK: - TODO (앱스토어 이동 로직 넣을 것)
|
||||||
|
} else {
|
||||||
|
naviState.set(act: .RESET, path: .Login)
|
||||||
|
}
|
||||||
|
}.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func loadVersion() -> Future<VersionData, Error> {
|
||||||
|
return Future { promise in
|
||||||
|
loadAPIData(url: "\(API_URL)",
|
||||||
|
path: "/api/v1/in/app/version",
|
||||||
|
parameters: ["type":"I"],
|
||||||
|
decodingType: APIResponse<VersionData>.self)
|
||||||
|
.sink { completion in
|
||||||
|
switch completion {
|
||||||
|
case .failure(let error):
|
||||||
|
printLog("\(error)")
|
||||||
|
promise(.failure(error))
|
||||||
|
case .finished: break
|
||||||
|
}
|
||||||
|
} receiveValue: { data in
|
||||||
|
guard let apiData = data as? APIResponse<VersionData> else {return}
|
||||||
|
printLog("\(apiData.data.toStringDict())")
|
||||||
|
promise(.success(apiData.data))
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func currentVersion() -> String {
|
||||||
|
guard let currentVer = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { return "" }
|
||||||
|
return currentVer
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func versionChange(ver: String) -> [Int] {
|
||||||
|
return ver.components(separatedBy: ["."]).map {Int($0) ?? 0}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//#Preview {
|
||||||
|
// IntroView(path: $NavigationPath())
|
||||||
|
//}
|
||||||
|
|
|
@ -8,88 +8,110 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
|
|
||||||
struct LoginView: View {
|
struct LoginView: View {
|
||||||
@EnvironmentObject var appVM: AppViewModel
|
@EnvironmentObject var appVM: AppViewModel
|
||||||
@StateObject var loginVM: LoginViewModel
|
@StateObject private var loginVM = LoginViewModel()
|
||||||
|
@State var cancellables: Set<AnyCancellable> = []
|
||||||
|
@Binding var naviState : NaviState
|
||||||
|
|
||||||
init(_ appVM: AppViewModel) {
|
@State var selectIdLogin: Bool = false
|
||||||
_loginVM = StateObject(wrappedValue: LoginViewModel(appVM))
|
|
||||||
}
|
@State var userId: String = ""
|
||||||
|
@State var password: String = ""
|
||||||
|
@State var isSecure: Bool = true
|
||||||
|
@State var isSave: Bool = false
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
Spacer().frame(height: 100)
|
Spacer().frame(height: 100)
|
||||||
Image(.Logo.crystalIcon)
|
Image(.Logo.appIcon)
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 200, height: 200)
|
.frame(width: 200, height: 200)
|
||||||
// .padding(.top, 80)
|
// .padding(.top, 80)
|
||||||
.padding(.bottom, 84)
|
.padding(.bottom, 84)
|
||||||
|
|
||||||
/// 앱 아이콘 이미지
|
/// 앱 아이콘 이미지
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
Button {
|
Button {
|
||||||
// MARK: - TODO, 카카오 계정 로그인 구현
|
// MARK: - TODO, 카카오 계정 로그인 구현
|
||||||
// loginVM.toggleLoading = true
|
appVM.isLoading.toggle()
|
||||||
loginVM.loginAction(type: .Kakao)
|
loginAction(type: .Kakao)
|
||||||
|
|
||||||
} label: {
|
} label: {
|
||||||
makeButton(image: Image(.Logo.kakaoIcon),color: Color(.Other.yellow), "카카오 계정으로 시작하기")
|
makeButton(image: Image(.Logo.kakaoIcon),color: Color(.Other.yellow), "카카오 계정으로 시작하기")
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
// MARK: - TODO, 애플 계정 로그인 구현
|
// MARK: - TODO, 애플 계정 로그인 구현
|
||||||
// appVM.naviState.set(act: .ADD, path: .SelectAcademy(bids: ["AA0000", "AA0001"]))
|
naviState.set(act: .MOVE, path: .Main)
|
||||||
// loginVM.toggleLoading = true
|
|
||||||
// loginVM.loginTest(type: .Kakao, id: "TestSNSID1@#")
|
|
||||||
// loginVM.USERPAITEST()
|
|
||||||
|
|
||||||
|
|
||||||
} label: {
|
} label: {
|
||||||
makeButton(image: Image(.Logo.appleIcon), color: Color(.Text.black), "애플 계정으로 시작하기")
|
makeButton(image: Image(.Logo.appleIcon), color: Color(.Text.black), "애플 계정으로 시작하기")
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomTxfView(placeholder: "id",
|
|
||||||
text: $loginVM.devId,
|
|
||||||
alignment: .center,
|
|
||||||
font: .nps(size: 12))
|
|
||||||
.frame(maxWidth: .infinity,maxHeight: 48)
|
|
||||||
.padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 24)
|
|
||||||
.foregroundStyle(Color(.Normal.light))
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
// loginVM.toggleLoading = true
|
|
||||||
loginVM.loginAction(type: .Dev)
|
|
||||||
|
|
||||||
} label: {
|
|
||||||
makeButton(image: Image(.Logo.logo),
|
|
||||||
color: Color(.Text.black), "계정으로 시작하기")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.padding([.leading,.trailing], 28)
|
.padding([.leading,.trailing], 28)
|
||||||
|
|
||||||
Spacer(minLength: 1)
|
Spacer(minLength: 1)
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
|
||||||
|
Button {
|
||||||
|
// MARK: TO-DO
|
||||||
|
// 카카오 로그인 연동
|
||||||
|
naviState.set(act: .MOVE, path: .Main)
|
||||||
|
} label: {
|
||||||
|
HStack(spacing: 24) {
|
||||||
|
Image("Kakao_Icon")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 32, height: 32)
|
||||||
|
Text("카카오 계정으로 시작하기")
|
||||||
|
.font(.nps(font: .regular, size: 16))
|
||||||
|
.foregroundStyle(Color(.Text.black))
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(12)
|
||||||
|
.background {
|
||||||
|
RoundedRectangle(cornerRadius: 12)
|
||||||
|
.foregroundStyle(Color(.Other.yellow))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
/// KAKAO 로그인 버튼
|
||||||
|
|
||||||
|
Button {
|
||||||
|
// MARK: TO-DO
|
||||||
|
// 애플 로그인 연동
|
||||||
|
} label: {
|
||||||
|
HStack(spacing: 24) {
|
||||||
|
Image(systemName: "apple.logo")
|
||||||
|
.resizable()
|
||||||
|
.accentColor(Color(.Text.white))
|
||||||
|
.frame(width: 32, height: 32)
|
||||||
|
|
||||||
|
Text("애플 계정으로 시작하기")
|
||||||
|
.font(.nps(font: .regular, size: 16))
|
||||||
|
.foregroundStyle(Color(.Text.white))
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(12)
|
||||||
|
.background {
|
||||||
|
RoundedRectangle(cornerRadius: 12)
|
||||||
|
.foregroundStyle(Color(.Text.black))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
/// APPLE 로그인 버튼
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
subscribeLoginAction()
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity,maxHeight: .infinity)
|
.frame(maxWidth: .infinity,maxHeight: .infinity)
|
||||||
.fullDrawView(.Normal.normal)
|
.fullDrawView(.Normal.normal)
|
||||||
.onAppear() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// .onChange(of: loginVM.pathName){ _, new in
|
|
||||||
// appVM.naviState.set(act: .ADD, path: new)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// .onChange(of: loginVM.toggleLoading) { _, new in
|
|
||||||
// appVM.isLoading = new
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeButton(image: Image, color: Color? = nil, _ body: String) -> some View {
|
func makeButton(image: Image, color: Color? = nil, _ body: String) -> some View {
|
||||||
return HStack {
|
return HStack {
|
||||||
image
|
image
|
||||||
|
@ -109,4 +131,60 @@ struct LoginView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func subscribeLoginAction() {
|
||||||
|
loginVM.loginAction
|
||||||
|
.sink { isTapped in
|
||||||
|
if isTapped {
|
||||||
|
if userId.isEmpty || password.isEmpty {
|
||||||
|
appVM.alertData = SetAlertData().setErrorLogin()
|
||||||
|
appVM.showAlert.toggle()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
}
|
||||||
|
printLog("로그인")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loginAction(type: SNSLoginType) {
|
||||||
|
LoginController().login(type)
|
||||||
|
.flatMap{ snsId in
|
||||||
|
loadAPIData(url: "\(API_URL)",
|
||||||
|
path: "/api/v1/in/user/login",
|
||||||
|
parameters: [
|
||||||
|
"sns_id": "\(snsId.snsId)",
|
||||||
|
"acctype": "\(type == .Apple ? "ST00": "ST01")"
|
||||||
|
],
|
||||||
|
decodingType: APIResponse<User_Academy>.self)
|
||||||
|
|
||||||
|
}
|
||||||
|
.sink { completion in
|
||||||
|
switch completion {
|
||||||
|
case .failure(let error):
|
||||||
|
printLog("\(error)")
|
||||||
|
appVM.isLoading.toggle()
|
||||||
|
case .finished:
|
||||||
|
appVM.isLoading.toggle()
|
||||||
|
}
|
||||||
|
} receiveValue: { response in
|
||||||
|
guard let ua = response as? APIResponse<User_Academy> else {return}
|
||||||
|
if let bids = ua.data.toStringDict()["bid"] {
|
||||||
|
printLog(bids)
|
||||||
|
if let bidArray: [String] = jsonToSwift(bids) {
|
||||||
|
printLog(bidArray[0])
|
||||||
|
} else {
|
||||||
|
printLog("JSON 변환 실패")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#Preview {
|
||||||
|
// LoginView()
|
||||||
|
//}
|
||||||
|
|
|
@ -1,293 +0,0 @@
|
||||||
//
|
|
||||||
// RegisterView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/20/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct RegisterView: View {
|
|
||||||
@EnvironmentObject var appVM: AppViewModel
|
|
||||||
@StateObject private var topVM = TopViewModel()
|
|
||||||
@StateObject var btnVM = ButtonViewModel()
|
|
||||||
@StateObject var registerVM: RegisterViewModel
|
|
||||||
|
|
||||||
// private let responseValue: (SNSLoginType, String)
|
|
||||||
|
|
||||||
init(_ appVM: AppViewModel, type: SNSLoginType, snsID: String) {
|
|
||||||
_registerVM = StateObject(wrappedValue: RegisterViewModel(appVM, type: type, snsID: snsID))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@State private var scrollOffset: CGPoint = .zero
|
|
||||||
@State private var showWebView = false
|
|
||||||
@State private var isLoading = false
|
|
||||||
|
|
||||||
@State private var isSelectAddr: Bool = false
|
|
||||||
|
|
||||||
@StateObject private var dropdownManager = DropdownManager()
|
|
||||||
|
|
||||||
|
|
||||||
@State private var onDomainTxf = false
|
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 회원가입 뷰 만들기
|
|
||||||
// 이름, 번호, 이메일, 주소, 생년월일
|
|
||||||
ZStack {
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
TopView(topVM: topVM)
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
Text("*")
|
|
||||||
.font(.nps(size: 12))
|
|
||||||
.foregroundStyle(Color(.Other.red))
|
|
||||||
Text("는 필수 입력 사항입니다.")
|
|
||||||
.font(.nps(size: 12))
|
|
||||||
}
|
|
||||||
.padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 16))
|
|
||||||
|
|
||||||
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
|
|
||||||
|
|
||||||
// 이름
|
|
||||||
HStack(spacing: 0){
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Text("이름")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
Text("*")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
.foregroundStyle(Color(.Other.red))
|
|
||||||
}
|
|
||||||
.frame(width: 60, alignment: .center)
|
|
||||||
.padding(.trailing,12)
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
CustomTxfView(placeholder: "이름 입력 (10 글자)", text: $registerVM.nameText, maxLength: 10, alignment: .center)
|
|
||||||
.frame(maxWidth: .infinity,maxHeight: 48)
|
|
||||||
.padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 24)
|
|
||||||
.foregroundStyle(Color(.Normal.light))
|
|
||||||
}.clipped()
|
|
||||||
}
|
|
||||||
.padding(EdgeInsets(top: 8, leading: 16, bottom: 16, trailing: 16))
|
|
||||||
|
|
||||||
// 생년월일
|
|
||||||
HStack(spacing: 0){
|
|
||||||
Text("생일")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
.frame(width: 60, alignment: .center)
|
|
||||||
.padding(.trailing,12)
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
DatePicker("", selection: $registerVM.selectDate, displayedComponents: [.date])
|
|
||||||
.datePickerStyle(.compact)
|
|
||||||
.environment(\.locale, Locale(identifier: "ko_KR"))
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
// E-Mail
|
|
||||||
HStack(spacing: 0){
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Text("이메일")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
Text("*")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
.foregroundStyle(Color(.Other.red))
|
|
||||||
}
|
|
||||||
.frame(width: 60, alignment: .center)
|
|
||||||
.padding(.trailing,12)
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
|
|
||||||
VStack (spacing: 4) {
|
|
||||||
CustomTxfView(placeholder: "이메일 입력(30 글자)", text: $registerVM.emailFrontText, maxLength: 30, alignment: .center)
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
.padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 24)
|
|
||||||
.foregroundStyle(Color(.Normal.light))
|
|
||||||
}
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Text("@")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
.padding([.leading, .trailing], 4)
|
|
||||||
// Spacer(minLength: 1)
|
|
||||||
|
|
||||||
DropdownButton(manager: dropdownManager, title: "도메인 선택",
|
|
||||||
items: registerVM.emailTailList){ index in
|
|
||||||
if registerVM.emailTailList.count-1 == index {
|
|
||||||
onDomainTxf = true
|
|
||||||
} else {
|
|
||||||
registerVM.emailTailText = registerVM.emailTailList[index]
|
|
||||||
onDomainTxf = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 24)
|
|
||||||
.foregroundStyle(Color(.Normal.light))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if onDomainTxf {
|
|
||||||
CustomTxfView(placeholder: "도메인 직접 입력(30 글자)", text: $registerVM.emailTailText, maxLength: 30, alignment: .center)
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
.padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 24)
|
|
||||||
.foregroundStyle(Color(.Normal.light))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
// Phone
|
|
||||||
HStack(spacing: 0){
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Text("연락처")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
Text("*")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
.foregroundStyle(Color(.Other.red))
|
|
||||||
}
|
|
||||||
.frame(width: 60, alignment: .center)
|
|
||||||
.padding(.trailing,12)
|
|
||||||
DropdownButton(manager: dropdownManager, title: "선택", items: registerVM.numberHeadList){ index in
|
|
||||||
registerVM.phoneTextSet.0 = registerVM.numberHeadList[index]
|
|
||||||
}
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 24)
|
|
||||||
.foregroundStyle(Color(.Normal.light))
|
|
||||||
}
|
|
||||||
Text("-")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
.padding([.leading, .trailing], 4)
|
|
||||||
|
|
||||||
CustomTxfView(placeholder: "0000", text: $registerVM.phoneTextSet.1, maxLength: 4, alignment: .center)
|
|
||||||
.frame(maxWidth: .infinity,maxHeight: 48)
|
|
||||||
.padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 24)
|
|
||||||
.foregroundStyle(Color(.Normal.light))
|
|
||||||
}
|
|
||||||
Text("-")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
.padding([.leading, .trailing], 4)
|
|
||||||
CustomTxfView(placeholder: "0000", text: $registerVM.phoneTextSet.2, maxLength: 4, alignment: .center)
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: 48)
|
|
||||||
.padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 24)
|
|
||||||
.foregroundStyle(Color(.Normal.light))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
HStack(alignment: .center, spacing: 0){
|
|
||||||
Text("주소")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
.frame(width: 60, alignment: .center)
|
|
||||||
.padding(.trailing,12)
|
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
SimpleBtnView(vm: btnVM, id: registerVM.addressBtnID)
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: 48)
|
|
||||||
.padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 24)
|
|
||||||
.foregroundStyle(Color(.Normal.light))
|
|
||||||
}
|
|
||||||
.padding(.bottom, isSelectAddr ? 4:0)
|
|
||||||
if isSelectAddr {
|
|
||||||
CustomTxfView(placeholder: "상세 주소 입력 (50 글자)", text: $registerVM.addrDetailText, maxLength: 50, alignment: .center)
|
|
||||||
.frame(maxWidth: .infinity,
|
|
||||||
maxHeight: isSelectAddr ? 48 : 0)
|
|
||||||
.padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 24)
|
|
||||||
.foregroundStyle(Color(.Normal.light))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
|
|
||||||
SimpleBtnView(vm: btnVM, id: registerVM.registerBtnID)
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: 48)
|
|
||||||
.padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
|
|
||||||
// .background {
|
|
||||||
// RoundedRectangle(cornerRadius: 24)
|
|
||||||
// .foregroundStyle(Color(.Normal.light))
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
GlobalDropdownOverlay(manager: dropdownManager)
|
|
||||||
}
|
|
||||||
.coordinateSpace(name: "dropdownArea")
|
|
||||||
.sheet(isPresented: $showWebView) {
|
|
||||||
WebView(url: URL(string: "https://sean-59.github.io/Kakao-Postcode/")!,
|
|
||||||
showWebView: $showWebView,
|
|
||||||
isLoading: $isLoading) { result in
|
|
||||||
if let result = result as? (String, String) {
|
|
||||||
print(result.0)
|
|
||||||
isSelectAddr = true
|
|
||||||
registerVM.addressText = result.0
|
|
||||||
|
|
||||||
btnVM.setSize(for: registerVM.addressBtnID, newWidth: .infinity, newHeight: .infinity)
|
|
||||||
btnVM.setText(for: registerVM.addressBtnID, newText: "\(registerVM.addressText)", newFont: .nps(size: 16))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
topVM.titleName = "회원가입"
|
|
||||||
|
|
||||||
topVM.setLeftBtn(Image(.Icon.left), size: CGPoint(x: 40, y: 40), action: leftAct)
|
|
||||||
topVM.setRightBtn(size: CGPoint(x: 40, y: 40), action: rightAct)
|
|
||||||
|
|
||||||
btnVM.setSize(for: registerVM.addressBtnID, newWidth: 80, newHeight: 24)
|
|
||||||
btnVM.setText(for: registerVM.addressBtnID, newText: "\(registerVM.addressText)", newFont: .nps(size: 16))
|
|
||||||
btnVM.setTextColor(for: registerVM.addressBtnID, newColor: Color.Text.black)
|
|
||||||
btnVM.setAction(for: registerVM.addressBtnID) { self.showWebView.toggle() }
|
|
||||||
|
|
||||||
btnVM.setSize(for: registerVM.registerBtnID, newWidth: .infinity, newHeight: 48)
|
|
||||||
btnVM.setText(for: registerVM.registerBtnID, newText: "회원가입", newFont: .nps(font: .bold, size: 24))
|
|
||||||
btnVM.setTextColor(for: registerVM.registerBtnID, newColor: Color.Point.dark)
|
|
||||||
btnVM.setAction(for: registerVM.registerBtnID) {
|
|
||||||
Task {
|
|
||||||
await registerVM.registerUser()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
dropdownManager.font = .nps(size: 16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func leftAct() {
|
|
||||||
printLog("왼쪽 버튼 클릭")
|
|
||||||
appVM.naviState.set(act: .POP)
|
|
||||||
}
|
|
||||||
func rightAct() {
|
|
||||||
printLog("오른쪽 버튼 클릭")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct selectPolicyView: View {
|
|
||||||
@ObservedObject var registerVM: RegisterViewModel
|
|
||||||
@ObservedObject var btnVM: ButtonViewModel
|
|
||||||
|
|
||||||
init(_ registerVM: RegisterViewModel, btnVM: ButtonViewModel) {
|
|
||||||
self.registerVM = registerVM
|
|
||||||
self.btnVM = btnVM
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
CircleBtnView(vm: btnVM, id: registerVM.policyBtn1ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,152 +0,0 @@
|
||||||
//
|
|
||||||
// SelectAcademyView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/18/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SelectAcademyView: View {
|
|
||||||
@EnvironmentObject var appVM: AppViewModel
|
|
||||||
@StateObject var vm: SelectAcademyViewModel
|
|
||||||
|
|
||||||
init(_ appVM: AppViewModel) {
|
|
||||||
_vm = StateObject(wrappedValue: SelectAcademyViewModel(appVM))
|
|
||||||
}
|
|
||||||
|
|
||||||
@State private var scrollOffset: CGPoint = .zero
|
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
Spacer()
|
|
||||||
.frame(maxHeight: 100)
|
|
||||||
Image(.Logo.appIcon)
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 200, height: 200)
|
|
||||||
|
|
||||||
VStack(spacing: 4) {
|
|
||||||
Button {
|
|
||||||
vm.moveChatting()
|
|
||||||
} label: {
|
|
||||||
Text("채팅 진입")
|
|
||||||
}
|
|
||||||
HStack(spacing: 0){
|
|
||||||
Text("학원 코드")
|
|
||||||
.font(.nps(font: .bold, size: 16))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
//MARK: TO-DO
|
|
||||||
// 문제
|
|
||||||
// 1. txf 클릭시 겉 뷰가 작아지는 현상
|
|
||||||
// 2. 코드 입력시 버튼 나타나게 하기
|
|
||||||
CustomTextField(placeholder: "학원 코드 입력", text: $vm.academyCode)
|
|
||||||
.frame(maxWidth: .infinity,maxHeight: 48)
|
|
||||||
.padding(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20))
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 24)
|
|
||||||
.foregroundStyle(Color(.Normal.light))
|
|
||||||
}
|
|
||||||
.clipped()
|
|
||||||
}
|
|
||||||
.padding(EdgeInsets(top: 12, leading: 24, bottom: 40, trailing: 24))
|
|
||||||
|
|
||||||
VStack(spacing: 4) {
|
|
||||||
HStack(spacing: 0){
|
|
||||||
Text("학원 목록")
|
|
||||||
.font(.nps(font: .bold, size: 16))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
}
|
|
||||||
.padding(EdgeInsets(top: 12, leading: 24, bottom: 0, trailing: 24))
|
|
||||||
if vm.academyList.count > 0 {
|
|
||||||
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
|
|
||||||
VStack(spacing: 12) {
|
|
||||||
ForEach(Array(vm.academyList.enumerated()), id: \.offset) { index, academy in
|
|
||||||
AcademyCell(numbering: index, academy: vm.academyList[index],selectNum: $vm.selectNum){
|
|
||||||
vm.toggleSelection(for: index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(EdgeInsets(top: 0, leading: 24, bottom: 12, trailing: 24))
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
EmptyBoxView(title: "등록된 학원이 없습니다.")
|
|
||||||
.padding(EdgeInsets(top: 0, leading: 24, bottom: 0, trailing: 24))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
|
|
||||||
Button {
|
|
||||||
appVM.naviState.set(act: .MOVE, path: .Main)
|
|
||||||
} label: {
|
|
||||||
ZStack {
|
|
||||||
RoundedRectangle(cornerRadius: 12)
|
|
||||||
.fill(Color(.Second.normal))
|
|
||||||
Text("입장하기")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
.foregroundStyle(Color(.Normal.light))
|
|
||||||
}
|
|
||||||
.frame(height: 56)
|
|
||||||
}
|
|
||||||
.opacity(vm.selectNum >= 0 ? 1 : 0)
|
|
||||||
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
|
|
||||||
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
vm.loadAcademy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AcademyCell: View {
|
|
||||||
let numbering: Int
|
|
||||||
let academy: AcademyName
|
|
||||||
@Binding var selectNum: Int
|
|
||||||
let action: VOID_TO_VOID
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack(alignment: .center, spacing: 0) {
|
|
||||||
Image(.Logo.pageIcon)
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 32, height: 32, alignment: .center)
|
|
||||||
.padding(12)
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
|
|
||||||
Text("\(academy.name)")
|
|
||||||
.font(.nps(size: 18))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
.multilineStyle(.center)
|
|
||||||
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
|
|
||||||
Button{
|
|
||||||
// saVM.toggleSelection(for: numbering)
|
|
||||||
action()
|
|
||||||
} label: {
|
|
||||||
Circle()
|
|
||||||
.stroke(Color(.Text.detail), lineWidth: 4)
|
|
||||||
.fill (selectNum == numbering ? Color(.Point.normal) : Color(.Normal.normal))
|
|
||||||
.frame(width: 18, height: 18)
|
|
||||||
}
|
|
||||||
.frame(width: 32, height: 32)
|
|
||||||
.padding(12)
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 12)
|
|
||||||
.stroke(Color(.Second.normal), lineWidth: 2)
|
|
||||||
}
|
|
||||||
.onTapGesture {
|
|
||||||
action()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -50,21 +50,20 @@ struct AttCellView: View {
|
||||||
.padding(.top,4)
|
.padding(.top,4)
|
||||||
HStack(alignment: .center, spacing: 2) {
|
HStack(alignment: .center, spacing: 2) {
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(valueGroup.0)")
|
Text("\(valueGroup.0)").font(.nps(font: .bold, size: 20))
|
||||||
.font(.nps(font: .bold, size: 20))
|
|
||||||
.foregroundStyle(((Double(valueGroup.0)/Double(valueGroup.1)) < 0.7) ? Color(.Other.red) : Color(.Other.blue))
|
.foregroundStyle(((Double(valueGroup.0)/Double(valueGroup.1)) < 0.7) ? Color(.Other.red) : Color(.Other.blue))
|
||||||
.frame(width: 28,alignment: .center)
|
.frame(width: 28,alignment: .center)
|
||||||
Text("/")
|
Text("/").font(.nps(size: 12))
|
||||||
.font(.nps(size: 12))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
.foregroundStyle(Color(.Text.detail))
|
||||||
Text("\(valueGroup.1)")
|
Text("\(valueGroup.1)").font(.nps(font: .bold, size: 20))
|
||||||
.font(.nps(font: .bold, size: 20))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
.foregroundStyle(Color(.Text.detail))
|
||||||
.frame(width: 28,alignment: .center)
|
.frame(width: 28,alignment: .center)
|
||||||
Text("\(cellText.group)")
|
Text("\(cellText.group)")
|
||||||
.font(.nps(size: 16))
|
.font(.nps(size: 16))
|
||||||
.foregroundStyle(Color(.Text.detail))
|
.foregroundStyle(Color(.Text.detail))
|
||||||
.multilineStyle()
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.5)
|
||||||
|
.truncationMode(.tail)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,9 @@ struct CalCellView: View {
|
||||||
Text("\(summaryCalData.summary)")
|
Text("\(summaryCalData.summary)")
|
||||||
.font(.nps(size: 20))
|
.font(.nps(size: 20))
|
||||||
.foregroundStyle(Color(.Text.black))
|
.foregroundStyle(Color(.Text.black))
|
||||||
.multilineStyle()
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.5)
|
||||||
|
.truncationMode(.tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,39 +8,26 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct HomeView: View {
|
struct HomeView: View {
|
||||||
@StateObject private var topVM = TopViewModel()
|
|
||||||
|
|
||||||
@State private var scrollOffset: CGPoint = .zero
|
@State private var scrollOffset: CGPoint = .zero
|
||||||
@State private var topViewState: Bool = false
|
@State private var topViewState: Bool = false
|
||||||
|
|
||||||
//MARK: - 변경 값
|
|
||||||
@Binding var myType: UserType
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
ZStack {
|
ZStack {
|
||||||
if !topViewState {
|
|
||||||
VStack {
|
|
||||||
Rectangle()
|
|
||||||
.foregroundStyle(Color(.Other.cell))
|
|
||||||
.frame(height: 100 + (scrollOffset.y < 0 ? scrollOffset.y * -1 : 0))
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.edgesIgnoringSafeArea(.all)
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
|
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
|
||||||
LazyVStack(spacing: 24) {
|
VStack(spacing: 0) {
|
||||||
|
|
||||||
TopProfileView(myType: myType)
|
TopProfileView(userType: .Student)
|
||||||
|
.padding(.bottom,12)
|
||||||
|
|
||||||
Group {
|
Group {
|
||||||
AttendanceBoxView()
|
AttendanceBoxView()
|
||||||
|
|
||||||
|
// CalendarBoxView(summaryCalDataList: [])
|
||||||
CalendarBoxView(summaryCalDataList: [
|
CalendarBoxView(summaryCalDataList: [
|
||||||
SummaryCalendar(id: "123", date: "2025-02-28", summary: "요약내용입니다."),
|
SummaryCalendar(id: "123", date: "2025-02-28", summary: "요약내용입니다."),
|
||||||
SummaryCalendar(id: "123", date: "2025-02-28", summary: "요약내용입니다.")])
|
SummaryCalendar(id: "123", date: "2025-02-28", summary: "요약내용입니다.")])
|
||||||
|
|
||||||
|
// ManagementBoxView(managementList: [])
|
||||||
ManagementBoxView(managementList: [
|
ManagementBoxView(managementList: [
|
||||||
SummaryManagement(id: "01", title: "과목 명1", teacher: "A", ratio: 27, homework: 3),
|
SummaryManagement(id: "01", title: "과목 명1", teacher: "A", ratio: 27, homework: 3),
|
||||||
SummaryManagement(id: "02", title: "과목 명2", teacher: "B", ratio: 80, homework: 10),
|
SummaryManagement(id: "02", title: "과목 명2", teacher: "B", ratio: 80, homework: 10),
|
||||||
|
@ -48,6 +35,7 @@ struct HomeView: View {
|
||||||
SummaryManagement(id: "04", title: "과목 명4", teacher: "D", ratio: 72, homework: 0),
|
SummaryManagement(id: "04", title: "과목 명4", teacher: "D", ratio: 72, homework: 0),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// NoticeBoxView(noticeList: [])
|
||||||
NoticeBoxView(noticeList: [
|
NoticeBoxView(noticeList: [
|
||||||
SummaryNotice(id: "00", title: "공지사항1", date: "2025-02-11", new: true),
|
SummaryNotice(id: "00", title: "공지사항1", date: "2025-02-11", new: true),
|
||||||
SummaryNotice(id: "01", title: "공지사항2", date: "2025-01-11", new: false),
|
SummaryNotice(id: "01", title: "공지사항2", date: "2025-01-11", new: false),
|
||||||
|
@ -68,13 +56,14 @@ struct HomeView: View {
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
.foregroundStyle(Color(.Other.cell))
|
.foregroundStyle(Color(.Other.cell))
|
||||||
}
|
}
|
||||||
.padding([.leading, .trailing], 24)
|
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if topViewState {
|
if topViewState {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
TopView(topVM: topVM)
|
TopView(titleName: "Name")
|
||||||
.transition(.move(edge: .top))
|
.transition(.move(edge: .top))
|
||||||
.animation(.easeInOut, value: scrollOffset)
|
.animation(.easeInOut, value: scrollOffset)
|
||||||
Spacer(minLength: 1)
|
Spacer(minLength: 1)
|
||||||
|
@ -83,20 +72,6 @@ struct HomeView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.onAppear {
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 여기도 수정봐야 함
|
|
||||||
topVM.titleName = "Name"
|
|
||||||
|
|
||||||
if myType == .Student {
|
|
||||||
topVM.setLeftBtn(Image(.Icon.face), size: CGPoint(x: 40, y: 40), action: leftAct)
|
|
||||||
} else {
|
|
||||||
topVM.setLeftBtn(text: "\(myType.rawValue)", font: .nps(font: .bold, size: 24),
|
|
||||||
size: CGPoint(x: 40, y: 40), action: leftAct)
|
|
||||||
}
|
|
||||||
topVM.setRightBtn(Image(.Icon.notificationSET), size: CGPoint(x: 40, y: 40), action: rightAct)
|
|
||||||
topVM.btnVM.setImage(for: topVM.rightBtnID, newImage: Image(.Icon.notificationSET))
|
|
||||||
}
|
|
||||||
.onChange(of: scrollOffset.y) { oldValue, newValue in
|
.onChange(of: scrollOffset.y) { oldValue, newValue in
|
||||||
if newValue > 200 && topViewState == false {
|
if newValue > 200 && topViewState == false {
|
||||||
topViewState = true
|
topViewState = true
|
||||||
|
@ -105,13 +80,23 @@ struct HomeView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func leftAct() {
|
|
||||||
printLog("왼쪽 버튼 클릭")
|
|
||||||
}
|
|
||||||
func rightAct() {
|
|
||||||
printLog("오른쪽 버튼 클릭")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct EmptyBoxView: View {
|
||||||
|
let title: String
|
||||||
|
var body: some View {
|
||||||
|
Text("\(title)")
|
||||||
|
.font(.nps(size: 20))
|
||||||
|
.foregroundStyle(Color(.Text.detail))
|
||||||
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.5)
|
||||||
|
.truncationMode(.tail)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding([.top,.bottom],12)
|
||||||
|
.background {
|
||||||
|
RoundedRectangle(cornerRadius: 4)
|
||||||
|
.stroke(Color(.Second.normal), lineWidth: 2)
|
||||||
|
.fill(Color(.Second.light))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import SwiftUI
|
||||||
struct TopProfileView: View {
|
struct TopProfileView: View {
|
||||||
@StateObject var btnVM = ButtonViewModel()
|
@StateObject var btnVM = ButtonViewModel()
|
||||||
|
|
||||||
var myType: UserType
|
var userType: UserType
|
||||||
|
|
||||||
// MARK: TO-DO
|
// MARK: TO-DO
|
||||||
// 여기서 이름 떙겨오는것도 고민을 해야 함
|
// 여기서 이름 떙겨오는것도 고민을 해야 함
|
||||||
|
@ -48,24 +48,27 @@ struct TopProfileView: View {
|
||||||
.padding([.top, .bottom], 40)
|
.padding([.top, .bottom], 40)
|
||||||
VStack(alignment: .center, spacing: 8) {
|
VStack(alignment: .center, spacing: 8) {
|
||||||
Text("\(self.academyName)")
|
Text("\(self.academyName)")
|
||||||
|
.frame(alignment: .center)
|
||||||
.font(.nps(font: .bold, size: 36))
|
.font(.nps(font: .bold, size: 36))
|
||||||
.foregroundStyle(Color(.Text.title))
|
.foregroundStyle(Color(.Text.title))
|
||||||
.multilineStyle()
|
.lineLimit(1)
|
||||||
.frame(alignment: .center)
|
.minimumScaleFactor(0.5)
|
||||||
|
.truncationMode(.tail)
|
||||||
Text("\(self.myName)")
|
Text("\(self.myName)")
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.frame(alignment: .center)
|
||||||
.font(.nps(size: 18))
|
.font(.nps(size: 18))
|
||||||
.foregroundStyle(Color(.Text.detail))
|
.foregroundStyle(Color(.Text.detail))
|
||||||
.multilineStyle()
|
.lineLimit(1)
|
||||||
.frame(alignment: .center)
|
.minimumScaleFactor(0.5)
|
||||||
|
.truncationMode(.tail)
|
||||||
}
|
}
|
||||||
} /// 위쪽 VStack
|
} /// 위쪽 VStack
|
||||||
.padding(EdgeInsets(top: 24, leading: 24, bottom: 12, trailing: 24))
|
.padding(EdgeInsets(top: 24, leading: 24, bottom: 12, trailing: 24))
|
||||||
|
|
||||||
// MARK: TO-DO
|
// MARK: TO-DO
|
||||||
// 여기에 가로스크롤 넣어야 할거 같음
|
// 여기에 가로스크롤 넣어야 할거 같음
|
||||||
if myType == .Parent {
|
if userType == .Parent {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
ForEach(Array(childIDList.enumerated()),id: \.offset){ index, id in
|
ForEach(Array(childIDList.enumerated()),id: \.offset){ index, id in
|
||||||
CircleBtnView(vm: btnVM, id: id)
|
CircleBtnView(vm: btnVM, id: id)
|
||||||
|
@ -92,7 +95,7 @@ struct TopProfileView: View {
|
||||||
// 마켓 버튼과 알림 버튼 동작 로직 구현하기
|
// 마켓 버튼과 알림 버튼 동작 로직 구현하기
|
||||||
|
|
||||||
|
|
||||||
switch self.myType {
|
switch self.userType {
|
||||||
case .Student:
|
case .Student:
|
||||||
typeName = "학생"
|
typeName = "학생"
|
||||||
case .Parent:
|
case .Parent:
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
//
|
||||||
|
// ManagementView.swift
|
||||||
|
// AcaMate
|
||||||
|
//
|
||||||
|
// Created by TAnine on 2/11/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ManagementView: View {
|
||||||
|
var body: some View {
|
||||||
|
Text("학습 관리")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,91 +0,0 @@
|
||||||
//
|
|
||||||
// ChatListView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/14/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct ChatListView: View {
|
|
||||||
var chatList: [SummaryChat]
|
|
||||||
var body: some View {
|
|
||||||
VStack(spacing: 10) {
|
|
||||||
ForEach(Array(chatList.enumerated()), id:\.offset) { index, chat in
|
|
||||||
ChatCellView(summaryChat: chat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ChatCellView: View {
|
|
||||||
@EnvironmentObject var appVM: AppViewModel
|
|
||||||
|
|
||||||
var summaryChat: SummaryChat
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(spacing: 8) {
|
|
||||||
HStack(alignment: .bottom, spacing: 4) {
|
|
||||||
Image(.Icon.chatting).resizable()
|
|
||||||
.frame(width: 24, height: 24, alignment: .center)
|
|
||||||
|
|
||||||
Text("\(summaryChat.chatName)")
|
|
||||||
.font(.nps(size: 20))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
.multilineStyle()
|
|
||||||
|
|
||||||
if summaryChat.groupNum > 0 {
|
|
||||||
HStack(alignment: .center, spacing: 0) {
|
|
||||||
Image(.Icon.person).resizable()
|
|
||||||
.frame(width: 12, height: 12, alignment: .center)
|
|
||||||
Text("\(summaryChat.groupNum)")
|
|
||||||
.font(.nps(size: 8))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
|
|
||||||
HStack(alignment: .bottom, spacing: 4) {
|
|
||||||
Image(summaryChat.notiState ? .Icon.notificationON : .Icon.notificationOFF).resizable()
|
|
||||||
.frame(width: 12, height: 12, alignment: .center)
|
|
||||||
Text("\(summaryChat.teacherName)")
|
|
||||||
.font(.nps(font: .bold, size: 12))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
Text("선생님")
|
|
||||||
.font(.nps(size: 8))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Text("\(summaryChat.lastMessage)")
|
|
||||||
.font(.nps(size: 10))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
.multilineStyle(limit: 2, scale: 1)
|
|
||||||
.padding(.trailing, 8)
|
|
||||||
|
|
||||||
HStack(alignment: .bottom, spacing: 4) {
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
Group {
|
|
||||||
Text("\(summaryChat.dayDate)")
|
|
||||||
.font(.nps(size: 8))
|
|
||||||
Text("\(summaryChat.timeDate)")
|
|
||||||
.font(.nps(font: .bold, size: 12))
|
|
||||||
}
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.padding(EdgeInsets(top: 8, leading: 12, bottom: 8, trailing: 12))
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 4)
|
|
||||||
.stroke(Color(.Second.normal), lineWidth: 2)
|
|
||||||
.fill(Color(.Second.light))
|
|
||||||
}
|
|
||||||
.onTapGesture {
|
|
||||||
printLog("채팅 내부 셀 클릭")
|
|
||||||
// MARK: TO-DO
|
|
||||||
appVM.naviState.set(act: .ADD, path: .ChatRoom(id: summaryChat.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
//
|
|
||||||
// ChattingRoomView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/17/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct ChattingRoomView: View {
|
|
||||||
@EnvironmentObject var appVM: AppViewModel
|
|
||||||
@StateObject private var topVM = TopViewModel()
|
|
||||||
|
|
||||||
|
|
||||||
// @StateObject private var chatVM = ChatViewModel()
|
|
||||||
|
|
||||||
@StateObject private var btnVM = ButtonViewModel()
|
|
||||||
@State var sendBtnID = UUID()
|
|
||||||
@State var addSomeBtnID = UUID()
|
|
||||||
@State var callCameraBtnID = UUID()
|
|
||||||
@State var addPhotoBtnID = UUID()
|
|
||||||
|
|
||||||
@State private var scrollOffset: CGPoint = .zero
|
|
||||||
|
|
||||||
@StateObject private var socketManager = WebSocketManager()
|
|
||||||
@State private var message = ""
|
|
||||||
|
|
||||||
let roomID: String
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .center, spacing: 0) {
|
|
||||||
TopView(topVM: topVM)
|
|
||||||
Text("Hello, World! \(roomID)")
|
|
||||||
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
|
|
||||||
|
|
||||||
}.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
|
|
||||||
TextField("SEND MESSAGE", text: $message)
|
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
||||||
.padding()
|
|
||||||
HStack(spacing: 10) {
|
|
||||||
Button {
|
|
||||||
socketManager.connect()
|
|
||||||
} label: {
|
|
||||||
Text("CONNECT")
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
socketManager.sendMessage(message)
|
|
||||||
message = ""
|
|
||||||
} label: {
|
|
||||||
Text("SEND")
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
socketManager.disconnect()
|
|
||||||
} label: {
|
|
||||||
Text("DISCONNECT")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List(socketManager.receivedMessage, id: \.self) { msg in
|
|
||||||
Text(msg)
|
|
||||||
.foregroundStyle(Color(.Other.cell))
|
|
||||||
}
|
|
||||||
|
|
||||||
.fullDrawView(Color(.Other.cell))
|
|
||||||
}
|
|
||||||
|
|
||||||
.onAppear {
|
|
||||||
topVM.titleName = "클래스 명"
|
|
||||||
topVM.setLeftBtn(Image(.Icon.back), size: CGPoint(x:40, y:40)) {
|
|
||||||
appVM.naviState.set(act: .POP, path: .ChatRoom(id: roomID))
|
|
||||||
}
|
|
||||||
topVM.setRightBtn(Image(.Icon.setting), size: CGPoint(x: 40, y: 40)) {
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 채팅방 설정 열기
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,183 +8,7 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ChattingView: View {
|
struct ChattingView: View {
|
||||||
@StateObject private var topVM = TopViewModel()
|
|
||||||
@StateObject private var btnVM = ButtonViewModel()
|
|
||||||
@StateObject private var vm: ChatViewModel
|
|
||||||
|
|
||||||
@State private var scrollOffset: CGPoint = .zero
|
|
||||||
|
|
||||||
@State private var leftBtnID = UUID()
|
|
||||||
@State private var rightBtnID = UUID()
|
|
||||||
|
|
||||||
|
|
||||||
init(_ appVM: AppViewModel, _ myType: Binding<UserType>) {
|
|
||||||
_vm = StateObject(wrappedValue: ChatViewModel(appVM))
|
|
||||||
_myType = myType
|
|
||||||
}
|
|
||||||
|
|
||||||
let classList = [
|
|
||||||
SummaryChat(id: "00", chatName: "Class 101", teacherName: "홍길동",
|
|
||||||
lastMessage: "여기에는 채팅이 나올 예정입니다. 2줄 정도로 나올 예정이며 끝자리는 잘려서 나올 것 입니다. 이정도의 채팅으로는 택도 없어서 조금 더 길게 길게 작성을 해봅니다.",
|
|
||||||
dayDate: "2025. 02. 14", timeDate: "PM 11:00", notiState: true, groupNum: 12),
|
|
||||||
SummaryChat(id: "01", chatName: "Class 101", teacherName: "홍길동",
|
|
||||||
lastMessage: "여기에는 채팅이 나올 예정입니다. 2줄 정도로 나올 예정이며 끝자리는 잘려서 나올 것 입니다. 이정도의 채팅으로는 택도 없어서 조금 더 길게 길게 작성을 해봅니다.",
|
|
||||||
dayDate: "2025. 02. 14", timeDate: "PM 11:00", notiState: false, groupNum: 12),
|
|
||||||
SummaryChat(id: "02", chatName: "Class 101", teacherName: "홍길동",
|
|
||||||
lastMessage: "여기에는 채팅이 나올 예정입니다. 2줄 정도로 나올 예정이며 끝자리는 잘려서 나올 것 입니다. 이정도의 채팅으로는 택도 없어서 조금 더 길게 길게 작성을 해봅니다.",
|
|
||||||
dayDate: "2025. 02. 14", timeDate: "PM 11:00", notiState: true, groupNum: 12)
|
|
||||||
]
|
|
||||||
|
|
||||||
//MARK: - 변경 값
|
|
||||||
@Binding var myType: UserType
|
|
||||||
|
|
||||||
@State var chatMenu: chatType = .Class
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
Text("채팅")
|
||||||
TopView(topVM: topVM)
|
|
||||||
if myType == .ETC || myType == .Employee {
|
|
||||||
EmptyBoxView(title: "이용하실 수 없는 기능입니다.")
|
|
||||||
.padding(24)
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if myType == .Teacher || myType == .Admin {
|
|
||||||
if myType == .Admin {
|
|
||||||
HStack {
|
|
||||||
SimpleBtnView(vm: btnVM, id: leftBtnID)
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
Text("선생님 이름")
|
|
||||||
.font(.nps(font: .bold, size: 24))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
SimpleBtnView(vm: btnVM, id: rightBtnID)
|
|
||||||
}
|
|
||||||
.padding(EdgeInsets(top: 12, leading: 24, bottom: 0, trailing: 24))
|
|
||||||
}
|
|
||||||
HStack {
|
|
||||||
SelectChatMenu(chatMenu: $chatMenu, tag: .Class, image: Image(.Icon.group), title: "클래스")
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
SelectChatMenu(chatMenu: $chatMenu, tag: .Student, image: Image(.Icon.talk), title: "학생")
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
SelectChatMenu(chatMenu: $chatMenu, tag: .Parent, image: Image(.Icon.talk), title: "학부모")
|
|
||||||
}
|
|
||||||
|
|
||||||
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
|
|
||||||
}
|
|
||||||
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
|
|
||||||
LazyVStack(spacing: 24) {
|
|
||||||
if myType == .Student || myType == .Parent {
|
|
||||||
Group {
|
|
||||||
DashBoardView(image: Image(.Icon.group), title: "클래스") {
|
|
||||||
ChatListView(chatList: classList)
|
|
||||||
}
|
|
||||||
|
|
||||||
DashBoardView(image: Image(.Icon.talk), title: "선생님과 1:1") {
|
|
||||||
|
|
||||||
}
|
|
||||||
DashBoardView(image: Image(.Icon.talk), title: "부모님과 1:1") {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 8)
|
|
||||||
.foregroundStyle(Color(.Other.cell))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Group {
|
|
||||||
switch chatMenu {
|
|
||||||
case .Class:
|
|
||||||
DashBoardView(image: Image(.Icon.group), title: "클래스") {
|
|
||||||
ChatListView(chatList: classList)
|
|
||||||
}
|
|
||||||
case .Student:
|
|
||||||
DashBoardView(image: Image(.Icon.talk), title: "학생과 1:1") {
|
|
||||||
|
|
||||||
}
|
|
||||||
case .Parent:
|
|
||||||
DashBoardView(image: Image(.Icon.talk), title: "부모님과 1:1") {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 8)
|
|
||||||
.foregroundStyle(Color(.Other.cell))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(EdgeInsets(
|
|
||||||
top: (myType == .Student || myType == .Parent) ? 24 : 12, leading: 24, bottom: 24, trailing: 24))
|
|
||||||
}
|
|
||||||
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 여기도 수정봐야 함
|
|
||||||
topVM.titleName = "Name"
|
|
||||||
|
|
||||||
if myType == .Student {
|
|
||||||
topVM.setLeftBtn(Image(.Icon.face), size: CGPoint(x: 40, y: 40), action: leftAct)
|
|
||||||
} else {
|
|
||||||
topVM.setLeftBtn(text: "\(myType.rawValue)", font: .nps(font: .bold, size: 24),
|
|
||||||
size: CGPoint(x: 40, y: 40), action: leftAct)
|
|
||||||
}
|
|
||||||
topVM.setRightBtn(Image(.Icon.plus), size: CGPoint(x: 40, y: 40), action: rightAct)
|
|
||||||
|
|
||||||
btnVM.setImage(for: leftBtnID, newImage: Image(.Icon.left))
|
|
||||||
btnVM.setImage(for: rightBtnID, newImage: Image(.Icon.right))
|
|
||||||
|
|
||||||
btnVM.setSize(for: leftBtnID, newWidth: 24, newHeight: 24)
|
|
||||||
btnVM.setSize(for: rightBtnID, newWidth: 24, newHeight: 24)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func leftAct() {
|
|
||||||
printLog("왼쪽 버튼 클릭")
|
|
||||||
}
|
|
||||||
func rightAct() {
|
|
||||||
printLog("오른쪽 버튼 클릭")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SelectChatMenu: View {
|
|
||||||
@Binding var chatMenu: chatType
|
|
||||||
let tag: chatType
|
|
||||||
let image: Image
|
|
||||||
let title: String
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack(alignment: .center, spacing: 4) {
|
|
||||||
if chatMenu == tag {
|
|
||||||
image.resizable()
|
|
||||||
.frame(width: 24, height: 24, alignment: .center)
|
|
||||||
Text("\(title)")
|
|
||||||
.font(.nps(font: .bold, size: 24))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
} else {
|
|
||||||
image.resizable()
|
|
||||||
.renderingMode(.template)
|
|
||||||
.frame(width: 24, height: 24, alignment: .center)
|
|
||||||
.foregroundStyle(Color(.Disable.normal))
|
|
||||||
Text("\(title)")
|
|
||||||
.font(.nps(size: 20))
|
|
||||||
.foregroundStyle(Color(.Disable.normal))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(2)
|
|
||||||
.background {
|
|
||||||
if chatMenu == tag {
|
|
||||||
RoundedRectangle(cornerRadius: 4)
|
|
||||||
.foregroundStyle(Color.Other.cell)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onTapGesture {
|
|
||||||
chatMenu = tag
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
//
|
|
||||||
// ManagementView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/11/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct ManagementView: View {
|
|
||||||
@StateObject private var topVM = TopViewModel()
|
|
||||||
|
|
||||||
@State private var scrollOffset: CGPoint = .zero
|
|
||||||
var body: some View {
|
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
TopView(topVM: topVM)
|
|
||||||
|
|
||||||
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
|
|
||||||
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
topVM.titleName = ""
|
|
||||||
topVM.setLeftBtn(size: CGPoint(x: 40, y: 40), action: leftAct)
|
|
||||||
topVM.setRightBtn(size: CGPoint(x: 40, y: 40), action: rightAct)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func leftAct() {
|
|
||||||
printLog("왼쪽 버튼 클릭")
|
|
||||||
}
|
|
||||||
func rightAct() {
|
|
||||||
printLog("오른쪽 버튼 클릭")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,33 +8,7 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct CalendarView: View {
|
struct CalendarView: View {
|
||||||
@StateObject private var topVM = TopViewModel()
|
|
||||||
|
|
||||||
@State private var scrollOffset: CGPoint = .zero
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
Text("일정")
|
||||||
VStack(spacing: 0) {
|
|
||||||
TopView(topVM: topVM)
|
|
||||||
|
|
||||||
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
|
|
||||||
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
topVM.titleName = ""
|
|
||||||
topVM.setLeftBtn(size: CGPoint(x: 40, y: 40), action: leftAct)
|
|
||||||
topVM.setRightBtn(size: CGPoint(x: 40, y: 40), action: rightAct)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func leftAct() {
|
|
||||||
printLog("왼쪽 버튼 클릭")
|
|
||||||
}
|
|
||||||
func rightAct() {
|
|
||||||
printLog("오른쪽 버튼 클릭")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
//
|
|
||||||
// AppInfoView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/13/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct AppInfoView: View {
|
|
||||||
@UserDefault(key:"currentVer", defaultValue: "0.0.0") var currentVer
|
|
||||||
@UserDefault(key:"finalVer", defaultValue: "0.0.0") var finalVer
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
EtcBoxView(title: "앱 정보"){
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(.Logo.pageIcon)
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 40, height: 40, alignment: .center)
|
|
||||||
.padding(.trailing, 12)
|
|
||||||
Text("설치 버전: \(currentVer)")
|
|
||||||
.font(.nps(font: .bold, size: 16))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
if currentVer == finalVer {
|
|
||||||
Text("최신버전입니다.")
|
|
||||||
.font(.nps(size: 12))
|
|
||||||
.foregroundStyle(Color(.Normal.light))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding([.top,.bottom],12)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
//
|
|
||||||
// CsCenterView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/13/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct CsCenterView: View {
|
|
||||||
var body: some View {
|
|
||||||
EtcBoxView(title: "고객 센터") {
|
|
||||||
VStack(spacing: 0){
|
|
||||||
EtcCellView(title: "공지사항") {
|
|
||||||
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 공지사항 이동
|
|
||||||
printLog("공지사항 이동")
|
|
||||||
}
|
|
||||||
DashedDivider()
|
|
||||||
EtcCellView(title: "1:1 문의") {
|
|
||||||
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 1:1 문의 이동
|
|
||||||
printLog("1:1 문의 이동")
|
|
||||||
}
|
|
||||||
DashedDivider()
|
|
||||||
EtcCellView(title: "자주 묻는 질문") {
|
|
||||||
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 자주 묻는 질문 이동
|
|
||||||
printLog("자주 묻는 질문 이동")
|
|
||||||
}
|
|
||||||
DashedDivider()
|
|
||||||
EtcCellView(title: "학원 정보") {
|
|
||||||
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 학원 정보 이동
|
|
||||||
printLog("학원 정보 이동")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
//
|
|
||||||
// DevInfoView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/13/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct DevInfoView: View {
|
|
||||||
|
|
||||||
private var attributeText: AttributedString {
|
|
||||||
let longText = """
|
|
||||||
아래 정보는 해당 프로그램에 관한 정보로서
|
|
||||||
학원에 대한 문의는 더보기 > 고객센터 > 1:1 문의를 이용해주시기 바랍니다.
|
|
||||||
"""
|
|
||||||
var attributeText = AttributedString(longText)
|
|
||||||
attributeText.font = .nps(size: 8)
|
|
||||||
if let range = attributeText.range(of: "더보기 > 고객센터 > 1:1 문의") {
|
|
||||||
attributeText[range].font = .nps(font: .bold, size: 8)
|
|
||||||
}
|
|
||||||
return attributeText
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 24) {
|
|
||||||
|
|
||||||
Text(attributeText)
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
.multilineStyle(.center, limit: 2)
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 12){
|
|
||||||
HStack(alignment: .center, spacing: 8) {
|
|
||||||
Image(.Logo.appIcon).resizable()
|
|
||||||
.frame(width: 40, height: 40,alignment: .leading)
|
|
||||||
Text("AcaMate")
|
|
||||||
.font(.nps(font: .bold, size: 16))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
}
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 이부분에 대한 내용은 후에 바꿀 예정
|
|
||||||
Text(verbatim:"""
|
|
||||||
문의: sean.kk@daum.net
|
|
||||||
문의: sean.kk@daum.net
|
|
||||||
""")
|
|
||||||
.font(.nps(size: 12))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Text("Copyright © Team.Stein. All Rights Reserved")
|
|
||||||
.font(.nps(size: 14))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
|
|
||||||
// .padding([.leading,.trailing],24)
|
|
||||||
}
|
|
||||||
.padding(24)
|
|
||||||
// .padding([.top,.bottom],24)
|
|
||||||
.background {
|
|
||||||
Color(.Disable.normal)
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
//
|
|
||||||
// EtcBoxView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/13/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct EtcBoxView<Content: View>: View {
|
|
||||||
let title: String
|
|
||||||
|
|
||||||
@ViewBuilder let content: Content
|
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(spacing: 24) {
|
|
||||||
Text("\(title)")
|
|
||||||
.font(.nps(font: .bold, size: 24))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
content
|
|
||||||
.padding([.leading,.trailing],24)
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 8)
|
|
||||||
.foregroundStyle(Color(.Other.cell))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct EtcCellView: View {
|
|
||||||
@StateObject var btnVM = ButtonViewModel()
|
|
||||||
let nextBtnID = UUID()
|
|
||||||
let title: String
|
|
||||||
let action: VOID_TO_VOID
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Text("\(title)")
|
|
||||||
.font(.nps(font: .bold, size: 20))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
SimpleBtnView(vm: btnVM, id: nextBtnID)
|
|
||||||
.padding([.top,.bottom],24)
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
btnVM.setSize(for: nextBtnID, newWidth: 24, newHeight: 24)
|
|
||||||
btnVM.setImage(for: nextBtnID, newImage: Image(.Icon.right))
|
|
||||||
btnVM.setAction(for: nextBtnID, newAction: action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DashedDivider: View {
|
|
||||||
var body: some View {
|
|
||||||
Rectangle()
|
|
||||||
.stroke(style: StrokeStyle(lineWidth: 1, dash: [5, 3]))
|
|
||||||
.foregroundColor(Color.Disable.normal)
|
|
||||||
.frame(height: 1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,46 +8,8 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct EtcView: View {
|
struct EtcView: View {
|
||||||
@StateObject private var topVM = TopViewModel()
|
|
||||||
@State private var scrollOffset: CGPoint = .zero
|
|
||||||
|
|
||||||
@Binding var myType: UserType
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
Text("더보기")
|
||||||
TopView(topVM: topVM)
|
|
||||||
|
|
||||||
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
|
|
||||||
VStack(spacing: 24) {
|
|
||||||
UserInfoView(userData: SummaryUser(profile: Image(.Icon.face), name: "이름", userID: "abcdefg", email: "abcdefg@gmail.com"))
|
|
||||||
UserSettingView(myType: myType)
|
|
||||||
CsCenterView()
|
|
||||||
TsCsView()
|
|
||||||
AppInfoView()
|
|
||||||
}
|
|
||||||
.padding(24)
|
|
||||||
DevInfoView()
|
|
||||||
|
|
||||||
// Rectangle()
|
|
||||||
// .foregroundStyle(Color(.Disable.normal))
|
|
||||||
// .frame(height: 500)
|
|
||||||
// .frame(maxWidth: .infinity)
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
topVM.titleName = ""
|
|
||||||
topVM.setLeftBtn(size: CGPoint(x: 40, y: 40), action: leftAct)
|
|
||||||
topVM.setRightBtn(size: CGPoint(x: 40, y: 40), action: rightAct)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func leftAct() {
|
|
||||||
printLog("왼쪽 버튼 클릭")
|
|
||||||
}
|
|
||||||
func rightAct() {
|
|
||||||
printLog("오른쪽 버튼 클릭")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
//
|
|
||||||
// TsCsView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/13/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct TsCsView: View {
|
|
||||||
var body: some View {
|
|
||||||
EtcBoxView(title: "약관"){
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
EtcCellView(title: "이용약관") {
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 이용약관 이동
|
|
||||||
printLog("이용약관 이동")
|
|
||||||
}
|
|
||||||
DashedDivider()
|
|
||||||
EtcCellView(title: "개인정보 처리방침") {
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 개인정보 처리방침 이동
|
|
||||||
printLog("개인정보 처리방침 이동")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 이용약관 개인정보 처리방침
|
|
|
@ -1,71 +0,0 @@
|
||||||
//
|
|
||||||
// UserInfoView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/13/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct UserInfoView: View {
|
|
||||||
@StateObject var btnVM = ButtonViewModel()
|
|
||||||
|
|
||||||
@State var notifyBtnID = UUID()
|
|
||||||
@State var userData: SummaryUser
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(spacing: 8 ) {
|
|
||||||
HStack(spacing: 12) {
|
|
||||||
userData.profile.resizable()
|
|
||||||
.frame(width: 30, height: 30, alignment: .center)
|
|
||||||
Text("\(userData.name)")
|
|
||||||
.font(.nps(font: .bold, size: 20))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
VStack(alignment: .center, spacing: 0) {
|
|
||||||
SimpleBtnView(vm: btnVM, id: notifyBtnID)
|
|
||||||
Text("알림설정")
|
|
||||||
.font(.nps(font: .bold, size: 8))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VStack(spacing: 10) {
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Text("ID")
|
|
||||||
.font(.nps(font: .bold, size: 16))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
Text("\(userData.userID)")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
}
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Text("E-mail")
|
|
||||||
.font(.nps(font: .bold, size: 16))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
Spacer(minLength: 1)
|
|
||||||
Text("\(userData.email)")
|
|
||||||
.font(.nps(size: 16))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
.multilineStyle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(10)
|
|
||||||
}
|
|
||||||
.padding(12)
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 8)
|
|
||||||
.foregroundStyle(Color(.Other.cell))
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
btnVM.setImage(for: notifyBtnID, newImage: Image(.Icon.notificationSET))
|
|
||||||
btnVM.setSize(for: notifyBtnID, newWidth: 24, newHeight: 24)
|
|
||||||
btnVM.setAction(for: notifyBtnID) {
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 알림 설정 페이지로 이동
|
|
||||||
printLog("알림 설정 페이지")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
//
|
|
||||||
// UserSettingView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/13/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct UserSettingView: View {
|
|
||||||
var myType: UserType
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
EtcBoxView(title: "설정") {
|
|
||||||
VStack(spacing: 0){
|
|
||||||
EtcCellView(title: "정보 변경") {
|
|
||||||
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 정보 변경 이동
|
|
||||||
printLog("정보 변경 이동")
|
|
||||||
}
|
|
||||||
DashedDivider()
|
|
||||||
EtcCellView(title: "계정 관리") {
|
|
||||||
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 계정 관리 이동
|
|
||||||
printLog("계정 관리 이동")
|
|
||||||
}
|
|
||||||
if myType == .Admin {
|
|
||||||
DashedDivider()
|
|
||||||
EtcCellView(title: "관리자 페이지") {
|
|
||||||
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 이거 분기 쳐서 특정 사용자 아니면 접근 못하게 막기
|
|
||||||
|
|
||||||
printLog("관리자 페이지 이동")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ struct BottomView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
|
.padding(EdgeInsets(top: 12, leading: 24, bottom: 0, trailing: 24))
|
||||||
|
|
||||||
.background {
|
.background {
|
||||||
Rectangle()
|
Rectangle()
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
//
|
|
||||||
// EmptyBoxView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/14/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct EmptyBoxView: View {
|
|
||||||
let title: String
|
|
||||||
var body: some View {
|
|
||||||
Text("\(title)")
|
|
||||||
.font(.nps(size: 20))
|
|
||||||
.foregroundStyle(Color(.Text.detail))
|
|
||||||
.multilineStyle()
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.padding([.top,.bottom],12)
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 4)
|
|
||||||
.stroke(Color(.Second.normal), lineWidth: 2)
|
|
||||||
.fill(Color(.Second.light))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,10 +10,9 @@ import Combine
|
||||||
|
|
||||||
struct MainView: View {
|
struct MainView: View {
|
||||||
@EnvironmentObject var appVM: AppViewModel
|
@EnvironmentObject var appVM: AppViewModel
|
||||||
|
@EnvironmentObject var alertController: AlertController
|
||||||
@State var cancellables: Set<AnyCancellable> = []
|
@State var cancellables: Set<AnyCancellable> = []
|
||||||
// @Binding var naviState : NaviState
|
@Binding var naviState : NaviState
|
||||||
|
|
||||||
@State private var myType: UserType = .Admin
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
|
@ -21,18 +20,18 @@ struct MainView: View {
|
||||||
Group {
|
Group {
|
||||||
switch appVM.menuName {
|
switch appVM.menuName {
|
||||||
case .Home:
|
case .Home:
|
||||||
HomeView(myType: $myType)
|
HomeView()
|
||||||
case .Management:
|
case .Management:
|
||||||
ManagementView()
|
ManagementView()
|
||||||
case .Chatting:
|
case .Chatting:
|
||||||
ChattingView(appVM, $myType)
|
ChattingView()
|
||||||
case .Calendar:
|
case .Calendar:
|
||||||
CalendarView()
|
CalendarView()
|
||||||
case .Etc:
|
case .Etc:
|
||||||
EtcView(myType: $myType)
|
EtcView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Spacer(minLength: 1)
|
Spacer(minLength: 1)
|
||||||
|
|
||||||
BottomView()
|
BottomView()
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
|
@ -8,36 +8,37 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct TopView: View {
|
struct TopView: View {
|
||||||
@ObservedObject var topVM: TopViewModel
|
@StateObject var btnVM = ButtonViewModel()
|
||||||
|
|
||||||
|
@State var titleName: String = ""
|
||||||
|
|
||||||
|
@State private var leftBtnID = UUID()
|
||||||
|
@State private var rightBtnID = UUID()
|
||||||
|
|
||||||
|
//MARK: - 변경 값
|
||||||
|
var myType: UserType = .Student
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center, spacing: 12) {
|
HStack(alignment: .center, spacing: 0) {
|
||||||
SimpleBtnView(vm: topVM.btnVM, id: topVM.leftBtnID)
|
SimpleBtnView(vm: btnVM, id: leftBtnID)
|
||||||
.background {
|
.background {
|
||||||
if let state = topVM.btnVM.btnStates[topVM.leftBtnID], state.image == nil, state.text != nil {
|
if let state = btnVM.btnStates[leftBtnID], state.image == nil {
|
||||||
Circle()
|
Circle()
|
||||||
.strokeBorder(Color(.Second.normal) ,lineWidth: 4)
|
.strokeBorder(Color(.Second.normal) ,lineWidth: 4)
|
||||||
.frame(width: state.width, height: state.height)
|
.frame(width: 40, height: 40)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 0))
|
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 12))
|
||||||
|
|
||||||
Text("\(topVM.titleName)")
|
|
||||||
|
Text("\(titleName)")
|
||||||
.foregroundStyle(Color(.Text.detail))
|
.foregroundStyle(Color(.Text.detail))
|
||||||
.font(.nps(font: .bold, size: 20))
|
.font(.nps(font: .bold, size: 20))
|
||||||
.frame(height: 40)
|
Spacer()
|
||||||
.padding(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12))
|
|
||||||
Spacer(minLength: 1)
|
SimpleBtnView(vm: btnVM, id: rightBtnID)
|
||||||
|
.padding(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 24))
|
||||||
|
|
||||||
SimpleBtnView(vm: topVM.btnVM, id: topVM.rightBtnID)
|
|
||||||
.background {
|
|
||||||
if let state = topVM.btnVM.btnStates[topVM.rightBtnID], state.image == nil, state.text != nil {
|
|
||||||
Circle()
|
|
||||||
.strokeBorder(Color(.Second.normal) ,lineWidth: 4)
|
|
||||||
.frame(width: state.width, height: state.height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(EdgeInsets(top: 12, leading: 0, bottom: 12, trailing: 24))
|
|
||||||
}
|
}
|
||||||
.background {
|
.background {
|
||||||
Rectangle()
|
Rectangle()
|
||||||
|
@ -46,9 +47,48 @@ struct TopView: View {
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
btnVM.btnStates[leftBtnID] = ButtonState()
|
||||||
|
btnVM.btnStates[rightBtnID] = ButtonState()
|
||||||
|
|
||||||
|
btnVM.setSize(for: leftBtnID, newWidth: 40, newHeight: 40)
|
||||||
|
btnVM.setSize(for: rightBtnID, newWidth: 40, newHeight: 40)
|
||||||
|
|
||||||
|
if self.myType == .Student {
|
||||||
|
btnVM.setImage(for: leftBtnID, newImage: Image(.Icon.face))
|
||||||
|
} else {
|
||||||
|
btnVM.setText(for: leftBtnID,
|
||||||
|
newText: "\(myType.rawValue)",
|
||||||
|
newFont: .nps(font: .bold, size: 24))
|
||||||
|
}
|
||||||
|
btnVM.setImage(for: rightBtnID, newImage: Image(.Icon.notificationSET))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//struct TypeIcon: View {
|
||||||
|
// var myType: UserType
|
||||||
|
//
|
||||||
|
// var body: some View {
|
||||||
|
|
||||||
|
// if self.myType == .Student {
|
||||||
|
|
||||||
|
// SimpleBtnView(image: Image(.Icon.face), title: nil, font: nil, width: 40, height: 40)
|
||||||
|
// } else {
|
||||||
|
// SimpleBtnView(image: nil, title: "\(self.myType.rawValue)", font: .nps(font: .bold, size: 24), width: 40, height: 40)
|
||||||
|
// .doAction {
|
||||||
|
// printLog("CHECK!!!")
|
||||||
|
// }
|
||||||
|
// .setTextColor(.red)
|
||||||
|
// .setIsUsable(false)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//#Preview {
|
||||||
|
// TopView(titleName: "Name")
|
||||||
|
//}
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
//
|
|
||||||
// API Request.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 3/18/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Alamofire
|
|
||||||
|
|
||||||
public struct APIRequest<T: APIResponseProtocol> {
|
|
||||||
let url, path: String
|
|
||||||
let method: HTTPMethod
|
|
||||||
var parameters: [String: Any]
|
|
||||||
let headers: HTTPHeaders
|
|
||||||
let decoding: T.Type
|
|
||||||
|
|
||||||
init(url: String = "\(API_URL)", path: String,
|
|
||||||
method: HTTPMethod = .get, headers: HTTPHeaders = [:],
|
|
||||||
parameters: [String : Any] = [:], decoding: T.Type) {
|
|
||||||
self.url = url
|
|
||||||
self.path = path
|
|
||||||
self.method = method
|
|
||||||
self.headers = headers
|
|
||||||
self.parameters = parameters
|
|
||||||
self.decoding = decoding
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,98 +6,30 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
public protocol APIResponseProtocol: Decodable {
|
|
||||||
var status: Status { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
class APIResponse<T: Codable>: Codable, APIResponseProtocol {
|
class APIResponse<T: Codable>: Codable {
|
||||||
let status: Status
|
let status: Status
|
||||||
let data: T?
|
let data: T
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Status: Codable {
|
class Status: Codable {
|
||||||
let code: APICode
|
let code: String
|
||||||
let message: String
|
let message: String
|
||||||
}
|
}
|
||||||
|
|
||||||
enum APICode: Codable, RawRepresentable {
|
// ----------------
|
||||||
case success(String)
|
|
||||||
case inputErr(String)
|
|
||||||
case outputErr(String)
|
|
||||||
case networkErr(String)
|
|
||||||
case unknownErr(String)
|
|
||||||
case anything(String)
|
|
||||||
|
|
||||||
var rawValue: String {
|
|
||||||
switch self {
|
|
||||||
case .success(let value),
|
|
||||||
.inputErr(let value),
|
|
||||||
.outputErr(let value),
|
|
||||||
.networkErr(let value),
|
|
||||||
.unknownErr(let value),
|
|
||||||
.anything(let value):
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init?(rawValue: String){
|
|
||||||
if rawValue.hasPrefix("0") {self = .success(rawValue)}
|
|
||||||
else if rawValue.hasPrefix("1") {self = .inputErr(rawValue)}
|
|
||||||
else if rawValue.hasPrefix("2") {self = .outputErr(rawValue)}
|
|
||||||
else if rawValue.hasPrefix("3") {self = .networkErr(rawValue)}
|
|
||||||
else if rawValue == "999" {self = .unknownErr(rawValue)}
|
|
||||||
else { self = .anything(rawValue)}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
|
||||||
let container = try decoder.singleValueContainer()
|
|
||||||
let rawValue = try container.decode(String.self)
|
|
||||||
self = APICode(rawValue: rawValue) ?? .anything(rawValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(to encoder: Encoder) throws {
|
|
||||||
var container = encoder.singleValueContainer()
|
|
||||||
try container.encode(self.rawValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// /api/v1/in/app ----------------
|
|
||||||
class Header: Codable {
|
|
||||||
let header: String
|
|
||||||
}
|
|
||||||
|
|
||||||
// /api/v1/in/app/version ----------------
|
|
||||||
class VersionData: Codable {
|
class VersionData: Codable {
|
||||||
let os_type, final_ver, dev_ver, force_ver: String
|
let os_type, final_ver, dev_ver, force_ver: String
|
||||||
let choice_update_yn: Bool
|
let choice_update_yn: Bool
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// /api/v1/in/app/retryAccess ----------------
|
// ----------------
|
||||||
class Access: Codable {
|
|
||||||
let access: String
|
|
||||||
}
|
|
||||||
|
|
||||||
// /api/v1/in/user ----------------
|
class User_Academy: Codable {
|
||||||
class User: Codable {
|
let uid: String
|
||||||
let uid, name, type, login_date: String
|
let bid: [String]
|
||||||
let device_id, push_token, birth: String?
|
|
||||||
let auto_login_yn: Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// /api/v1/in/user/login ----------------
|
|
||||||
// /api/v1/in/user/register ----------------
|
|
||||||
class User_Token: Codable {
|
|
||||||
let token: String?
|
|
||||||
let refresh: String?
|
|
||||||
// let bids: [String]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// /api/v1/in/member/academy ----------------
|
|
||||||
class AcademyName: Codable {
|
|
||||||
let bid: String?
|
|
||||||
let name: String?
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -82,18 +82,6 @@ struct SetAlertData {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 서버에서 발생한 크리티컬한 오류 - 앱 종료가 최선
|
|
||||||
func setServerError(action: CurrentValueSubject<String?, Never>) -> AlertData {
|
|
||||||
return AlertData(title: "시스템 오류", body: "시스템이 정상적이지 않습니다. \n확인 후 다시 시도해주세요.",
|
|
||||||
button: [
|
|
||||||
ButtonType(name: "확인", role: .cancel,
|
|
||||||
function: {
|
|
||||||
printLog("alertAction 'exit' send 실행됨")
|
|
||||||
action.send("exit")
|
|
||||||
})
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 로그인 문제 발생
|
/// 로그인 문제 발생
|
||||||
func setErrorLogin() -> AlertData {
|
func setErrorLogin() -> AlertData {
|
||||||
return AlertData(title: "로그인",
|
return AlertData(title: "로그인",
|
||||||
|
|
|
@ -10,7 +10,7 @@ import SwiftUI
|
||||||
struct ButtonState {
|
struct ButtonState {
|
||||||
var image: Image? = nil
|
var image: Image? = nil
|
||||||
|
|
||||||
var text: String? = nil
|
var title: String? = nil
|
||||||
var font: Font? = nil
|
var font: Font? = nil
|
||||||
|
|
||||||
var width: CGFloat = 0
|
var width: CGFloat = 0
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
//
|
|
||||||
// Chat Data.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/14/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
enum chatType {
|
|
||||||
case Class
|
|
||||||
case Student
|
|
||||||
case Parent
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SummaryChat {
|
|
||||||
var id: String
|
|
||||||
|
|
||||||
var chatName: String
|
|
||||||
var teacherName: String
|
|
||||||
var lastMessage: String
|
|
||||||
var dayDate: String
|
|
||||||
var timeDate: String
|
|
||||||
|
|
||||||
var notiState: Bool
|
|
||||||
|
|
||||||
var groupNum: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ChatMesage {
|
|
||||||
var roomID: String
|
|
||||||
var chatID: String
|
|
||||||
var senderID: String
|
|
||||||
var message: String
|
|
||||||
var sendTime: String
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
//
|
|
||||||
// CustomError.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 3/14/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class ACA_ERROR: Error {
|
|
||||||
let message: String
|
|
||||||
|
|
||||||
init(_ msg: String) {
|
|
||||||
message = msg
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,6 +13,8 @@ struct NaviState: Equatable {
|
||||||
var act: NaviAction
|
var act: NaviAction
|
||||||
var path: PathName
|
var path: PathName
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static func == (lhs: NaviState, rhs: NaviState) -> Bool {
|
static func == (lhs: NaviState, rhs: NaviState) -> Bool {
|
||||||
return lhs.act == rhs.act && lhs.path == rhs.path
|
return lhs.act == rhs.act && lhs.path == rhs.path
|
||||||
}
|
}
|
||||||
|
@ -42,10 +44,8 @@ enum NaviAction: Hashable {
|
||||||
enum PathName: Hashable {
|
enum PathName: Hashable {
|
||||||
case Intro
|
case Intro
|
||||||
case Login
|
case Login
|
||||||
case Register(_ type: SNSLoginType, id: String)
|
|
||||||
case SelectAcademy
|
|
||||||
case Main
|
case Main
|
||||||
case ChatRoom(id: String)
|
|
||||||
case NONE
|
case NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,9 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
enum SNSLoginType: String{
|
enum SNSLoginType{
|
||||||
case Apple = "ST00"
|
case Kakao
|
||||||
case Kakao = "ST01"
|
case Apple
|
||||||
case Dev = "ST02"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SNSID: Codable {
|
struct SNSID: Codable {
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
//
|
|
||||||
// UserType.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/5/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
enum UserType: String {
|
|
||||||
case Admin
|
|
||||||
case Employee
|
|
||||||
case Student
|
|
||||||
case Teacher
|
|
||||||
case Parent
|
|
||||||
case ETC
|
|
||||||
|
|
||||||
var code: String {
|
|
||||||
switch self {
|
|
||||||
case .Admin: return "UT00"
|
|
||||||
case .Employee: return "UT01"
|
|
||||||
case .Student: return "UT02"
|
|
||||||
case .Teacher: return "UT03"
|
|
||||||
case .Parent: return "UT04"
|
|
||||||
case .ETC: return "UT05"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var name: String {
|
|
||||||
switch self {
|
|
||||||
case .Admin: return "A"
|
|
||||||
case .Employee: return "E"
|
|
||||||
case .Student: return "S"
|
|
||||||
case .Teacher: return "T"
|
|
||||||
case .Parent: return "P"
|
|
||||||
case .ETC: return "V"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SummaryUser {
|
|
||||||
var profile: Image
|
|
||||||
var name: String
|
|
||||||
var userID: String
|
|
||||||
var email: String
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RegisterUserInfo {
|
|
||||||
var name: String
|
|
||||||
var birth: Date
|
|
||||||
var type: String = "UT05"
|
|
||||||
var device_id: String // 진짜 디바이스 아이디
|
|
||||||
var auto_login_yn: String
|
|
||||||
var push_token: String // APNs 용 토큰
|
|
||||||
var email: String
|
|
||||||
var phone: String
|
|
||||||
var address: String
|
|
||||||
var sns_id: String
|
|
||||||
var sns_type: String
|
|
||||||
var location_yn, camera_yn, photo_yn, push_yn: Bool?
|
|
||||||
var market_app_yn, market_sms_yn, market_email_yn: Bool?
|
|
||||||
}
|
|
17
AcaMate/2. Model/User Type.swift
Normal file
17
AcaMate/2. Model/User Type.swift
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
//
|
||||||
|
// UserType.swift
|
||||||
|
// AcaMate
|
||||||
|
//
|
||||||
|
// Created by TAnine on 2/5/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum UserType: String {
|
||||||
|
case Student = "S"
|
||||||
|
case Parent = "P"
|
||||||
|
case Teacher = "T"
|
||||||
|
case Admin = "A"
|
||||||
|
case Employee = "E"
|
||||||
|
case ETC = "V"
|
||||||
|
}
|
19
AcaMate/3. ViewModel/AlertViewModel.swift
Normal file
19
AcaMate/3. ViewModel/AlertViewModel.swift
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// AlertController.swift
|
||||||
|
// AcaMate
|
||||||
|
//
|
||||||
|
// Created by Sean Kim on 12/13/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
|
||||||
|
class AlertViewModel2: ObservableObject {
|
||||||
|
@Published var showAlert: Bool = false
|
||||||
|
var alertData: AlertData = .init(body: "")
|
||||||
|
|
||||||
|
let alertAction = CurrentValueSubject<String?, Never>(nil)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -7,30 +7,14 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Combine
|
import Combine
|
||||||
//
|
|
||||||
//import AVFoundation
|
|
||||||
//import Photos
|
|
||||||
//import CoreLocation
|
|
||||||
//import UserNotifications
|
|
||||||
|
|
||||||
class AppViewModel: ObservableObject {
|
class AppViewModel: ObservableObject {
|
||||||
// public static let shared = AppViewModel()
|
|
||||||
|
|
||||||
@Published var isLoading: Bool = false
|
@Published var isLoading: Bool = false
|
||||||
@Published var showAlert: Bool = false
|
@Published var showAlert: Bool = false
|
||||||
@Published var menuName: MenuName = .Home
|
@Published var menuName: MenuName = .Home
|
||||||
@Published var naviState: NaviState = .init(act: .NONE, path: .Intro)
|
|
||||||
|
|
||||||
@Published var alertData: AlertData = .init(body: "")
|
var alertData: AlertData = .init(body: "")
|
||||||
|
|
||||||
/// 항상 최신값을 가지고 있다가 구독자 추가 되면 그 즉시 값을 전달하고 이후 업데이트 되는 값을 계속 보내주는 역할을 함
|
|
||||||
let alertAction = CurrentValueSubject<String?, Never>(nil)
|
let alertAction = CurrentValueSubject<String?, Never>(nil)
|
||||||
var apiManager: APIManager = APIManager()
|
|
||||||
var permissionManager = PermissionManager()
|
|
||||||
|
|
||||||
// init() {
|
|
||||||
// permissionManager.location
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ class ButtonViewModel: ObservableObject {
|
||||||
func setText(for id: UUID, newText: String?, newFont: Font?) {
|
func setText(for id: UUID, newText: String?, newFont: Font?) {
|
||||||
var state = btnStates[id] ?? ButtonState()
|
var state = btnStates[id] ?? ButtonState()
|
||||||
|
|
||||||
state.text = newText
|
state.title = newText
|
||||||
state.font = newFont
|
state.font = newFont
|
||||||
|
|
||||||
btnStates[id] = state
|
btnStates[id] = state
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
//
|
|
||||||
// ChatViewModel.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/17/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
class ChatViewModel: ObservableObject {
|
|
||||||
private let appVM: AppViewModel
|
|
||||||
|
|
||||||
@Published var messages: [ChatMesage] = []
|
|
||||||
|
|
||||||
init(_ appVM: AppViewModel) {
|
|
||||||
self.appVM = appVM
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,164 +0,0 @@
|
||||||
//
|
|
||||||
// IntroViewModel.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 3/20/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Combine
|
|
||||||
|
|
||||||
|
|
||||||
class IntroViewModel: ObservableObject {
|
|
||||||
private let appVM: AppViewModel
|
|
||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
|
||||||
|
|
||||||
@UserDefault(key: "header", defaultValue: "headerValue") var headerValue
|
|
||||||
|
|
||||||
init(_ appVM: AppViewModel) {
|
|
||||||
self.appVM = appVM
|
|
||||||
}
|
|
||||||
|
|
||||||
func appStart() {
|
|
||||||
subscribeAlertAction()
|
|
||||||
searchHeader()
|
|
||||||
.flatMap { success -> Future<VersionData, Error> in
|
|
||||||
return self.loadVersion()
|
|
||||||
}
|
|
||||||
.sink { [weak self] completion in
|
|
||||||
guard let self = self else {return}
|
|
||||||
switch completion {
|
|
||||||
case .failure(let error):
|
|
||||||
// 만약 여기서 에러가 난다면 서버에 문제가 있다는 것
|
|
||||||
printLog(error)
|
|
||||||
self.appVM.alertData = SetAlertData().setServerError(action: appVM.alertAction)
|
|
||||||
self.appVM.showAlert.toggle()
|
|
||||||
case .finished: break
|
|
||||||
}
|
|
||||||
} receiveValue: { [weak self] version in
|
|
||||||
guard let self = self else {return}
|
|
||||||
@UserDefault(key:"currentVer", defaultValue: "0.0.0") var currentVer
|
|
||||||
@UserDefault(key:"finalVer", defaultValue: "0.0.0") var finalVer
|
|
||||||
currentVer = currentVersion()
|
|
||||||
finalVer = version.final_ver
|
|
||||||
|
|
||||||
let compareForce = compareVersion(version.force_ver, currentVer)//currentVersion())
|
|
||||||
let compareChoice = compareVersion(version.final_ver, currentVer)//currentVersion())
|
|
||||||
|
|
||||||
if compareForce == .bigger {
|
|
||||||
appVM.alertData = SetAlertData().setForceUpdate(
|
|
||||||
action: appVM.alertAction
|
|
||||||
)
|
|
||||||
appVM.showAlert.toggle()
|
|
||||||
} else if compareChoice == .bigger && version.choice_update_yn {
|
|
||||||
appVM.alertData = SetAlertData().setSelectUpdate(
|
|
||||||
action: appVM.alertAction
|
|
||||||
)
|
|
||||||
appVM.showAlert.toggle()
|
|
||||||
} else {
|
|
||||||
// 정상 동작 넘어감
|
|
||||||
appVM.naviState.set(act: .RESET, path: .Login)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
|
|
||||||
func searchHeader() -> Future<Bool, Error> {
|
|
||||||
return Future { [weak self] promise in
|
|
||||||
|
|
||||||
guard let self = self else {
|
|
||||||
promise(.failure(ACA_ERROR("Self 전환 오류")))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let deviceId = UIDevice.current.identifierForVendor?.uuidString,
|
|
||||||
let bundleId = Bundle.main.bundleIdentifier else {
|
|
||||||
promise(.failure(ACA_ERROR("번들/디바이스 아이디 조회 불량")))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = APIRequest(path: "/api/v1/in/app",
|
|
||||||
parameters: ["type": "I", "specific": deviceId, "project": bundleId],
|
|
||||||
decoding: APIResponse<Header>.self)
|
|
||||||
appVM.apiManager.loadAPIData(request)
|
|
||||||
.sink { completion in
|
|
||||||
switch completion {
|
|
||||||
case .failure(let error):
|
|
||||||
printLog("최종 에러: \(error)")
|
|
||||||
promise(.failure(error))
|
|
||||||
|
|
||||||
case .finished:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} receiveValue: { [weak self] response in
|
|
||||||
guard let self = self else {return}
|
|
||||||
if let data = response.data {
|
|
||||||
self.headerValue = data.header
|
|
||||||
promise(.success(true))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
promise(.failure(ACA_ERROR("데이터 없음")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &self.cancellables)
|
|
||||||
}
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private func subscribeAlertAction() {
|
|
||||||
appVM.alertAction
|
|
||||||
.compactMap { $0 }
|
|
||||||
.sink { [weak self] action in
|
|
||||||
guard let self = self else {return}
|
|
||||||
if action == "exit" {
|
|
||||||
exit(1)
|
|
||||||
} else if action == "updateNow" {
|
|
||||||
exit(1)
|
|
||||||
//MARK: - TODO (앱스토어 이동 로직 넣을 것)
|
|
||||||
} else {
|
|
||||||
appVM.naviState.set(act: .RESET, path: .Login)
|
|
||||||
}
|
|
||||||
}.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func loadVersion() -> Future<VersionData, Error> {
|
|
||||||
return Future { [weak self] promise in
|
|
||||||
guard let self = self else {return}
|
|
||||||
let request = APIRequest(path: "/api/v1/in/app/version",
|
|
||||||
headers: [API_HEADER : self.headerValue],
|
|
||||||
parameters: ["type":"I"],
|
|
||||||
decoding: APIResponse<VersionData>.self)
|
|
||||||
|
|
||||||
appVM.apiManager.loadAPIData(request)
|
|
||||||
.sink { completion in
|
|
||||||
switch completion {
|
|
||||||
case .failure(let error):
|
|
||||||
printLog("\(error)")
|
|
||||||
promise(.failure(error))
|
|
||||||
case .finished: break
|
|
||||||
}
|
|
||||||
} receiveValue: { versionData in
|
|
||||||
guard let version = versionData.data else {return}
|
|
||||||
printLog("\(version.toStringDict())")
|
|
||||||
promise(.success(version))
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private func currentVersion() -> String {
|
|
||||||
guard let currentVer = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { return "" }
|
|
||||||
return currentVer
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private func versionChange(ver: String) -> [Int] {
|
|
||||||
return ver.components(separatedBy: ["."]).map {Int($0) ?? 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -9,87 +9,5 @@ import SwiftUI
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
class LoginViewModel: ObservableObject {
|
class LoginViewModel: ObservableObject {
|
||||||
private let appVM: AppViewModel
|
let loginAction = CurrentValueSubject<Bool, Never>(false)
|
||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
|
||||||
|
|
||||||
// @Published var toggleLoading: Bool = false
|
|
||||||
// @Published var pathName: PathName = .NONE
|
|
||||||
|
|
||||||
@UserDefault(key: "token", defaultValue: "accToken") var accToken
|
|
||||||
@UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh
|
|
||||||
@UserDefault(key: "header", defaultValue: "headerValue") var headerValue
|
|
||||||
|
|
||||||
@Published var devId: String = ""
|
|
||||||
|
|
||||||
var bidArray: [String] = []
|
|
||||||
|
|
||||||
|
|
||||||
init(_ appVM: AppViewModel) {
|
|
||||||
self.appVM = appVM
|
|
||||||
}
|
|
||||||
|
|
||||||
func loginAction(type: SNSLoginType) {
|
|
||||||
appVM.isLoading = true
|
|
||||||
let acctype: String = type == .Apple ? "ST00": (type == .Kakao ? "ST01" : "ST02")
|
|
||||||
LoginController().login(type, devId)
|
|
||||||
.flatMap{ snsId in
|
|
||||||
self.appVM.apiManager.loadAPIData(
|
|
||||||
APIRequest(path: "/api/v1/in/user/login",
|
|
||||||
headers: [API_HEADER : self.headerValue],
|
|
||||||
parameters: [
|
|
||||||
"acctype": acctype,
|
|
||||||
"snsId": "\(snsId.snsId)"
|
|
||||||
],
|
|
||||||
decoding: APIResponse<User_Token>.self))
|
|
||||||
.map { response in
|
|
||||||
return (snsId: snsId.snsId, response: response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sink { [weak self] completion in
|
|
||||||
guard let self = self else { return }
|
|
||||||
// API 자체적으로 내보내는 에러는 여기서 거를거고
|
|
||||||
switch completion {
|
|
||||||
case .failure(let error):
|
|
||||||
self.appVM.isLoading = false
|
|
||||||
printLog("\(error)")
|
|
||||||
case .finished:
|
|
||||||
self.appVM.isLoading = false
|
|
||||||
}
|
|
||||||
} receiveValue: { [weak self] response in
|
|
||||||
guard let self = self else { return }
|
|
||||||
let snsId = response.snsId
|
|
||||||
switch response.response.status.code {
|
|
||||||
case .success(let code):
|
|
||||||
if code == "000" {
|
|
||||||
if let data = response.response.data,
|
|
||||||
let accToken = data.token,
|
|
||||||
let refresh = data.refresh {
|
|
||||||
printLog(accToken)
|
|
||||||
printLog(refresh)
|
|
||||||
|
|
||||||
self.accToken = accToken
|
|
||||||
self.refresh = refresh
|
|
||||||
|
|
||||||
appVM.naviState.set(act: .ADD, path: .SelectAcademy)
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 회원가입 진행
|
|
||||||
// 여기다가 타입도 추가해야 함
|
|
||||||
appVM.naviState.set(act: .ADD, path: .Register(type, id: "\(snsId)"))
|
|
||||||
}
|
|
||||||
case .anything(let apiCode):
|
|
||||||
// MARK: TO-DO
|
|
||||||
// 이거도 걍 에러인데? 에러 처리 로직으로
|
|
||||||
printLog("\(apiCode) : 로그인 정보 없음")
|
|
||||||
printLog("ERROR")
|
|
||||||
// self.pathName = .Register(type, id: "\(id)")
|
|
||||||
default:
|
|
||||||
// 그외에 서버에서 처리를 하다가 문제가 생겨서 발생하는 에러는 여기로 보낼거임
|
|
||||||
printLog("ERROR")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,168 +0,0 @@
|
||||||
//
|
|
||||||
// RegisterViewModel.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 3/24/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Combine
|
|
||||||
|
|
||||||
class RegisterViewModel: ObservableObject {
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
|
||||||
private let appVM: AppViewModel
|
|
||||||
private let responseValue: (SNSLoginType, String)
|
|
||||||
|
|
||||||
init(_ appVM: AppViewModel, type: SNSLoginType, snsID: String) {
|
|
||||||
self.appVM = appVM
|
|
||||||
self.responseValue = (type, snsID)
|
|
||||||
}
|
|
||||||
|
|
||||||
@UserDefault(key: "token", defaultValue: "accToken") var accToken
|
|
||||||
@UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh
|
|
||||||
@UserDefault(key: "header", defaultValue: "headerValue") var headerValue
|
|
||||||
@UserDefault(key: "pushToken", defaultValue: "pushToken") var pushToken
|
|
||||||
|
|
||||||
let addressBtnID = UUID()
|
|
||||||
let registerBtnID = UUID()
|
|
||||||
let policyBtn1ID = UUID()
|
|
||||||
|
|
||||||
@Published var selectDate: Date = {
|
|
||||||
let calendar = Calendar.current
|
|
||||||
return calendar.date(byAdding: .year, value: 0, to: Date()) ?? Date()
|
|
||||||
}() {
|
|
||||||
didSet {
|
|
||||||
if selectDate != oldValue {
|
|
||||||
changeDate = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var changeDate: Bool = false
|
|
||||||
|
|
||||||
@Published var nameText: String = ""
|
|
||||||
@Published var emailFrontText: String = ""
|
|
||||||
@Published var emailTailText: String = ""
|
|
||||||
@Published var numberHead: String = ""
|
|
||||||
@Published var phoneTextSet: (String,String,String) = ("","","")
|
|
||||||
@Published var addressText: String = "주소 입력"
|
|
||||||
@Published var addrDetailText: String = ""
|
|
||||||
|
|
||||||
let numberHeadList = ["010","011","016","017","018","019"]
|
|
||||||
let emailTailList = ["gmail.com",
|
|
||||||
"naver.com",
|
|
||||||
"daum.net",
|
|
||||||
"hanmail.net",
|
|
||||||
"nate.com",
|
|
||||||
"outlook.com",
|
|
||||||
"icloud.com",
|
|
||||||
"kakao.com",
|
|
||||||
"yahoo.com",
|
|
||||||
"protonmail.com",
|
|
||||||
"직접 입력"]
|
|
||||||
|
|
||||||
func registerUser() async {
|
|
||||||
// 필수 값
|
|
||||||
if nameText != "" && emailFrontText != "" && emailTailText != "" && phoneTextSet.0 != "" && phoneTextSet.1 != "" && phoneTextSet.2 != "" {
|
|
||||||
var param: [String:Any] = [:]
|
|
||||||
|
|
||||||
param["name"] = "\(nameText)"
|
|
||||||
if changeDate { param["birth"] = "\(selectDate.convertString("yyyy-MM-dd"))"}
|
|
||||||
param["type"] = UserType.ETC.code //"UT05" // 일반 유저
|
|
||||||
|
|
||||||
if let deviceId = await UIDevice.current.identifierForVendor?.uuidString {
|
|
||||||
param["device_id"] = "\(deviceId)"
|
|
||||||
}
|
|
||||||
|
|
||||||
param["auto_login_yn"] = false
|
|
||||||
param["login_date"] = Date().convertString("yyyy-MM-dd'T'HH:mm:ss")
|
|
||||||
param["push_token"] = "\(pushToken)"
|
|
||||||
param["email"] = "\(emailFrontText)@\(emailTailText)"
|
|
||||||
|
|
||||||
param["phone"] = "\(phoneTextSet.0)\(phoneTextSet.1)\(phoneTextSet.2)"
|
|
||||||
if addressText != "주소 입력" {
|
|
||||||
if addrDetailText == "" { param["address"] = "\(self.addressText)" }
|
|
||||||
else { param["address"] = "\(self.addressText) \(self.addrDetailText)"}
|
|
||||||
}
|
|
||||||
param["location_yn"] = appVM.permissionManager.checkLocationPermission()
|
|
||||||
param["camera_yn"] = appVM.permissionManager.checkCameraPermission()
|
|
||||||
param["photo_yn"] = appVM.permissionManager.checkPhotoPermission()
|
|
||||||
appVM.permissionManager.checkPushPermission(completion: { status in
|
|
||||||
param["push_yn"] = status
|
|
||||||
})
|
|
||||||
|
|
||||||
param["market_app_yn"] = true
|
|
||||||
param["market_sms_yn"] = true
|
|
||||||
param["market_email_yn"] = true
|
|
||||||
|
|
||||||
param["sns_id"] = self.responseValue.1
|
|
||||||
param["sns_type"] = self.responseValue.0.rawValue
|
|
||||||
|
|
||||||
|
|
||||||
if !changeDate || addressText == "주소 입력" {
|
|
||||||
appVM.alertData = AlertData(
|
|
||||||
title: "알림",
|
|
||||||
body: "\(changeDate ? "":"[생일]")\((addressText != "주소 입력") ? "":"[주소]")의 내용이 없습니다.\n 계속해서 진행할까요?",
|
|
||||||
button: [
|
|
||||||
ButtonType(name: "돌아가기", role: .destructive, function: nil),
|
|
||||||
ButtonType(name: "가입하기", role: .cancel) {
|
|
||||||
self.callAPI(param)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
)
|
|
||||||
appVM.showAlert.toggle()
|
|
||||||
// 넘어가는 로직도 추가
|
|
||||||
} else {
|
|
||||||
self.callAPI(param)
|
|
||||||
//정상 동작
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
appVM.alertData = AlertData(
|
|
||||||
title: "경고", body: "필수 입력 사항이 누락되었습니다.",
|
|
||||||
button: [ButtonType(name: "확인", role: .cancel, function: nil)])
|
|
||||||
appVM.showAlert.toggle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func callAPI(_ param: [String: Any]) {
|
|
||||||
|
|
||||||
self.appVM.apiManager.loadAPIData(
|
|
||||||
APIRequest(path: "/api/v1/in/user/register",
|
|
||||||
method: .post,
|
|
||||||
headers: [API_HEADER : self.headerValue],
|
|
||||||
parameters: param,
|
|
||||||
decoding: APIResponse<User_Token>.self)
|
|
||||||
)
|
|
||||||
.sink { [weak self] completion in
|
|
||||||
guard let self = self else { return }
|
|
||||||
// API 자체적으로 내보내는 에러는 여기서 거를거고
|
|
||||||
switch completion {
|
|
||||||
case .failure(let error):
|
|
||||||
printLog("\(error)")
|
|
||||||
case .finished:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} receiveValue: { [weak self] response in
|
|
||||||
guard let self = self else { return }
|
|
||||||
switch response.status.code {
|
|
||||||
case .success(let code):
|
|
||||||
if code == "000" {
|
|
||||||
if let data = response.data, let accToken = data.token, let refresh = data.refresh{
|
|
||||||
self.accToken = accToken
|
|
||||||
self.refresh = refresh
|
|
||||||
|
|
||||||
appVM.naviState.set(act: .ADD, path: .SelectAcademy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
//
|
|
||||||
// SelectAcademyViewModel.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/19/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Combine
|
|
||||||
|
|
||||||
class SelectAcademyViewModel: ObservableObject {
|
|
||||||
private var appVM: AppViewModel
|
|
||||||
private var cancellables: Set<AnyCancellable> = []
|
|
||||||
|
|
||||||
init(_ appVM: AppViewModel) {
|
|
||||||
self.appVM = appVM
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var academyCode: String = ""
|
|
||||||
@Published var academyList: [AcademyName] = []
|
|
||||||
@Published var selectNum: Int = -1
|
|
||||||
|
|
||||||
func moveChatting() {
|
|
||||||
appVM.naviState.set(act: .RESET, path: .Main)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func loadAcademy() {
|
|
||||||
@UserDefault(key: "token", defaultValue: "accToken") var token
|
|
||||||
@UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh
|
|
||||||
|
|
||||||
let request = APIRequest(path: "/api/v1/in/user/academy",
|
|
||||||
parameters: ["token": token, "refresh": refresh],
|
|
||||||
decoding: APIResponse<[AcademyName]>.self)
|
|
||||||
|
|
||||||
appVM.apiManager.loadAPIData(request)
|
|
||||||
.sink { completion in
|
|
||||||
switch completion {
|
|
||||||
case .failure(let error):
|
|
||||||
printLog("\(error)")
|
|
||||||
case .finished:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} receiveValue: { [weak self] response in
|
|
||||||
guard let self = self else {return}
|
|
||||||
guard let academyNames = response as? APIResponse<[AcademyName]> else {return}
|
|
||||||
guard let academyList = academyNames.data else { return }
|
|
||||||
self.academyList = academyList
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toggleSelection(for index: Int){
|
|
||||||
if selectNum == index {
|
|
||||||
selectNum = -1
|
|
||||||
} else {
|
|
||||||
selectNum = index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
//
|
|
||||||
// TopViewModel.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/13/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
class TopViewModel: ObservableObject {
|
|
||||||
@Published var btnVM = ButtonViewModel()
|
|
||||||
|
|
||||||
@Published var titleName: String = ""
|
|
||||||
|
|
||||||
let leftBtnID = UUID()
|
|
||||||
let rightBtnID = UUID()
|
|
||||||
|
|
||||||
init() {
|
|
||||||
|
|
||||||
btnVM.btnStates[leftBtnID] = ButtonState()
|
|
||||||
btnVM.btnStates[rightBtnID] = ButtonState()
|
|
||||||
}
|
|
||||||
|
|
||||||
func setLeftBtn(_ image: Image? = nil, text: String? = nil, font: Font? = nil, size: CGPoint, action: @escaping VOID_TO_VOID) {
|
|
||||||
btnVM.setSize(for: leftBtnID, newWidth: size.x, newHeight: size.y)
|
|
||||||
if text != nil {
|
|
||||||
btnVM.setText(for: leftBtnID, newText: text, newFont: font)
|
|
||||||
} else if let image = image {
|
|
||||||
btnVM.setImage(for: leftBtnID, newImage: image)
|
|
||||||
}
|
|
||||||
btnVM.setAction(for: leftBtnID, newAction: action)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setRightBtn(_ image: Image? = nil, text: String? = nil, font: Font? = nil, size: CGPoint, action: @escaping VOID_TO_VOID) {
|
|
||||||
btnVM.setSize(for: rightBtnID, newWidth: size.x, newHeight: size.y)
|
|
||||||
if text != nil {
|
|
||||||
btnVM.setText(for: rightBtnID, newText: text, newFont: font)
|
|
||||||
} else if let image = image {
|
|
||||||
btnVM.setImage(for: rightBtnID, newImage: image)
|
|
||||||
}
|
|
||||||
btnVM.setAction(for: rightBtnID, newAction: action)
|
|
||||||
}
|
|
||||||
// 여기에 프린트 문만 찍어줘
|
|
||||||
}
|
|
37
AcaMate/4. Controller/APIController.swift
Normal file
37
AcaMate/4. Controller/APIController.swift
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// APIController.swift
|
||||||
|
// AcaMate
|
||||||
|
//
|
||||||
|
// Created by Sean Kim on 11/26/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
import Alamofire
|
||||||
|
|
||||||
|
public func loadAPIData<T: Decodable>(url: String, path: String,
|
||||||
|
method: HTTPMethod = .get,
|
||||||
|
parameters: [String: String],
|
||||||
|
headers: HTTPHeaders = [:],
|
||||||
|
decodingType: T.Type) -> Future<Any, Error> {
|
||||||
|
return Future { promise in
|
||||||
|
AF.request("\(url)\(path)",
|
||||||
|
method: method,
|
||||||
|
parameters: parameters,
|
||||||
|
headers: headers
|
||||||
|
)
|
||||||
|
.validate(statusCode: 200 ..< 300)
|
||||||
|
.responseString { response in
|
||||||
|
printLog(response)
|
||||||
|
}
|
||||||
|
.responseDecodable(of: decodingType) { response in
|
||||||
|
switch response.result {
|
||||||
|
case .success(let value):
|
||||||
|
promise(.success(value))
|
||||||
|
case .failure(let error):
|
||||||
|
promise(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,12 +11,13 @@ import KakaoSDKCommon
|
||||||
import KakaoSDKAuth
|
import KakaoSDKAuth
|
||||||
import KakaoSDKUser
|
import KakaoSDKUser
|
||||||
|
|
||||||
|
import Alamofire
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class LoginController {
|
class LoginController {
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
func login(_ type: SNSLoginType, _ id: String = "") -> AnyPublisher<SNSID,Error> {
|
func login(_ type: SNSLoginType) -> AnyPublisher<SNSID,Error> {
|
||||||
switch type {
|
switch type {
|
||||||
case .Kakao:
|
case .Kakao:
|
||||||
return self.checkKakaoToken()
|
return self.checkKakaoToken()
|
||||||
|
@ -28,21 +29,13 @@ class LoginController {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
//
|
||||||
|
|
||||||
case .Apple:
|
case .Apple:
|
||||||
return Fail(error: NSError(domain: "Apple login not implemented", code: 1, userInfo: nil))
|
return Fail(error: NSError(domain: "Apple login not implemented", code: 1, userInfo: nil))
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
case .Dev:
|
|
||||||
return Future<SNSID, Error> { promise in
|
|
||||||
var snsId = SNSID()
|
|
||||||
snsId.acctType = "dev"
|
|
||||||
snsId.snsId = "\(id)"
|
|
||||||
snsId.snsToken = "devToken"
|
|
||||||
promise(.success(snsId))
|
|
||||||
}
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +51,6 @@ class LoginController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .Apple: break
|
case .Apple: break
|
||||||
case .Dev: break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +132,7 @@ extension LoginController {
|
||||||
if let sdkError = error as? SdkError, sdkError.isInvalidTokenError() == true {
|
if let sdkError = error as? SdkError, sdkError.isInvalidTokenError() == true {
|
||||||
// 로그인이 필요
|
// 로그인이 필요
|
||||||
self.loginKakao()
|
self.loginKakao()
|
||||||
// 로그인 후 동작이 sink에서 처리 될것
|
// 로그인 후 동작이 sink에서 처리 될것
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
switch completion {
|
switch completion {
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
|
|
|
@ -1,134 +0,0 @@
|
||||||
//
|
|
||||||
// APIController.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by Sean Kim on 11/26/24.
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Combine
|
|
||||||
import Alamofire
|
|
||||||
|
|
||||||
public class APIManager {
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
|
||||||
// public static let shared = APIManager()
|
|
||||||
|
|
||||||
@UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh
|
|
||||||
@UserDefault(key: "token", defaultValue: "accToken") var accToken
|
|
||||||
|
|
||||||
// private init(cancellables: Set<AnyCancellable> = Set<AnyCancellable>()) {
|
|
||||||
// self.cancellables = cancellables
|
|
||||||
// }
|
|
||||||
|
|
||||||
public func loadAPIData<T: APIResponseProtocol>(_ request: APIRequest<T>) -> Future<T, Error> {
|
|
||||||
let encoding: ParameterEncoding = (request.method == .get) ? URLEncoding.default : JSONEncoding.default
|
|
||||||
|
|
||||||
return Future { promise in
|
|
||||||
printLog(request.parameters)
|
|
||||||
AF.request("\(request.url)\(request.path)",
|
|
||||||
method: request.method,
|
|
||||||
parameters: request.parameters,
|
|
||||||
encoding: encoding,
|
|
||||||
headers: request.headers
|
|
||||||
)
|
|
||||||
.validate(statusCode: 200 ..< 300)
|
|
||||||
.responseDecodable(of: request.decoding) { response in
|
|
||||||
|
|
||||||
switch response.result {
|
|
||||||
case .success(let value):
|
|
||||||
printLog("Good: \(value)")
|
|
||||||
promise(.success(value))
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
printLog("Bad: \(error))")
|
|
||||||
promise(.failure(error))
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public func reloadAccessToken() -> Future<Any, Error> {
|
|
||||||
return Future { [weak self] promise in
|
|
||||||
guard let self = self else {return}
|
|
||||||
|
|
||||||
let request = APIRequest.init(path: "/api/v1/in/app/retryAccess",
|
|
||||||
parameters: ["refresh": refresh],
|
|
||||||
decoding: APIResponse<Access>.self)
|
|
||||||
|
|
||||||
APIManager().loadAPIData(request)
|
|
||||||
.sink { completion in
|
|
||||||
switch completion {
|
|
||||||
case .failure(let error):
|
|
||||||
promise(.failure(error))
|
|
||||||
printLog("\(error)")
|
|
||||||
case .finished:
|
|
||||||
printLog("엑세스 토큰 재발급 완료")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} receiveValue: { response in
|
|
||||||
guard let accToken = response as? APIResponse<Access> else {
|
|
||||||
promise(.failure(ACA_ERROR("Unknown ERROR")))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch accToken.status.code {
|
|
||||||
case .success(let code):
|
|
||||||
if code == "000" {
|
|
||||||
if let tknData = accToken.data { promise(.success(tknData)) }
|
|
||||||
}
|
|
||||||
case .inputErr(let code):
|
|
||||||
// 로그인 화면으로 전환하기
|
|
||||||
promise(.failure(ACA_ERROR("Refresh token ERROR(\(code), \(accToken.status.message)")))
|
|
||||||
default:
|
|
||||||
// 그외에 서버에서 처리를 하다가 문제가 생겨서 발생하는 에러는 여기로 보낼거임
|
|
||||||
promise(.failure(ACA_ERROR("Unknown ERROR")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &self.cancellables)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public func loadUserAPIData<T: APIResponseProtocol & Codable>(request: APIRequest<T>) -> Future<T, Error>{
|
|
||||||
return Future { [weak self] promise in
|
|
||||||
guard let self = self else {return}
|
|
||||||
printLog(request.parameters["token"])
|
|
||||||
loadAPIData(request)
|
|
||||||
.flatMap { (response: T) -> AnyPublisher<T, Error> in
|
|
||||||
switch response.status.code {
|
|
||||||
case .inputErr(let code) where code == "101":
|
|
||||||
return self.reloadAccessToken()
|
|
||||||
.flatMap { response -> AnyPublisher<T, Error> in
|
|
||||||
self.accToken = (response as! Access).access
|
|
||||||
var updateRequest = request
|
|
||||||
updateRequest.parameters["token"] = self.accToken
|
|
||||||
printLog("Acc: \(self.accToken)")
|
|
||||||
return APIManager().loadAPIData(updateRequest)
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
default:
|
|
||||||
return Just(response)
|
|
||||||
.setFailureType(to: Error.self)
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sink { completion in
|
|
||||||
switch completion {
|
|
||||||
case .failure(let error):
|
|
||||||
promise(.failure(error))
|
|
||||||
case .finished:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} receiveValue: { finalResponse in
|
|
||||||
promise(.success(finalResponse))
|
|
||||||
}
|
|
||||||
.store(in: &self.cancellables)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
//
|
|
||||||
// PermissionManager.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 3/28/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import AVFoundation
|
|
||||||
import Photos
|
|
||||||
import CoreLocation
|
|
||||||
import UserNotifications
|
|
||||||
|
|
||||||
class PermissionManager: NSObject, ObservableObject {
|
|
||||||
|
|
||||||
private let locationManager = CLLocationManager()
|
|
||||||
private var locationRequestCallback: ((CLAuthorizationStatus) -> Void)?
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
super.init()
|
|
||||||
locationManager.delegate = self
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 푸시 권한 요청
|
|
||||||
func requestPushPermission(completion: @escaping (Bool) -> Void) {
|
|
||||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(granted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 카메라 권한 요청
|
|
||||||
func requestCameraPermission(completion: @escaping (Bool) -> Void) {
|
|
||||||
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(granted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 앨범 권한 요청
|
|
||||||
func requestPhotoPermission(completion: @escaping (PHAuthorizationStatus) -> Void) {
|
|
||||||
PHPhotoLibrary.requestAuthorization { status in
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 위치 권한 요청
|
|
||||||
func requestLocationPermission(completion: @escaping (CLAuthorizationStatus) -> Void) {
|
|
||||||
locationRequestCallback = completion
|
|
||||||
locationManager.requestWhenInUseAuthorization()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 현재 권한 상태 확인
|
|
||||||
func checkPushPermission(completion: @escaping (Bool) -> Void) {
|
|
||||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(settings.authorizationStatus == .authorized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCameraPermission() -> Bool {
|
|
||||||
return AVCaptureDevice.authorizationStatus(for: .video) == .authorized
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkPhotoPermission() -> Bool {
|
|
||||||
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
|
|
||||||
return status == .authorized || status == .limited
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkLocationPermission() -> Bool {
|
|
||||||
let status = locationManager.authorizationStatus
|
|
||||||
return status == .authorizedAlways || status == .authorizedWhenInUse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
extension PermissionManager: CLLocationManagerDelegate {
|
|
||||||
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
|
||||||
let status = manager.authorizationStatus
|
|
||||||
locationRequestCallback?(status)
|
|
||||||
locationRequestCallback = nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
//
|
|
||||||
// WebSocketManager.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by TAnine on 2/17/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Combine
|
|
||||||
|
|
||||||
import Starscream
|
|
||||||
|
|
||||||
// SignalR은 메세지의 끝이 \u{1E} 해당 유니코드 문자를 원함
|
|
||||||
class WebSocketManager: ObservableObject ,WebSocketDelegate {
|
|
||||||
|
|
||||||
@Published var socket: WebSocket?
|
|
||||||
@Published var receivedMessage: [String] = []
|
|
||||||
|
|
||||||
init() {
|
|
||||||
guard let url = URL(string: "\(WS_URL)/chatHub?transport=webSockets") else { return }
|
|
||||||
var request = URLRequest(url: url)
|
|
||||||
request.timeoutInterval = 5
|
|
||||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
||||||
request.setValue("Upgrade", forHTTPHeaderField: "Connection")
|
|
||||||
|
|
||||||
socket = WebSocket(request: request)
|
|
||||||
socket?.delegate = self
|
|
||||||
}
|
|
||||||
|
|
||||||
func connect() {
|
|
||||||
printLog("TRY CONNECT SOCKET")
|
|
||||||
socket?.connect()
|
|
||||||
}
|
|
||||||
|
|
||||||
func didReceive(event: Starscream.WebSocketEvent, client: WebSocketClient) {
|
|
||||||
switch event {
|
|
||||||
case .connected(let header):
|
|
||||||
printLog("CONNECTED : \(header)")
|
|
||||||
// 우리는 현재 JSON기반으로 메시지를 주고 받을거고 SignalR JSON 프로토콜의 버전은 1이다
|
|
||||||
let handShakeMsg =
|
|
||||||
"""
|
|
||||||
{
|
|
||||||
"protocol":"json",
|
|
||||||
"version":1
|
|
||||||
}\u{1E}
|
|
||||||
"""
|
|
||||||
self.socket?.write(string: handShakeMsg)
|
|
||||||
|
|
||||||
case .disconnected(let reason, let code):
|
|
||||||
printLog("❌ DISCONNECTED: [\(code)] - \(reason)")
|
|
||||||
|
|
||||||
case .error(let error):
|
|
||||||
printLog("🆘 SOCKET ERROR: \(error?.localizedDescription ?? "unknown")")
|
|
||||||
|
|
||||||
case .text(let text):
|
|
||||||
// 서버에서 메세지를 보내면 여기로 들어올거고
|
|
||||||
// 받아온 텍스트의 앞은 target, [sendet, message] 순서로 들어온다.
|
|
||||||
printLog("📩 SERVER SENT: \(text)")
|
|
||||||
|
|
||||||
if let data = try? JSONSerialization.jsonObject(with: Data(text.utf8)) as? [String: Any],
|
|
||||||
let target = data["target"] as? String,
|
|
||||||
let args = data["arguments"] as? [String] {
|
|
||||||
|
|
||||||
if target == "ReceiveMessage" {
|
|
||||||
let sender = args[0]
|
|
||||||
let message = args[1]
|
|
||||||
receivedMessage.append(message)
|
|
||||||
print("💬 [\(sender)] \(message)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
case .binary(let binary):
|
|
||||||
printLog("BINARY?: \(binary)")
|
|
||||||
|
|
||||||
case .cancelled:
|
|
||||||
printLog("SOCKET CONNECTED CANCELLED!!!")
|
|
||||||
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func disconnect() {
|
|
||||||
socket?.disconnect()
|
|
||||||
socket?.delegate = nil // 추가: delegate 해제
|
|
||||||
socket = nil // 필요시, 소켓 객체 해제
|
|
||||||
printLog("CLOSE SOCKET")
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendMessage(_ message: String) {
|
|
||||||
let json = """
|
|
||||||
{
|
|
||||||
"type": 1,
|
|
||||||
"target":"SendMessage",
|
|
||||||
"arguments":["iOS", "\(message)"]
|
|
||||||
}\u{1E}
|
|
||||||
"""
|
|
||||||
socket?.write(string: json)
|
|
||||||
printLog("SEND THE MESSAGE: \(message)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func joinRoom(_ cid: String){
|
|
||||||
let json = """
|
|
||||||
{
|
|
||||||
"type": 1,
|
|
||||||
"target":"JoinRoom",
|
|
||||||
"arguments":["\(cid)"]
|
|
||||||
}\u{1E}
|
|
||||||
"""
|
|
||||||
socket?.write(string: json)
|
|
||||||
printLog("JOIN: \(cid)")
|
|
||||||
}
|
|
||||||
}
|
|
13
AcaMate/5. Modifier/Text.swift
Normal file
13
AcaMate/5. Modifier/Text.swift
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
//
|
||||||
|
// Text.swift
|
||||||
|
// AcaMate
|
||||||
|
//
|
||||||
|
// Created by Sean Kim on 12/1/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
|
extension Text {
|
||||||
|
|
||||||
|
}
|
65
AcaMate/5. Modifier/TextField.swift
Normal file
65
AcaMate/5. Modifier/TextField.swift
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
//
|
||||||
|
// UIView.swift
|
||||||
|
// AcaMate
|
||||||
|
//
|
||||||
|
// Created by Sean Kim on 12/14/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct CustomTextField: UIViewRepresentable {
|
||||||
|
var placeholder: String
|
||||||
|
@Binding var text: String
|
||||||
|
|
||||||
|
var isSecure: Binding<Bool> = .constant(false)
|
||||||
|
|
||||||
|
var textColor = UIColor(Color(.Text.detail))
|
||||||
|
var font = UIFont(name: "NPS-font-Regular", size: 16)
|
||||||
|
|
||||||
|
// [필수] 초기화 시 UIView 생성
|
||||||
|
func makeUIView(context: Context) -> UITextField {
|
||||||
|
let txf = UITextField()
|
||||||
|
txf.placeholder = placeholder
|
||||||
|
txf.isSecureTextEntry = isSecure.wrappedValue
|
||||||
|
|
||||||
|
txf.textColor = textColor
|
||||||
|
txf.font = font
|
||||||
|
|
||||||
|
txf.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
let height = (font?.lineHeight ?? 10) + 8
|
||||||
|
txf.frame.size.height = height
|
||||||
|
|
||||||
|
txf.autocorrectionType = .no
|
||||||
|
txf.autocapitalizationType = .none
|
||||||
|
txf.smartInsertDeleteType = .no
|
||||||
|
txf.textContentType = .oneTimeCode
|
||||||
|
|
||||||
|
txf.delegate = context.coordinator
|
||||||
|
return txf
|
||||||
|
}
|
||||||
|
|
||||||
|
// [필수] 스유 상태 변화 따라 UIView 업데이트
|
||||||
|
func updateUIView(_ uiView: UITextField, context: Context) {
|
||||||
|
uiView.text = text
|
||||||
|
uiView.isSecureTextEntry = isSecure.wrappedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Coordinator: NSObject, UITextFieldDelegate {
|
||||||
|
var parent: CustomTextField
|
||||||
|
|
||||||
|
init(_ parent: CustomTextField) {
|
||||||
|
self.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
func textFieldDidChangeSelection(_ textField: UITextField) {
|
||||||
|
parent.text = textField.text ?? ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,24 +14,13 @@ struct NetworkModifier: ViewModifier {
|
||||||
@EnvironmentObject var appVM: AppViewModel
|
@EnvironmentObject var appVM: AppViewModel
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
if #available(iOS 17.0, *) {
|
content
|
||||||
content
|
.onChange(of: networkMonitor.isConnected) { _ , new in
|
||||||
.onChange(of: networkMonitor.isConnected) { _ , new in
|
if !new {
|
||||||
if !new {
|
appVM.alertData = SetAlertData().setErrorNetwork()
|
||||||
appVM.alertData = SetAlertData().setErrorNetwork()
|
appVM.showAlert.toggle()
|
||||||
appVM.showAlert.toggle()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
content
|
|
||||||
.onChange(of: networkMonitor.isConnected) { new in
|
|
||||||
if !new {
|
|
||||||
appVM.alertData = SetAlertData().setErrorNetwork()
|
|
||||||
appVM.showAlert.toggle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,10 +56,10 @@ struct LoadingModifier: ViewModifier {
|
||||||
.blur(radius: isLoading ? 3:0)
|
.blur(radius: isLoading ? 3:0)
|
||||||
if isLoading {
|
if isLoading {
|
||||||
Color.Text.detail.opacity(0.6)
|
Color.Text.detail.opacity(0.6)
|
||||||
// Color.Second.normal.opacity(0.3)
|
// Color.Second.normal.opacity(0.3)
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
ProgressView("Loading...")
|
ProgressView("Loading...")
|
||||||
// .tint(Color.Text.black)
|
// .tint(Color.Text.black)
|
||||||
.tint(Color.Normal.normal)
|
.tint(Color.Normal.normal)
|
||||||
.scaleEffect(1.5)
|
.scaleEffect(1.5)
|
||||||
.foregroundStyle(Color.Normal.normal)
|
.foregroundStyle(Color.Normal.normal)
|
||||||
|
@ -135,7 +124,7 @@ extension View {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 loadingView(isLoading: Binding<Bool>) -> some View {
|
func loadingView(isLoading: Binding<Bool>) -> some View {
|
||||||
|
@ -152,7 +141,12 @@ extension View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// func pressAnimation(scale: CGFloat = 0.95, opacity: CGFloat = 0.85, duration: Double = 0.1) -> some View {
|
// func pressAnimation(scale: CGFloat = 0.95, opacity: CGFloat = 0.85, duration: Double = 0.1) -> some View {
|
||||||
// self.modifier(PressEffect(scale: scale, opacity: opacity, duration: duration))
|
// self.modifier(PressEffect(scale: scale, opacity: opacity, duration: duration))
|
||||||
// }
|
// }
|
||||||
|
}
|
||||||
|
extension View {
|
||||||
|
// func pressColorAnimation(backgroundColor: Color = Color.black.opacity(0.1), duration: Double = 0.1) -> some View {
|
||||||
|
// self.modifier(PressBackgroundEffect(backgroundColor: backgroundColor, duration: duration))
|
||||||
|
// }
|
||||||
}
|
}
|
|
@ -1,25 +0,0 @@
|
||||||
//
|
|
||||||
// Text.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by Sean Kim on 12/1/24.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
protocol MultilineStyle: View {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Text: MultilineStyle {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension MultilineStyle {
|
|
||||||
func multilineStyle(_ alignment: TextAlignment = .leading, limit: Int = 1, scale: CGFloat = 0.5) -> some View {
|
|
||||||
return self
|
|
||||||
.lineLimit(limit)
|
|
||||||
.minimumScaleFactor(scale)
|
|
||||||
.multilineTextAlignment(alignment)
|
|
||||||
.truncationMode(.tail)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,150 +0,0 @@
|
||||||
//
|
|
||||||
// UIView.swift
|
|
||||||
// AcaMate
|
|
||||||
//
|
|
||||||
// Created by Sean Kim on 12/14/24.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
struct FixedSizeWrapper<Content: UIView>: UIViewRepresentable {
|
|
||||||
let content: () -> Content
|
|
||||||
let width: CGFloat
|
|
||||||
let height: CGFloat
|
|
||||||
|
|
||||||
func makeUIView(context: Context) -> Content {
|
|
||||||
let view = content()
|
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
|
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
view.widthAnchor.constraint(equalToConstant: width),
|
|
||||||
view.heightAnchor.constraint(equalToConstant: height)
|
|
||||||
])
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ uiView: Content, context: Context) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CustomTxfView: View {
|
|
||||||
|
|
||||||
var placeholder: String
|
|
||||||
@Binding var text: String
|
|
||||||
|
|
||||||
var maxLength: Int = 100
|
|
||||||
var isSecure: Binding<Bool> = .constant(false)
|
|
||||||
var alignment: TextAlignment = .leading
|
|
||||||
|
|
||||||
var textColor = Color(.Text.detail)
|
|
||||||
var font: Font = .nps(size:16)
|
|
||||||
// UIFont(name: "NPS-font-Regular", size: 16)
|
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
TextField(placeholder, text: $text)
|
|
||||||
// Binding<String>(
|
|
||||||
// get: { text },
|
|
||||||
// set: { newValue in
|
|
||||||
// text = String(newValue.prefix(maxLength))
|
|
||||||
// }
|
|
||||||
// ))
|
|
||||||
.font(font)
|
|
||||||
.tint(textColor)
|
|
||||||
.lineLimit(1)
|
|
||||||
.multilineTextAlignment(alignment)
|
|
||||||
.minimumScaleFactor(0.5)
|
|
||||||
.truncationMode(.tail)
|
|
||||||
.clipped()
|
|
||||||
|
|
||||||
.onChange(of: text) { old, new in
|
|
||||||
if new.count > maxLength {
|
|
||||||
text = String(new.prefix(maxLength))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct CustomTextField: UIViewRepresentable {
|
|
||||||
var placeholder: String
|
|
||||||
@Binding var text: String
|
|
||||||
var maxLength: Int?
|
|
||||||
var isSecure: Binding<Bool> = .constant(false)
|
|
||||||
|
|
||||||
var textColor = UIColor(Color(.Text.detail))
|
|
||||||
var font = UIFont(name: "NPS-font-Regular", size: 16)
|
|
||||||
var alignment: NSTextAlignment = .left
|
|
||||||
|
|
||||||
// [필수] 초기화 시 UIView 생성
|
|
||||||
func makeUIView(context: Context) -> UITextField {
|
|
||||||
let txf = UITextField()
|
|
||||||
|
|
||||||
|
|
||||||
// txf.placeholder = placeholder
|
|
||||||
txf.attributedPlaceholder = NSAttributedString(
|
|
||||||
string: "\(placeholder)",
|
|
||||||
attributes: [NSAttributedString.Key.foregroundColor : UIColor(.Text.border)])
|
|
||||||
|
|
||||||
txf.isSecureTextEntry = isSecure.wrappedValue
|
|
||||||
|
|
||||||
txf.adjustsFontSizeToFitWidth = true
|
|
||||||
txf.minimumFontSize = 4
|
|
||||||
|
|
||||||
txf.textColor = textColor
|
|
||||||
let height = (font?.lineHeight ?? 10) + 8
|
|
||||||
txf.frame.size.height = height
|
|
||||||
|
|
||||||
txf.borderStyle = .none
|
|
||||||
txf.autocorrectionType = .no
|
|
||||||
txf.autocapitalizationType = .none
|
|
||||||
txf.smartInsertDeleteType = .no
|
|
||||||
txf.textContentType = .oneTimeCode
|
|
||||||
txf.textAlignment = self.alignment
|
|
||||||
txf.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
txf.setContentHuggingPriority(.required, for: .horizontal)
|
|
||||||
txf.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
||||||
|
|
||||||
txf.delegate = context.coordinator
|
|
||||||
return txf
|
|
||||||
}
|
|
||||||
|
|
||||||
// [필수] 스유 상태 변화 따라 UIView 업데이트
|
|
||||||
func updateUIView(_ uiView: UITextField, context: Context) {
|
|
||||||
uiView.text = text
|
|
||||||
uiView.isSecureTextEntry = isSecure.wrappedValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator {
|
|
||||||
Coordinator(self, max: maxLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Coordinator: NSObject, UITextFieldDelegate {
|
|
||||||
var parent: CustomTextField
|
|
||||||
var maxLength: Int?
|
|
||||||
|
|
||||||
init(_ parent: CustomTextField, max: Int?) {
|
|
||||||
self.parent = parent
|
|
||||||
self.maxLength = max
|
|
||||||
}
|
|
||||||
|
|
||||||
func textFieldDidChangeSelection(_ textField: UITextField) {
|
|
||||||
parent.text = textField.text ?? ""
|
|
||||||
}
|
|
||||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
|
||||||
let current = textField.text ?? ""
|
|
||||||
guard let stringRange = Range(range, in: current) else { return false}
|
|
||||||
let updated = current.replacingCharacters(in: stringRange, with: string)
|
|
||||||
return updated.count <= (maxLength ?? 9999)
|
|
||||||
}
|
|
||||||
|
|
||||||
// func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
|
||||||
// let currentText = textField.text ?? ""
|
|
||||||
// guard let stringRange = Range(range, in: currentText) else { return false }
|
|
||||||
// let updatedText = currentText.replacingCharacters(in: stringRange, with: string)
|
|
||||||
// return updatedText.count <= maxLength
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Before Width: | Height: | Size: 811 KiB After Width: | Height: | Size: 811 KiB |
Binary file not shown.
After Width: | Height: | Size: 811 KiB |
BIN
AcaMate/6. Resources/Assets.xcassets/AppIcon.appiconset/LOGO.png
Normal file
BIN
AcaMate/6. Resources/Assets.xcassets/AppIcon.appiconset/LOGO.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 811 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user