스플래시 페이지 추가
This commit is contained in:
parent
058c907609
commit
84d3dd61ca
|
@ -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>
|
|
@ -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
|
||||||
|
}
|
|
@ -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 |
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -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 |
|
@ -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>
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 SwiftUI
|
||||||
|
import AppTrackingTransparency
|
||||||
|
|
||||||
|
import FirebaseDynamicLinks
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct SodaLiveApp: App {
|
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 {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
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
|
import SwiftUI
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
|
@StateObject private var appState = AppState.shared
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
ZStack {
|
||||||
Image(systemName: "globe")
|
Color.black.ignoresSafeArea()
|
||||||
.imageScale(.large)
|
|
||||||
.foregroundColor(.accentColor)
|
switch appState.appStep {
|
||||||
Text("Hello, world!")
|
case .splash:
|
||||||
|
SplashView()
|
||||||
|
|
||||||
|
default:
|
||||||
|
EmptyView()
|
||||||
|
.frame(width: 0, height: 0, alignment: .topLeading)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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: ":")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
|
@ -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)")
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue