AppDelegate.swift 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. import AudioToolbox
  2. import Reachability
  3. import SwiftyBeaver
  4. import UIKit
  5. import UserNotifications
  6. import DcCore
  7. import DBDebugToolkit
  8. import SDWebImageWebPCoder
  9. import Intents
  10. let logger = SwiftyBeaver.self
  11. @UIApplicationMain
  12. class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
  13. private let dcAccounts = DcAccounts()
  14. var appCoordinator: AppCoordinator!
  15. var relayHelper: RelayHelper!
  16. var locationManager: LocationManager!
  17. var notificationManager: NotificationManager!
  18. private var backgroundTask: UIBackgroundTaskIdentifier = .invalid
  19. var reachability = Reachability()!
  20. var window: UIWindow?
  21. var notifyToken: String?
  22. // purpose of `bgIoTimestamp` is to block rapidly subsequent calls to remote- or local-wakeups:
  23. //
  24. // `bgIoTimestamp` is set to last init, enter-background or last remote- or local-wakeup;
  25. // in the minute after these events, subsequent remote- or local-wakeups are skipped
  26. // in favor to the chance of being awakened when it makes more sense
  27. // and to avoid issues with calling concurrent series of startIo/maybeNetwork/stopIo.
  28. private var bgIoTimestamp: Double = 0.0
  29. // MARK: - app main entry point
  30. // `didFinishLaunchingWithOptions` is the main entry point
  31. // that is called if the app is started for the first time
  32. // or after the app is killed.
  33. //
  34. // `didFinishLaunchingWithOptions` creates the context object and sets
  35. // up other global things.
  36. //
  37. // `didFinishLaunchingWithOptions` is _not_ called
  38. // when the app wakes up from "suspended" state
  39. // (app is in memory in the background but no code is executed, IO stopped)
  40. func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  41. // explicitly ignore SIGPIPE to avoid crashes, see https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/CommonPitfalls/CommonPitfalls.html
  42. // setupCrashReporting() may create an additional handler, but we do not want to rely on that
  43. signal(SIGPIPE, SIG_IGN)
  44. bgIoTimestamp = Double(Date().timeIntervalSince1970)
  45. DBDebugToolkit.setup(with: []) // empty array will override default device shake trigger
  46. DBDebugToolkit.setupCrashReporting()
  47. let console = ConsoleDestination()
  48. console.format = "$DHH:mm:ss.SSS$d $C$L$c $M" // see https://docs.swiftybeaver.com/article/20-custom-format
  49. logger.addDestination(console)
  50. dcAccounts.logger = DcLogger()
  51. dcAccounts.openDatabase()
  52. migrateToDcAccounts()
  53. if dcAccounts.getAll().isEmpty, dcAccounts.add() == 0 {
  54. fatalError("Could not initialize a new account.")
  55. }
  56. logger.info("➡️ didFinishLaunchingWithOptions")
  57. window = UIWindow(frame: UIScreen.main.bounds)
  58. guard let window = window else {
  59. fatalError("window was nil in app delegate")
  60. }
  61. if #available(iOS 13.0, *) {
  62. window.backgroundColor = UIColor.systemBackground
  63. } else {
  64. window.backgroundColor = UIColor.white
  65. }
  66. installEventHandler()
  67. relayHelper = RelayHelper.setup(dcAccounts.getSelected())
  68. appCoordinator = AppCoordinator(window: window, dcAccounts: dcAccounts)
  69. locationManager = LocationManager(dcAccounts: dcAccounts)
  70. UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
  71. notificationManager = NotificationManager(dcAccounts: dcAccounts)
  72. dcAccounts.startIo()
  73. setStockTranslations()
  74. reachability.whenReachable = { reachability in
  75. // maybeNetwork() shall not be called in ui thread;
  76. // Reachability::reachabilityChanged uses DispatchQueue.main.async only
  77. logger.info("network: reachable", reachability.connection.description)
  78. DispatchQueue.global(qos: .background).async { [weak self] in
  79. self?.dcAccounts.maybeNetwork()
  80. }
  81. }
  82. reachability.whenUnreachable = { _ in
  83. logger.info("network: not reachable")
  84. DispatchQueue.global(qos: .background).async { [weak self] in
  85. self?.dcAccounts.maybeNetworkLost()
  86. }
  87. }
  88. do {
  89. try reachability.startNotifier()
  90. } catch {
  91. logger.error("Unable to start notifier")
  92. }
  93. if let notificationOption = launchOptions?[.remoteNotification] {
  94. logger.info("Notifications: remoteNotification: \(String(describing: notificationOption))")
  95. increaseDebugCounter("notify-remote-launch")
  96. }
  97. if dcAccounts.getSelected().isConfigured() && !UserDefaults.standard.bool(forKey: "notifications_disabled") {
  98. registerForNotifications()
  99. }
  100. let webPCoder = SDImageWebPCoder.shared
  101. SDImageCodersManager.shared.addCoder(webPCoder)
  102. return true
  103. }
  104. // `open` is called when an url should be opened by Delta Chat.
  105. // we currently use that for handling oauth2 and for handing openpgp4fpr.
  106. //
  107. // before `open` gets called, `didFinishLaunchingWithOptions` is called.
  108. func application(_: UIApplication, open url: URL, options _: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
  109. logger.info("➡️ open url")
  110. // gets here when app returns from oAuth2-Setup process - the url contains the provided token
  111. // if let params = url.queryParameters, let token = params["code"] {
  112. // NotificationCenter.default.post(name: NSNotification.Name("oauthLoginApproved"), object: nil, userInfo: ["token": token])
  113. // }
  114. switch url.scheme?.lowercased() {
  115. case "openpgp4fpr":
  116. // Hack to format url properly
  117. let urlString = url.absoluteString
  118. .replacingOccurrences(of: "openpgp4fpr", with: "OPENPGP4FPR", options: .literal, range: nil)
  119. .replacingOccurrences(of: "%23", with: "#", options: .literal, range: nil)
  120. self.appCoordinator.handleQRCode(urlString)
  121. return true
  122. case "mailto":
  123. return self.appCoordinator.handleMailtoURL(url)
  124. default:
  125. return false
  126. }
  127. }
  128. // MARK: - app lifecycle
  129. func applicationWillEnterForeground(_: UIApplication) {
  130. logger.info("➡️ applicationWillEnterForeground")
  131. dcAccounts.startIo()
  132. DispatchQueue.global(qos: .background).async { [weak self] in
  133. guard let self = self else { return }
  134. if self.reachability.connection != .none {
  135. self.dcAccounts.maybeNetwork()
  136. }
  137. if let userDefaults = UserDefaults.shared, userDefaults.bool(forKey: UserDefaults.hasExtensionAttemptedToSend) {
  138. userDefaults.removeObject(forKey: UserDefaults.hasExtensionAttemptedToSend)
  139. DispatchQueue.main.async {
  140. NotificationCenter.default.post(
  141. name: dcNotificationChanged,
  142. object: nil,
  143. userInfo: [:]
  144. )
  145. }
  146. }
  147. }
  148. }
  149. func applicationWillResignActive(_: UIApplication) {
  150. logger.info("⬅️ applicationWillResignActive")
  151. registerBackgroundTask()
  152. }
  153. func applicationDidEnterBackground(_: UIApplication) {
  154. logger.info("⬅️ applicationDidEnterBackground")
  155. }
  156. func applicationWillTerminate(_: UIApplication) {
  157. logger.info("⬅️ applicationWillTerminate")
  158. dcAccounts.closeDatabase()
  159. reachability.stopNotifier()
  160. }
  161. // MARK: - fade out app smoothly
  162. // let the app run in background for a little while
  163. // eg. to complete sending messages out and to react to responses.
  164. private func registerBackgroundTask() {
  165. logger.info("⬅️ registering background task")
  166. bgIoTimestamp = Double(Date().timeIntervalSince1970)
  167. unregisterBackgroundTask()
  168. backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
  169. // usually, the background thread is finished before in maybeStop()
  170. logger.info("⬅️ background expirationHandler called")
  171. self?.unregisterBackgroundTask()
  172. }
  173. maybeStop()
  174. }
  175. private func unregisterBackgroundTask() {
  176. if backgroundTask != .invalid {
  177. UIApplication.shared.endBackgroundTask(backgroundTask)
  178. backgroundTask = .invalid
  179. }
  180. }
  181. private func maybeStop() {
  182. DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
  183. let app = UIApplication.shared
  184. if app.applicationState != .background {
  185. logger.info("⬅️ no longer in background")
  186. self.unregisterBackgroundTask()
  187. } else if app.backgroundTimeRemaining < 10 {
  188. logger.info("⬅️ few background time, \(app.backgroundTimeRemaining), stopping")
  189. self.dcAccounts.stopIo()
  190. // to avoid 0xdead10cc exceptions, scheduled jobs need to be done before we get suspended;
  191. // we increase the probabilty that this happens by waiting a moment before calling unregisterBackgroundTask()
  192. DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
  193. logger.info("⬅️ few background time, \(app.backgroundTimeRemaining), done")
  194. self.unregisterBackgroundTask()
  195. }
  196. } else {
  197. logger.info("⬅️ remaining background time: \(app.backgroundTimeRemaining)")
  198. self.maybeStop()
  199. }
  200. }
  201. }
  202. // MARK: - background fetch and notifications
  203. // `registerForNotifications` asks the user if they want to get notifiations shown.
  204. // if so, it registers for receiving remote notifications.
  205. func registerForNotifications() {
  206. UNUserNotificationCenter.current().delegate = self
  207. notifyToken = nil
  208. // register for showing notifications
  209. UNUserNotificationCenter.current()
  210. .requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
  211. if granted {
  212. // we are allowed to show notifications:
  213. // register for receiving remote notifications
  214. logger.info("Notifications: Permission granted: \(granted)")
  215. self?.maybeRegisterForRemoteNotifications()
  216. } else {
  217. logger.info("Notifications: Permission not granted.")
  218. }
  219. }
  220. }
  221. // register on apple server for receiving remote notifications
  222. // and pass the token to the app's notification server.
  223. //
  224. // on success, we get a token at didRegisterForRemoteNotificationsWithDeviceToken;
  225. // on failure, didFailToRegisterForRemoteNotificationsWithError is called
  226. private func maybeRegisterForRemoteNotifications() {
  227. UNUserNotificationCenter.current().getNotificationSettings { settings in
  228. logger.info("Notifications: Settings: \(settings)")
  229. switch settings.authorizationStatus {
  230. case .authorized, .provisional, .ephemeral:
  231. DispatchQueue.main.async {
  232. UIApplication.shared.registerForRemoteNotifications()
  233. }
  234. case .denied, .notDetermined:
  235. break
  236. }
  237. }
  238. }
  239. // `didRegisterForRemoteNotificationsWithDeviceToken` is called by iOS
  240. // when the call to `UIApplication.shared.registerForRemoteNotifications` succeeded.
  241. //
  242. // we pass the received token to the app's notification server then.
  243. func application(
  244. _ application: UIApplication,
  245. didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
  246. ) {
  247. let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
  248. let tokenString = tokenParts.joined()
  249. #if DEBUG
  250. let endpoint = "https://sandbox.notifications.delta.chat/register"
  251. #else
  252. let endpoint = "https://notifications.delta.chat/register"
  253. #endif
  254. logger.info("Notifications: POST token: \(tokenString) to \(endpoint)")
  255. if let url = URL(string: endpoint) {
  256. var request = URLRequest(url: url)
  257. request.httpMethod = "POST"
  258. let body = "{ \"token\": \"\(tokenString)\" }"
  259. request.httpBody = body.data(using: String.Encoding.utf8)
  260. let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
  261. if let error = error {
  262. logger.error("Notifications: cannot POST to notification server: \(error)")
  263. return
  264. }
  265. if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode == 200 {
  266. logger.info("Notifications: request to notification server succeeded")
  267. } else {
  268. logger.error("Notifications: request to notification server failed: \(String(describing: response)), \(String(describing: data))")
  269. }
  270. self.notifyToken = tokenString
  271. }
  272. task.resume()
  273. } else {
  274. logger.error("Notifications: cannot create URL for token: \(tokenString)")
  275. }
  276. }
  277. // `didFailToRegisterForRemoteNotificationsWithError` is called by iOS
  278. // when the call to `UIApplication.shared.registerForRemoteNotifications` failed.
  279. func application(
  280. _ application: UIApplication,
  281. didFailToRegisterForRemoteNotificationsWithError error: Error) {
  282. logger.error("Notifications: Failed to register: \(error)")
  283. }
  284. // `didReceiveRemoteNotification` is called by iOS when a remote notification is received.
  285. //
  286. // we need to ensure IO is running as the function may be called from suspended state
  287. // (with app in memory, but gracefully shut down before; sort of freezed).
  288. // if the function was not called from suspended state,
  289. // the call to startIo() did nothing, therefore, interrupt and force fetch.
  290. //
  291. // we have max. 30 seconds time for our job and to call the completion handler.
  292. // as the system tracks the elapsed time, power usage, and data costs, we return faster,
  293. // after 10 seconds, things should be done.
  294. // (see https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623013-application)
  295. // (at some point it would be nice if we get a clear signal from the core)
  296. func application(
  297. _ application: UIApplication,
  298. didReceiveRemoteNotification userInfo: [AnyHashable: Any],
  299. fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
  300. ) {
  301. logger.info("➡️ Notifications: didReceiveRemoteNotification \(userInfo)")
  302. increaseDebugCounter("notify-remote-receive")
  303. performFetch(completionHandler: completionHandler)
  304. }
  305. // `performFetchWithCompletionHandler` is called by iOS on local wakeup.
  306. //
  307. // this requires "UIBackgroundModes: fetch" to be set in Info.plist
  308. // ("App downloads content from the network" in Xcode)
  309. //
  310. // we have 30 seconds time for our job, things are quite similar as in `didReceiveRemoteNotification`
  311. func application(
  312. _ application: UIApplication,
  313. performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
  314. ) {
  315. logger.info("➡️ Notifications: performFetchWithCompletionHandler")
  316. increaseDebugCounter("notify-local-wakeup")
  317. performFetch(completionHandler: completionHandler)
  318. }
  319. private func performFetch(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  320. // `didReceiveRemoteNotification` as well as `performFetchWithCompletionHandler` might be called if we're in foreground,
  321. // in this case, there is no need to wait for things or do sth.
  322. if appIsInForeground() {
  323. logger.info("➡️ app already in foreground")
  324. completionHandler(.newData)
  325. return
  326. }
  327. // from time to time, `didReceiveRemoteNotification` and `performFetchWithCompletionHandler`
  328. // are actually called at the same millisecond.
  329. //
  330. // therefore, if last fetch is less than a minute ago, we skip this call;
  331. // this also lets the completionHandler being called earlier so that we maybe get awakened when it makes more sense.
  332. //
  333. // nb: calling the completion handler with .noData results in less calls overall.
  334. // if at some point we do per-message-push-notifications, we need to tweak this gate.
  335. let nowTimestamp = Double(Date().timeIntervalSince1970)
  336. if nowTimestamp < bgIoTimestamp + 60 {
  337. logger.info("➡️ fetch was just executed, skipping")
  338. completionHandler(.newData)
  339. return
  340. }
  341. bgIoTimestamp = nowTimestamp
  342. // make sure to balance each call to `beginBackgroundTask` with `endBackgroundTask`
  343. var backgroundTask: UIBackgroundTaskIdentifier = .invalid
  344. backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
  345. // usually, this handler is not used as we are taking care of timings below.
  346. logger.info("⬅️ finishing fetch by system urgency requests")
  347. self?.dcAccounts.stopIo()
  348. completionHandler(.newData)
  349. if backgroundTask != .invalid {
  350. UIApplication.shared.endBackgroundTask(backgroundTask)
  351. backgroundTask = .invalid
  352. }
  353. }
  354. // we're in background, run IO for a little time
  355. dcAccounts.startIo()
  356. dcAccounts.maybeNetwork()
  357. DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self] in
  358. logger.info("⬅️ finishing fetch")
  359. guard let self = self else {
  360. completionHandler(.failed)
  361. return
  362. }
  363. if !self.appIsInForeground() {
  364. self.dcAccounts.stopIo()
  365. }
  366. // to avoid 0xdead10cc exceptions, scheduled jobs need to be done before we get suspended;
  367. // we increase the probabilty that this happens by waiting a moment before calling completionHandler()
  368. DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  369. logger.info("⬅️ fetch done")
  370. completionHandler(.newData)
  371. if backgroundTask != .invalid {
  372. UIApplication.shared.endBackgroundTask(backgroundTask)
  373. backgroundTask = .invalid
  374. }
  375. }
  376. }
  377. }
  378. // MARK: - handle notification banners
  379. // This method will be called if an incoming message was received while the app was in foreground.
  380. // We don't show foreground notifications in the notification center because they don't get grouped properly
  381. func userNotificationCenter(_: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
  382. logger.info("Notifications: foreground notification")
  383. completionHandler([.badge])
  384. }
  385. // this method will be called if the user tapped on a notification
  386. func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
  387. if !response.notification.request.identifier.containsExact(subSequence: Constants.notificationIdentifier).isEmpty {
  388. logger.info("Notifications: notification tapped")
  389. let userInfo = response.notification.request.content.userInfo
  390. if let chatId = userInfo["chat_id"] as? Int,
  391. let msgId = userInfo["message_id"] as? Int {
  392. appCoordinator.showChat(chatId: chatId, msgId: msgId, animated: false, clearViewControllerStack: true)
  393. }
  394. }
  395. completionHandler()
  396. }
  397. // MARK: - misc.
  398. func migrateToDcAccounts() {
  399. let dbHelper = DatabaseHelper()
  400. if let databaseLocation = dbHelper.unmanagedDatabaseLocation {
  401. if dcAccounts.migrate(dbLocation: databaseLocation) == 0 {
  402. fatalError("Account could not be migrated")
  403. // TODO: show error message in UI
  404. }
  405. INInteraction.deleteAll(completion: nil)
  406. }
  407. }
  408. func reloadDcContext() {
  409. setStockTranslations()
  410. locationManager.reloadDcContext()
  411. notificationManager.reloadDcContext()
  412. RelayHelper.sharedInstance.cancel()
  413. _ = RelayHelper.setup(dcAccounts.getSelected())
  414. if dcAccounts.getSelected().isConfigured() {
  415. appCoordinator.resetTabBarRootViewControllers()
  416. } else {
  417. appCoordinator.presentWelcomeController()
  418. }
  419. }
  420. func installEventHandler() {
  421. DispatchQueue.global(qos: .background).async { [weak self] in
  422. guard let self = self else { return }
  423. let eventHandler = DcEventHandler(dcAccounts: self.dcAccounts)
  424. let eventEmitter = self.dcAccounts.getEventEmitter()
  425. while true {
  426. guard let event = eventEmitter.getNextEvent() else { break }
  427. eventHandler.handleEvent(event: event)
  428. }
  429. logger.info("⬅️ event emitter finished")
  430. }
  431. }
  432. private func increaseDebugCounter(_ name: String) {
  433. let nowDate = Date()
  434. let nowTimestamp = Double(nowDate.timeIntervalSince1970)
  435. let startTimestamp = UserDefaults.standard.double(forKey: name + "-start")
  436. if nowTimestamp > startTimestamp + 60*60*24 {
  437. let cal: Calendar = Calendar(identifier: .gregorian)
  438. let newStartDate: Date = cal.date(bySettingHour: 0, minute: 0, second: 0, of: nowDate)!
  439. UserDefaults.standard.set(0, forKey: name + "-count")
  440. UserDefaults.standard.set(Double(newStartDate.timeIntervalSince1970), forKey: name + "-start")
  441. }
  442. let cnt = UserDefaults.standard.integer(forKey: name + "-count")
  443. UserDefaults.standard.set(cnt + 1, forKey: name + "-count")
  444. UserDefaults.standard.set(nowTimestamp, forKey: name + "-last")
  445. }
  446. private func setStockTranslations() {
  447. let dcContext = dcAccounts.getSelected()
  448. dcContext.setStockTranslation(id: DC_STR_NOMESSAGES, localizationKey: "chat_no_messages")
  449. dcContext.setStockTranslation(id: DC_STR_SELF, localizationKey: "self")
  450. dcContext.setStockTranslation(id: DC_STR_DRAFT, localizationKey: "draft")
  451. dcContext.setStockTranslation(id: DC_STR_VOICEMESSAGE, localizationKey: "voice_message")
  452. dcContext.setStockTranslation(id: DC_STR_IMAGE, localizationKey: "image")
  453. dcContext.setStockTranslation(id: DC_STR_VIDEO, localizationKey: "video")
  454. dcContext.setStockTranslation(id: DC_STR_AUDIO, localizationKey: "audio")
  455. dcContext.setStockTranslation(id: DC_STR_FILE, localizationKey: "file")
  456. dcContext.setStockTranslation(id: DC_STR_STATUSLINE, localizationKey: "pref_default_status_text")
  457. dcContext.setStockTranslation(id: DC_STR_NEWGROUPDRAFT, localizationKey: "group_hello_draft")
  458. dcContext.setStockTranslation(id: DC_STR_MSGGRPNAME, localizationKey: "systemmsg_group_name_changed")
  459. dcContext.setStockTranslation(id: DC_STR_MSGGRPIMGCHANGED, localizationKey: "systemmsg_group_image_changed")
  460. dcContext.setStockTranslation(id: DC_STR_MSGADDMEMBER, localizationKey: "systemmsg_member_added")
  461. dcContext.setStockTranslation(id: DC_STR_MSGDELMEMBER, localizationKey: "systemmsg_member_removed")
  462. dcContext.setStockTranslation(id: DC_STR_MSGGROUPLEFT, localizationKey: "systemmsg_group_left")
  463. dcContext.setStockTranslation(id: DC_STR_GIF, localizationKey: "gif")
  464. dcContext.setStockTranslation(id: DC_STR_ENCRYPTEDMSG, localizationKey: "encrypted_message")
  465. dcContext.setStockTranslation(id: DC_STR_CANTDECRYPT_MSG_BODY, localizationKey: "systemmsg_cannot_decrypt")
  466. dcContext.setStockTranslation(id: DC_STR_READRCPT, localizationKey: "systemmsg_read_receipt_subject")
  467. dcContext.setStockTranslation(id: DC_STR_READRCPT_MAILBODY, localizationKey: "systemmsg_read_receipt_body")
  468. dcContext.setStockTranslation(id: DC_STR_MSGGRPIMGDELETED, localizationKey: "systemmsg_group_image_deleted")
  469. dcContext.setStockTranslation(id: DC_STR_CONTACT_VERIFIED, localizationKey: "contact_verified")
  470. dcContext.setStockTranslation(id: DC_STR_CONTACT_NOT_VERIFIED, localizationKey: "contact_not_verified")
  471. dcContext.setStockTranslation(id: DC_STR_CONTACT_SETUP_CHANGED, localizationKey: "contact_setup_changed")
  472. dcContext.setStockTranslation(id: DC_STR_ARCHIVEDCHATS, localizationKey: "chat_archived_chats_title")
  473. dcContext.setStockTranslation(id: DC_STR_AC_SETUP_MSG_SUBJECT, localizationKey: "autocrypt_asm_subject")
  474. dcContext.setStockTranslation(id: DC_STR_AC_SETUP_MSG_BODY, localizationKey: "autocrypt_asm_general_body")
  475. dcContext.setStockTranslation(id: DC_STR_CANNOT_LOGIN, localizationKey: "login_error_cannot_login")
  476. dcContext.setStockTranslation(id: DC_STR_MSGACTIONBYUSER, localizationKey: "systemmsg_action_by_user")
  477. dcContext.setStockTranslation(id: DC_STR_MSGACTIONBYME, localizationKey: "systemmsg_action_by_me")
  478. dcContext.setStockTranslation(id: DC_STR_DEVICE_MESSAGES, localizationKey: "device_talk")
  479. dcContext.setStockTranslation(id: DC_STR_SAVED_MESSAGES, localizationKey: "saved_messages")
  480. dcContext.setStockTranslation(id: DC_STR_DEVICE_MESSAGES_HINT, localizationKey: "device_talk_explain")
  481. dcContext.setStockTranslation(id: DC_STR_WELCOME_MESSAGE, localizationKey: "device_talk_welcome_message")
  482. dcContext.setStockTranslation(id: DC_STR_UNKNOWN_SENDER_FOR_CHAT, localizationKey: "systemmsg_unknown_sender_for_chat")
  483. dcContext.setStockTranslation(id: DC_STR_SUBJECT_FOR_NEW_CONTACT, localizationKey: "systemmsg_subject_for_new_contact")
  484. dcContext.setStockTranslation(id: DC_STR_FAILED_SENDING_TO, localizationKey: "systemmsg_failed_sending_to")
  485. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_DISABLED, localizationKey: "systemmsg_ephemeral_timer_disabled")
  486. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_SECONDS, localizationKey: "systemmsg_ephemeral_timer_enabled")
  487. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_MINUTE, localizationKey: "systemmsg_ephemeral_timer_minute")
  488. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_HOUR, localizationKey: "systemmsg_ephemeral_timer_hour")
  489. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_DAY, localizationKey: "systemmsg_ephemeral_timer_day")
  490. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_WEEK, localizationKey: "systemmsg_ephemeral_timer_week")
  491. dcContext.setStockTranslation(id: DC_STR_EPHEMERAL_FOUR_WEEKS, localizationKey: "systemmsg_ephemeral_timer_four_weeks")
  492. dcContext.setStockTranslation(id: DC_STR_VIDEOCHAT_INVITATION, localizationKey: "videochat_invitation")
  493. dcContext.setStockTranslation(id: DC_STR_VIDEOCHAT_INVITE_MSG_BODY, localizationKey: "videochat_invitation_body")
  494. dcContext.setStockTranslation(id: DC_STR_CONFIGURATION_FAILED, localizationKey: "configuration_failed_with_error")
  495. dcContext.setStockTranslation(id: DC_STR_PROTECTION_ENABLED, localizationKey: "systemmsg_chat_protection_enabled")
  496. dcContext.setStockTranslation(id: DC_STR_PROTECTION_DISABLED, localizationKey: "systemmsg_chat_protection_disabled")
  497. dcContext.setStockTranslation(id: DC_STR_REPLY_NOUN, localizationKey: "reply_noun")
  498. dcContext.setStockTranslation(id: DC_STR_FORWARDED, localizationKey: "forwarded")
  499. }
  500. func appIsInForeground() -> Bool {
  501. switch UIApplication.shared.applicationState {
  502. case .background, .inactive:
  503. return false
  504. case .active:
  505. return true
  506. }
  507. }
  508. }