스플래시 페이지 추가
This commit is contained in:
		| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
| 	<key>IDEDidComputeMac32BitWarning</key> | ||||
| 	<true/> | ||||
| </dict> | ||||
| </plist> | ||||
							
								
								
									
										104
									
								
								SodaLive.xcworkspace/xcshareddata/swiftpm/Package.resolved
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								SodaLive.xcworkspace/xcshareddata/swiftpm/Package.resolved
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| { | ||||
|   "pins" : [ | ||||
|     { | ||||
|       "identity" : "abseil-cpp-binary", | ||||
|       "kind" : "remoteSourceControl", | ||||
|       "location" : "https://github.com/google/abseil-cpp-binary.git", | ||||
|       "state" : { | ||||
|         "revision" : "bfc0b6f81adc06ce5121eb23f628473638d67c5c", | ||||
|         "version" : "1.2022062300.0" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "identity" : "firebase-ios-sdk", | ||||
|       "kind" : "remoteSourceControl", | ||||
|       "location" : "https://github.com/firebase/firebase-ios-sdk.git", | ||||
|       "state" : { | ||||
|         "revision" : "df2171b0c6afb9e9d4f7e07669d558c510b9f6be", | ||||
|         "version" : "10.13.0" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "identity" : "googleappmeasurement", | ||||
|       "kind" : "remoteSourceControl", | ||||
|       "location" : "https://github.com/google/GoogleAppMeasurement.git", | ||||
|       "state" : { | ||||
|         "revision" : "03b9beee1a61f62d32c521e172e192a1663a5e8b", | ||||
|         "version" : "10.13.0" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "identity" : "googledatatransport", | ||||
|       "kind" : "remoteSourceControl", | ||||
|       "location" : "https://github.com/google/GoogleDataTransport.git", | ||||
|       "state" : { | ||||
|         "revision" : "aae45a320fd0d11811820335b1eabc8753902a40", | ||||
|         "version" : "9.2.5" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "identity" : "googleutilities", | ||||
|       "kind" : "remoteSourceControl", | ||||
|       "location" : "https://github.com/google/GoogleUtilities.git", | ||||
|       "state" : { | ||||
|         "revision" : "c38ce365d77b04a9a300c31061c5227589e5597b", | ||||
|         "version" : "7.11.5" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "identity" : "grpc-binary", | ||||
|       "kind" : "remoteSourceControl", | ||||
|       "location" : "https://github.com/google/grpc-binary.git", | ||||
|       "state" : { | ||||
|         "revision" : "f1b366129d1125be7db83247e003fc333104b569", | ||||
|         "version" : "1.50.2" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "identity" : "gtm-session-fetcher", | ||||
|       "kind" : "remoteSourceControl", | ||||
|       "location" : "https://github.com/google/gtm-session-fetcher.git", | ||||
|       "state" : { | ||||
|         "revision" : "d415594121c9e8a4f9d79cecee0965cf35e74dbd", | ||||
|         "version" : "3.1.1" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "identity" : "leveldb", | ||||
|       "kind" : "remoteSourceControl", | ||||
|       "location" : "https://github.com/firebase/leveldb.git", | ||||
|       "state" : { | ||||
|         "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", | ||||
|         "version" : "1.22.2" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "identity" : "nanopb", | ||||
|       "kind" : "remoteSourceControl", | ||||
|       "location" : "https://github.com/firebase/nanopb.git", | ||||
|       "state" : { | ||||
|         "revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692", | ||||
|         "version" : "2.30909.0" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "identity" : "promises", | ||||
|       "kind" : "remoteSourceControl", | ||||
|       "location" : "https://github.com/google/promises.git", | ||||
|       "state" : { | ||||
|         "revision" : "e70e889c0196c76d22759eb50d6a0270ca9f1d9e", | ||||
|         "version" : "2.3.1" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "identity" : "swift-protobuf", | ||||
|       "kind" : "remoteSourceControl", | ||||
|       "location" : "https://github.com/apple/swift-protobuf.git", | ||||
|       "state" : { | ||||
|         "revision" : "ce20dc083ee485524b802669890291c0d8090170", | ||||
|         "version" : "1.22.1" | ||||
|       } | ||||
|     } | ||||
|   ], | ||||
|   "version" : 2 | ||||
| } | ||||
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/splash_bubble.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/splash_bubble.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
|   "images" : [ | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "filename" : "splash_bubble.png", | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "3x" | ||||
|     } | ||||
|   ], | ||||
|   "info" : { | ||||
|     "author" : "xcode", | ||||
|     "version" : 1 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/splash_bubble.imageset/splash_bubble.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/splash_bubble.imageset/splash_bubble.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 11 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/splash_logo.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/splash_logo.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
|   "images" : [ | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "filename" : "splash_logo.png", | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "3x" | ||||
|     } | ||||
|   ], | ||||
|   "info" : { | ||||
|     "author" : "xcode", | ||||
|     "version" : 1 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/splash_logo.imageset/splash_logo.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/splash_logo.imageset/splash_logo.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 55 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/splash_text.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/splash_text.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
|   "images" : [ | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "filename" : "splash_text.png", | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "3x" | ||||
|     } | ||||
|   ], | ||||
|   "info" : { | ||||
|     "author" : "xcode", | ||||
|     "version" : 1 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/splash_text.imageset/splash_text.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/splash_text.imageset/splash_text.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/vividnext_logo.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/vividnext_logo.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
|   "images" : [ | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "filename" : "vividnext_logo.png", | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "3x" | ||||
|     } | ||||
|   ], | ||||
|   "info" : { | ||||
|     "author" : "xcode", | ||||
|     "version" : 1 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/vividnext_logo.imageset/vividnext_logo.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/vividnext_logo.imageset/vividnext_logo.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 6.6 KiB | 
							
								
								
									
										34
									
								
								SodaLive/Resources/Debug/GoogleService-Info.plist
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								SodaLive/Resources/Debug/GoogleService-Info.plist
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
| 	<key>CLIENT_ID</key> | ||||
| 	<string>758414412471-rgobklhtodvt7c2crp4cmh7q1kd89nha.apps.googleusercontent.com</string> | ||||
| 	<key>REVERSED_CLIENT_ID</key> | ||||
| 	<string>com.googleusercontent.apps.758414412471-rgobklhtodvt7c2crp4cmh7q1kd89nha</string> | ||||
| 	<key>API_KEY</key> | ||||
| 	<string>AIzaSyAYscsMZzW1m0btcs6c2zkI8pLtcDE_Eqg</string> | ||||
| 	<key>GCM_SENDER_ID</key> | ||||
| 	<string>758414412471</string> | ||||
| 	<key>PLIST_VERSION</key> | ||||
| 	<string>1</string> | ||||
| 	<key>BUNDLE_ID</key> | ||||
| 	<string>kr.co.vividnext.sodalive.dev</string> | ||||
| 	<key>PROJECT_ID</key> | ||||
| 	<string>sodalive-test</string> | ||||
| 	<key>STORAGE_BUCKET</key> | ||||
| 	<string>sodalive-test.appspot.com</string> | ||||
| 	<key>IS_ADS_ENABLED</key> | ||||
| 	<false></false> | ||||
| 	<key>IS_ANALYTICS_ENABLED</key> | ||||
| 	<false></false> | ||||
| 	<key>IS_APPINVITE_ENABLED</key> | ||||
| 	<true></true> | ||||
| 	<key>IS_GCM_ENABLED</key> | ||||
| 	<true></true> | ||||
| 	<key>IS_SIGNIN_ENABLED</key> | ||||
| 	<true></true> | ||||
| 	<key>GOOGLE_APP_ID</key> | ||||
| 	<string>1:758414412471:ios:866b0814ab94bdf77a5b32</string> | ||||
| </dict> | ||||
| </plist> | ||||
							
								
								
									
										110
									
								
								SodaLive/Sources/App/AppDelegate.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								SodaLive/Sources/App/AppDelegate.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| // | ||||
| //  AppDelegate.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/09. | ||||
| // | ||||
|  | ||||
| import UIKit | ||||
|  | ||||
| import FirebaseCore | ||||
| import FirebaseMessaging | ||||
|  | ||||
| class AppDelegate: UIResponder, UIApplicationDelegate { | ||||
|      | ||||
|     private let gcmMessageIDKey = "gcm.message_id" | ||||
|      | ||||
|     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { | ||||
|         FirebaseApp.configure() | ||||
|          | ||||
|         Messaging.messaging().delegate = self | ||||
|          | ||||
|         // For iOS 10 display notification (sent via APNS) | ||||
|         UNUserNotificationCenter.current().delegate = self | ||||
|          | ||||
|         let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] | ||||
|         UNUserNotificationCenter.current().requestAuthorization( | ||||
|             options: authOptions, | ||||
|             completionHandler: {_, _ in }) | ||||
|          | ||||
|         application.registerForRemoteNotifications() | ||||
|         UIApplication.shared.applicationIconBadgeNumber = 0 | ||||
|          | ||||
|         return true | ||||
|     } | ||||
|      | ||||
|     func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { | ||||
|         Messaging.messaging().appDidReceiveMessage(userInfo) | ||||
|         // Print message ID. | ||||
|         if let messageID = userInfo[gcmMessageIDKey] { | ||||
|             DEBUG_LOG("Message ID: \(messageID)") | ||||
|         } | ||||
|          | ||||
|         // Print full message. | ||||
|         DEBUG_LOG("userInfo: \(userInfo)") | ||||
|          | ||||
|         completionHandler(UIBackgroundFetchResult.newData) | ||||
|     } | ||||
|      | ||||
|     func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { | ||||
|         UserDefaults.set(deviceToken, forKey: .devicePushToken) | ||||
|         Messaging.messaging().apnsToken = deviceToken | ||||
|     } | ||||
|      | ||||
|     func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| extension AppDelegate: MessagingDelegate { | ||||
|     func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { | ||||
|         if let fcmToken = fcmToken { | ||||
|             DEBUG_LOG("fcmToken: \(fcmToken)") | ||||
|             UserDefaults.set(fcmToken, forKey: .pushToken) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| extension AppDelegate : UNUserNotificationCenterDelegate { | ||||
|      | ||||
|     // Receive displayed notifications for iOS 10 devices. | ||||
|     func userNotificationCenter( | ||||
|         _ center: UNUserNotificationCenter, | ||||
|         willPresent notification: UNNotification, | ||||
|         withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void | ||||
|     ) { | ||||
|         let userInfo = notification.request.content.userInfo | ||||
|          | ||||
|         // With swizzling disabled you must let Messaging know about the message, for Analytics | ||||
|         Messaging.messaging().appDidReceiveMessage(userInfo) | ||||
|          | ||||
|         // ... | ||||
|          | ||||
|         // Print full message. | ||||
|         DEBUG_LOG("userInfo: \(userInfo)") | ||||
|          | ||||
|         // Change this to your preferred presentation option | ||||
|         completionHandler([.banner, .badge, .sound]) | ||||
|     } | ||||
|      | ||||
|     func userNotificationCenter(_ center: UNUserNotificationCenter, | ||||
|                                 didReceive response: UNNotificationResponse, | ||||
|                                 withCompletionHandler completionHandler: @escaping () -> Void) { | ||||
|         let userInfo = response.notification.request.content.userInfo | ||||
|          | ||||
|         // With swizzling disabled you must let Messaging know about the message, for Analytics | ||||
|         Messaging.messaging().appDidReceiveMessage(userInfo) | ||||
|          | ||||
|         let roomIdString = userInfo["suda_room_id"] as? String | ||||
|         let audioContentIdString = userInfo["audio_content_id"] as? String | ||||
|          | ||||
|         if let roomIdString = roomIdString, let roomId = Int(roomIdString), roomId > 0 { | ||||
|             AppState.shared.pushRoomId = roomId | ||||
|         } | ||||
|          | ||||
|         if let audioContentIdString = audioContentIdString, let audioContentId = Int(audioContentIdString), audioContentId > 0 { | ||||
|             AppState.shared.pushAudioContentId = audioContentId | ||||
|         } | ||||
|          | ||||
|         completionHandler() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										58
									
								
								SodaLive/Sources/App/AppState.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								SodaLive/Sources/App/AppState.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| // | ||||
| //  AppState.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/09. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| class AppState: ObservableObject { | ||||
|     static let shared = AppState() | ||||
|      | ||||
|     private var appStepBackStack = [AppStep]() | ||||
|      | ||||
|     @Published private(set) var appStep: AppStep = .splash | ||||
|      | ||||
|     @Published var isShowPlayer = false { | ||||
|         didSet { | ||||
|             if isShowPlayer { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     @Published var isShowNotificationSettingsDialog = false | ||||
|      | ||||
|     @Published var pushRoomId = 0 | ||||
|     @Published var pushChannelId = 0 | ||||
|     @Published var pushAudioContentId = 0 | ||||
|     @Published var roomId = 0 { | ||||
|         didSet { | ||||
|             if roomId <= 0 { | ||||
|                 isShowPlayer = false | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     func setAppStep(step: AppStep) { | ||||
|         switch step { | ||||
|         case .splash, .main: | ||||
|             appStepBackStack.removeAll() | ||||
|              | ||||
|         default: | ||||
|             appStepBackStack.append(appStep) | ||||
|         } | ||||
|          | ||||
|         DispatchQueue.main.async { | ||||
|             self.appStep = step | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     func back() { | ||||
|         if let step = appStepBackStack.popLast() { | ||||
|             self.appStep = step | ||||
|         } else { | ||||
|             self.appStep = .main | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								SodaLive/Sources/App/AppStep.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								SodaLive/Sources/App/AppStep.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| // | ||||
| //  AppStep.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/09. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| enum AppStep { | ||||
|     case splash | ||||
|      | ||||
|     case main | ||||
| } | ||||
| @@ -6,12 +6,81 @@ | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
| import AppTrackingTransparency | ||||
|  | ||||
| import FirebaseDynamicLinks | ||||
|  | ||||
| @main | ||||
| struct SodaLiveApp: App { | ||||
|      | ||||
|     @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate | ||||
|      | ||||
|     func handleIncomingDynamicLink(_ dynamicLink: DynamicLink) { | ||||
|         guard let url = dynamicLink.url else { | ||||
|             DEBUG_LOG("That's weired. My dynamic link object has no url") | ||||
|             return | ||||
|         } | ||||
|          | ||||
|         DEBUG_LOG("incoming link parameter is \(url.absoluteString)") | ||||
|          | ||||
|         let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems | ||||
|         let roomId = queryItems?.filter({$0.name == "room_id"}).first?.value | ||||
|         let channelId = queryItems?.filter({$0.name == "channel_id"}).first?.value | ||||
|         let audioContentId = queryItems?.filter({$0.name == "audio_content_id"}).first?.value | ||||
|          | ||||
|         if let roomId = roomId { | ||||
|             DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { | ||||
|                 AppState.shared.pushRoomId = Int(roomId) ?? 0 | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         if let channelId = channelId { | ||||
|             DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { | ||||
|                 AppState.shared.pushChannelId = Int(channelId) ?? 0 | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         if let audioContentId = audioContentId { | ||||
|             DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { | ||||
|                 AppState.shared.pushAudioContentId = Int(audioContentId) ?? 0 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     var body: some Scene { | ||||
|         WindowGroup { | ||||
|             ContentView() | ||||
|                 .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in | ||||
|                     UIApplication.shared.applicationIconBadgeNumber = 0 | ||||
|                      | ||||
|                     ATTrackingManager.requestTrackingAuthorization { _ in } | ||||
|                 } | ||||
|                 .onOpenURL { url in | ||||
|                     DEBUG_LOG("I have received a URL through a custom scheme! \(url.absoluteString)") | ||||
|                     if let scheme = url.scheme { | ||||
|                         if scheme == "kr.co.vividnext.yozm" { | ||||
|                             if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) { | ||||
|                                 self.handleIncomingDynamicLink(dynamicLink) | ||||
|                             } else { | ||||
|                                 DEBUG_LOG("dynamic link fail") | ||||
|                             } | ||||
|                         } else { | ||||
|                             DynamicLinks.dynamicLinks().handleUniversalLink(url) { dynamicLink, error in | ||||
|                                 guard error == nil else { | ||||
|                                     DEBUG_LOG("Found an error! \(error!.localizedDescription)") | ||||
|                                     DEBUG_LOG("dynamic link fail") | ||||
|                                     return | ||||
|                                 } | ||||
|                                  | ||||
|                                 if let dynamicLink = dynamicLink { | ||||
|                                     self.handleIncomingDynamicLink(dynamicLink) | ||||
|                                 } else { | ||||
|                                     DEBUG_LOG("dynamic link fail") | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -8,14 +8,21 @@ | ||||
| import SwiftUI | ||||
|  | ||||
| struct ContentView: View { | ||||
|     @StateObject private var appState = AppState.shared | ||||
|      | ||||
|     var body: some View { | ||||
|         VStack { | ||||
|             Image(systemName: "globe") | ||||
|                 .imageScale(.large) | ||||
|                 .foregroundColor(.accentColor) | ||||
|             Text("Hello, world!") | ||||
|         ZStack { | ||||
|             Color.black.ignoresSafeArea() | ||||
|              | ||||
|             switch appState.appStep { | ||||
|             case .splash: | ||||
|                 SplashView() | ||||
|                  | ||||
|             default: | ||||
|                 EmptyView() | ||||
|                     .frame(width: 0, height: 0, alignment: .topLeading) | ||||
|             } | ||||
|         } | ||||
|         .padding() | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										16
									
								
								SodaLive/Sources/Debug/Utils/Constants.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								SodaLive/Sources/Debug/Utils/Constants.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| // | ||||
| //  Constants.swift | ||||
| //  SodaLive-dev | ||||
| // | ||||
| //  Created by klaus on 2023/08/09. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| let BASE_URL = "https://test-api.sodalive.net" | ||||
| let APPLY_YOZM_CREATOR = "https://forms.gle/mmhJKmijjVRnwtdZ7" | ||||
|  | ||||
| let AGORA_APP_ID = "b96574e191a9430fa54c605528aa3ef7" | ||||
| let AGORA_APP_CERTIFICATE = "ae18ade3afcf4086bd4397726eb0654c" | ||||
|  | ||||
| let BOOTPAY_APP_ID = "6242a7772701800023f68b2f" | ||||
							
								
								
									
										40
									
								
								SodaLive/Sources/Extensions/ColorExtension.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								SodaLive/Sources/Extensions/ColorExtension.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| // | ||||
| //  ColorExtension.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/09. | ||||
| // | ||||
| //  https://seons-dev.tistory.com/174 | ||||
| // | ||||
|   | ||||
| import SwiftUI | ||||
|   | ||||
| extension Color { | ||||
|   init(hex: String) { | ||||
|     let scanner = Scanner(string: hex) | ||||
|     _ = scanner.scanString("#") | ||||
|      | ||||
|     var rgb: UInt64 = 0 | ||||
|     scanner.scanHexInt64(&rgb) | ||||
|      | ||||
|     let r = Double((rgb >> 16) & 0xFF) / 255.0 | ||||
|     let g = Double((rgb >>  8) & 0xFF) / 255.0 | ||||
|     let b = Double((rgb >>  0) & 0xFF) / 255.0 | ||||
|     self.init(red: r, green: g, blue: b) | ||||
|   } | ||||
| } | ||||
|  | ||||
| extension UIColor { | ||||
|     convenience init(hex: String, alpha: CGFloat = 1.0) { | ||||
|         let scanner = Scanner(string: hex) | ||||
|         _ = scanner.scanString("#") | ||||
|          | ||||
|         var rgb: UInt64 = 0 | ||||
|         scanner.scanHexInt64(&rgb) | ||||
|          | ||||
|         let r = Double((rgb >> 16) & 0xFF) / 255.0 | ||||
|         let g = Double((rgb >>  8) & 0xFF) / 255.0 | ||||
|         let b = Double((rgb >>  0) & 0xFF) / 255.0 | ||||
|         self.init(red: r, green: g, blue: b, alpha: alpha) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										21
									
								
								SodaLive/Sources/Extensions/DateExtension.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								SodaLive/Sources/Extensions/DateExtension.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| // | ||||
| //  DateExtension.swift | ||||
| //  yozm | ||||
| // | ||||
| //  Created by klaus on 2022/05/27. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| extension Date { | ||||
|     func convertDateFormat(dateFormat: String = "yyyy.MM.dd") -> String { | ||||
|         let formatter = DateFormatter() | ||||
|         formatter.dateFormat = dateFormat | ||||
|         formatter.locale = Locale(identifier: "ko") | ||||
|         return formatter.string(from: self) | ||||
|     } | ||||
|      | ||||
|     func currentTimeMillis() -> Int64 { | ||||
|         return Int64(self.timeIntervalSince1970 * 1000) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										32
									
								
								SodaLive/Sources/Extensions/IntExtension.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								SodaLive/Sources/Extensions/IntExtension.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| // | ||||
| //  IntExtension.swift | ||||
| //  yozm | ||||
| // | ||||
| //  Created by klaus on 2022/06/21. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| extension Int64 { | ||||
|     func durationText() -> String { | ||||
|         let duration = self | ||||
|          | ||||
|         let convertedTime = Int(duration / 1000) | ||||
|         let hour = Int(convertedTime / 3600) | ||||
|         let minute = Int(convertedTime / 60) % 60 | ||||
|         let second = Int(convertedTime % 60) | ||||
|          | ||||
|         // update UI | ||||
|         var timeText = [String]() | ||||
|          | ||||
|         if hour > 0 { | ||||
|             timeText.append(String(hour)) | ||||
|             timeText.append(String(format: "%02d", minute)) | ||||
|         } else { | ||||
|             timeText.append(String(format: "%02d", minute)) | ||||
|             timeText.append(String(format: "%02d", second)) | ||||
|         } | ||||
|          | ||||
|         return timeText.joined(separator: ":") | ||||
|     } | ||||
| } | ||||
							
								
								
									
										43
									
								
								SodaLive/Sources/Extensions/StringExtension.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								SodaLive/Sources/Extensions/StringExtension.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| // | ||||
| //  StringExtension.swift | ||||
| //  yozm | ||||
| // | ||||
| //  Created by klaus on 2022/06/03. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| extension Optional where Wrapped == String { | ||||
|     func isNullOrBlank() -> Bool { | ||||
|         return self == nil || self!.trimmingCharacters(in: .whitespaces).isEmpty | ||||
|     } | ||||
| } | ||||
|  | ||||
| extension String { | ||||
|     func convertDateFormat(from: String, to: String) -> String { | ||||
|         let fromFormatter = DateFormatter() | ||||
|         fromFormatter.dateFormat = from | ||||
|         fromFormatter.timeZone = TimeZone(identifier: TimeZone.current.identifier) | ||||
|          | ||||
|         if let date = fromFormatter.date(from: self) { | ||||
|             return date.convertDateFormat(dateFormat: to) | ||||
|         } else { | ||||
|             return self | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     func substring(from: Int, to: Int) -> String { | ||||
|         guard from < count, to >= 0, to - from >= 0 else { | ||||
|             return "" | ||||
|         } | ||||
|          | ||||
|         // Index 값 획득 | ||||
|         let startIndex = index(self.startIndex, offsetBy: from) | ||||
|         let endIndex = index(self.startIndex, offsetBy: to + 1) // '+1'이 있는 이유: endIndex는 문자열의 마지막 그 다음을 가리키기 때문 | ||||
|          | ||||
|         // 파싱 | ||||
|         return String(self[startIndex ..< endIndex]) | ||||
|          | ||||
|         // 출처 - https://ios-development.tistory.com/379 | ||||
|     } | ||||
| } | ||||
							
								
								
									
										72
									
								
								SodaLive/Sources/Extensions/UserDefaultsExtension.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								SodaLive/Sources/Extensions/UserDefaultsExtension.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| // | ||||
| //  UserDefaultsExtension.swift | ||||
| //  yozm | ||||
| // | ||||
| //  Created by klaus on 2022/05/20. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| enum UserDefaultsKey: String, CaseIterable { | ||||
|     case auth | ||||
|     case role | ||||
|     case coin | ||||
|     case token | ||||
|     case email | ||||
|     case userId | ||||
|     case nickname | ||||
|     case pushToken | ||||
|     case profileImage | ||||
|     case voipPushToken | ||||
|     case devicePushToken | ||||
|     case isContentPlayLoop | ||||
|     case isFollowedCreatorLive | ||||
|     case isViewedOnboardingView | ||||
|     case notShowingEventPopupId | ||||
| } | ||||
|  | ||||
| extension UserDefaults { | ||||
|     static func set(_ value: Bool, forKey key: UserDefaultsKey) { | ||||
|         let key = key.rawValue | ||||
|         UserDefaults.standard.set(value, forKey: key) | ||||
|     } | ||||
|      | ||||
|     static func bool(forKey key: UserDefaultsKey) -> Bool { | ||||
|         let key = key.rawValue | ||||
|         return UserDefaults.standard.bool(forKey: key) | ||||
|     } | ||||
|      | ||||
|     static func set(_ value: String, forKey key: UserDefaultsKey) { | ||||
|         let key = key.rawValue | ||||
|         UserDefaults.standard.set(value, forKey: key) | ||||
|     } | ||||
|      | ||||
|     static func string(forKey key: UserDefaultsKey) -> String { | ||||
|         let key = key.rawValue | ||||
|         return UserDefaults.standard.string(forKey: key) ?? "" | ||||
|     } | ||||
|      | ||||
|     static func set(_ value: Int, forKey key: UserDefaultsKey) { | ||||
|         let key = key.rawValue | ||||
|         UserDefaults.standard.set(value, forKey: key) | ||||
|     } | ||||
|      | ||||
|     static  func int(forKey key: UserDefaultsKey) -> Int { | ||||
|         let key = key.rawValue | ||||
|         return UserDefaults.standard.integer(forKey: key) | ||||
|     } | ||||
|      | ||||
|     static func set(_ value: Data, forKey key: UserDefaultsKey) { | ||||
|         let key = key.rawValue | ||||
|         UserDefaults.standard.set(value, forKey: key) | ||||
|     } | ||||
|      | ||||
|     static  func data(forKey key: UserDefaultsKey) -> Data? { | ||||
|         let key = key.rawValue | ||||
|         return UserDefaults.standard.data(forKey: key) | ||||
|     } | ||||
|      | ||||
|     static func reset() { | ||||
|         UserDefaultsKey.allCases.forEach { UserDefaults.standard.removeObject(forKey: $0.rawValue) } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										49
									
								
								SodaLive/Sources/Extensions/ViewExtension.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								SodaLive/Sources/Extensions/ViewExtension.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| // | ||||
| //  ViewExtension.swift | ||||
| //  yozm | ||||
| // | ||||
| //  Created by klaus on 2022/05/19. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
| import Combine | ||||
|  | ||||
| extension View { | ||||
|     func screenSize() -> CGRect { | ||||
|         return UIScreen.main.bounds | ||||
|     } | ||||
|      | ||||
|     // 출처 - https://stackoverflow.com/a/58606176 | ||||
|     func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { | ||||
|         clipShape( RoundedCorner(radius: radius, corners: corners) ) | ||||
|     } | ||||
|      | ||||
|     func hideKeyboard() { | ||||
|         UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) | ||||
|     } | ||||
|      | ||||
|     /// A backwards compatible wrapper for iOS 14 `onChange` | ||||
|     @ViewBuilder func valueChanged<T: Equatable>(value: T, onChange: @escaping (T) -> Void) -> some View { | ||||
|         if #available(iOS 14.0, *) { | ||||
|             self.onChange(of: value, perform: onChange) | ||||
|         } else { | ||||
|             self.onReceive(Just(value)) { (value) in | ||||
|                 onChange(value) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     @ViewBuilder func scrollContentBackgroundHidden() -> some View { | ||||
|         if #available(iOS 16.0, *) { | ||||
|             self.scrollContentBackground(.hidden) | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     @ViewBuilder func progressColor(color: Color) -> some View { | ||||
|         if #available(iOS 16.0, *) { | ||||
|             self.tint(color) | ||||
|         } else { | ||||
|             self.accentColor(color) | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										20
									
								
								SodaLive/Sources/Shape/RoundedCorner.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								SodaLive/Sources/Shape/RoundedCorner.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| // | ||||
| //  RoundedCorner.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/09. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| // 출처 - https://stackoverflow.com/a/58606176 | ||||
|  | ||||
| struct RoundedCorner: Shape { | ||||
|     var radius: CGFloat = .infinity | ||||
|     var corners: UIRectCorner = .allCorners | ||||
|      | ||||
|     func path(in rect: CGRect) -> Path { | ||||
|         let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) | ||||
|         return Path(path.cgPath) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										106
									
								
								SodaLive/Sources/Splash/SplashView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								SodaLive/Sources/Splash/SplashView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | ||||
| // | ||||
| //  SplashView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/09. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
| import FirebaseRemoteConfig | ||||
|  | ||||
| struct SplashView: View { | ||||
|      | ||||
|     @State private var isShowForcedUpdatePopup = false | ||||
|     @State private var isShowUpdatePopup = false | ||||
|      | ||||
|     var body: some View { | ||||
|         ZStack(alignment: .top) { | ||||
|             LinearGradient( | ||||
|                 gradient: Gradient(colors: [Color(hex: "a0e2ff"), Color(hex: "ecfaff")]), | ||||
|                 startPoint: .bottom, | ||||
|                 endPoint: .top | ||||
|             ).ignoresSafeArea() | ||||
|              | ||||
|             Image("splash_bubble") | ||||
|                 .padding(.top, 11) | ||||
|              | ||||
|             VStack(spacing: 0) { | ||||
|                 Image("splash_text") | ||||
|                     .padding(.top, 111) | ||||
|                  | ||||
|                 Image("splash_logo") | ||||
|                     .padding(.top, 77.3) | ||||
|                  | ||||
|                 Spacer() | ||||
|                  | ||||
|                 Image("vividnext_logo") | ||||
|                     .padding(.bottom, 36) | ||||
|             } | ||||
|         } | ||||
|         .onAppear { | ||||
|             fetchLastestVersion() | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private func fetchLastestVersion() { | ||||
|         let remoteConfig = RemoteConfig.remoteConfig() | ||||
|         let configSettings = RemoteConfigSettings() | ||||
|         configSettings.minimumFetchInterval = 300 | ||||
|         remoteConfig.configSettings = configSettings | ||||
|          | ||||
|         remoteConfig.fetch { status, error in | ||||
|             if status == .success { | ||||
|                 remoteConfig.activate { changed, error in | ||||
|                     checkAppVersion(latestVersion: remoteConfig["ios_latest_version"].stringValue) | ||||
|                 } | ||||
|             } else { | ||||
|                 withAnimation { | ||||
|                     DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) { | ||||
|                         AppState.shared.setAppStep(step: .main) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private func checkAppVersion(latestVersion: String?) { | ||||
|         let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String | ||||
|          | ||||
|         if let latestVersion = latestVersion, let version = version { | ||||
|             let latestVersions = latestVersion.split(separator: ".") | ||||
|             let versions = version.split(separator: ".") | ||||
|              | ||||
|             let latestMajor = Int(latestVersions[0])! | ||||
|             let latestMinor = Int(latestVersions[1])! | ||||
|             let latestPatch = Int(latestVersions[2])! | ||||
|              | ||||
|             let major = Int(versions[0])! | ||||
|             let minor = Int(versions[1])! | ||||
|             let patch = Int(versions[2])! | ||||
|              | ||||
|             if latestMajor > major || (latestMajor == major && latestMinor > minor) { | ||||
|                 self.isShowForcedUpdatePopup = true | ||||
|             } else if latestMajor == major && latestMinor == minor && latestPatch > patch { | ||||
|                 self.isShowUpdatePopup = true | ||||
|             } else { | ||||
|                 DispatchQueue.main.asyncAfter(deadline: .now() + 1) { | ||||
|                     withAnimation { | ||||
|                         AppState.shared.setAppStep(step: .main) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             DispatchQueue.main.asyncAfter(deadline: .now() + 1) { | ||||
|                 withAnimation { | ||||
|                     AppState.shared.setAppStep(step: .main) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct SplashView_Previews: PreviewProvider { | ||||
|     static var previews: some View { | ||||
|         SplashView() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										16
									
								
								SodaLive/Sources/Utils/Constants.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								SodaLive/Sources/Utils/Constants.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| // | ||||
| //  Constants.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/09. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| let BASE_URL = "https://api.sodalive.net" | ||||
| let APPLY_YOZM_CREATOR = "https://forms.gle/mmhJKmijjVRnwtdZ7" | ||||
|  | ||||
| let AGORA_APP_ID = "e34e40046e9847baba3adfe2b8ffb4f6" | ||||
| let AGORA_APP_CERTIFICATE = "15cadeea4ba94ff7b091c9a10f4bf4a6" | ||||
|  | ||||
| let BOOTPAY_APP_ID = "64c35be1d25985001dc50c88" | ||||
							
								
								
									
										23
									
								
								SodaLive/Sources/Utils/Log.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								SodaLive/Sources/Utils/Log.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| // | ||||
| //  Log.swift | ||||
| //  yozm | ||||
| // | ||||
| //  Created by klaus on 2022/05/20. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| func DEBUG_LOG(_ msg: String, file: String = #file, function: String = #function, line: Int = #line) { | ||||
|     #if DEBUG | ||||
|     let filename = file.split(separator: "/").last ?? "" | ||||
|     let funcName = function.split(separator: "(").first ?? "" | ||||
|     print("👻 [\(filename)] \(funcName)(\(line)): \(msg)") | ||||
|     #endif | ||||
| } | ||||
|  | ||||
| func ERROR_LOG(_ msg: String, file: String = #file, function: String = #function, line: Int = #line) { | ||||
|     let filename = file.split(separator: "/").last ?? "" | ||||
|     let funcName = function.split(separator: "(").first ?? "" | ||||
|     print("🤯😡 [\(filename)] \(funcName)(\(line)): \(msg)") | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung