Files
Swiftgram/submodules/AppLock/Sources/AppLock.swift
Kylmakalle fd86110711 Version 11.3.1
Fixes

fix localeWithStrings globally (#30)

Fix badge on zoomed devices. closes #9

Hide channel bottom panel closes #27

Another attempt to fix badge on some Zoomed devices

Force System Share sheet tg://sg/debug

fixes for device badge

New Crowdin updates (#34)

* New translations sglocalizable.strings (Chinese Traditional)

* New translations sglocalizable.strings (Chinese Simplified)

* New translations sglocalizable.strings (Chinese Traditional)

Fix input panel hidden on selection (#31)

* added if check for selectionState != nil

* same order of subnodes

Revert "Fix input panel hidden on selection (#31)"

This reverts commit e8a8bb1496.

Fix input panel for channels Closes #37

Quickly share links with system's share menu

force tabbar when editing

increase height for correct animation

New translations sglocalizable.strings (Ukrainian) (#38)

Hide Post Story button

Fix 10.15.1

Fix archive option for long-tap

Enable in-app Safari

Disable some unsupported purchases

disableDeleteChatSwipeOption + refactor restart alert

Hide bot in suggestions list

Fix merge v11.0

Fix exceptions for safari webview controller

New Crowdin updates (#47)

* New translations sglocalizable.strings (Romanian)

* New translations sglocalizable.strings (French)

* New translations sglocalizable.strings (Spanish)

* New translations sglocalizable.strings (Afrikaans)

* New translations sglocalizable.strings (Arabic)

* New translations sglocalizable.strings (Catalan)

* New translations sglocalizable.strings (Czech)

* New translations sglocalizable.strings (Danish)

* New translations sglocalizable.strings (German)

* New translations sglocalizable.strings (Greek)

* New translations sglocalizable.strings (Finnish)

* New translations sglocalizable.strings (Hebrew)

* New translations sglocalizable.strings (Hungarian)

* New translations sglocalizable.strings (Italian)

* New translations sglocalizable.strings (Japanese)

* New translations sglocalizable.strings (Korean)

* New translations sglocalizable.strings (Dutch)

* New translations sglocalizable.strings (Norwegian)

* New translations sglocalizable.strings (Polish)

* New translations sglocalizable.strings (Portuguese)

* New translations sglocalizable.strings (Serbian (Cyrillic))

* New translations sglocalizable.strings (Swedish)

* New translations sglocalizable.strings (Turkish)

* New translations sglocalizable.strings (Vietnamese)

* New translations sglocalizable.strings (Indonesian)

* New translations sglocalizable.strings (Hindi)

* New translations sglocalizable.strings (Uzbek)

New Crowdin updates (#49)

* New translations sglocalizable.strings (Arabic)

* New translations sglocalizable.strings (Arabic)

New translations sglocalizable.strings (Russian) (#51)

Call confirmation

WIP Settings search

Settings Search

Localize placeholder

Update AccountUtils.swift

mark mutual contact

Align back context action to left

New Crowdin updates (#54)

* New translations sglocalizable.strings (Chinese Simplified)

* New translations sglocalizable.strings (Chinese Traditional)

* New translations sglocalizable.strings (Ukrainian)

Independent Playground app for simulator

New translations sglocalizable.strings (Ukrainian) (#55)

Playground UIKit base and controllers

Inject SwiftUI view with overflow to AsyncDisplayKit

Launch Playgound project on simulator

Create .swiftformat

Move Playground to example

Update .swiftformat

Init SwiftUIViewController

wip

New translations sglocalizable.strings (Chinese Traditional) (#57)

Xcode 16 fixes

Fix

New translations sglocalizable.strings (Italian) (#59)

New translations sglocalizable.strings (Chinese Simplified) (#63)

Force disable CallKit integration due to missing NSE Entitlement

Fix merge

Fix whole chat translator

Sweetpad config

Bump version

11.3.1 fixes

Mutual contact placement fix

Disable Video PIP swipe

Update versions.json

Fix PIP crash
2024-12-20 09:38:13 +02:00

387 lines
17 KiB
Swift

import Foundation
import UIKit
import TelegramCore
import Display
import SwiftSignalKit
import MonotonicTime
import AccountContext
import TelegramPresentationData
import PasscodeUI
import TelegramUIPreferences
import ImageBlur
import FastBlur
import AppLockState
import PassKit
private func isLocked(passcodeSettings: PresentationPasscodeSettings, state: LockState, isApplicationActive: Bool) -> Bool {
if state.isManuallyLocked {
return true
} else if let autolockTimeout = passcodeSettings.autolockTimeout {
var bootTimestamp: Int32 = 0
let uptime = getDeviceUptimeSeconds(&bootTimestamp)
let timestamp = MonotonicTimestamp(bootTimestamp: bootTimestamp, uptime: uptime)
let applicationActivityTimestamp = state.applicationActivityTimestamp
if let applicationActivityTimestamp = applicationActivityTimestamp {
if timestamp.bootTimestamp != applicationActivityTimestamp.bootTimestamp {
return true
}
if timestamp.uptime >= applicationActivityTimestamp.uptime + autolockTimeout {
return true
}
} else {
return true
}
}
return false
}
private func getCoveringViewSnaphot(window: Window1) -> UIImage? {
let scale: CGFloat = 0.5
let unscaledSize = window.hostView.containerView.frame.size
return generateImage(CGSize(width: floor(unscaledSize.width * scale), height: floor(unscaledSize.height * scale)), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.scaleBy(x: scale, y: scale)
UIGraphicsPushContext(context)
window.badgeView.alpha = 0.0
window.forEachViewController({ controller in
if let controller = controller as? PasscodeEntryController {
controller.displayNode.alpha = 0.0
}
return true
})
window.hostView.containerView.drawHierarchy(in: CGRect(origin: CGPoint(), size: unscaledSize), afterScreenUpdates: false)
window.forEachViewController({ controller in
if let controller = controller as? PasscodeEntryController {
controller.displayNode.alpha = 1.0
}
return true
})
window.badgeView.alpha = 1.0
UIGraphicsPopContext()
}).flatMap(applyScreenshotEffectToImage)
}
public final class AppLockContextImpl: AppLockContext {
private let rootPath: String
private let syncQueue = Queue()
private var disposable: Disposable?
private var autolockTimeoutDisposable: Disposable?
private let applicationBindings: TelegramApplicationBindings
private let accountManager: AccountManager<TelegramAccountManagerTypes>
private let presentationDataSignal: Signal<PresentationData, NoError>
private let window: Window1?
private let rootController: UIViewController?
private var coveringView: LockedWindowCoveringView?
private var passcodeController: PasscodeEntryController?
private var timestampRenewTimer: SwiftSignalKit.Timer?
private var currentStateValue: LockState
private let currentState = Promise<LockState>()
private let autolockTimeout = ValuePromise<Int32?>(nil, ignoreRepeated: true)
private let autolockReportTimeout = ValuePromise<Int32?>(nil, ignoreRepeated: true)
private let isCurrentlyLockedPromise = Promise<Bool>()
public var isCurrentlyLocked: Signal<Bool, NoError> {
return self.isCurrentlyLockedPromise.get()
|> distinctUntilChanged
}
private var lastActiveTimestamp: Double?
private var lastActiveValue: Bool = false
public init(rootPath: String, window: Window1?, rootController: UIViewController?, applicationBindings: TelegramApplicationBindings, accountManager: AccountManager<TelegramAccountManagerTypes>, presentationDataSignal: Signal<PresentationData, NoError>, lockIconInitialFrame: @escaping () -> CGRect?) {
assert(Queue.mainQueue().isCurrent())
self.applicationBindings = applicationBindings
self.accountManager = accountManager
self.presentationDataSignal = presentationDataSignal
self.rootPath = rootPath
self.window = window
self.rootController = rootController
if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: self.rootPath))), let current = try? JSONDecoder().decode(LockState.self, from: data) {
self.currentStateValue = current
} else {
self.currentStateValue = LockState()
}
self.autolockTimeout.set(self.currentStateValue.autolockTimeout)
self.disposable = (combineLatest(queue: .mainQueue(),
accountManager.accessChallengeData(),
accountManager.sharedData(keys: Set([ApplicationSpecificSharedDataKeys.presentationPasscodeSettings])),
presentationDataSignal,
applicationBindings.applicationIsActive,
self.currentState.get()
)
|> deliverOnMainQueue).startStrict(next: { [weak self] accessChallengeData, sharedData, presentationData, appInForeground, state in
guard let strongSelf = self else {
return
}
let passcodeSettings: PresentationPasscodeSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationPasscodeSettings]?.get(PresentationPasscodeSettings.self) ?? .defaultSettings
let timestamp = CFAbsoluteTimeGetCurrent()
var becameActiveRecently = false
if appInForeground {
if !strongSelf.lastActiveValue {
strongSelf.lastActiveValue = true
strongSelf.lastActiveTimestamp = timestamp
if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: strongSelf.rootPath))), let current = try? JSONDecoder().decode(LockState.self, from: data) {
strongSelf.currentStateValue = current
}
}
if let lastActiveTimestamp = strongSelf.lastActiveTimestamp {
if lastActiveTimestamp + 0.5 > timestamp {
becameActiveRecently = true
}
}
} else {
strongSelf.lastActiveValue = false
}
var shouldDisplayCoveringView = false
var isCurrentlyLocked = false
if !accessChallengeData.data.isLockable {
if let passcodeController = strongSelf.passcodeController {
strongSelf.passcodeController = nil
passcodeController.dismiss()
}
strongSelf.autolockTimeout.set(nil)
strongSelf.autolockReportTimeout.set(nil)
} else {
if let _ = passcodeSettings.autolockTimeout, !appInForeground {
shouldDisplayCoveringView = true
}
if !appInForeground {
if let autolockTimeout = passcodeSettings.autolockTimeout {
strongSelf.autolockReportTimeout.set(autolockTimeout)
} else if state.isManuallyLocked {
strongSelf.autolockReportTimeout.set(1)
} else {
strongSelf.autolockReportTimeout.set(nil)
}
} else {
strongSelf.autolockReportTimeout.set(nil)
}
strongSelf.autolockTimeout.set(passcodeSettings.autolockTimeout)
if isLocked(passcodeSettings: passcodeSettings, state: state, isApplicationActive: appInForeground) {
isCurrentlyLocked = true
let biometrics: PasscodeEntryControllerBiometricsMode
if passcodeSettings.enableBiometrics {
biometrics = .enabled(passcodeSettings.biometricsDomainState)
} else {
biometrics = .none
}
if let passcodeController = strongSelf.passcodeController {
if becameActiveRecently, case .enabled = biometrics, appInForeground {
passcodeController.requestBiometrics()
}
passcodeController.ensureInputFocused()
} else {
let passcodeController = PasscodeEntryController(applicationBindings: strongSelf.applicationBindings, accountManager: strongSelf.accountManager, appLockContext: strongSelf, presentationData: presentationData, presentationDataSignal: strongSelf.presentationDataSignal, statusBarHost: window?.statusBarHost, challengeData: accessChallengeData.data, biometrics: biometrics, arguments: PasscodeEntryControllerPresentationArguments(animated: !becameActiveRecently, lockIconInitialFrame: {
if let lockViewFrame = lockIconInitialFrame() {
return lockViewFrame
} else {
return CGRect()
}
}))
if becameActiveRecently, appInForeground {
passcodeController.presentationCompleted = { [weak passcodeController] in
if case .enabled = biometrics {
passcodeController?.requestBiometrics()
}
passcodeController?.ensureInputFocused()
}
}
passcodeController.presentedOverCoveringView = true
passcodeController.isOpaqueWhenInOverlay = true
strongSelf.passcodeController = passcodeController
if let rootViewController = strongSelf.rootController {
if let _ = rootViewController.presentedViewController as? UIActivityViewController {
} else if let _ = rootViewController.presentedViewController as? PKPaymentAuthorizationViewController {
} else {
rootViewController.dismiss(animated: false, completion: nil)
}
}
strongSelf.window?.present(passcodeController, on: .passcode)
}
} else if let passcodeController = strongSelf.passcodeController {
strongSelf.passcodeController = nil
passcodeController.dismiss()
}
}
strongSelf.updateTimestampRenewTimer(shouldRun: appInForeground && !isCurrentlyLocked)
strongSelf.isCurrentlyLockedPromise.set(.single(!appInForeground || isCurrentlyLocked))
if shouldDisplayCoveringView {
if strongSelf.coveringView == nil, let window = strongSelf.window {
let coveringView = LockedWindowCoveringView(theme: presentationData.theme)
coveringView.updateSnapshot(getCoveringViewSnaphot(window: window))
strongSelf.coveringView = coveringView
window.coveringView = coveringView
if let rootViewController = strongSelf.rootController {
if let _ = rootViewController.presentedViewController as? UIActivityViewController {
} else if let _ = rootViewController.presentedViewController as? PKPaymentAuthorizationViewController {
} else {
rootViewController.dismiss(animated: false, completion: nil)
}
}
}
} else {
if let _ = strongSelf.coveringView {
strongSelf.coveringView = nil
strongSelf.window?.coveringView = nil
}
}
})
self.currentState.set(.single(self.currentStateValue))
self.autolockTimeoutDisposable = (self.autolockTimeout.get()
|> deliverOnMainQueue).startStrict(next: { [weak self] autolockTimeout in
self?.updateLockState { state in
var state = state
state.autolockTimeout = autolockTimeout
return state
}
})
}
deinit {
self.disposable?.dispose()
self.autolockTimeoutDisposable?.dispose()
}
private func updateTimestampRenewTimer(shouldRun: Bool) {
if shouldRun {
if self.timestampRenewTimer == nil { // MARK: Swiftgram
let timestampRenewTimer = SwiftSignalKit.Timer(timeout: 2.5, repeat: true, completion: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.updateApplicationActivityTimestamp()
}, queue: .mainQueue())
self.timestampRenewTimer = timestampRenewTimer
timestampRenewTimer.start()
}
} else {
if let timestampRenewTimer = self.timestampRenewTimer {
self.timestampRenewTimer = nil
timestampRenewTimer.invalidate()
}
}
}
private func updateApplicationActivityTimestamp() {
self.updateLockState { state in
var bootTimestamp: Int32 = 0
let uptime = getDeviceUptimeSeconds(&bootTimestamp)
var state = state
state.applicationActivityTimestamp = MonotonicTimestamp(bootTimestamp: bootTimestamp, uptime: uptime)
return state
}
}
private func updateLockState(_ f: @escaping (LockState) -> LockState) {
Queue.mainQueue().async {
let updatedState = f(self.currentStateValue)
if updatedState != self.currentStateValue {
self.currentStateValue = updatedState
self.currentState.set(.single(updatedState))
let path = appLockStatePath(rootPath: self.rootPath)
self.syncQueue.async {
if let data = try? JSONEncoder().encode(updatedState) {
let _ = try? data.write(to: URL(fileURLWithPath: path), options: .atomic)
}
}
}
}
}
public var invalidAttempts: Signal<AccessChallengeAttempts?, NoError> {
return self.currentState.get()
|> map { state in
return state.unlockAttempts.flatMap { unlockAttempts in
return AccessChallengeAttempts(count: unlockAttempts.count, bootTimestamp: unlockAttempts.timestamp.bootTimestamp, uptime: unlockAttempts.timestamp.uptime)
}
}
}
public var autolockDeadline: Signal<Int32?, NoError> {
return self.autolockReportTimeout.get()
|> distinctUntilChanged
|> map { value -> Int32? in
if let value = value {
return Int32(Date().timeIntervalSince1970) + value
} else {
return nil
}
}
}
public func lock() {
self.updateLockState { state in
var state = state
state.isManuallyLocked = true
return state
}
}
public func unlock() {
self.updateLockState { state in
var state = state
state.unlockAttempts = nil
state.isManuallyLocked = false
var bootTimestamp: Int32 = 0
let uptime = getDeviceUptimeSeconds(&bootTimestamp)
let timestamp = MonotonicTimestamp(bootTimestamp: bootTimestamp, uptime: uptime)
state.applicationActivityTimestamp = timestamp
return state
}
}
public func failedUnlockAttempt() {
self.updateLockState { state in
var state = state
var unlockAttempts = state.unlockAttempts ?? UnlockAttempts(count: 0, timestamp: MonotonicTimestamp(bootTimestamp: 0, uptime: 0))
unlockAttempts.count += 1
var bootTimestamp: Int32 = 0
let uptime = getDeviceUptimeSeconds(&bootTimestamp)
let timestamp = MonotonicTimestamp(bootTimestamp: bootTimestamp, uptime: uptime)
unlockAttempts.timestamp = timestamp
state.unlockAttempts = unlockAttempts
return state
}
}
}