diff --git a/Podfile b/Podfile index dbac9d6..c0148f1 100644 --- a/Podfile +++ b/Podfile @@ -6,6 +6,7 @@ target 'SodaLive' do use_frameworks! # Pods for SodaLive + pod 'BootpayUI', '4.3.0' end @@ -14,5 +15,17 @@ target 'SodaLive-dev' do use_frameworks! # Pods for SodaLive-dev + pod 'BootpayUI', '4.3.0' end + +post_install do |installer| + installer.generated_projects.each do |project| + project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0' + end + end + end +end + diff --git a/Podfile.lock b/Podfile.lock index 78c6c10..3cf44b9 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,3 +1,50 @@ -PODFILE CHECKSUM: 40957185212259305ead8c0beb4b1e5415b9b0df +PODS: + - Alamofire (5.7.1) + - Bootpay (4.2.9): + - CryptoSwift + - ObjectMapper + - BootpayUI (4.3.0): + - Alamofire + - Bootpay (~> 4.2.8) + - CryptoSwift + - JGProgressHUD + - ObjectMapper + - SCLAlertView + - SnapKit + - SwiftyJSON + - CryptoSwift (1.7.1) + - JGProgressHUD (2.2) + - ObjectMapper (4.2.0) + - SCLAlertView (0.8) + - SnapKit (5.6.0) + - SwiftyJSON (5.0.1) + +DEPENDENCIES: + - BootpayUI (= 4.3.0) + +SPEC REPOS: + trunk: + - Alamofire + - Bootpay + - BootpayUI + - CryptoSwift + - JGProgressHUD + - ObjectMapper + - SCLAlertView + - SnapKit + - SwiftyJSON + +SPEC CHECKSUMS: + Alamofire: 0123a34370cb170936ae79a8df46cc62b2edeb88 + Bootpay: d753088334a16ce99094142beb66a6610a15d84b + BootpayUI: 54dcbe59a23e0d91b07a8add8115e1a6deace0f0 + CryptoSwift: d3d18dc357932f7e6d580689e065cf1f176007c1 + JGProgressHUD: d83d7a981b85d11205e19ff8ad5bb9c40571c847 + ObjectMapper: 1eb41f610210777375fa806bf161dc39fb832b81 + SCLAlertView: 6a77bb2edfc65e04dbe57725546cb4107a506b85 + SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25 + SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e + +PODFILE CHECKSUM: 2581dac8090335f039e33fdbf3ec7d78d7f961e8 COCOAPODS: 1.12.1 diff --git a/SodaLive/Resources/Assets.xcassets/ic_blog_circle.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_blog_circle.imageset/Contents.json new file mode 100644 index 0000000..d5cea0f --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_blog_circle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_blog_circle.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_blog_circle.imageset/ic_blog_circle.png b/SodaLive/Resources/Assets.xcassets/ic_blog_circle.imageset/ic_blog_circle.png new file mode 100644 index 0000000..d6f9ab5 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_blog_circle.imageset/ic_blog_circle.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_coin_w.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_coin_w.imageset/Contents.json new file mode 100644 index 0000000..f7840b5 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_coin_w.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_coin_w.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_coin_w.imageset/ic_coin_w.png b/SodaLive/Resources/Assets.xcassets/ic_coin_w.imageset/ic_coin_w.png new file mode 100644 index 0000000..bbfaa89 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_coin_w.imageset/ic_coin_w.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_forward.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_forward.imageset/Contents.json new file mode 100644 index 0000000..cc06a6f --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_forward.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_forward.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_forward.imageset/ic_forward.png b/SodaLive/Resources/Assets.xcassets/ic_forward.imageset/ic_forward.png new file mode 100644 index 0000000..7c07cae Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_forward.imageset/ic_forward.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_headphones.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_headphones.imageset/Contents.json new file mode 100644 index 0000000..d7e0f4b --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_headphones.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_headphones.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_headphones.imageset/ic_headphones.png b/SodaLive/Resources/Assets.xcassets/ic_headphones.imageset/ic_headphones.png new file mode 100644 index 0000000..7b4cd4b Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_headphones.imageset/ic_headphones.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_headphones_purple 1.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_headphones_purple 1.imageset/Contents.json new file mode 100644 index 0000000..5f15106 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_headphones_purple 1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_headphones_purple.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_headphones_purple 1.imageset/ic_headphones_purple.png b/SodaLive/Resources/Assets.xcassets/ic_headphones_purple 1.imageset/ic_headphones_purple.png new file mode 100644 index 0000000..d285d38 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_headphones_purple 1.imageset/ic_headphones_purple.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_instagram_circle.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_instagram_circle.imageset/Contents.json new file mode 100644 index 0000000..52e66b4 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_instagram_circle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_instagram_circle.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_instagram_circle.imageset/ic_instagram_circle.png b/SodaLive/Resources/Assets.xcassets/ic_instagram_circle.imageset/ic_instagram_circle.png new file mode 100644 index 0000000..07ba473 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_instagram_circle.imageset/ic_instagram_circle.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_message_square_777.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_message_square_777.imageset/Contents.json new file mode 100644 index 0000000..94a4c86 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_message_square_777.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_message_square_777.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_message_square_777.imageset/ic_message_square_777.png b/SodaLive/Resources/Assets.xcassets/ic_message_square_777.imageset/ic_message_square_777.png new file mode 100644 index 0000000..b4a2ed2 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_message_square_777.imageset/ic_message_square_777.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_myinfo_edit.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_myinfo_edit.imageset/Contents.json new file mode 100644 index 0000000..a68c1c1 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_myinfo_edit.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_myinfo_edit.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_myinfo_edit.imageset/ic_myinfo_edit.png b/SodaLive/Resources/Assets.xcassets/ic_myinfo_edit.imageset/ic_myinfo_edit.png new file mode 100644 index 0000000..133480b Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_myinfo_edit.imageset/ic_myinfo_edit.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_settings.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_settings.imageset/Contents.json new file mode 100644 index 0000000..51c56c0 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_settings.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_settings.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_settings.imageset/ic_settings.png b/SodaLive/Resources/Assets.xcassets/ic_settings.imageset/ic_settings.png new file mode 100644 index 0000000..a31d5ae Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_settings.imageset/ic_settings.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_website_circle.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_website_circle.imageset/Contents.json new file mode 100644 index 0000000..80c43ec --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_website_circle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_website_circle.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_website_circle.imageset/ic_website_circle.png b/SodaLive/Resources/Assets.xcassets/ic_website_circle.imageset/ic_website_circle.png new file mode 100644 index 0000000..6490fbb Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_website_circle.imageset/ic_website_circle.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_youtube_circle.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_youtube_circle.imageset/Contents.json new file mode 100644 index 0000000..9bdb43c --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_youtube_circle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_youtube_circle.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_youtube_circle.imageset/ic_youtube_circle.png b/SodaLive/Resources/Assets.xcassets/ic_youtube_circle.imageset/ic_youtube_circle.png new file mode 100644 index 0000000..80b4764 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_youtube_circle.imageset/ic_youtube_circle.png differ diff --git a/SodaLive/Sources/App/AppStep.swift b/SodaLive/Sources/App/AppStep.swift index c21729c..ec23b2f 100644 --- a/SodaLive/Sources/App/AppStep.swift +++ b/SodaLive/Sources/App/AppStep.swift @@ -21,4 +21,6 @@ enum AppStep { case writeTextMessage(userId: Int?, nickname: String?) case writeVoiceMessage(userId: Int?, nickname: String?, onRefresh: () -> Void) + + case settings } diff --git a/SodaLive/Sources/ContentView.swift b/SodaLive/Sources/ContentView.swift index 21e2248..328f3f3 100644 --- a/SodaLive/Sources/ContentView.swift +++ b/SodaLive/Sources/ContentView.swift @@ -35,6 +35,10 @@ struct ContentView: View { case .writeVoiceMessage(let userId, let nickname, let onRefresh): VoiceMessageWriteView(replySenderId: userId, replySenderNickname: nickname, onRefresh: onRefresh) + case .settings: + EmptyView() + .frame(width: 0, height: 0, alignment: .topLeading) + default: EmptyView() .frame(width: 0, height: 0, alignment: .topLeading) diff --git a/SodaLive/Sources/MyPage/Auth/AuthApi.swift b/SodaLive/Sources/MyPage/Auth/AuthApi.swift new file mode 100644 index 0000000..a589b2a --- /dev/null +++ b/SodaLive/Sources/MyPage/Auth/AuthApi.swift @@ -0,0 +1,44 @@ +// +// AuthApi.swift +// SodaLive +// +// Created by klaus on 2023/08/10. +// + +import Foundation +import Moya + +enum AuthApi { + case auth(request: AuthVerifyRequest) +} + +extension AuthApi: TargetType { + var baseURL: URL { + return URL(string: BASE_URL)! + } + + var path: String { + switch self { + case .auth: + return "/auth" + } + } + + var method: Moya.Method { + return .post + } + + var task: Task { + switch self { + case .auth(let request): + return .requestJSONEncodable(request) + } + } + + var headers: [String : String]? { + switch self { + default: + return ["Authorization": "Bearer \(UserDefaults.string(forKey: UserDefaultsKey.token))"] + } + } +} diff --git a/SodaLive/Sources/MyPage/Auth/AuthButtonView.swift b/SodaLive/Sources/MyPage/Auth/AuthButtonView.swift new file mode 100644 index 0000000..8ad1b66 --- /dev/null +++ b/SodaLive/Sources/MyPage/Auth/AuthButtonView.swift @@ -0,0 +1,27 @@ +// +// AuthButtonView.swift +// SodaLive +// +// Created by klaus on 2023/08/10. +// + +import SwiftUI + +struct AuthButtonView: View { + var body: some View { + Text("본인인증") + .font(.custom(Font.bold.rawValue, size: 15.3)) + .foregroundColor(Color(hex: "eeeeee")) + .padding(.horizontal, 13.3) + .padding(.vertical, 20) + .frame(width: screenSize().width - 26.7, alignment: .leading) + .background(Color(hex: "664aab")) + .cornerRadius(6.7) + } +} + +struct AuthButtonView_Previews: PreviewProvider { + static var previews: some View { + AuthButtonView() + } +} diff --git a/SodaLive/Sources/MyPage/Auth/AuthRepository.swift b/SodaLive/Sources/MyPage/Auth/AuthRepository.swift new file mode 100644 index 0000000..668fab3 --- /dev/null +++ b/SodaLive/Sources/MyPage/Auth/AuthRepository.swift @@ -0,0 +1,20 @@ +// +// AuthRepository.swift +// SodaLive +// +// Created by klaus on 2023/08/10. +// + +import Foundation +import CombineMoya +import Combine +import Moya + +final class AuthRepository { + private let api = MoyaProvider() + + func authCertificate(receiptId: String) -> AnyPublisher { + return api.requestPublisher(.auth(request: AuthVerifyRequest(receiptId: receiptId))) + } +} + diff --git a/SodaLive/Sources/MyPage/Auth/AuthVerifyRequest.swift b/SodaLive/Sources/MyPage/Auth/AuthVerifyRequest.swift new file mode 100644 index 0000000..3e4004a --- /dev/null +++ b/SodaLive/Sources/MyPage/Auth/AuthVerifyRequest.swift @@ -0,0 +1,16 @@ +// +// AuthVerifyRequest.swift +// SodaLive +// +// Created by klaus on 2023/08/10. +// + +import Foundation + +struct AuthVerifyRequest: Encodable { + let receiptId: String + + enum CodingKeys : String, CodingKey { + case receiptId = "receipt_id" + } +} diff --git a/SodaLive/Sources/MyPage/CanCardView.swift b/SodaLive/Sources/MyPage/CanCardView.swift new file mode 100644 index 0000000..07235ba --- /dev/null +++ b/SodaLive/Sources/MyPage/CanCardView.swift @@ -0,0 +1,77 @@ +// +// CanCardView.swift +// SodaLive +// +// Created by klaus on 2023/08/10. +// + +import SwiftUI + +struct CanCardView: View { + let data: MyPageResponse + let refresh: () -> Void + + var body: some View { + HStack(spacing: 0) { + Button(action: {}) { + HStack(spacing: 6.7) { + Text("\(data.chargeCan + data.rewardCan)") + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color(hex: "eeeeee")) + + Image("ic_can") + + Image("ic_forward") + .resizable() + .frame(width: 20, height: 20) + } + } + + Spacer() + + Button(action: {}) { + HStack(spacing: 7) { + Image("ic_coin_w") + .resizable() + .frame(width: 26.7, height: 26.7) + + Text("충전") + .font(.custom(Font.bold.rawValue, size: 12)) + .foregroundColor(Color(hex: "fdca2f")) + } + .padding(.horizontal, 11.3) + .padding(.vertical, 7) + .overlay( + RoundedRectangle(cornerRadius: CGFloat(16.7)) + .stroke(lineWidth: 1) + .foregroundColor(Color(hex: "fdca2f")) + ) + .cornerRadius(16.7) + } + } + .padding(.horizontal, 13.3) + .padding(.vertical, 16.7) + .background(Color(hex: "222222")) + .cornerRadius(16.7) + } +} + +struct CanCardView_Previews: PreviewProvider { + static var previews: some View { + CanCardView( + data: MyPageResponse( + nickname: "완다 막시모프", + profileUrl: "https://test-cf.sodalive.net/profile/default-profile.png", + chargeCan: 0, + rewardCan: 150, + youtubeUrl: "", + instagramUrl: "", + websiteUrl: "", + blogUrl: "", + liveReservationCount: 0, + isAuth: false + ), + refresh: {} + ) + } +} diff --git a/SodaLive/Sources/MyPage/MyInfoCardView.swift b/SodaLive/Sources/MyPage/MyInfoCardView.swift new file mode 100644 index 0000000..0bdfbfe --- /dev/null +++ b/SodaLive/Sources/MyPage/MyInfoCardView.swift @@ -0,0 +1,95 @@ +// +// MyInfoCardView.swift +// SodaLive +// +// Created by klaus on 2023/08/10. +// + +import SwiftUI +import Kingfisher + +struct MyInfoCardView: View { + + let data: MyPageResponse + let refresh: () -> Void + + var body: some View { + HStack(spacing: 13.3) { + KFImage(URL(string: data.profileUrl)) + .resizable() + .scaledToFill() + .frame(width: 90, height: 90, alignment: .top) + .clipShape(Circle()) + + VStack(alignment: .leading, spacing: 13.3) { + HStack(spacing: 0) { + Text(data.nickname) + .font(.custom(Font.bold.rawValue, size: 20)) + .foregroundColor(Color(hex: "eeeeee")) + + Spacer() + + Button(action: { + if AppState.shared.roomId <= 0 { + } + }) { + Image("ic_myinfo_edit") + .resizable() + .frame(width: 20, height: 20) + } + } + + HStack(spacing: 10) { + Spacer() + + if let websiteUrl = data.websiteUrl, websiteUrl.count > 0, let url = URL(string: websiteUrl), UIApplication.shared.canOpenURL(url) { + Button(action: {UIApplication.shared.open(url)}) { + Image("ic_website_circle") + } + } + + if let blogUrl = data.blogUrl, blogUrl.count > 0, let url = URL(string: blogUrl), UIApplication.shared.canOpenURL(url) { + Button(action: {UIApplication.shared.open(url)}) { + Image("ic_blog_circle") + } + } + + if let instagramUrl = data.instagramUrl, instagramUrl.count > 0, let url = URL(string: instagramUrl), UIApplication.shared.canOpenURL(url) { + Button(action: {UIApplication.shared.open(url)}) { + Image("ic_instagram_circle") + } + } + + if let youtubeUrl = data.youtubeUrl, youtubeUrl.count > 0, let url = URL(string: youtubeUrl), UIApplication.shared.canOpenURL(url) { + Button(action: {UIApplication.shared.open(url)}) { + Image("ic_youtube_circle") + } + } + } + } + } + .padding(20) + .background(Color(hex: "222222")) + .cornerRadius(16.7) + } +} + +struct MyInfoCardView_Previews: PreviewProvider { + static var previews: some View { + MyInfoCardView( + data: MyPageResponse( + nickname: "완다 막시모프", + profileUrl: "https://test-cf.sodalive.net/profile/default-profile.png", + chargeCan: 0, + rewardCan: 150, + youtubeUrl: "", + instagramUrl: "", + websiteUrl: "", + blogUrl: "", + liveReservationCount: 0, + isAuth: false + ), + refresh: {} + ) + } +} diff --git a/SodaLive/Sources/MyPage/MyPageResponse.swift b/SodaLive/Sources/MyPage/MyPageResponse.swift new file mode 100644 index 0000000..1635cf1 --- /dev/null +++ b/SodaLive/Sources/MyPage/MyPageResponse.swift @@ -0,0 +1,22 @@ +// +// MyPageResponse.swift +// SodaLive +// +// Created by klaus on 2023/08/10. +// + +import Foundation + +struct MyPageResponse: Decodable { + let nickname: String + let profileUrl: String + let chargeCan: Int + let rewardCan: Int + let youtubeUrl: String? + let instagramUrl: String? + let websiteUrl: String? + let blogUrl: String? + let liveReservationCount: Int + let isAuth: Bool +} + diff --git a/SodaLive/Sources/MyPage/MyPageView.swift b/SodaLive/Sources/MyPage/MyPageView.swift index be2a68d..ff5ce0d 100644 --- a/SodaLive/Sources/MyPage/MyPageView.swift +++ b/SodaLive/Sources/MyPage/MyPageView.swift @@ -7,9 +7,138 @@ import SwiftUI +import Bootpay +import BootpayUI +import PopupView +import RefreshableScrollView + struct MyPageView: View { + + @StateObject var viewModel = MyPageViewModel() + + @State private var payload = Payload() + var body: some View { - Text("MyPage") + BaseView(isLoading: $viewModel.isLoading) { + if viewModel.isShowAuthView { + BootpayUI(payload: payload, requestType: BootpayRequest.TYPE_AUTHENTICATION) + .onConfirm { + DEBUG_LOG("onConfirm: \($0)") + return true + } + .onCancel { + DEBUG_LOG("onCancel: \($0)") + } + .onError { + DEBUG_LOG("onError: \($0)") + viewModel.errorMessage = "본인인증 중 오류가 발생했습니다." + viewModel.isShowPopup = true + viewModel.isShowAuthView = false + } + .onDone { + DEBUG_LOG("onDone: \($0)") + viewModel.authVerify($0) + } + .onClose { + DEBUG_LOG("onClose") + viewModel.isShowAuthView = false + } + } else { + GeometryReader { geo in + VStack { + HomeNavigationBar(title: "마이 페이지") { + Image("ic_settings") + .resizable() + .frame(width: 20, height: 20) + .onTapGesture { + AppState.shared.setAppStep(step: .settings) + } + } + RefreshableScrollView( + refreshing: $viewModel.isLoading, + action: { + viewModel.getMypage() + } + ) { + VStack(spacing: 0) { + if let data = viewModel.myPageResponse { + MyInfoCardView(data: data) { + viewModel.getMypage() + } + .frame(width: screenSize().width - 26.7) + .padding(.top, 13.3) + + if UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue { + Text("내 채널 보기") + .frame(width: screenSize().width - 26.7, height: 46.7) + .font(.custom(Font.bold.rawValue, size: 15.3)) + .foregroundColor(Color(hex: "eeeeee")) + .background(Color(hex: "352953")) + .cornerRadius(6.7) + .overlay( + RoundedRectangle(cornerRadius: 6.7) + .stroke(Color(hex: "9970ff"), lineWidth: 1.3) + ) + .padding(.top, 26.7) + .onTapGesture {} + } + + CanCardView(data: data) { + viewModel.getMypage() + } + .frame(width: screenSize().width - 26.7) + .padding(.top, 26.7) + + ReservationStatusView(data: data) + .padding(.top, 33.3) + + ServiceCenterButtonView() + .padding(.top, 40) + .padding(.bottom, data.isAuth ? 40 : 0) + + if !data.isAuth { + AuthButtonView() + .padding(.vertical, 40) + .onTapGesture { + viewModel.isShowAuthView = true + } + } + } + } + } + } + .frame(width: geo.size.width, height: geo.size.height) + } + .onAppear { + viewModel.getMypage() + } + } + } + .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) { + GeometryReader { geo in + HStack { + Spacer() + Text(viewModel.errorMessage) + .padding(.vertical, 13.3) + .frame(width: geo.size.width - 66.7, alignment: .center) + .font(.custom(Font.medium.rawValue, size: 12)) + .background(Color(hex: "9970ff")) + .foregroundColor(Color.white) + .multilineTextAlignment(.center) + .cornerRadius(20) + .padding(.top, 66.7) + Spacer() + } + } + } + .onAppear { + payload.applicationId = BOOTPAY_APP_ID + payload.price = 0 + payload.pg = "다날" + payload.method = "본인인증" + payload.orderName = "본인인증" + payload.authenticationId = "\(UserDefaults.string(forKey: .nickname))__\(String(NSTimeIntervalSince1970))" + } } } diff --git a/SodaLive/Sources/MyPage/MyPageViewModel.swift b/SodaLive/Sources/MyPage/MyPageViewModel.swift new file mode 100644 index 0000000..ca990ef --- /dev/null +++ b/SodaLive/Sources/MyPage/MyPageViewModel.swift @@ -0,0 +1,110 @@ +// +// MyPageViewModel.swift +// SodaLive +// +// Created by klaus on 2023/08/10. +// + +import Foundation +import Combine + +final class MyPageViewModel: ObservableObject { + + private let repository = UserRepository() + private let authRepository = AuthRepository() + private var subscription = Set() + + @Published var myPageResponse: MyPageResponse? = nil + + @Published var errorMessage = "" + @Published var isShowPopup = false + @Published var isLoading = false + @Published var isShowAuthView = false + + func getMypage() { + isLoading = true + + repository.getMypage() + .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.self, from: responseData) + + if let data = decoded.data, decoded.success { + self.myPageResponse = data + } 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) + } + + func authVerify(_ data: [String: Any]) { + isLoading = true + + let _data = data["data"] as? [String: Any] + + if let data = _data { + let receiptId = data["receipt_id"] as! String + + authRepository.authCertificate(receiptId: receiptId) + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { [unowned self] response in + self.isLoading = false + let responseData = response.data + + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) + + if decoded.success { + self.errorMessage = "본인인증이 완료되었습니다." + self.isShowPopup = true + self.getMypage() + } 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) + } else { + isLoading = false + errorMessage = "본인인증 중 오류가 발생했습니다." + isShowPopup = true + } + } +} diff --git a/SodaLive/Sources/MyPage/ReservationStatusView.swift b/SodaLive/Sources/MyPage/ReservationStatusView.swift new file mode 100644 index 0000000..96a95e1 --- /dev/null +++ b/SodaLive/Sources/MyPage/ReservationStatusView.swift @@ -0,0 +1,67 @@ +// +// ReservationStatusView.swift +// SodaLive +// +// Created by klaus on 2023/08/10. +// + +import SwiftUI + +struct ReservationStatusView: View { + let data: MyPageResponse + + var body: some View { + VStack(alignment: .leading, spacing: 13.3) { + Text("예약현황") + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color(hex: "eeeeee")) + + HStack(spacing: 0) { + let width = screenSize().width - 26.7 + + HStack(spacing: 6.7) { + Image("ic_tabbar_live_selected") + .resizable() + .frame(width: 20, height: 20) + + Text("라이브") + .font(.custom(Font.medium.rawValue, size: 14.7)) + .foregroundColor(Color(hex: "eeeeee")) + + Text("\(data.liveReservationCount)") + .font(.custom(Font.medium.rawValue, size: 14.7)) + .foregroundColor(Color(hex: "9970ff")) + } + .frame(width: width, height: 46.7) + .background(Color(hex: "352953")) + .cornerRadius(6.7) + .overlay( + RoundedRectangle(cornerRadius: 6.7) + .stroke(Color(hex: "9970ff"), lineWidth: 1.3) + ) + .onTapGesture { + } + } + } + .frame(width: screenSize().width - 26.7, alignment: .leading) + } +} + +struct ReservationStatusView_Previews: PreviewProvider { + static var previews: some View { + ReservationStatusView( + data: MyPageResponse( + nickname: "완다 막시모프", + profileUrl: "https://test-cf.sodalive.net/profile/default-profile.png", + chargeCan: 0, + rewardCan: 150, + youtubeUrl: "", + instagramUrl: "", + websiteUrl: "", + blogUrl: "", + liveReservationCount: 0, + isAuth: false + ) + ) + } +} diff --git a/SodaLive/Sources/MyPage/ServiceCenterButtonView.swift b/SodaLive/Sources/MyPage/ServiceCenterButtonView.swift new file mode 100644 index 0000000..77eb9b0 --- /dev/null +++ b/SodaLive/Sources/MyPage/ServiceCenterButtonView.swift @@ -0,0 +1,40 @@ +// +// ServiceCenterButtonView.swift +// SodaLive +// +// Created by klaus on 2023/08/10. +// + +import SwiftUI + +struct ServiceCenterButtonView: View { + var body: some View { + HStack(spacing: 13.7) { + Image("ic_headphones") + .resizable() + .frame(width: 26.7, height: 26.7) + + Text("소다라이브 고객센터") + .font(.custom(Font.bold.rawValue, size: 15.3)) + .foregroundColor(.white) + + Spacer() + + Image("ic_forward") + .resizable() + .frame(width: 20, height: 20) + } + .padding(.horizontal, 13.3) + .frame(width: screenSize().width - 26.7, height: 66.7) + .background(Color(hex: "664aab")) + .cornerRadius(6.7) + .onTapGesture { + } + } +} + +struct ServiceCenterButtonView_Previews: PreviewProvider { + static var previews: some View { + ServiceCenterButtonView() + } +} diff --git a/SodaLive/Sources/Settings/Notification/GetMemberInfoResponse.swift b/SodaLive/Sources/Settings/Notification/GetMemberInfoResponse.swift new file mode 100644 index 0000000..c7be304 --- /dev/null +++ b/SodaLive/Sources/Settings/Notification/GetMemberInfoResponse.swift @@ -0,0 +1,21 @@ +// +// GetMemberInfoResponse.swift +// SodaLive +// +// Created by klaus on 2023/08/10. +// + +import Foundation + +enum MemberRole: String, Decodable { + case USER, CREATOR, AGENT, ADMIN, BOT +} + +struct GetMemberInfoResponse: Decodable { + let can: Int + let isAuth: Bool + let role: MemberRole + let messageNotice: Bool? + let followingChannelLiveNotice: Bool? + let followingChannelUploadContentNotice: Bool? +} diff --git a/SodaLive/Sources/User/UserApi.swift b/SodaLive/Sources/User/UserApi.swift index afa3eed..8f82bfb 100644 --- a/SodaLive/Sources/User/UserApi.swift +++ b/SodaLive/Sources/User/UserApi.swift @@ -13,6 +13,7 @@ enum UserApi { case signUp(parameters: [MultipartFormData]) case findPassword(request: ForgotPasswordRequest) case searchUser(nickname: String) + case getMypage } extension UserApi: TargetType { @@ -33,6 +34,9 @@ extension UserApi: TargetType { case .searchUser: return "/member/search" + + case .getMypage: + return "/member/mypage" } } @@ -41,7 +45,7 @@ extension UserApi: TargetType { case .login, .signUp, .findPassword: return .post - case .searchUser: + case .searchUser, .getMypage: return .get } } @@ -59,6 +63,9 @@ extension UserApi: TargetType { case .searchUser(let nickname): return .requestParameters(parameters: ["nickname" : nickname], encoding: URLEncoding.queryString) + + case .getMypage: + return .requestParameters(parameters: ["container" : "ios"], encoding: URLEncoding.queryString) } } diff --git a/SodaLive/Sources/User/UserRepository.swift b/SodaLive/Sources/User/UserRepository.swift index 729d40a..ce3633c 100644 --- a/SodaLive/Sources/User/UserRepository.swift +++ b/SodaLive/Sources/User/UserRepository.swift @@ -28,4 +28,8 @@ final class UserRepository { func searchUser(nickname: String) -> AnyPublisher { return api.requestPublisher(.searchUser(nickname: nickname)) } + + func getMypage() -> AnyPublisher { + return api.requestPublisher(.getMypage) + } }