마이페이지 추가
							
								
								
									
										13
									
								
								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 | ||||
|  | ||||
|   | ||||
							
								
								
									
										49
									
								
								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 | ||||
|   | ||||
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_blog_circle.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_blog_circle.imageset/ic_blog_circle.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.8 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_coin_w.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_coin_w.imageset/ic_coin_w.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_forward.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_forward.imageset/ic_forward.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 223 B | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_headphones.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_headphones.imageset/ic_headphones.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 756 B | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_headphones_purple 1.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_headphones_purple 1.imageset/ic_headphones_purple.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_instagram_circle.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_instagram_circle.imageset/ic_instagram_circle.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.1 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_message_square_777.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_message_square_777.imageset/ic_message_square_777.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 471 B | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_myinfo_edit.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_myinfo_edit.imageset/ic_myinfo_edit.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 332 B | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_settings.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_settings.imageset/ic_settings.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 976 B | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_website_circle.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_website_circle.imageset/ic_website_circle.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.7 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_youtube_circle.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_youtube_circle.imageset/ic_youtube_circle.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.3 KiB | 
| @@ -21,4 +21,6 @@ enum AppStep { | ||||
|     case writeTextMessage(userId: Int?, nickname: String?) | ||||
|      | ||||
|     case writeVoiceMessage(userId: Int?, nickname: String?, onRefresh: () -> Void) | ||||
|      | ||||
|     case settings | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
							
								
								
									
										44
									
								
								SodaLive/Sources/MyPage/Auth/AuthApi.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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))"] | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										27
									
								
								SodaLive/Sources/MyPage/Auth/AuthButtonView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										20
									
								
								SodaLive/Sources/MyPage/Auth/AuthRepository.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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<AuthApi>() | ||||
|      | ||||
|     func authCertificate(receiptId: String) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.auth(request: AuthVerifyRequest(receiptId: receiptId))) | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										16
									
								
								SodaLive/Sources/MyPage/Auth/AuthVerifyRequest.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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" | ||||
|     } | ||||
| } | ||||
							
								
								
									
										77
									
								
								SodaLive/Sources/MyPage/CanCardView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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: {} | ||||
|         ) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										95
									
								
								SodaLive/Sources/MyPage/MyInfoCardView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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: {} | ||||
|         ) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										22
									
								
								SodaLive/Sources/MyPage/MyPageResponse.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
| } | ||||
|  | ||||
| @@ -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))" | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										110
									
								
								SodaLive/Sources/MyPage/MyPageViewModel.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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<AnyCancellable>() | ||||
|      | ||||
|     @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<MyPageResponse>.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 | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										67
									
								
								SodaLive/Sources/MyPage/ReservationStatusView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										40
									
								
								SodaLive/Sources/MyPage/ServiceCenterButtonView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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() | ||||
|     } | ||||
| } | ||||
| @@ -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? | ||||
| } | ||||
| @@ -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) | ||||
|         } | ||||
|     } | ||||
|      | ||||
|   | ||||
| @@ -28,4 +28,8 @@ final class UserRepository { | ||||
|     func searchUser(nickname: String) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.searchUser(nickname: nickname)) | ||||
|     } | ||||
|      | ||||
|     func getMypage() -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getMypage) | ||||
|     } | ||||
| } | ||||
|   | ||||
 Yu Sung
					Yu Sung