mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 12:19:54 +00:00
* core: pass event and response error without dedicated constructor * ios: WIP * android, desktop: update UI for new API * ios: fix parser * fix showing invalid chats * fix mobile api tests * ios: split ChatResponse to 3 enums, decode API results on the same thread * tweak types * remove throws * rename
209 lines
9.8 KiB
Swift
209 lines
9.8 KiB
Swift
//
|
|
// AppDelegate.swift
|
|
// SimpleX
|
|
//
|
|
// Created by Evgeny on 30/03/2022.
|
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import UIKit
|
|
import SimpleXChat
|
|
import SwiftUI
|
|
|
|
class AppDelegate: NSObject, UIApplicationDelegate {
|
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
|
logger.debug("AppDelegate: didFinishLaunchingWithOptions")
|
|
application.registerForRemoteNotifications()
|
|
removePasscodesIfReinstalled()
|
|
prepareForLaunch()
|
|
deleteOldChatArchive()
|
|
return true
|
|
}
|
|
|
|
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
|
let token = deviceToken.map { String(format: "%02hhx", $0) }.joined()
|
|
logger.debug("AppDelegate: didRegisterForRemoteNotificationsWithDeviceToken \(token)")
|
|
let m = ChatModel.shared
|
|
let deviceToken = DeviceToken(pushProvider: PushProvider(env: pushEnvironment), token: token)
|
|
m.deviceToken = deviceToken
|
|
// savedToken is set in startChat, when it is started before this method is called
|
|
if m.savedToken != nil {
|
|
registerToken(token: deviceToken)
|
|
}
|
|
}
|
|
|
|
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
|
logger.error("AppDelegate: didFailToRegisterForRemoteNotificationsWithError \(error.localizedDescription)")
|
|
}
|
|
|
|
func application(_ application: UIApplication,
|
|
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
|
|
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
|
logger.debug("AppDelegate: didReceiveRemoteNotification")
|
|
let m = ChatModel.shared
|
|
if let ntfData = userInfo["notificationData"] as? [AnyHashable : Any],
|
|
m.notificationMode != .off {
|
|
if let verification = ntfData["verification"] as? String,
|
|
let nonce = ntfData["nonce"] as? String {
|
|
if let token = m.deviceToken {
|
|
logger.debug("AppDelegate: didReceiveRemoteNotification: verification, confirming \(verification)")
|
|
Task {
|
|
do {
|
|
if case .active = m.tokenStatus {} else { m.tokenStatus = .confirmed }
|
|
try await apiVerifyToken(token: token, nonce: nonce, code: verification)
|
|
m.tokenStatus = .active
|
|
} catch {
|
|
if let cr = error as? ChatError, case .errorAgent(.NTF(.AUTH)) = cr {
|
|
m.tokenStatus = .expired
|
|
}
|
|
logger.error("AppDelegate: didReceiveRemoteNotification: apiVerifyToken or apiIntervalNofication error: \(responseError(error))")
|
|
}
|
|
completionHandler(.newData)
|
|
}
|
|
} else {
|
|
completionHandler(.noData)
|
|
}
|
|
} else if let checkMessages = ntfData["checkMessages"] as? Bool, checkMessages {
|
|
logger.debug("AppDelegate: didReceiveRemoteNotification: checkMessages")
|
|
if m.ntfEnablePeriodic && allowBackgroundRefresh() && BGManager.shared.lastRanLongAgo {
|
|
receiveMessages(completionHandler)
|
|
} else {
|
|
completionHandler(.noData)
|
|
}
|
|
} else {
|
|
completionHandler(.noData)
|
|
}
|
|
} else {
|
|
completionHandler(.noData)
|
|
}
|
|
}
|
|
|
|
func applicationWillTerminate(_ application: UIApplication) {
|
|
logger.debug("DEBUGGING: AppDelegate: applicationWillTerminate")
|
|
ChatModel.shared.filesToDelete.forEach {
|
|
removeFile($0)
|
|
}
|
|
ChatModel.shared.filesToDelete = []
|
|
terminateChat()
|
|
}
|
|
|
|
func application(_ application: UIApplication,
|
|
configurationForConnecting connectingSceneSession: UISceneSession,
|
|
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
|
let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
|
|
if connectingSceneSession.role == .windowApplication {
|
|
configuration.delegateClass = SceneDelegate.self
|
|
}
|
|
return configuration
|
|
}
|
|
|
|
private func receiveMessages(_ completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
|
let complete = BGManager.shared.completionHandler {
|
|
logger.debug("AppDelegate: completed BGManager.receiveMessages")
|
|
completionHandler(.newData)
|
|
}
|
|
|
|
BGManager.shared.receiveMessages(complete)
|
|
}
|
|
|
|
private func removePasscodesIfReinstalled() {
|
|
// Check for the database existence, because app and self destruct passcodes
|
|
// will be saved and restored by iOS when a user deletes and re-installs the app.
|
|
// In this case the database and settings will be deleted, but the passcodes won't be.
|
|
// Deleting passcodes ensures that the user will not get stuck on "Opening app..." screen.
|
|
if (kcAppPassword.get() != nil || kcSelfDestructPassword.get() != nil) &&
|
|
!UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA) && !hasDatabase() {
|
|
_ = kcAppPassword.remove()
|
|
_ = kcSelfDestructPassword.remove()
|
|
_ = kcDatabasePassword.remove()
|
|
}
|
|
}
|
|
|
|
private func prepareForLaunch() {
|
|
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
|
|
}
|
|
|
|
static func keepScreenOn(_ on: Bool) {
|
|
UIApplication.shared.isIdleTimerDisabled = on
|
|
}
|
|
}
|
|
|
|
class SceneDelegate: NSObject, ObservableObject, UIWindowSceneDelegate {
|
|
var window: UIWindow?
|
|
static var windowStatic: UIWindow?
|
|
var windowScene: UIWindowScene?
|
|
|
|
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
|
UITableView.appearance().backgroundColor = .clear
|
|
guard let windowScene = scene as? UIWindowScene else { return }
|
|
self.windowScene = windowScene
|
|
window = windowScene.keyWindow
|
|
SceneDelegate.windowStatic = windowScene.keyWindow
|
|
migrateAccentColorAndTheme()
|
|
ThemeManager.applyTheme(currentThemeDefault.get())
|
|
ThemeManager.adjustWindowStyle()
|
|
}
|
|
|
|
private func migrateAccentColorAndTheme() {
|
|
let defs = UserDefaults.standard
|
|
/// For checking migration
|
|
// themeOverridesDefault.set([])
|
|
// currentThemeDefault.set(DefaultTheme.SYSTEM_THEME_NAME)
|
|
// defs.set(0.5, forKey: DEFAULT_ACCENT_COLOR_RED)
|
|
// defs.set(0.3, forKey: DEFAULT_ACCENT_COLOR_GREEN)
|
|
// defs.set(0.8, forKey: DEFAULT_ACCENT_COLOR_BLUE)
|
|
|
|
let userInterfaceStyle = getUserInterfaceStyleDefault()
|
|
if defs.double(forKey: DEFAULT_ACCENT_COLOR_GREEN) == 0 && userInterfaceStyle == .unspecified {
|
|
// No migration needed or already migrated
|
|
return
|
|
}
|
|
|
|
let defaultAccentColor = Color(cgColor: CGColor(red: 0.000, green: 0.533, blue: 1.000, alpha: 1))
|
|
let accentColor = Color(cgColor: getUIAccentColorDefault())
|
|
if accentColor != defaultAccentColor {
|
|
let colors = ThemeColors(primary: accentColor.toReadableHex())
|
|
var overrides = themeOverridesDefault.get()
|
|
var themeIds = currentThemeIdsDefault.get()
|
|
switch userInterfaceStyle {
|
|
case .light:
|
|
let light = ThemeOverrides(base: DefaultTheme.LIGHT, colors: colors, wallpaper: ThemeWallpaper(preset: PresetWallpaper.school.filename))
|
|
overrides.append(light)
|
|
themeOverridesDefault.set(overrides)
|
|
themeIds[DefaultTheme.LIGHT.themeName] = light.themeId
|
|
currentThemeIdsDefault.set(themeIds)
|
|
ThemeManager.applyTheme(DefaultTheme.LIGHT.themeName)
|
|
case .dark:
|
|
let dark = ThemeOverrides(base: DefaultTheme.DARK, colors: colors, wallpaper: ThemeWallpaper(preset: PresetWallpaper.school.filename))
|
|
overrides.append(dark)
|
|
themeOverridesDefault.set(overrides)
|
|
themeIds[DefaultTheme.DARK.themeName] = dark.themeId
|
|
currentThemeIdsDefault.set(themeIds)
|
|
ThemeManager.applyTheme(DefaultTheme.DARK.themeName)
|
|
case .unspecified:
|
|
let light = ThemeOverrides(base: DefaultTheme.LIGHT, colors: colors, wallpaper: ThemeWallpaper(preset: PresetWallpaper.school.filename))
|
|
let dark = ThemeOverrides(base: DefaultTheme.DARK, colors: colors, wallpaper: ThemeWallpaper(preset: PresetWallpaper.school.filename))
|
|
overrides.append(light)
|
|
overrides.append(dark)
|
|
themeOverridesDefault.set(overrides)
|
|
themeIds[DefaultTheme.LIGHT.themeName] = light.themeId
|
|
themeIds[DefaultTheme.DARK.themeName] = dark.themeId
|
|
currentThemeIdsDefault.set(themeIds)
|
|
ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME)
|
|
@unknown default: ()
|
|
}
|
|
} else if userInterfaceStyle != .unspecified {
|
|
let themeName = switch userInterfaceStyle {
|
|
case .light: DefaultTheme.LIGHT.themeName
|
|
case .dark: DefaultTheme.DARK.themeName
|
|
default: DefaultTheme.SYSTEM_THEME_NAME
|
|
}
|
|
ThemeManager.applyTheme(themeName)
|
|
}
|
|
defs.removeObject(forKey: DEFAULT_ACCENT_COLOR_RED)
|
|
defs.removeObject(forKey: DEFAULT_ACCENT_COLOR_GREEN)
|
|
defs.removeObject(forKey: DEFAULT_ACCENT_COLOR_BLUE)
|
|
defs.removeObject(forKey: DEFAULT_USER_INTERFACE_STYLE)
|
|
}
|
|
}
|