Swiftgram/submodules/TelegramUI/Sources/WidgetDataContext.swift
2022-11-09 16:39:28 +04:00

332 lines
14 KiB
Swift

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).start(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.flags.contains(.isForum) {
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).start(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).start(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).start(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()
}
}