로그인 페이지
|
@ -9,6 +9,15 @@
|
||||||
"version" : "1.2022062300.0"
|
"version" : "1.2022062300.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "alamofire",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/Alamofire/Alamofire.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "bc268c28fb170f494de9e9927c371b8342979ece",
|
||||||
|
"version" : "5.7.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "firebase-ios-sdk",
|
"identity" : "firebase-ios-sdk",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
@ -63,6 +72,15 @@
|
||||||
"version" : "3.1.1"
|
"version" : "3.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "kingfisher",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/onevcat/Kingfisher.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "c75584ac759cbb16b204d0a7de3ebf53ea6b304d",
|
||||||
|
"version" : "7.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "leveldb",
|
"identity" : "leveldb",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
@ -72,6 +90,15 @@
|
||||||
"version" : "1.22.2"
|
"version" : "1.22.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "moya",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/Moya/Moya.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "c263811c1f3dbf002be9bd83107f7cdc38992b26",
|
||||||
|
"version" : "15.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "nanopb",
|
"identity" : "nanopb",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
@ -81,6 +108,15 @@
|
||||||
"version" : "2.30909.0"
|
"version" : "2.30909.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "popupview",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/exyte/PopupView.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "68349a0ae704b9a7041f756f3f4f460ddbf7ba8d",
|
||||||
|
"version" : "2.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "promises",
|
"identity" : "promises",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
@ -90,6 +126,33 @@
|
||||||
"version" : "2.3.1"
|
"version" : "2.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "reactiveswift",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c",
|
||||||
|
"version" : "6.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "refreshablescrollview",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/phuhuynh2411/RefreshableScrollView",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "e06edf5dc4facc7fbf71179e8a94f0d1c7035ce3",
|
||||||
|
"version" : "1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "rxswift",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/ReactiveX/RxSwift.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4",
|
||||||
|
"version" : "6.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-protobuf",
|
"identity" : "swift-protobuf",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|
21
SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "btn_select_checked.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/btn_select_checked.png
vendored
Normal file
After Width: | Height: | Size: 544 B |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "btn_select_normal.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/btn_select_normal.imageset/btn_select_normal.png
vendored
Normal file
After Width: | Height: | Size: 680 B |
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "loading_1.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/loading/loading_1.imageset/loading_1.png
vendored
Normal file
After Width: | Height: | Size: 6.3 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "loading_2.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/loading/loading_2.imageset/loading_2.png
vendored
Normal file
After Width: | Height: | Size: 3.7 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "loading_3.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/loading/loading_3.imageset/loading_3.png
vendored
Normal file
After Width: | Height: | Size: 3.2 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "loading_4.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/loading/loading_4.imageset/loading_4.png
vendored
Normal file
After Width: | Height: | Size: 5.1 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "loading_5.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/loading/loading_5.imageset/loading_5.png
vendored
Normal file
After Width: | Height: | Size: 4.6 KiB |
|
@ -11,4 +11,8 @@ enum AppStep {
|
||||||
case splash
|
case splash
|
||||||
|
|
||||||
case main
|
case main
|
||||||
|
|
||||||
|
case signUp
|
||||||
|
|
||||||
|
case findPassword
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
//
|
||||||
|
// ApiResponse.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2023/08/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct ApiResponse<T: Decodable>: Decodable {
|
||||||
|
let success: Bool
|
||||||
|
let data: T?
|
||||||
|
let message: String?
|
||||||
|
let errorProperty: String?
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
//
|
||||||
|
// LoadingView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2023/08/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LoadingView: View {
|
||||||
|
@State var index = 0
|
||||||
|
|
||||||
|
@State var timer = Timer.publish(every: 0.35, on: .current, in: .common).autoconnect()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Color.primary.opacity(0.2)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
ZStack {
|
||||||
|
Image("loading_1")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 100, height: 100)
|
||||||
|
.opacity(index == 0 ? 1.0 : 0.0)
|
||||||
|
|
||||||
|
Image("loading_2")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 100, height: 100)
|
||||||
|
.opacity(index == 1 ? 1.0 : 0.0)
|
||||||
|
|
||||||
|
Image("loading_3")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 100, height: 100)
|
||||||
|
.opacity(index == 2 ? 1.0 : 0.0)
|
||||||
|
|
||||||
|
Image("loading_4")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 100, height: 100)
|
||||||
|
.opacity(index == 3 ? 1.0 : 0.0)
|
||||||
|
|
||||||
|
Image("loading_5")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 100, height: 100)
|
||||||
|
.opacity(index == 4 ? 1.0 : 0.0)
|
||||||
|
}
|
||||||
|
.frame(width: 150, height: 150)
|
||||||
|
.background(Color.white)
|
||||||
|
.cornerRadius(14)
|
||||||
|
.shadow(color: Color.primary.opacity(0.07), radius: 5, x: 5, y: 5)
|
||||||
|
.shadow(color: Color.primary.opacity(0.07), radius: 5, x: -5, y: -5)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
.onReceive(timer) { _ in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if index == 4 {
|
||||||
|
index = 0
|
||||||
|
} else {
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoadingView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
LoadingView()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// HomeNavigationBar.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2023/08/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct HomeNavigationBar<Content: View>: View {
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
let content: Content
|
||||||
|
|
||||||
|
init(
|
||||||
|
title: String,
|
||||||
|
@ViewBuilder content: () -> Content
|
||||||
|
) {
|
||||||
|
self.title = title
|
||||||
|
self.content = content()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
Text(title)
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(Color(hex: "eeeeee"))
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
content
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
.frame(width: screenSize().width, height: 50, alignment: .center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HomeNavigationBar_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
HomeNavigationBar(title: "홈") {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
//
|
||||||
|
// LoginRequest.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2023/08/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct LoginRequest: Encodable {
|
||||||
|
let email: String
|
||||||
|
let password: String
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
//
|
||||||
|
// LoginResponse.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2023/08/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct LoginResponse: Decodable {
|
||||||
|
let userId: Int
|
||||||
|
let token: String
|
||||||
|
let nickname: String
|
||||||
|
let email: String
|
||||||
|
let profileImage: String
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
//
|
||||||
|
// LoginView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2023/08/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import PopupView
|
||||||
|
|
||||||
|
struct LoginView: View {
|
||||||
|
|
||||||
|
@ObservedObject var viewModel = LoginViewModel()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Color.black.ignoresSafeArea()
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
HomeNavigationBar(title: "로그인") {}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
UserTextField(
|
||||||
|
title: "이메일",
|
||||||
|
hint: "이메일 주소를 입력해 주세요",
|
||||||
|
isSecure: false,
|
||||||
|
variable: $viewModel.email,
|
||||||
|
keyboardType: .emailAddress
|
||||||
|
)
|
||||||
|
.padding(.horizontal, 26.7)
|
||||||
|
|
||||||
|
UserTextField(
|
||||||
|
title: "비밀번호",
|
||||||
|
hint: "비밀번호를 입력해 주세요",
|
||||||
|
isSecure: true,
|
||||||
|
variable: $viewModel.password,
|
||||||
|
isPasswordVisibleButton: true
|
||||||
|
)
|
||||||
|
.padding(.top, 33.3)
|
||||||
|
.padding(.horizontal, 26.7)
|
||||||
|
|
||||||
|
Button(action: { viewModel.login() }) {
|
||||||
|
Text("로그인")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 15))
|
||||||
|
.frame(width: screenSize().width - 26.6, height: 46.7)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.background(Color(hex: "9970ff"))
|
||||||
|
.cornerRadius(6.7)
|
||||||
|
}
|
||||||
|
.padding(.top, 40)
|
||||||
|
|
||||||
|
HStack(spacing: 10) {
|
||||||
|
Text("비밀번호 재설정")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color(hex: "bbbbbb"))
|
||||||
|
.onTapGesture { AppState.shared.setAppStep(step: .findPassword) }
|
||||||
|
|
||||||
|
Text("|")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color(hex: "bbbbbb"))
|
||||||
|
|
||||||
|
Text("회원가입")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color(hex: "bbbbbb"))
|
||||||
|
.onTapGesture { AppState.shared.setAppStep(step: .signUp) }
|
||||||
|
}
|
||||||
|
.padding(.top, 40)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.isLoading {
|
||||||
|
LoadingView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoginView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
LoginView()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
//
|
||||||
|
// LoginViewModel.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2023/08/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Moya
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
final class LoginViewModel: ObservableObject {
|
||||||
|
private let repository = UserRepository()
|
||||||
|
private var subscription = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
@Published var email = ""
|
||||||
|
@Published var password = ""
|
||||||
|
|
||||||
|
@Published var errorMessage = ""
|
||||||
|
@Published var isShowPopup = false
|
||||||
|
@Published var isLoading = false
|
||||||
|
|
||||||
|
func login() {
|
||||||
|
if email.isEmpty {
|
||||||
|
self.errorMessage = "이메일을 입력해 주세요."
|
||||||
|
self.isShowPopup = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if password.isEmpty {
|
||||||
|
self.errorMessage = "비밀번호를 입력해 주세요."
|
||||||
|
self.isShowPopup = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = true
|
||||||
|
repository.login(request: LoginRequest(email: email, password: password))
|
||||||
|
.sink { result in
|
||||||
|
switch result {
|
||||||
|
case .finished:
|
||||||
|
DEBUG_LOG("finish")
|
||||||
|
case .failure(let error):
|
||||||
|
ERROR_LOG(error.localizedDescription)
|
||||||
|
}
|
||||||
|
} receiveValue: { response in
|
||||||
|
self.isLoading = false
|
||||||
|
let responseData = response.data
|
||||||
|
do {
|
||||||
|
let jsonDecoder = JSONDecoder()
|
||||||
|
let decoded = try jsonDecoder.decode(ApiResponse<LoginResponse>.self, from: responseData)
|
||||||
|
|
||||||
|
if let data = decoded.data, decoded.success {
|
||||||
|
UserDefaults.set(data.profileImage, forKey: .profileImage)
|
||||||
|
UserDefaults.set(data.nickname, forKey: .nickname)
|
||||||
|
UserDefaults.set(data.userId, forKey: .userId)
|
||||||
|
UserDefaults.set(data.email, forKey: .email)
|
||||||
|
UserDefaults.set(data.token, forKey: .token)
|
||||||
|
} else {
|
||||||
|
if let message = decoded.message {
|
||||||
|
self.errorMessage = message
|
||||||
|
} else {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &subscription)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +0,0 @@
|
||||||
//
|
|
||||||
// LoginView.swift
|
|
||||||
// SodaLive
|
|
||||||
//
|
|
||||||
// Created by klaus on 2023/08/09.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct LoginView: View {
|
|
||||||
var body: some View {
|
|
||||||
Text("Login View")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LoginView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
LoginView()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
//
|
||||||
|
// UserApi.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2023/08/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Moya
|
||||||
|
|
||||||
|
enum UserApi {
|
||||||
|
case login(request: LoginRequest)
|
||||||
|
case signUp(parameters: [MultipartFormData])
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UserApi: TargetType {
|
||||||
|
var baseURL: URL {
|
||||||
|
return URL(string: BASE_URL)!
|
||||||
|
}
|
||||||
|
|
||||||
|
var path: String {
|
||||||
|
switch self {
|
||||||
|
case .login:
|
||||||
|
return "/member/login"
|
||||||
|
|
||||||
|
case .signUp:
|
||||||
|
return "/member/signup"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var method: Moya.Method {
|
||||||
|
switch self {
|
||||||
|
case .login, .signUp:
|
||||||
|
return .post
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var task: Task {
|
||||||
|
switch self {
|
||||||
|
case .login(let request):
|
||||||
|
return .requestJSONEncodable(request)
|
||||||
|
|
||||||
|
case .signUp(let parameters):
|
||||||
|
return .uploadMultipart(parameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var headers: [String : String]? {
|
||||||
|
switch self {
|
||||||
|
case .login, .signUp:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ["Authorization": "Bearer \(UserDefaults.string(forKey: UserDefaultsKey.token))"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// UserRepository.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2023/08/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CombineMoya
|
||||||
|
import Combine
|
||||||
|
import Moya
|
||||||
|
|
||||||
|
final class UserRepository {
|
||||||
|
private let api = MoyaProvider<UserApi>()
|
||||||
|
|
||||||
|
func login(request: LoginRequest) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.login(request: request))
|
||||||
|
}
|
||||||
|
|
||||||
|
func signUp(parameters: [MultipartFormData]) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.signUp(parameters: parameters))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
//
|
||||||
|
// UserTextField.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2023/08/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct UserTextField: View {
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
let hint: String
|
||||||
|
let isSecure: Bool
|
||||||
|
@Binding var variable: String
|
||||||
|
|
||||||
|
var isPasswordVisibleButton: Bool = false
|
||||||
|
var keyboardType: UIKeyboardType = .default
|
||||||
|
|
||||||
|
@State private var visiblePassword = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Text(title)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.foregroundColor(Color(hex: "eeeeee"))
|
||||||
|
.padding(.leading, 6.7)
|
||||||
|
|
||||||
|
if isSecure && !visiblePassword{
|
||||||
|
SecureField(hint, text: $variable)
|
||||||
|
.autocapitalization(.none)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color(hex: "eeeeee"))
|
||||||
|
.padding(.top, 12)
|
||||||
|
.padding(.leading, 6.7)
|
||||||
|
} else {
|
||||||
|
TextField(hint, text: $variable)
|
||||||
|
.autocapitalization(.none)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color(hex: "eeeeee"))
|
||||||
|
.keyboardType(keyboardType)
|
||||||
|
.padding(.top, 12)
|
||||||
|
.padding(.leading, 6.7)
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.frame(height: 0.3)
|
||||||
|
.foregroundColor(Color(hex: "909090"))
|
||||||
|
.padding(.top, 8.3)
|
||||||
|
|
||||||
|
if isSecure && isPasswordVisibleButton {
|
||||||
|
Button(action: { visiblePassword.toggle() }) {
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
if visiblePassword {
|
||||||
|
Image("btn_select_checked")
|
||||||
|
} else {
|
||||||
|
Image("btn_select_normal")
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("비밀번호 표시")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color(hex: "eeeeee"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 20)
|
||||||
|
.padding(.leading, 6.7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UserTextField_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
UserTextField(
|
||||||
|
title: "이메일",
|
||||||
|
hint: "user_id@email.com",
|
||||||
|
isSecure: true,
|
||||||
|
variable: .constant("test"),
|
||||||
|
isPasswordVisibleButton: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|