diff --git a/WaiterRobot/AppDelegate.swift b/WaiterRobot/AppDelegate.swift
deleted file mode 100644
index ae13469..0000000
--- a/WaiterRobot/AppDelegate.swift
+++ /dev/null
@@ -1,38 +0,0 @@
-import shared
-import SwiftUI
-
-class AppDelegate: NSObject, UIApplicationDelegate {
- func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
- // Init CommonApp right at the start as e.g. koin might depend on some properties of it
- var appVersion = readFromInfoPlist(withKey: "CFBundleShortVersionString")
- let versionSuffix = readFromInfoPlist(withKey: "VERSION_SUFFIX")
- if !versionSuffix.isEmpty {
- appVersion += "-\(versionSuffix)"
- }
-
- CommonApp.shared.doInit(
- appVersion: appVersion,
- appBuild: Int32(readFromInfoPlist(withKey: "CFBundleVersion"))!,
- phoneModel: UIDevice.current.deviceType,
- os: OS.Ios(version: UIDevice.current.systemVersion),
- apiBaseUrl: readFromInfoPlist(withKey: "API_BASE")
- )
-
- KoinKt.doInitKoinIos()
- let logger = koin.logger(tag: "AppDelegate")
- logger.d { "initialized Koin" }
-
- KMMResourcesLocalizationKt.localizationBundle = Bundle(for: shared.L.self)
- logger.d { "initialized localization bundle" }
-
- return true
- }
-
- private func readFromInfoPlist(withKey key: String) -> String {
- guard let value = Bundle.main.infoDictionary?[key] as? String else {
- fatalError("Could not find key '\(key)' in info.plist file.")
- }
-
- return value
- }
-}
diff --git a/WaiterRobot/LaunchScreen.storyboard b/WaiterRobot/LaunchScreen.storyboard
deleted file mode 100644
index 9dfceba..0000000
--- a/WaiterRobot/LaunchScreen.storyboard
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/WaiterRobot/LaunchScreen.swift b/WaiterRobot/LaunchScreen.swift
new file mode 100644
index 0000000..68d676e
--- /dev/null
+++ b/WaiterRobot/LaunchScreen.swift
@@ -0,0 +1,99 @@
+import Foundation
+import shared
+import SwiftUI
+
+struct LaunchScreen: View {
+ private let minimumOnScreenTimeSeconds = 3.0
+ private let device = UIDevice.current.userInterfaceIdiom
+
+ @State private var startupFinished = false
+
+ var body: some View {
+ VStack {
+ if case .phone = device {
+ VStack {
+ Spacer()
+
+ Image(.launch)
+ .resizable()
+ .scaledToFit()
+ }
+ .padding(.horizontal, -2)
+ .ignoresSafeArea()
+ } else {
+ ZStack {
+ Image(.logoRounded)
+ .resizable()
+ .scaledToFit()
+ .frame(width: 150)
+ .padding()
+
+ VStack {
+ Spacer()
+
+ ProgressView()
+ .padding()
+ .padding(.bottom)
+ }
+ }
+ }
+ }
+ .onAppear {
+ Task {
+ async let setup: () = setupApp()
+ async let delay: () = delay()
+
+ _ = await [setup, delay]
+
+ startupFinished = true
+ }
+ }
+ .fullScreenCover(isPresented: $startupFinished) {
+ MainView()
+ }
+ }
+
+ /// Setup of frameworks and all the other related stuff which is needed everywhere in the app
+ private func setupApp() {
+ print("started app setup")
+ var appVersion = readFromInfoPlist(withKey: "CFBundleShortVersionString")
+ let versionSuffix = readFromInfoPlist(withKey: "VERSION_SUFFIX")
+ if !versionSuffix.isEmpty {
+ appVersion += "-\(versionSuffix)"
+ }
+
+ CommonApp.shared.doInit(
+ appVersion: appVersion,
+ appBuild: Int32(readFromInfoPlist(withKey: "CFBundleVersion"))!,
+ phoneModel: UIDevice.current.deviceType,
+ os: OS.Ios(version: UIDevice.current.systemVersion),
+ apiBaseUrl: readFromInfoPlist(withKey: "API_BASE")
+ )
+
+ KoinKt.doInitKoinIos()
+ let logger = koin.logger(tag: "AppDelegate")
+ logger.d { "initialized Koin" }
+
+ KMMResourcesLocalizationKt.localizationBundle = Bundle(for: shared.L.self)
+ logger.d { "initialized localization bundle" }
+ print("finished app setup")
+ }
+
+ private func delay() async {
+ print("started delay")
+ try? await Task.sleep(seconds: minimumOnScreenTimeSeconds)
+ print("finished delay")
+ }
+
+ private func readFromInfoPlist(withKey key: String) -> String {
+ guard let value = Bundle.main.infoDictionary?[key] as? String else {
+ fatalError("Could not find key '\(key)' in info.plist file.")
+ }
+
+ return value
+ }
+}
+
+#Preview {
+ LaunchScreen()
+}
diff --git a/WaiterRobot/MainView.swift b/WaiterRobot/MainView.swift
new file mode 100644
index 0000000..b252bef
--- /dev/null
+++ b/WaiterRobot/MainView.swift
@@ -0,0 +1,126 @@
+//
+// MainView.swift
+// WaiterRobot
+//
+// Created by Alexander Kauer on 29.12.23.
+//
+
+import shared
+import SwiftUI
+import UIPilot
+
+struct MainView: View {
+ @State private var snackBarMessage: String?
+ @State private var showUpdateAvailableAlert: Bool = false
+ @StateObject private var navigator: UIPilot = UIPilot(initial: Screen.RootScreen.shared, debug: true)
+ @StateObject private var strongVM = ObservableViewModel(vm: koin.rootVM())
+
+ private var selectedScheme: ColorScheme? {
+ switch strongVM.state.selectedTheme {
+ case .dark:
+ .dark
+ case .light:
+ .light
+ default:
+ nil
+ }
+ }
+
+ var body: some View {
+ unowned let vm = strongVM
+
+ ZStack {
+ UIPilotHost(navigator) { route in
+ switch route {
+ case is Screen.RootScreen: RootScreen(strongVM: vm)
+ case is Screen.LoginScannerScreen: LoginScannerScreen()
+ case is Screen.SwitchEventScreen: SwitchEventScreen()
+ case is Screen.SettingsScreen: SettingsScreen()
+ case is Screen.UpdateApp: UpdateAppScreen()
+ case let screen as Screen.RegisterScreen: RegisterScreen(createToken: screen.createToken)
+ case let screen as Screen.TableDetailScreen: TableDetailScreen(table: screen.table)
+ case let screen as Screen.OrderScreen: OrderScreen(table: screen.table, initialItemId: screen.initialItemId)
+ case let screen as Screen.BillingScreen: BillingScreen(table: screen.table)
+ default:
+ Text("No view defined for \(route.description)") // TODO:
+ Button {
+ navigator.pop()
+ } label: {
+ Text("Back")
+ }.onAppear {
+ koin.logger(tag: "WaiterRobotApp").e { "No view defined for \(route.description)" }
+ }
+ }
+ }
+ }
+ .preferredColorScheme(selectedScheme)
+ .overlay(alignment: .bottom) {
+ if let message = snackBarMessage {
+ ZStack {
+ HStack {
+ Text(message)
+ .foregroundColor(.white)
+ Spacer()
+ Button {
+ snackBarMessage = nil
+ } label: {
+ Image(systemName: "xmark.circle")
+ }
+ }
+ .frame(maxWidth: .infinity)
+ .padding()
+ .background(.gray)
+ .clipShape(RoundedRectangle(cornerRadius: 10))
+ }
+ .padding()
+ }
+ }
+ .handleSideEffects(of: vm, navigator) { effect in
+ switch effect {
+ case let snackBar as RootEffect.ShowSnackBar:
+ snackBarMessage = snackBar.message
+ DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
+ snackBarMessage = nil
+ }
+ default:
+ return false
+ }
+ return true
+ }
+ .onOpenURL { url in
+ vm.actual.onDeepLink(url: url.absoluteString)
+ }
+ .alert(
+ localize.app.updateAvailable.title(),
+ isPresented: $showUpdateAvailableAlert
+ ) {
+ Button(localize.dialog.cancel(), role: .cancel) {
+ showUpdateAvailableAlert = false
+ }
+
+ Button(localize.app.forceUpdate.openStore(value0: "App Store")) {
+ guard let storeUrl = VersionChecker.shared.storeUrl,
+ let url = URL(string: storeUrl)
+ else {
+ return
+ }
+
+ DispatchQueue.main.async {
+ UIApplication.shared.open(url, options: [:], completionHandler: nil)
+ }
+ }
+ } message: {
+ Text(localize.app.updateAvailable.message())
+ }
+ .onAppear {
+ VersionChecker.shared.checkVersion {
+ showUpdateAvailableAlert = true
+ }
+ }
+ .tint(.main)
+ }
+}
+
+#Preview {
+ MainView()
+}
diff --git a/WaiterRobot/Util/Extensions/Task+Extensions.swift b/WaiterRobot/Util/Extensions/Task+Extensions.swift
new file mode 100644
index 0000000..e1b3cf6
--- /dev/null
+++ b/WaiterRobot/Util/Extensions/Task+Extensions.swift
@@ -0,0 +1,8 @@
+import Foundation
+
+extension Task where Success == Never, Failure == Never {
+ static func sleep(seconds: Double) async throws {
+ let duration = UInt64(seconds * 1_000_000_000)
+ try await Task.sleep(nanoseconds: duration)
+ }
+}
diff --git a/WaiterRobot/WaiterRobotApp.swift b/WaiterRobot/WaiterRobotApp.swift
index a56caae..4ae1e7e 100644
--- a/WaiterRobot/WaiterRobotApp.swift
+++ b/WaiterRobot/WaiterRobotApp.swift
@@ -4,117 +4,9 @@ import UIPilot
@main
struct WaiterRobotApp: App {
- @UIApplicationDelegateAdaptor var appDelegate: AppDelegate
-
- @State private var snackBarMessage: String?
- @State private var showUpdateAvailableAlert: Bool = false
- @StateObject private var navigator: UIPilot = UIPilot(initial: Screen.RootScreen.shared, debug: true)
- @StateObject private var strongVM = ObservableViewModel(vm: koin.rootVM())
-
- private var selectedScheme: ColorScheme? {
- switch strongVM.state.selectedTheme {
- case .dark:
- .dark
- case .light:
- .light
- default:
- nil
- }
- }
-
var body: some Scene {
- unowned let vm = strongVM
-
WindowGroup {
- ZStack {
- UIPilotHost(navigator) { route in
- switch route {
- case is Screen.RootScreen: RootScreen(strongVM: vm)
- case is Screen.LoginScannerScreen: LoginScannerScreen()
- case is Screen.SwitchEventScreen: SwitchEventScreen()
- case is Screen.SettingsScreen: SettingsScreen()
- case is Screen.UpdateApp: UpdateAppScreen()
- case let screen as Screen.RegisterScreen: RegisterScreen(createToken: screen.createToken)
- case let screen as Screen.TableDetailScreen: TableDetailScreen(table: screen.table)
- case let screen as Screen.OrderScreen: OrderScreen(table: screen.table, initialItemId: screen.initialItemId)
- case let screen as Screen.BillingScreen: BillingScreen(table: screen.table)
- default:
- Text("No view defined for \(route.description)") // TODO:
- Button {
- navigator.pop()
- } label: {
- Text("Back")
- }.onAppear {
- koin.logger(tag: "WaiterRobotApp").e { "No view defined for \(route.description)" }
- }
- }
- }
- }
- .preferredColorScheme(selectedScheme)
- .overlay(alignment: .bottom) {
- if let message = snackBarMessage {
- ZStack {
- HStack {
- Text(message)
- .foregroundColor(.white)
- Spacer()
- Button {
- snackBarMessage = nil
- } label: {
- Image(systemName: "xmark.circle")
- }
- }
- .frame(maxWidth: .infinity)
- .padding()
- .background(.gray)
- .clipShape(RoundedRectangle(cornerRadius: 10))
- }
- .padding()
- }
- }
- .handleSideEffects(of: vm, navigator) { effect in
- switch effect {
- case let snackBar as RootEffect.ShowSnackBar:
- snackBarMessage = snackBar.message
- DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
- snackBarMessage = nil
- }
- default:
- return false
- }
- return true
- }
- .onOpenURL { url in
- vm.actual.onDeepLink(url: url.absoluteString)
- }
- .alert(
- localize.app.updateAvailable.title(),
- isPresented: $showUpdateAvailableAlert
- ) {
- Button(localize.dialog.cancel(), role: .cancel) {
- showUpdateAvailableAlert = false
- }
-
- Button(localize.app.forceUpdate.openStore(value0: "App Store")) {
- guard let storeUrl = VersionChecker.shared.storeUrl,
- let url = URL(string: storeUrl)
- else {
- return
- }
-
- DispatchQueue.main.async {
- UIApplication.shared.open(url, options: [:], completionHandler: nil)
- }
- }
- } message: {
- Text(localize.app.updateAvailable.message())
- }
- .onAppear {
- VersionChecker.shared.checkVersion {
- showUpdateAvailableAlert = true
- }
- }
- .tint(.main)
+ LaunchScreen()
}
}
}
diff --git a/project.yml b/project.yml
index 11f34d8..9e0e0fd 100644
--- a/project.yml
+++ b/project.yml
@@ -67,13 +67,12 @@ targetTemplates:
CFBundleName: "${target_name}"
CFBundlePackageType: "$(PRODUCT_BUNDLE_PACKAGE_TYPE)"
ITSAppUsesNonExemptEncryption: false
- LSRequiresIPhoneOS: true
+ UILaunchStoryboardName: ""
NSAppTransportSecurity:
NSAllowsLocalNetworking: true
NSCameraUsageDescription: "Camera is needed to scan QR-Codes"
UIApplicationSceneManifest:
UIApplicationSupportsMultipleScenes: false
- UILaunchStoryboardName: "LaunchScreen"
UIRequiredDeviceCapabilities:
- armv7
UISupportedInterfaceOrientations: