mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
332 lines
14 KiB
Swift
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()
|
|
}
|
|
}
|