mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 20:29:53 +00:00
* core, ui: optionally use short links (#5799) * core: optionally use short links * update test * update simplexmq, short group links * fix query * fix parser for _connect * ios: use short links * shorten links to remove fingerprint and onion hosts from known servers * fix parser * tests * nix * update query plans * update simplexmq, simplex: schema for short links * simplexmq * update ios * fix short links in ios * android: use short links * fix short group links, test short link connection plans * core: fix connection plan to recognize own short links * update simplexmq * space * all tests * relative symlinks in simplexmq to fix windows build * core: improve connection plan for short links (#5825) * core: improve connection plan for short links * improve connection plans * update UI * update simplexmq * ios: add preset server domains to entitlements, add short link paths to .well-known/apple-app-site-association * update simplexmq * fix group short link in iOS, fix simplex:/ scheme saved to database or used for connection plans * update simplexmq * ios: delay opening URI from outside until the app is started * update simplexmq
182 lines
8 KiB
Swift
182 lines
8 KiB
Swift
//
|
|
// SimpleXApp.swift
|
|
// Shared
|
|
//
|
|
// Created by Evgeny Poberezkin on 17/01/2022.
|
|
//
|
|
|
|
import SwiftUI
|
|
import OSLog
|
|
import SimpleXChat
|
|
|
|
let logger = Logger()
|
|
|
|
@main
|
|
struct SimpleXApp: App {
|
|
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
|
@StateObject private var chatModel = ChatModel.shared
|
|
@ObservedObject var alertManager = AlertManager.shared
|
|
|
|
@Environment(\.scenePhase) var scenePhase
|
|
@State private var enteredBackgroundAuthenticated: TimeInterval? = nil
|
|
@State private var appOpenUrlLater: URL?
|
|
|
|
init() {
|
|
DispatchQueue.global(qos: .background).sync {
|
|
haskell_init()
|
|
// hs_init(0, nil)
|
|
}
|
|
UserDefaults.standard.register(defaults: appDefaults)
|
|
setGroupDefaults()
|
|
registerGroupDefaults()
|
|
setDbContainer()
|
|
BGManager.shared.register()
|
|
NtfManager.shared.registerCategories()
|
|
}
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
// contentAccessAuthenticationExtended has to be passed to ContentView on view initialization,
|
|
// so that it's computed by the time view renders, and not on event after rendering
|
|
ContentView(contentAccessAuthenticationExtended: !authenticationExpired())
|
|
.environmentObject(chatModel)
|
|
.environmentObject(AppTheme.shared)
|
|
.onOpenURL { url in
|
|
logger.debug("ContentView.onOpenURL: \(url)")
|
|
if AppChatState.shared.value == .active {
|
|
chatModel.appOpenUrl = url
|
|
} else {
|
|
appOpenUrlLater = url
|
|
}
|
|
}
|
|
.onAppear() {
|
|
// Present screen for continue migration if it wasn't finished yet
|
|
if chatModel.migrationState != nil {
|
|
// It's important, otherwise, user may be locked in undefined state
|
|
onboardingStageDefault.set(.step1_SimpleXInfo)
|
|
chatModel.onboardingStage = onboardingStageDefault.get()
|
|
} else if kcAppPassword.get() == nil || kcSelfDestructPassword.get() == nil {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
|
initChatAndMigrate()
|
|
}
|
|
}
|
|
}
|
|
.onChange(of: scenePhase) { phase in
|
|
logger.debug("scenePhase was \(String(describing: scenePhase)), now \(String(describing: phase))")
|
|
AppSheetState.shared.scenePhaseActive = phase == .active
|
|
switch (phase) {
|
|
case .background:
|
|
// --- authentication
|
|
// see ContentView .onChange(of: scenePhase) for remaining authentication logic
|
|
if chatModel.contentViewAccessAuthenticated {
|
|
enteredBackgroundAuthenticated = ProcessInfo.processInfo.systemUptime
|
|
}
|
|
chatModel.contentViewAccessAuthenticated = false
|
|
// authentication ---
|
|
|
|
if CallController.useCallKit() && chatModel.activeCall != nil {
|
|
CallController.shared.shouldSuspendChat = true
|
|
} else {
|
|
suspendChat()
|
|
BGManager.shared.schedule()
|
|
}
|
|
NtfManager.shared.setNtfBadgeCount(chatModel.totalUnreadCountForAllUsers())
|
|
case .active:
|
|
CallController.shared.shouldSuspendChat = false
|
|
let appState = AppChatState.shared.value
|
|
|
|
if appState != .stopped {
|
|
startChatAndActivate {
|
|
if chatModel.chatRunning == true {
|
|
if let ntfResponse = chatModel.notificationResponse {
|
|
chatModel.notificationResponse = nil
|
|
NtfManager.shared.processNotificationResponse(ntfResponse)
|
|
}
|
|
if appState.inactive {
|
|
Task {
|
|
await updateChats()
|
|
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
|
|
await updateCallInvitations()
|
|
}
|
|
if let url = appOpenUrlLater {
|
|
await MainActor.run {
|
|
appOpenUrlLater = nil
|
|
chatModel.appOpenUrl = url
|
|
}
|
|
}
|
|
}
|
|
} else if let url = appOpenUrlLater {
|
|
appOpenUrlLater = nil
|
|
chatModel.appOpenUrl = url
|
|
}
|
|
}
|
|
}
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func setDbContainer() {
|
|
// Uncomment and run once to open DB in app documents folder:
|
|
// dbContainerGroupDefault.set(.documents)
|
|
// v3DBMigrationDefault.set(.offer)
|
|
// to create database in app documents folder also uncomment:
|
|
// let legacyDatabase = true
|
|
let legacyDatabase = hasLegacyDatabase()
|
|
if legacyDatabase, case .documents = dbContainerGroupDefault.get() {
|
|
dbContainerGroupDefault.set(.documents)
|
|
setMigrationState(.offer)
|
|
logger.debug("SimpleXApp init: using legacy DB in documents folder: \(getAppDatabasePath())*.db")
|
|
} else {
|
|
dbContainerGroupDefault.set(.group)
|
|
setMigrationState(.ready)
|
|
logger.debug("SimpleXApp init: using DB in app group container: \(getAppDatabasePath())*.db")
|
|
logger.debug("SimpleXApp init: legacy DB\(legacyDatabase ? "" : " not") present")
|
|
}
|
|
}
|
|
|
|
private func setMigrationState(_ state: V3DBMigrationState) {
|
|
if case .migrated = v3DBMigrationDefault.get() { return }
|
|
v3DBMigrationDefault.set(state)
|
|
}
|
|
|
|
private func authenticationExpired() -> Bool {
|
|
if let enteredBackgroundAuthenticated = enteredBackgroundAuthenticated {
|
|
let delay = Double(UserDefaults.standard.integer(forKey: DEFAULT_LA_LOCK_DELAY))
|
|
return ProcessInfo.processInfo.systemUptime - enteredBackgroundAuthenticated >= delay
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
private func updateChats() async {
|
|
do {
|
|
let chats = try await apiGetChatsAsync()
|
|
await MainActor.run { chatModel.updateChats(chats) }
|
|
if let id = chatModel.chatId,
|
|
let chat = chatModel.getChat(id),
|
|
!NtfManager.shared.navigatingToChat {
|
|
Task { await loadChat(chat: chat, clearItems: false) }
|
|
}
|
|
if let ncr = chatModel.ntfContactRequest {
|
|
await MainActor.run { chatModel.ntfContactRequest = nil }
|
|
if case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo {
|
|
Task { await acceptContactRequest(incognito: ncr.incognito, contactRequest: contactRequest) }
|
|
}
|
|
}
|
|
} catch let error {
|
|
logger.error("apiGetChats: cannot update chats \(responseError(error))")
|
|
}
|
|
}
|
|
|
|
private func updateCallInvitations() async {
|
|
do {
|
|
try await refreshCallInvitations()
|
|
} catch let error {
|
|
logger.error("apiGetCallInvitations: cannot update call invitations \(responseError(error))")
|
|
}
|
|
}
|
|
}
|