Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,331 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramCore
import WidgetItems
import TelegramPresentationData
import NotificationsPresentationData
import WidgetKit
import TelegramUIPreferences
import WidgetItemsUtils
import AccountContext
import AppLock
import GeneratedSources
@available(iOSApplicationExtension 14.0, iOS 14.0, *)
private extension SelectFriendsIntent {
var configurationHash: String {
var result = "widget"
if let items = self.friends {
for item in items {
if let identifier = item.identifier {
result.append("+\(identifier)")
}
}
}
return result
}
}
private final class WidgetReloadManager {
private var inForeground = false
private var inForegroundDisposable: Disposable?
private var isReloadRequested = false
private var lastBackgroundReload: Double?
init(inForeground: Signal<Bool, NoError>) {
self.inForegroundDisposable = (inForeground
|> distinctUntilChanged
|> deliverOnMainQueue).startStrict(next: { [weak self] value in
guard let strongSelf = self else {
return
}
if strongSelf.inForeground != value {
strongSelf.inForeground = value
if value {
strongSelf.performReloadIfNeeded()
}
}
})
}
deinit {
self.inForegroundDisposable?.dispose()
}
func requestReload() {
self.isReloadRequested = true
if self.inForeground {
self.performReloadIfNeeded()
} else {
let timestamp = CFAbsoluteTimeGetCurrent()
if let lastBackgroundReloadValue = self.lastBackgroundReload {
if abs(lastBackgroundReloadValue - timestamp) > 25.0 * 60.0 {
self.lastBackgroundReload = timestamp
performReloadIfNeeded()
}
} else {
self.lastBackgroundReload = timestamp
performReloadIfNeeded()
}
}
}
private func performReloadIfNeeded() {
if !self.isReloadRequested {
return
}
self.isReloadRequested = false
DispatchQueue.global(qos: .background).async {
if #available(iOSApplicationExtension 14.0, iOS 14.0, *) {
#if arch(arm64) || arch(i386) || arch(x86_64)
WidgetCenter.shared.reloadAllTimelines()
#endif
}
}
}
}
final class WidgetDataContext {
private let reloadManager: WidgetReloadManager
private var disposable: Disposable?
private var widgetPresentationDataDisposable: Disposable?
private var notificationPresentationDataDisposable: Disposable?
init(basePath: String, inForeground: Signal<Bool, NoError>, activeAccounts: Signal<[Account], NoError>, presentationData: Signal<PresentationData, NoError>, appLockContext: AppLockContextImpl) {
self.reloadManager = WidgetReloadManager(inForeground: inForeground)
let queue = Queue()
let updatedAdditionalPeerIds: Signal<[AccountRecordId: Set<PeerId>], NoError> = Signal { subscriber in
if #available(iOSApplicationExtension 14.0, iOS 14.0, *) {
#if arch(arm64) || arch(x86_64)
WidgetCenter.shared.getCurrentConfigurations { result in
var peerIds: [AccountRecordId: Set<PeerId>] = [:]
func processFriend(_ item: Friend) {
guard let identifier = item.identifier else {
return
}
guard let index = identifier.firstIndex(of: ":") else {
return
}
guard let accountIdValue = Int64(identifier[identifier.startIndex ..< index]) else {
return
}
guard let peerIdValue = Int64(identifier[identifier.index(after: index)...]) else {
return
}
let accountId = AccountRecordId(rawValue: accountIdValue)
let peerId = PeerId(peerIdValue)
if peerIds[accountId] == nil {
peerIds[accountId] = Set()
}
peerIds[accountId]?.insert(peerId)
}
if case let .success(infos) = result {
for info in infos {
if let configuration = info.configuration as? SelectFriendsIntent {
if let items = configuration.friends {
for item in items {
processFriend(item)
}
}
} else if let configuration = info.configuration as? SelectAvatarFriendsIntent {
if let items = configuration.friends {
for item in items {
processFriend(item)
}
}
}
}
}
subscriber.putNext(peerIds)
subscriber.putCompletion()
}
#else
subscriber.putNext([:])
subscriber.putCompletion()
#endif
} else {
subscriber.putNext([:])
subscriber.putCompletion()
}
return EmptyDisposable
}
|> runOn(queue)
|> then(
Signal<[AccountRecordId: Set<PeerId>], NoError>.complete()
|> delay(10.0, queue: queue)
)
|> restart
self.disposable = (combineLatest(queue: queue,
updatedAdditionalPeerIds |> distinctUntilChanged,
activeAccounts |> distinctUntilChanged(isEqual: { lhs, rhs in
if lhs.count != rhs.count {
return false
}
for i in 0 ..< lhs.count {
if lhs[i] !== rhs[i] {
return false
}
}
return true
})
)
|> mapToSignal { peerIdsByAccount, accounts -> Signal<[WidgetDataPeer], NoError> in
var accountSignals: [Signal<[WidgetDataPeer], NoError>] = []
for (accountId, peerIds) in peerIdsByAccount {
var accountValue: Account?
for value in accounts {
if value.id == accountId {
accountValue = value
break
}
}
guard let account = accountValue else {
continue
}
if peerIds.isEmpty {
continue
}
accountSignals.append(TelegramEngine(account: account).data.subscribe(EngineDataMap(
peerIds.map(TelegramEngine.EngineData.Item.Messages.TopMessage.init)
))
|> map { topMessages -> [WidgetDataPeer] in
var result: [WidgetDataPeer] = []
for (peerId, message) in topMessages {
guard let message = message else {
continue
}
guard let peer = message.peers[message.id.peerId] else {
continue
}
var name: String = ""
var lastName: String?
if let user = peer as? TelegramUser {
if let firstName = user.firstName {
name = firstName
lastName = user.lastName
} else if let lastName = user.lastName {
name = lastName
} else if let phone = user.phone, !phone.isEmpty {
name = phone
}
} else {
name = peer.debugDisplayTitle
}
var isForum = false
if let peer = peer as? TelegramChannel, peer.isForumOrMonoForum {
isForum = true
}
result.append(WidgetDataPeer(id: peerId.toInt64(), name: name, lastName: lastName, letters: [], avatarPath: nil, badge: nil, message: WidgetDataPeer.Message(accountPeerId: account.peerId, message: message), isForum: isForum))
}
result.sort(by: { lhs, rhs in
return lhs.id < rhs.id
})
return result
})
}
return combineLatest(queue: queue, accountSignals)
|> map { lists -> [WidgetDataPeer] in
var result: [WidgetDataPeer] = []
for list in lists {
result.append(contentsOf: list)
}
result.sort(by: { lhs, rhs in
return lhs.id < rhs.id
})
return result
}
}
|> distinctUntilChanged
|> deliverOnMainQueue).startStrict(next: { [weak self] _ in
self?.reloadManager.requestReload()
})
self.widgetPresentationDataDisposable = (presentationData
|> map { presentationData -> WidgetPresentationData in
return WidgetPresentationData(
widgetChatsGalleryTitle: presentationData.strings.Widget_ChatsGalleryTitle,
widgetChatsGalleryDescription: presentationData.strings.Widget_ChatsGalleryDescription,
widgetShortcutsGalleryTitle: presentationData.strings.Widget_ShortcutsGalleryTitle,
widgetShortcutsGalleryDescription: presentationData.strings.Widget_ShortcutsGalleryDescription,
widgetLongTapToEdit: presentationData.strings.Widget_LongTapToEdit,
widgetUpdatedTodayAt: presentationData.strings.Widget_UpdatedTodayAt,
widgetUpdatedAt: presentationData.strings.Widget_UpdatedAt,
messageAuthorYou: presentationData.strings.DialogList_You,
messagePhoto: presentationData.strings.Message_Photo,
messageVideo: presentationData.strings.Message_Video,
messageAnimation: presentationData.strings.Message_Animation,
messageVoice: presentationData.strings.Message_Audio,
messageVideoMessage: presentationData.strings.Message_VideoMessage,
messageSticker: presentationData.strings.Message_Sticker,
messageVoiceCall: presentationData.strings.Watch_Message_Call,
messageVideoCall: presentationData.strings.Watch_Message_Call,
messageLocation: presentationData.strings.Message_Location,
autodeleteTimerUpdated: presentationData.strings.Widget_MessageAutoremoveTimerUpdated,
autodeleteTimerRemoved: presentationData.strings.Widget_MessageAutoremoveTimerRemoved,
generalLockedTitle: presentationData.strings.Intents_ErrorLockedTitle,
generalLockedText: presentationData.strings.Intents_ErrorLockedText,
chatSavedMessages: presentationData.strings.DialogList_SavedMessages
)
}
|> distinctUntilChanged).startStrict(next: { value in
let path = widgetPresentationDataPath(rootPath: basePath)
if let data = try? JSONEncoder().encode(value) {
let _ = try? data.write(to: URL(fileURLWithPath: path), options: [.atomic])
} else {
let _ = try? FileManager.default.removeItem(atPath: path)
}
if #available(iOSApplicationExtension 14.0, iOS 14.0, *) {
#if arch(arm64) || arch(i386) || arch(x86_64)
WidgetCenter.shared.reloadAllTimelines()
#endif
}
})
self.notificationPresentationDataDisposable = (presentationData
|> map { presentationData -> NotificationsPresentationData in
var incomingCallString = presentationData.strings.PUSH_PHONE_CALL_REQUEST("").string
if let range = incomingCallString.range(of: "|") {
incomingCallString = String(incomingCallString[range.upperBound...])
}
return NotificationsPresentationData(
applicationLockedMessageString: presentationData.strings.PUSH_LOCKED_MESSAGE("").string,
incomingCallString: incomingCallString
)
}
|> distinctUntilChanged).startStrict(next: { value in
let path = notificationsPresentationDataPath(rootPath: basePath)
if let data = try? JSONEncoder().encode(value) {
let _ = try? data.write(to: URL(fileURLWithPath: path), options: [.atomic])
} else {
let _ = try? FileManager.default.removeItem(atPath: path)
}
})
}
deinit {
self.disposable?.dispose()
self.widgetPresentationDataDisposable?.dispose()
self.notificationPresentationDataDisposable?.dispose()
}
}