123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580 |
- import AudioToolbox
- import Reachability
- import SwiftyBeaver
- import UIKit
- import UserNotifications
- import DcCore
- import DBDebugToolkit
- import SDWebImageWebPCoder
- import Intents
- let logger = SwiftyBeaver.self
- @UIApplicationMain
- class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
- private let dcAccounts = DcAccounts()
- var appCoordinator: AppCoordinator!
- var relayHelper: RelayHelper!
- var locationManager: LocationManager!
- var notificationManager: NotificationManager!
- private var backgroundTask: UIBackgroundTaskIdentifier = .invalid
- var reachability = Reachability()!
- var window: UIWindow?
- var notifyToken: String?
- // purpose of `bgIoTimestamp` is to block rapidly subsequent calls to remote- or local-wakeups:
- //
- // `bgIoTimestamp` is set to last init, enter-background or last remote- or local-wakeup;
- // in the minute after these events, subsequent remote- or local-wakeups are skipped
- // in favor to the chance of being awakened when it makes more sense
- // and to avoid issues with calling concurrent series of startIo/maybeNetwork/stopIo.
- private var bgIoTimestamp: Double = 0.0
- // MARK: - app main entry point
- // `didFinishLaunchingWithOptions` is the main entry point
- // that is called if the app is started for the first time
- // or after the app is killed.
- //
- // `didFinishLaunchingWithOptions` creates the context object and sets
- // up other global things.
- //
- // `didFinishLaunchingWithOptions` is _not_ called
- // when the app wakes up from "suspended" state
- // (app is in memory in the background but no code is executed, IO stopped)
- func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
- // explicitly ignore SIGPIPE to avoid crashes, see https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/CommonPitfalls/CommonPitfalls.html
- // setupCrashReporting() may create an additional handler, but we do not want to rely on that
- signal(SIGPIPE, SIG_IGN)
- bgIoTimestamp = Double(Date().timeIntervalSince1970)
- DBDebugToolkit.setup(with: []) // empty array will override default device shake trigger
- DBDebugToolkit.setupCrashReporting()
-
- let console = ConsoleDestination()
- console.format = "$DHH:mm:ss.SSS$d $C$L$c $M" // see https://docs.swiftybeaver.com/article/20-custom-format
- logger.addDestination(console)
- dcAccounts.logger = DcLogger()
- dcAccounts.openDatabase()
- migrateToDcAccounts()
- if dcAccounts.getAll().isEmpty, dcAccounts.add() == 0 {
- fatalError("Could not initialize a new account.")
- }
- logger.info("➡️ didFinishLaunchingWithOptions")
- window = UIWindow(frame: UIScreen.main.bounds)
- guard let window = window else {
- fatalError("window was nil in app delegate")
- }
- if #available(iOS 13.0, *) {
- window.backgroundColor = UIColor.systemBackground
- } else {
- window.backgroundColor = UIColor.white
- }
- installEventHandler()
- relayHelper = RelayHelper.setup(dcAccounts.getSelected())
- appCoordinator = AppCoordinator(window: window, dcAccounts: dcAccounts)
- locationManager = LocationManager(dcAccounts: dcAccounts)
- UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
- notificationManager = NotificationManager(dcAccounts: dcAccounts)
- dcAccounts.startIo()
- setStockTranslations()
- reachability.whenReachable = { reachability in
- // maybeNetwork() shall not be called in ui thread;
- // Reachability::reachabilityChanged uses DispatchQueue.main.async only
- logger.info("network: reachable", reachability.connection.description)
- DispatchQueue.global(qos: .background).async { [weak self] in
- self?.dcAccounts.maybeNetwork()
- }
- }
- reachability.whenUnreachable = { _ in
- logger.info("network: not reachable")
- DispatchQueue.global(qos: .background).async { [weak self] in
- self?.dcAccounts.maybeNetworkLost()
- }
- }
- do {
- try reachability.startNotifier()
- } catch {
- logger.error("Unable to start notifier")
- }
-
- if let notificationOption = launchOptions?[.remoteNotification] {
- logger.info("Notifications: remoteNotification: \(String(describing: notificationOption))")
- increaseDebugCounter("notify-remote-launch")
- }
- if dcAccounts.getSelected().isConfigured() && !UserDefaults.standard.bool(forKey: "notifications_disabled") {
- registerForNotifications()
- }
- let webPCoder = SDImageWebPCoder.shared
- SDImageCodersManager.shared.addCoder(webPCoder)
- return true
- }
- // `open` is called when an url should be opened by Delta Chat.
- // we currently use that for handling oauth2 and for handing openpgp4fpr.
- //
- // before `open` gets called, `didFinishLaunchingWithOptions` is called.
- func application(_: UIApplication, open url: URL, options _: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
- logger.info("➡️ open url")
- // gets here when app returns from oAuth2-Setup process - the url contains the provided token
- // if let params = url.queryParameters, let token = params["code"] {
- // NotificationCenter.default.post(name: NSNotification.Name("oauthLoginApproved"), object: nil, userInfo: ["token": token])
- // }
- switch url.scheme?.lowercased() {
- case "openpgp4fpr":
- // Hack to format url properly
- let urlString = url.absoluteString
- .replacingOccurrences(of: "openpgp4fpr", with: "OPENPGP4FPR", options: .literal, range: nil)
- .replacingOccurrences(of: "%23", with: "#", options: .literal, range: nil)
- self.appCoordinator.handleQRCode(urlString)
- return true
- case "mailto":
- return self.appCoordinator.handleMailtoURL(url)
- default:
- return false
- }
- }
- // MARK: - app lifecycle
- func applicationWillEnterForeground(_: UIApplication) {
- logger.info("➡️ applicationWillEnterForeground")
- dcAccounts.startIo()
- DispatchQueue.global(qos: .background).async { [weak self] in
- guard let self = self else { return }
- if self.reachability.connection != .none {
- self.dcAccounts.maybeNetwork()
- }
- if let userDefaults = UserDefaults.shared, userDefaults.bool(forKey: UserDefaults.hasExtensionAttemptedToSend) {
- userDefaults.removeObject(forKey: UserDefaults.hasExtensionAttemptedToSend)
- DispatchQueue.main.async {
- NotificationCenter.default.post(
- name: dcNotificationChanged,
- object: nil,
- userInfo: [:]
- )
- }
- }
- }
- }
- func applicationWillResignActive(_: UIApplication) {
- logger.info("⬅️ applicationWillResignActive")
- registerBackgroundTask()
- }
- func applicationDidEnterBackground(_: UIApplication) {
- logger.info("⬅️ applicationDidEnterBackground")
- }
- func applicationWillTerminate(_: UIApplication) {
- logger.info("⬅️ applicationWillTerminate")
- dcAccounts.closeDatabase()
- reachability.stopNotifier()
- }
- // MARK: - fade out app smoothly
- // let the app run in background for a little while
- // eg. to complete sending messages out and to react to responses.
- private func registerBackgroundTask() {
- logger.info("⬅️ registering background task")
- bgIoTimestamp = Double(Date().timeIntervalSince1970)
- unregisterBackgroundTask()
- backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
- // usually, the background thread is finished before in maybeStop()
- logger.info("⬅️ background expirationHandler called")
- self?.unregisterBackgroundTask()
- }
- maybeStop()
- }
- private func unregisterBackgroundTask() {
- if backgroundTask != .invalid {
- UIApplication.shared.endBackgroundTask(backgroundTask)
- backgroundTask = .invalid
- }
- }
- private func maybeStop() {
- DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
- let app = UIApplication.shared
- if app.applicationState != .background {
- logger.info("⬅️ no longer in background")
- self.unregisterBackgroundTask()
- } else if app.backgroundTimeRemaining < 10 {
- logger.info("⬅️ few background time, \(app.backgroundTimeRemaining), stopping")
- self.dcAccounts.stopIo()
- // to avoid 0xdead10cc exceptions, scheduled jobs need to be done before we get suspended;
- // we increase the probabilty that this happens by waiting a moment before calling unregisterBackgroundTask()
- DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
- logger.info("⬅️ few background time, \(app.backgroundTimeRemaining), done")
- self.unregisterBackgroundTask()
- }
- } else {
- logger.info("⬅️ remaining background time: \(app.backgroundTimeRemaining)")
- self.maybeStop()
- }
- }
- }
- // MARK: - background fetch and notifications
- // `registerForNotifications` asks the user if they want to get notifiations shown.
- // if so, it registers for receiving remote notifications.
- func registerForNotifications() {
- UNUserNotificationCenter.current().delegate = self
- notifyToken = nil
- // register for showing notifications
- UNUserNotificationCenter.current()
- .requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
- if granted {
- // we are allowed to show notifications:
- // register for receiving remote notifications
- logger.info("Notifications: Permission granted: \(granted)")
- self?.maybeRegisterForRemoteNotifications()
- } else {
- logger.info("Notifications: Permission not granted.")
- }
- }
- }
- // register on apple server for receiving remote notifications
- // and pass the token to the app's notification server.
- //
- // on success, we get a token at didRegisterForRemoteNotificationsWithDeviceToken;
- // on failure, didFailToRegisterForRemoteNotificationsWithError is called
- private func maybeRegisterForRemoteNotifications() {
- UNUserNotificationCenter.current().getNotificationSettings { settings in
- logger.info("Notifications: Settings: \(settings)")
- switch settings.authorizationStatus {
- case .authorized, .provisional, .ephemeral:
- DispatchQueue.main.async {
- UIApplication.shared.registerForRemoteNotifications()
- }
- case .denied, .notDetermined:
- break
- }
- }
- }
- // `didRegisterForRemoteNotificationsWithDeviceToken` is called by iOS
- // when the call to `UIApplication.shared.registerForRemoteNotifications` succeeded.
- //
- // we pass the received token to the app's notification server then.
- func application(
- _ application: UIApplication,
- didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
- ) {
- let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
- let tokenString = tokenParts.joined()
- #if DEBUG
- let endpoint = "https://sandbox.notifications.delta.chat/register"
- #else
- let endpoint = "https://notifications.delta.chat/register"
- #endif
- logger.info("Notifications: POST token: \(tokenString) to \(endpoint)")
- if let url = URL(string: endpoint) {
- var request = URLRequest(url: url)
- request.httpMethod = "POST"
- let body = "{ \"token\": \"\(tokenString)\" }"
- request.httpBody = body.data(using: String.Encoding.utf8)
- let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
- if let error = error {
- logger.error("Notifications: cannot POST to notification server: \(error)")
- return
- }
- if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode == 200 {
- logger.info("Notifications: request to notification server succeeded")
- } else {
- logger.error("Notifications: request to notification server failed: \(String(describing: response)), \(String(describing: data))")
- }
- self.notifyToken = tokenString
- }
- task.resume()
- } else {
- logger.error("Notifications: cannot create URL for token: \(tokenString)")
- }
- }
- // `didFailToRegisterForRemoteNotificationsWithError` is called by iOS
- // when the call to `UIApplication.shared.registerForRemoteNotifications` failed.
- func application(
- _ application: UIApplication,
- didFailToRegisterForRemoteNotificationsWithError error: Error) {
- logger.error("Notifications: Failed to register: \(error)")
- }
- // `didReceiveRemoteNotification` is called by iOS when a remote notification is received.
- //
- // we need to ensure IO is running as the function may be called from suspended state
- // (with app in memory, but gracefully shut down before; sort of freezed).
- // if the function was not called from suspended state,
- // the call to startIo() did nothing, therefore, interrupt and force fetch.
- //
- // we have max. 30 seconds time for our job and to call the completion handler.
- // as the system tracks the elapsed time, power usage, and data costs, we return faster,
- // after 10 seconds, things should be done.
- // (see https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623013-application)
- // (at some point it would be nice if we get a clear signal from the core)
- func application(
- _ application: UIApplication,
- didReceiveRemoteNotification userInfo: [AnyHashable: Any],
- fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
- ) {
- logger.info("➡️ Notifications: didReceiveRemoteNotification \(userInfo)")
- increaseDebugCounter("notify-remote-receive")
- performFetch(completionHandler: completionHandler)
- }
- // `performFetchWithCompletionHandler` is called by iOS on local wakeup.
- //
- // this requires "UIBackgroundModes: fetch" to be set in Info.plist
- // ("App downloads content from the network" in Xcode)
- //
- // we have 30 seconds time for our job, things are quite similar as in `didReceiveRemoteNotification`
- func application(
- _ application: UIApplication,
- performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
- ) {
- logger.info("➡️ Notifications: performFetchWithCompletionHandler")
- increaseDebugCounter("notify-local-wakeup")
- performFetch(completionHandler: completionHandler)
- }
- private func performFetch(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
- // `didReceiveRemoteNotification` as well as `performFetchWithCompletionHandler` might be called if we're in foreground,
- // in this case, there is no need to wait for things or do sth.
- if appIsInForeground() {
- logger.info("➡️ app already in foreground")
- completionHandler(.newData)
- return
- }
- // from time to time, `didReceiveRemoteNotification` and `performFetchWithCompletionHandler`
- // are actually called at the same millisecond.
- //
- // therefore, if last fetch is less than a minute ago, we skip this call;
- // this also lets the completionHandler being called earlier so that we maybe get awakened when it makes more sense.
- //
- // nb: calling the completion handler with .noData results in less calls overall.
- // if at some point we do per-message-push-notifications, we need to tweak this gate.
- let nowTimestamp = Double(Date().timeIntervalSince1970)
- if nowTimestamp < bgIoTimestamp + 60 {
- logger.info("➡️ fetch was just executed, skipping")
- completionHandler(.newData)
- return
- }
- bgIoTimestamp = nowTimestamp
- // make sure to balance each call to `beginBackgroundTask` with `endBackgroundTask`
- var backgroundTask: UIBackgroundTaskIdentifier = .invalid
- backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
- // usually, this handler is not used as we are taking care of timings below.
- logger.info("⬅️ finishing fetch by system urgency requests")
- self?.dcAccounts.stopIo()
- completionHandler(.newData)
- if backgroundTask != .invalid {
- UIApplication.shared.endBackgroundTask(backgroundTask)
- backgroundTask = .invalid
- }
- }
- // we're in background, run IO for a little time
- dcAccounts.startIo()
- dcAccounts.maybeNetwork()
- DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self] in
- logger.info("⬅️ finishing fetch")
- guard let self = self else {
- completionHandler(.failed)
- return
- }
- if !self.appIsInForeground() {
- self.dcAccounts.stopIo()
- }
- // to avoid 0xdead10cc exceptions, scheduled jobs need to be done before we get suspended;
- // we increase the probabilty that this happens by waiting a moment before calling completionHandler()
- DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
- logger.info("⬅️ fetch done")
- completionHandler(.newData)
- if backgroundTask != .invalid {
- UIApplication.shared.endBackgroundTask(backgroundTask)
- backgroundTask = .invalid
- }
- }
- }
- }
- // MARK: - handle notification banners
- // This method will be called if an incoming message was received while the app was in foreground.
- // We don't show foreground notifications in the notification center because they don't get grouped properly
- func userNotificationCenter(_: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
- logger.info("Notifications: foreground notification")
- completionHandler([.badge])
- }
- // this method will be called if the user tapped on a notification
- func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
- if !response.notification.request.identifier.containsExact(subSequence: Constants.notificationIdentifier).isEmpty {
- logger.info("Notifications: notification tapped")
- let userInfo = response.notification.request.content.userInfo
- if let chatId = userInfo["chat_id"] as? Int,
- let msgId = userInfo["message_id"] as? Int {
- appCoordinator.showChat(chatId: chatId, msgId: msgId, animated: false, clearViewControllerStack: true)
- }
- }
- completionHandler()
- }
- // MARK: - misc.
- func migrateToDcAccounts() {
- let dbHelper = DatabaseHelper()
- if let databaseLocation = dbHelper.unmanagedDatabaseLocation {
- if dcAccounts.migrate(dbLocation: databaseLocation) == 0 {
- fatalError("Account could not be migrated")
- // TODO: show error message in UI
- }
- INInteraction.deleteAll(completion: nil)
- }
- }
- func reloadDcContext() {
- setStockTranslations()
- locationManager.reloadDcContext()
- notificationManager.reloadDcContext()
- RelayHelper.sharedInstance.cancel()
- _ = RelayHelper.setup(dcAccounts.getSelected())
- if dcAccounts.getSelected().isConfigured() {
- appCoordinator.resetTabBarRootViewControllers()
- } else {
- appCoordinator.presentWelcomeController()
- }
- }
- func installEventHandler() {
- DispatchQueue.global(qos: .background).async { [weak self] in
- guard let self = self else { return }
- let eventHandler = DcEventHandler(dcAccounts: self.dcAccounts)
- let eventEmitter = self.dcAccounts.getEventEmitter()
- while true {
- guard let event = eventEmitter.getNextEvent() else { break }
- eventHandler.handleEvent(event: event)
- }
- logger.info("⬅️ event emitter finished")
- }
- }
- private func increaseDebugCounter(_ name: String) {
- let nowDate = Date()
- let nowTimestamp = Double(nowDate.timeIntervalSince1970)
- let startTimestamp = UserDefaults.standard.double(forKey: name + "-start")
- if nowTimestamp > startTimestamp + 60*60*24 {
- let cal: Calendar = Calendar(identifier: .gregorian)
- let newStartDate: Date = cal.date(bySettingHour: 0, minute: 0, second: 0, of: nowDate)!
- UserDefaults.standard.set(0, forKey: name + "-count")
- UserDefaults.standard.set(Double(newStartDate.timeIntervalSince1970), forKey: name + "-start")
- }
- let cnt = UserDefaults.standard.integer(forKey: name + "-count")
- UserDefaults.standard.set(cnt + 1, forKey: name + "-count")
- UserDefaults.standard.set(nowTimestamp, forKey: name + "-last")
- }
- private func setStockTranslations() {
- let dcContext = dcAccounts.getSelected()
- dcContext.setStockTranslation(id: DC_STR_NOMESSAGES, localizationKey: "chat_no_messages")
- dcContext.setStockTranslation(id: DC_STR_SELF, localizationKey: "self")
- dcContext.setStockTranslation(id: DC_STR_DRAFT, localizationKey: "draft")
- dcContext.setStockTranslation(id: DC_STR_VOICEMESSAGE, localizationKey: "voice_message")
- dcContext.setStockTranslation(id: DC_STR_IMAGE, localizationKey: "image")
- dcContext.setStockTranslation(id: DC_STR_VIDEO, localizationKey: "video")
- dcContext.setStockTranslation(id: DC_STR_AUDIO, localizationKey: "audio")
- dcContext.setStockTranslation(id: DC_STR_FILE, localizationKey: "file")
- dcContext.setStockTranslation(id: DC_STR_STATUSLINE, localizationKey: "pref_default_status_text")
- dcContext.setStockTranslation(id: DC_STR_NEWGROUPDRAFT, localizationKey: "group_hello_draft")
- dcContext.setStockTranslation(id: DC_STR_MSGGRPNAME, localizationKey: "systemmsg_group_name_changed")
- dcContext.setStockTranslation(id: DC_STR_MSGGRPIMGCHANGED, localizationKey: "systemmsg_group_image_changed")
- dcContext.setStockTranslation(id: DC_STR_MSGADDMEMBER, localizationKey: "systemmsg_member_added")
- dcContext.setStockTranslation(id: DC_STR_MSGDELMEMBER, localizationKey: "systemmsg_member_removed")
- dcContext.setStockTranslation(id: DC_STR_MSGGROUPLEFT, localizationKey: "systemmsg_group_left")
- dcContext.setStockTranslation(id: DC_STR_GIF, localizationKey: "gif")
- dcContext.setStockTranslation(id: DC_STR_ENCRYPTEDMSG, localizationKey: "encrypted_message")
- dcContext.setStockTranslation(id: DC_STR_CANTDECRYPT_MSG_BODY, localizationKey: "systemmsg_cannot_decrypt")
- dcContext.setStockTranslation(id: DC_STR_READRCPT, localizationKey: "systemmsg_read_receipt_subject")
- dcContext.setStockTranslation(id: DC_STR_READRCPT_MAILBODY, localizationKey: "systemmsg_read_receipt_body")
- dcContext.setStockTranslation(id: DC_STR_MSGGRPIMGDELETED, localizationKey: "systemmsg_group_image_deleted")
- dcContext.setStockTranslation(id: DC_STR_CONTACT_VERIFIED, localizationKey: "contact_verified")
- dcContext.setStockTranslation(id: DC_STR_CONTACT_NOT_VERIFIED, localizationKey: "contact_not_verified")
- dcContext.setStockTranslation(id: DC_STR_CONTACT_SETUP_CHANGED, localizationKey: "contact_setup_changed")
- dcContext.setStockTranslation(id: DC_STR_ARCHIVEDCHATS, localizationKey: "chat_archived_chats_title")
- dcContext.setStockTranslation(id: DC_STR_AC_SETUP_MSG_SUBJECT, localizationKey: "autocrypt_asm_subject")
- dcContext.setStockTranslation(id: DC_STR_AC_SETUP_MSG_BODY, localizationKey: "autocrypt_asm_general_body")
- dcContext.setStockTranslation(id: DC_STR_CANNOT_LOGIN, localizationKey: "login_error_cannot_login")
- dcContext.setStockTranslation(id: DC_STR_MSGACTIONBYUSER, localizationKey: "systemmsg_action_by_user")
- dcContext.setStockTranslation(id: DC_STR_MSGACTIONBYME, localizationKey: "systemmsg_action_by_me")
- dcContext.setStockTranslation(id: DC_STR_DEVICE_MESSAGES, localizationKey: "device_talk")
- dcContext.setStockTranslation(id: DC_STR_SAVED_MESSAGES, localizationKey: "saved_messages")
- dcContext.setStockTranslation(id: DC_STR_DEVICE_MESSAGES_HINT, localizationKey: "device_talk_explain")
- dcContext.setStockTranslation(id: DC_STR_WELCOME_MESSAGE, localizationKey: "device_talk_welcome_message")
- dcContext.setStockTranslation(id: DC_STR_UNKNOWN_SENDER_FOR_CHAT, localizationKey: "systemmsg_unknown_sender_for_chat")
- dcContext.setStockTranslation(id: DC_STR_SUBJECT_FOR_NEW_CONTACT, localizationKey: "systemmsg_subject_for_new_contact")
- dcContext.setStockTranslation(id: DC_STR_FAILED_SENDING_TO, localizationKey: "systemmsg_failed_sending_to")
- dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_DISABLED, localizationKey: "systemmsg_ephemeral_timer_disabled")
- dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_SECONDS, localizationKey: "systemmsg_ephemeral_timer_enabled")
- dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_MINUTE, localizationKey: "systemmsg_ephemeral_timer_minute")
- dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_HOUR, localizationKey: "systemmsg_ephemeral_timer_hour")
- dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_DAY, localizationKey: "systemmsg_ephemeral_timer_day")
- dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_WEEK, localizationKey: "systemmsg_ephemeral_timer_week")
- dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_FOUR_WEEKS, localizationKey: "systemmsg_ephemeral_timer_four_weeks")
- dcContext.setStockTranslation(id: DC_STR_VIDEOCHAT_INVITATION, localizationKey: "videochat_invitation")
- dcContext.setStockTranslation(id: DC_STR_VIDEOCHAT_INVITE_MSG_BODY, localizationKey: "videochat_invitation_body")
- dcContext.setStockTranslation(id: DC_STR_CONFIGURATION_FAILED, localizationKey: "configuration_failed_with_error")
- dcContext.setStockTranslation(id: DC_STR_PROTECTION_ENABLED, localizationKey: "systemmsg_chat_protection_enabled")
- dcContext.setStockTranslation(id: DC_STR_PROTECTION_DISABLED, localizationKey: "systemmsg_chat_protection_disabled")
- dcContext.setStockTranslation(id: DC_STR_REPLY_NOUN, localizationKey: "reply_noun")
- dcContext.setStockTranslation(id: DC_STR_FORWARDED, localizationKey: "forwarded")
- }
- func appIsInForeground() -> Bool {
- switch UIApplication.shared.applicationState {
- case .background, .inactive:
- return false
- case .active:
- return true
- }
- }
- }
|