diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index 2837bcbd8d..7b335f548b 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -424,7 +424,7 @@ private func peerAvatar(mediaBox: MediaBox, accountPeerId: PeerId, peer: Peer) - } @available(iOSApplicationExtension 10.0, iOS 10.0, *) -private struct NotificationContent { +private struct NotificationContent: CustomStringConvertible { var title: String? var subtitle: String? var body: String? @@ -438,6 +438,21 @@ private struct NotificationContent { var senderPerson: INPerson? var senderImage: INImage? + var description: String { + var string = "{" + string += " title: \(String(describing: self.title))\n" + string += " subtitle: \(String(describing: self.subtitle))\n" + string += " body: \(String(describing: self.body)),\n" + string += " threadId: \(String(describing: self.threadId)),\n" + string += " sound: \(String(describing: self.sound)),\n" + string += " badge: \(String(describing: self.badge)),\n" + string += " category: \(String(describing: self.category)),\n" + string += " userInfo: \(String(describing: self.userInfo)),\n" + string += " senderImage: \(self.senderImage != nil ? "non-empty" : "empty"),\n" + string += "}" + return string + } + mutating func addSenderInfo(mediaBox: MediaBox, accountPeerId: PeerId, peer: Peer) { if #available(iOS 15.0, *) { let image = peerAvatar(mediaBox: mediaBox, accountPeerId: accountPeerId, peer: peer) @@ -548,6 +563,8 @@ private final class NotificationServiceHandler { init?(queue: Queue, updateCurrentContent: @escaping (NotificationContent) -> Void, completed: @escaping () -> Void, payload: [AnyHashable: Any]) { self.queue = queue + let episode = String(UInt32.random(in: 0 ..< UInt32.max), radix: 16) + guard let appBundleIdentifier = Bundle.main.bundleIdentifier, let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else { return nil } @@ -586,7 +603,10 @@ private final class NotificationServiceHandler { let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), resolvedDeviceName: nil) + Logger.shared.log("NotificationService \(episode)", "Begin processing payload \(payload)") + guard var encryptedPayload = payload["p"] as? String else { + Logger.shared.log("NotificationService \(episode)", "Invalid payload 1") return nil } encryptedPayload = encryptedPayload.replacingOccurrences(of: "-", with: "+") @@ -595,6 +615,7 @@ private final class NotificationServiceHandler { encryptedPayload.append("=") } guard let payloadData = Data(base64Encoded: encryptedPayload) else { + Logger.shared.log("NotificationService \(episode)", "Invalid payload 2") return nil } @@ -621,6 +642,8 @@ private final class NotificationServiceHandler { } guard let strongSelf = self, let recordId = recordId else { + Logger.shared.log("NotificationService \(episode)", "Couldn't find a matching decryption key") + let content = NotificationContent() updateCurrentContent(content) completed() @@ -641,6 +664,8 @@ private final class NotificationServiceHandler { return } guard let stateManager = stateManager else { + Logger.shared.log("NotificationService \(episode)", "Didn't receive stateManager") + let content = NotificationContent() updateCurrentContent(content) completed() @@ -658,6 +683,8 @@ private final class NotificationServiceHandler { return } guard let notificationsKey = notificationsKey else { + Logger.shared.log("NotificationService \(episode)", "Didn't receive decryption key") + let content = NotificationContent() updateCurrentContent(content) completed() @@ -665,6 +692,8 @@ private final class NotificationServiceHandler { return } guard let decryptedPayload = decryptedNotificationPayload(key: notificationsKey, data: payloadData) else { + Logger.shared.log("NotificationService \(episode)", "Couldn't decrypt payload") + let content = NotificationContent() updateCurrentContent(content) completed() @@ -672,6 +701,8 @@ private final class NotificationServiceHandler { return } guard let payloadJson = try? JSONSerialization.jsonObject(with: decryptedPayload, options: []) as? [String: Any] else { + Logger.shared.log("NotificationService \(episode)", "Couldn't process payload as JSON") + let content = NotificationContent() updateCurrentContent(content) completed() @@ -679,6 +710,8 @@ private final class NotificationServiceHandler { return } + Logger.shared.log("NotificationService \(episode)", "Decrypted payload: \(payloadJson)") + var peerId: PeerId? var messageId: MessageId.Id? var mediaAttachment: Media? @@ -842,8 +875,10 @@ private final class NotificationServiceHandler { if let action = action { switch action { case .logout: + Logger.shared.log("NotificationService \(episode)", "Will logout") completed() case let .poll(peerId, initialContent): + Logger.shared.log("NotificationService \(episode)", "Will poll") if let stateManager = strongSelf.stateManager { let pollCompletion: (NotificationContent) -> Void = { content in var content = content @@ -912,6 +947,7 @@ private final class NotificationServiceHandler { } } + Logger.shared.log("NotificationService \(episode)", "Will fetch media") let _ = (fetchMediaSignal |> timeout(10.0, queue: queue, alternate: .single(nil)) |> deliverOn(queue)).start(next: { mediaData in @@ -920,6 +956,9 @@ private final class NotificationServiceHandler { return } + Logger.shared.log("NotificationService \(episode)", "Did fetch media \(mediaData == nil ? "Non-empty" : "Empty")") + + Logger.shared.log("NotificationService \(episode)", "Will get unread count") let _ = (getCurrentRenderedTotalUnreadCount( accountManager: strongSelf.accountManager, postbox: stateManager.postbox @@ -934,6 +973,8 @@ private final class NotificationServiceHandler { content.badge = Int(value.0) } + Logger.shared.log("NotificationService \(episode)", "Unread count: \(value.0), isCurrentAccount: \(isCurrentAccount)") + if let image = mediaAttachment as? TelegramMediaImage, let resource = largestImageRepresentation(image.representations)?.resource { if let mediaData = mediaData { stateManager.postbox.mediaBox.storeResourceData(resource.id, data: mediaData, synchronous: true) @@ -988,6 +1029,8 @@ private final class NotificationServiceHandler { } } + Logger.shared.log("NotificationService \(episode)", "Updating content to \(content)") + updateCurrentContent(content) completed() @@ -1000,6 +1043,8 @@ private final class NotificationServiceHandler { stateManager.network.shouldKeepConnection.set(.single(true)) if peerId.namespace == Namespaces.Peer.CloudChannel { + Logger.shared.log("NotificationService \(episode)", "Will poll channel \(peerId)") + pollSignal = standalonePollChannelOnce( postbox: stateManager.postbox, network: stateManager.network, @@ -1007,6 +1052,7 @@ private final class NotificationServiceHandler { stateManager: stateManager ) } else { + Logger.shared.log("NotificationService \(episode)", "Will perform non-specific getDifference") enum ControlError { case restart } @@ -1054,6 +1100,7 @@ private final class NotificationServiceHandler { completed() } case let .deleteMessage(ids): + Logger.shared.log("NotificationService \(episode)", "Will delete messages \(ids)") let mediaBox = stateManager.postbox.mediaBox let _ = (stateManager.postbox.transaction { transaction -> Void in _internal_deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids, deleteMedia: true) @@ -1084,6 +1131,8 @@ private final class NotificationServiceHandler { if isCurrentAccount { content.badge = Int(value.0) } + Logger.shared.log("NotificationService \(episode)", "Unread count: \(value.0), isCurrentAccount: \(isCurrentAccount)") + Logger.shared.log("NotificationService \(episode)", "Updating content to \(content)") updateCurrentContent(content) @@ -1092,6 +1141,7 @@ private final class NotificationServiceHandler { } if !removeIdentifiers.isEmpty { + Logger.shared.log("NotificationService \(episode)", "Will try to remove \(removeIdentifiers.count) notifications") UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: removeIdentifiers) queue.after(1.0, { completeRemoval() @@ -1102,6 +1152,7 @@ private final class NotificationServiceHandler { }) }) case let .readMessage(id): + Logger.shared.log("NotificationService \(episode)", "Will read message \(id)") let _ = (stateManager.postbox.transaction { transaction -> Void in transaction.applyIncomingReadMaxId(id) } @@ -1130,6 +1181,9 @@ private final class NotificationServiceHandler { content.badge = Int(value.0) } + Logger.shared.log("NotificationService \(episode)", "Unread count: \(value.0), isCurrentAccount: \(isCurrentAccount)") + Logger.shared.log("NotificationService \(episode)", "Updating content to \(content)") + updateCurrentContent(content) completed() @@ -1137,6 +1191,7 @@ private final class NotificationServiceHandler { } if !removeIdentifiers.isEmpty { + Logger.shared.log("NotificationService \(episode)", "Will try to remove \(removeIdentifiers.count) notifications") UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: removeIdentifiers) queue.after(1.0, { completeRemoval() diff --git a/submodules/DebugSettingsUI/BUILD b/submodules/DebugSettingsUI/BUILD index f536359fa2..9c920b19db 100644 --- a/submodules/DebugSettingsUI/BUILD +++ b/submodules/DebugSettingsUI/BUILD @@ -22,6 +22,7 @@ swift_library( "//submodules/OverlayStatusController:OverlayStatusController", "//submodules/AccountContext:AccountContext", "//submodules/AppBundle:AppBundle", + "//submodules/GZip:GZip" ], visibility = [ "//visibility:public", diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 72dc352b68..8b043d797e 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -13,6 +13,7 @@ import PresentationDataUtils import OverlayStatusController import AccountContext import AppBundle +import GZip @objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate { public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { @@ -219,42 +220,44 @@ private enum DebugControllerEntry: ItemListNodeEntry { |> deliverOnMainQueue).start(next: { logs in let presentationData = arguments.sharedContext.currentPresentationData.with { $0 } let actionSheet = ActionSheetController(presentationData: presentationData) - + var items: [ActionSheetButtonItem] = [] - + if let context = arguments.context, context.sharedContext.applicationBindings.isMainApp { items.append(ActionSheetButtonItem(title: "Via Telegram", color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() - + let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled])) controller.peerSelected = { [weak controller] peer in let peerId = peer.id - + if let strongController = controller { strongController.dismiss() - + let lineFeed = "\n".data(using: .utf8)! - var logData: Data = Data() + var rawLogData: Data = Data() for (name, path) in logs { - if !logData.isEmpty { - logData.append(lineFeed) - logData.append(lineFeed) + if !rawLogData.isEmpty { + rawLogData.append(lineFeed) + rawLogData.append(lineFeed) } - - logData.append("------ File: \(name) ------\n".data(using: .utf8)!) - + + rawLogData.append("------ File: \(name) ------\n".data(using: .utf8)!) + if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { - logData.append(data) + rawLogData.append(data) } } - + + let gzippedData = TGGZipData(rawLogData, 1.0) + let id = Int64.random(in: Int64.min ... Int64.max) - let fileResource = LocalFileMediaResource(fileId: id, size: logData.count, isSecretRelated: false) - context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: logData) - - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: logData.count, attributes: [.FileName(fileName: "Log-iOS-Full.txt")]) + let fileResource = LocalFileMediaResource(fileId: id, size: gzippedData.count, isSecretRelated: false) + context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: gzippedData) + + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: gzippedData.count, attributes: [.FileName(fileName: "Log-iOS-Full.txt.gz")]) let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil, correlationId: nil) - + let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() } } @@ -263,7 +266,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { } items.append(ActionSheetButtonItem(title: "Via Email", color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() - + let composeController = MFMailComposeViewController() composeController.mailComposeDelegate = arguments.mailComposeDelegate composeController.setSubject("Telegram Logs") @@ -274,12 +277,12 @@ private enum DebugControllerEntry: ItemListNodeEntry { } arguments.getRootController()?.present(composeController, animated: true, completion: nil) })) - + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in actionSheet?.dismissAnimated() }) - ])]) + ])]) arguments.presentController(actionSheet, nil) }) }) @@ -371,42 +374,44 @@ private enum DebugControllerEntry: ItemListNodeEntry { |> deliverOnMainQueue).start(next: { logs in let presentationData = arguments.sharedContext.currentPresentationData.with { $0 } let actionSheet = ActionSheetController(presentationData: presentationData) - + var items: [ActionSheetButtonItem] = [] - + if let context = arguments.context, context.sharedContext.applicationBindings.isMainApp { items.append(ActionSheetButtonItem(title: "Via Telegram", color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() - + let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled])) controller.peerSelected = { [weak controller] peer in let peerId = peer.id - + if let strongController = controller { strongController.dismiss() - + let lineFeed = "\n".data(using: .utf8)! - var logData: Data = Data() + var rawLogData: Data = Data() for (name, path) in logs { - if !logData.isEmpty { - logData.append(lineFeed) - logData.append(lineFeed) + if !rawLogData.isEmpty { + rawLogData.append(lineFeed) + rawLogData.append(lineFeed) } - - logData.append("------ File: \(name) ------\n".data(using: .utf8)!) - + + rawLogData.append("------ File: \(name) ------\n".data(using: .utf8)!) + if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { - logData.append(data) + rawLogData.append(data) } } - + + let gzippedData = TGGZipData(rawLogData, 1.0) + let id = Int64.random(in: Int64.min ... Int64.max) - let fileResource = LocalFileMediaResource(fileId: id, size: logData.count, isSecretRelated: false) - context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: logData) - - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: logData.count, attributes: [.FileName(fileName: "Log-iOS-Full.txt")]) + let fileResource = LocalFileMediaResource(fileId: id, size: gzippedData.count, isSecretRelated: false) + context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: gzippedData) + + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: gzippedData.count, attributes: [.FileName(fileName: "Log-iOS-Full.txt.gz")]) let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil, correlationId: nil) - + let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() } } @@ -415,7 +420,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { } items.append(ActionSheetButtonItem(title: "Via Email", color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() - + let composeController = MFMailComposeViewController() composeController.mailComposeDelegate = arguments.mailComposeDelegate composeController.setSubject("Telegram Logs") @@ -426,39 +431,86 @@ private enum DebugControllerEntry: ItemListNodeEntry { } arguments.getRootController()?.present(composeController, animated: true, completion: nil) })) - + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in actionSheet?.dismissAnimated() }) - ])]) + ])]) arguments.presentController(actionSheet, nil) }) }) case .sendNotificationLogs: - return ItemListDisclosureItem(presentationData: presentationData, title: "Send Notification Logs", label: "", sectionId: self.section, style: .blocks, action: { - let _ = (Logger(rootPath: arguments.sharedContext.basePath, basePath: arguments.sharedContext.basePath + "/notificationServiceLogs").collectLogs() + return ItemListDisclosureItem(presentationData: presentationData, title: "Send Notification Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: { + let _ = (Logger(rootPath: arguments.sharedContext.basePath, basePath: arguments.sharedContext.basePath + "/notification-logs").collectLogs() |> deliverOnMainQueue).start(next: { logs in - guard let context = arguments.context else { - return - } - let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled])) - controller.peerSelected = { [weak controller] peer in - let peerId = peer.id - - if let strongController = controller { - strongController.dismiss() - - let messages = logs.map { (name, path) -> EnqueueMessage in + let presentationData = arguments.sharedContext.currentPresentationData.with { $0 } + let actionSheet = ActionSheetController(presentationData: presentationData) + + var items: [ActionSheetButtonItem] = [] + + if let context = arguments.context, context.sharedContext.applicationBindings.isMainApp { + items.append(ActionSheetButtonItem(title: "Via Telegram", color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + + let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled])) + controller.peerSelected = { [weak controller] peer in + let peerId = peer.id + + if let strongController = controller { + strongController.dismiss() + + let lineFeed = "\n".data(using: .utf8)! + var rawLogData: Data = Data() + for (name, path) in logs { + if !rawLogData.isEmpty { + rawLogData.append(lineFeed) + rawLogData.append(lineFeed) + } + + rawLogData.append("------ File: \(name) ------\n".data(using: .utf8)!) + + if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { + rawLogData.append(data) + } + } + + let gzippedData = TGGZipData(rawLogData, 1.0) + let id = Int64.random(in: Int64.min ... Int64.max) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)]) - return .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil, correlationId: nil) + let fileResource = LocalFileMediaResource(fileId: id, size: gzippedData.count, isSecretRelated: false) + context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: gzippedData) + + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: gzippedData.count, attributes: [.FileName(fileName: "Log-iOS-Full.txt.gz")]) + let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil, correlationId: nil) + + let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() } - let _ = enqueueMessages(account: context.account, peerId: peerId, messages: messages).start() + } + arguments.pushController(controller) + })) + } + items.append(ActionSheetButtonItem(title: "Via Email", color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + + let composeController = MFMailComposeViewController() + composeController.mailComposeDelegate = arguments.mailComposeDelegate + composeController.setSubject("Telegram Logs") + for (name, path) in logs { + if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) { + composeController.addAttachmentData(data, mimeType: "application/text", fileName: name) } } - arguments.pushController(controller) - }) + arguments.getRootController()?.present(composeController, animated: true, completion: nil) + })) + + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + arguments.presentController(actionSheet, nil) + }) }) case .sendCriticalLogs: return ItemListDisclosureItem(presentationData: presentationData, title: "Send Critical Logs", label: "", sectionId: self.section, style: .blocks, action: { @@ -837,7 +889,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present // entries.append(.testStickerImport(presentationData.theme)) entries.append(.sendLogs(presentationData.theme)) - entries.append(.sendOneLog(presentationData.theme)) + //entries.append(.sendOneLog(presentationData.theme)) entries.append(.sendShareLogs) entries.append(.sendNotificationLogs(presentationData.theme)) entries.append(.sendCriticalLogs(presentationData.theme)) diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index c41a279029..2e12e5845f 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -2064,6 +2064,18 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } @objc func pictureInPictureButtonPressed() { + var isNativePictureInPictureSupported = false + switch self.item?.contentInfo { + case let .message(message): + for media in message.media { + if let media = media as? TelegramMediaFile, media.isVideo { + isNativePictureInPictureSupported = true + } + } + default: + break + } + if let item = self.item, let videoNode = self.videoNode, let overlayController = self.context.sharedContext.mediaManager.overlayMediaManager.controller { videoNode.setContinuePlayingWithoutSoundOnLostAudioSession(false) @@ -2071,7 +2083,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { let baseNavigationController = self.baseNavigationController() let playbackRate = self.playbackRate - if #available(iOSApplicationExtension 15.0, iOS 15.0, *), AVPictureInPictureController.isPictureInPictureSupported() { + if #available(iOSApplicationExtension 15.0, iOS 15.0, *), AVPictureInPictureController.isPictureInPictureSupported(), isNativePictureInPictureSupported { self.disablePictureInPicturePlaceholder = true let overlayVideoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: self.context.sharedContext.mediaManager.audioSession, manager: self.context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .overlay) diff --git a/submodules/Postbox/Sources/AdditionalChatListItemsView.swift b/submodules/Postbox/Sources/AdditionalChatListItemsView.swift index 4c54a00236..6bf80cc6c9 100644 --- a/submodules/Postbox/Sources/AdditionalChatListItemsView.swift +++ b/submodules/Postbox/Sources/AdditionalChatListItemsView.swift @@ -14,6 +14,10 @@ final class MutableAdditionalChatListItemsView: MutablePostboxView { } return false } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return AdditionalChatListItemsView(self) diff --git a/submodules/Postbox/Sources/AllChatListHolesView.swift b/submodules/Postbox/Sources/AllChatListHolesView.swift index 4e582e9ed6..3cadfe60a7 100644 --- a/submodules/Postbox/Sources/AllChatListHolesView.swift +++ b/submodules/Postbox/Sources/AllChatListHolesView.swift @@ -48,6 +48,10 @@ final class MutableAllChatListHolesView: MutablePostboxView { return false } } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return AllChatListHolesView(self) diff --git a/submodules/Postbox/Sources/CachedItemView.swift b/submodules/Postbox/Sources/CachedItemView.swift index cbaefed63d..6da64b46ed 100644 --- a/submodules/Postbox/Sources/CachedItemView.swift +++ b/submodules/Postbox/Sources/CachedItemView.swift @@ -16,6 +16,10 @@ final class MutableCachedItemView: MutablePostboxView { } return false } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return CachedItemView(self) diff --git a/submodules/Postbox/Sources/CachedPeerDataView.swift b/submodules/Postbox/Sources/CachedPeerDataView.swift index c3cb02ac7b..9e432d5f25 100644 --- a/submodules/Postbox/Sources/CachedPeerDataView.swift +++ b/submodules/Postbox/Sources/CachedPeerDataView.swift @@ -17,6 +17,10 @@ final class MutableCachedPeerDataView: MutablePostboxView { return false } } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return CachedPeerDataView(self) diff --git a/submodules/Postbox/Sources/ContactPeersView.swift b/submodules/Postbox/Sources/ContactPeersView.swift index 341646e179..b48fe93722 100644 --- a/submodules/Postbox/Sources/ContactPeersView.swift +++ b/submodules/Postbox/Sources/ContactPeersView.swift @@ -70,6 +70,10 @@ final class MutableContactPeersView: MutablePostboxView { return updated } + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } + func immutableView() -> PostboxView { return ContactPeersView(self) } diff --git a/submodules/Postbox/Sources/DeletedMessagesView.swift b/submodules/Postbox/Sources/DeletedMessagesView.swift index 95c8e2d528..7f3b4edaaf 100644 --- a/submodules/Postbox/Sources/DeletedMessagesView.swift +++ b/submodules/Postbox/Sources/DeletedMessagesView.swift @@ -33,6 +33,10 @@ final class MutableDeletedMessagesView: MutablePostboxView { return updated } + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } + func immutableView() -> PostboxView { return DeletedMessagesView(self) } diff --git a/submodules/Postbox/Sources/GlobalMessageTagsView.swift b/submodules/Postbox/Sources/GlobalMessageTagsView.swift index 941f6c8096..6733a52339 100644 --- a/submodules/Postbox/Sources/GlobalMessageTagsView.swift +++ b/submodules/Postbox/Sources/GlobalMessageTagsView.swift @@ -202,6 +202,25 @@ final class MutableGlobalMessageTagsView: MutablePostboxView { return hasChanges } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + let (entries, lower, upper) = postbox.messageHistoryTable.entriesAround(globalTagMask: globalTag, index: position, count: count) + + self.entries = entries.map { entry -> InternalGlobalMessageTagsEntry in + switch entry { + case let .message(message): + return .intermediateMessage(message) + case let .hole(index): + return .hole(index) + } + } + self.earlier = lower + self.later = upper + + self.render(postbox: postbox) + + return true + } private func add(_ entry: InternalGlobalMessageTagsEntry) -> Bool { if self.entries.count == 0 { diff --git a/submodules/Postbox/Sources/HistoryTagInfoView.swift b/submodules/Postbox/Sources/HistoryTagInfoView.swift index d3bd9de9a6..7892353518 100644 --- a/submodules/Postbox/Sources/HistoryTagInfoView.swift +++ b/submodules/Postbox/Sources/HistoryTagInfoView.swift @@ -61,6 +61,22 @@ final class MutableHistoryTagInfoView: MutablePostboxView { return false } } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + var currentIndex: MessageIndex? + for namespace in postbox.messageHistoryIndexTable.existingNamespaces(peerId: self.peerId) { + if let index = postbox.messageHistoryTagsTable.latestIndex(tag: self.tag, peerId: self.peerId, namespace: namespace) { + currentIndex = index + break + } + } + if self.currentIndex != currentIndex { + self.currentIndex = currentIndex + return true + } else { + return false + } + } func immutableView() -> PostboxView { return HistoryTagInfoView(self) diff --git a/submodules/Postbox/Sources/InvalidatedMessageHistoryTagSummariesView.swift b/submodules/Postbox/Sources/InvalidatedMessageHistoryTagSummariesView.swift index 7e082850ff..13224a2502 100644 --- a/submodules/Postbox/Sources/InvalidatedMessageHistoryTagSummariesView.swift +++ b/submodules/Postbox/Sources/InvalidatedMessageHistoryTagSummariesView.swift @@ -37,6 +37,19 @@ final class MutableInvalidatedMessageHistoryTagSummariesView: MutablePostboxView } return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + var entries = Set() + for entry in postbox.invalidatedMessageHistoryTagsSummaryTable.get(tagMask: tagMask, namespace: namespace) { + entries.insert(entry) + } + if self.entries != entries { + self.entries = entries + return true + } else { + return false + } + } func immutableView() -> PostboxView { return InvalidatedMessageHistoryTagSummariesView(self) diff --git a/submodules/Postbox/Sources/ItemCollectionIdsView.swift b/submodules/Postbox/Sources/ItemCollectionIdsView.swift index fed265431f..232419de61 100644 --- a/submodules/Postbox/Sources/ItemCollectionIdsView.swift +++ b/submodules/Postbox/Sources/ItemCollectionIdsView.swift @@ -40,6 +40,20 @@ final class MutableItemCollectionIdsView: MutablePostboxView { } return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + var idsByNamespace: [ItemCollectionId.Namespace: Set] = [:] + for namespace in namespaces { + let ids = postbox.itemCollectionInfoTable.getIds(namespace: namespace) + idsByNamespace[namespace] = Set(ids) + } + if self.idsByNamespace != idsByNamespace { + self.idsByNamespace = idsByNamespace + return true + } else { + return false + } + } func immutableView() -> PostboxView { return ItemCollectionIdsView(self) diff --git a/submodules/Postbox/Sources/ItemCollectionInfoView.swift b/submodules/Postbox/Sources/ItemCollectionInfoView.swift index 7ce6008419..6eb601595f 100644 --- a/submodules/Postbox/Sources/ItemCollectionInfoView.swift +++ b/submodules/Postbox/Sources/ItemCollectionInfoView.swift @@ -48,6 +48,10 @@ final class MutableItemCollectionInfoView: MutablePostboxView { } return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return ItemCollectionInfoView(self) diff --git a/submodules/Postbox/Sources/ItemCollectionInfosView.swift b/submodules/Postbox/Sources/ItemCollectionInfosView.swift index d82b6af593..c39cbe7164 100644 --- a/submodules/Postbox/Sources/ItemCollectionInfosView.swift +++ b/submodules/Postbox/Sources/ItemCollectionInfosView.swift @@ -90,12 +90,14 @@ final class MutableItemCollectionInfosView: MutablePostboxView { } return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return ItemCollectionInfosView(self) } - - } public final class ItemCollectionInfosView: PostboxView { diff --git a/submodules/Postbox/Sources/LocalMessageTagsView.swift b/submodules/Postbox/Sources/LocalMessageTagsView.swift index 356f6c8efb..10ec9cc8fa 100644 --- a/submodules/Postbox/Sources/LocalMessageTagsView.swift +++ b/submodules/Postbox/Sources/LocalMessageTagsView.swift @@ -50,6 +50,10 @@ final class MutableLocalMessageTagsView: MutablePostboxView { } return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return LocalMessageTagsView(self) diff --git a/submodules/Postbox/Sources/MessageHistoryTagSummaryView.swift b/submodules/Postbox/Sources/MessageHistoryTagSummaryView.swift index b56759162c..4e62e434cf 100644 --- a/submodules/Postbox/Sources/MessageHistoryTagSummaryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryTagSummaryView.swift @@ -25,6 +25,16 @@ final class MutableMessageHistoryTagSummaryView: MutablePostboxView { return hasChanges } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + let count = postbox.messageHistoryTagsSummaryTable.get(MessageHistoryTagsSummaryKey(tag: self.tag, peerId: self.peerId, namespace: self.namespace))?.count + if self.count != count { + self.count = count + return true + } else { + return false + } + } func immutableView() -> PostboxView { return MessageHistoryTagSummaryView(self) diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index 7543827b81..280bdb4bf0 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -739,7 +739,7 @@ final class MutableMessageHistoryView { } case let .peerChatState(peerId, _): if transaction.currentUpdatedPeerChatStates.contains(peerId) { - updated[i] = .peerChatState(peerId, postbox.peerChatStateTable.get(peerId) as? PeerChatState) + updated[i] = .peerChatState(peerId, postbox.peerChatStateTable.get(peerId)?.getLegacy() as? PeerChatState) hasChanges = true } case .totalUnreadState: diff --git a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift index ea2e2dbb41..033d9fec90 100644 --- a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift +++ b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift @@ -171,6 +171,10 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { return false } } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return MessageOfInterestHolesView(self) diff --git a/submodules/Postbox/Sources/MessagesView.swift b/submodules/Postbox/Sources/MessagesView.swift index ae4a1b4d94..91808bb160 100644 --- a/submodules/Postbox/Sources/MessagesView.swift +++ b/submodules/Postbox/Sources/MessagesView.swift @@ -51,6 +51,10 @@ final class MutableMessagesView: MutablePostboxView { return false } } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return MessagesView(self) diff --git a/submodules/Postbox/Sources/MutableBasicPeerView.swift b/submodules/Postbox/Sources/MutableBasicPeerView.swift index a2f30f8f6b..0236960e1d 100644 --- a/submodules/Postbox/Sources/MutableBasicPeerView.swift +++ b/submodules/Postbox/Sources/MutableBasicPeerView.swift @@ -42,6 +42,10 @@ final class MutableBasicPeerView: MutablePostboxView { return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return BasicPeerView(self) diff --git a/submodules/Postbox/Sources/MutablePeerChatInclusionView.swift b/submodules/Postbox/Sources/MutablePeerChatInclusionView.swift index 9b7d5d1952..5c5daec04e 100644 --- a/submodules/Postbox/Sources/MutablePeerChatInclusionView.swift +++ b/submodules/Postbox/Sources/MutablePeerChatInclusionView.swift @@ -22,6 +22,10 @@ final class MutablePeerChatInclusionView: MutablePostboxView { return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return PeerChatInclusionView(self) diff --git a/submodules/Postbox/Sources/OrderedItemListView.swift b/submodules/Postbox/Sources/OrderedItemListView.swift index bdd066a8fc..d8bf403a00 100644 --- a/submodules/Postbox/Sources/OrderedItemListView.swift +++ b/submodules/Postbox/Sources/OrderedItemListView.swift @@ -51,6 +51,10 @@ final class MutableOrderedItemListView: MutablePostboxView { return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return OrderedItemListView(self) diff --git a/submodules/Postbox/Sources/PeerChatStateTable.swift b/submodules/Postbox/Sources/PeerChatStateTable.swift index b0f059fc17..716d025d4b 100644 --- a/submodules/Postbox/Sources/PeerChatStateTable.swift +++ b/submodules/Postbox/Sources/PeerChatStateTable.swift @@ -5,7 +5,7 @@ final class PeerChatStateTable: Table { return ValueBoxTable(id: id, keyType: .int64, compactValuesOnCreation: false) } - private var cachedPeerChatStates: [PeerId: PostboxCoding?] = [:] + private var cachedPeerChatStates: [PeerId: CodableEntry?] = [:] private var updatedPeerIds = Set() private let sharedKey = ValueBoxKey(length: 8) @@ -15,11 +15,12 @@ final class PeerChatStateTable: Table { return self.sharedKey } - func get(_ id: PeerId) -> PostboxCoding? { + func get(_ id: PeerId) -> CodableEntry? { if let state = self.cachedPeerChatStates[id] { return state } else { - if let value = self.valueBox.get(self.table, key: self.key(id)), let state = PostboxDecoder(buffer: value).decodeRootObject() { + if let value = self.valueBox.get(self.table, key: self.key(id)) { + let state = CodableEntry(data: value.makeData()) self.cachedPeerChatStates[id] = state return state } else { @@ -29,7 +30,7 @@ final class PeerChatStateTable: Table { } } - func set(_ id: PeerId, state: PostboxCoding?) { + func set(_ id: PeerId, state: CodableEntry?) { self.cachedPeerChatStates[id] = state self.updatedPeerIds.insert(id) } @@ -41,12 +42,9 @@ final class PeerChatStateTable: Table { override func beforeCommit() { if !self.updatedPeerIds.isEmpty { - let sharedEncoder = PostboxEncoder() for id in self.updatedPeerIds { if let wrappedState = self.cachedPeerChatStates[id], let state = wrappedState { - sharedEncoder.reset() - sharedEncoder.encodeRootObject(state) - self.valueBox.set(self.table, key: self.key(id), value: sharedEncoder.readBufferNoCopy()) + self.valueBox.set(self.table, key: self.key(id), value: ReadBuffer(data: state.data)) } else { self.valueBox.remove(self.table, key: self.key(id), secure: false) } diff --git a/submodules/Postbox/Sources/PeerChatStateView.swift b/submodules/Postbox/Sources/PeerChatStateView.swift index 3c0edec8d6..df81cba5b3 100644 --- a/submodules/Postbox/Sources/PeerChatStateView.swift +++ b/submodules/Postbox/Sources/PeerChatStateView.swift @@ -2,7 +2,7 @@ import Foundation final class MutablePeerChatStateView: MutablePostboxView { let peerId: PeerId - var chatState: PostboxCoding? + var chatState: CodableEntry? init(postbox: PostboxImpl, peerId: PeerId) { self.peerId = peerId @@ -17,6 +17,16 @@ final class MutablePeerChatStateView: MutablePostboxView { return false } } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + let chatState = postbox.peerChatStateTable.get(self.peerId) + if self.chatState != chatState { + self.chatState = chatState + return true + } else { + return false + } + } func immutableView() -> PostboxView { return PeerChatStateView(self) @@ -25,7 +35,7 @@ final class MutablePeerChatStateView: MutablePostboxView { public final class PeerChatStateView: PostboxView { public let peerId: PeerId - public let chatState: PostboxCoding? + public let chatState: CodableEntry? init(_ view: MutablePeerChatStateView) { self.peerId = view.peerId diff --git a/submodules/Postbox/Sources/PeerNotificationSettingsBehaviorTimestampView.swift b/submodules/Postbox/Sources/PeerNotificationSettingsBehaviorTimestampView.swift index dd6448f5b8..a00d2b33b6 100644 --- a/submodules/Postbox/Sources/PeerNotificationSettingsBehaviorTimestampView.swift +++ b/submodules/Postbox/Sources/PeerNotificationSettingsBehaviorTimestampView.swift @@ -19,6 +19,16 @@ final class MutablePeerNotificationSettingsBehaviorTimestampView: MutablePostbox return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + let earliestTimestamp = postbox.peerNotificationSettingsBehaviorTable.getEarliest()?.1 + if self.earliestTimestamp != earliestTimestamp { + self.earliestTimestamp = earliestTimestamp + return true + } else { + return false + } + } func immutableView() -> PostboxView { return PeerNotificationSettingsBehaviorTimestampView(self) diff --git a/submodules/Postbox/Sources/PeerNotificationSettingsView.swift b/submodules/Postbox/Sources/PeerNotificationSettingsView.swift index a1521e17a0..2d85dcf466 100644 --- a/submodules/Postbox/Sources/PeerNotificationSettingsView.swift +++ b/submodules/Postbox/Sources/PeerNotificationSettingsView.swift @@ -36,6 +36,43 @@ final class MutablePeerNotificationSettingsView: MutablePostboxView { return false } } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + var notificationSettings: [PeerId: PeerNotificationSettings] = [:] + for peerId in self.peerIds { + var notificationPeerId = peerId + if let peer = postbox.peerTable.get(peerId), let associatedPeerId = peer.associatedPeerId { + notificationPeerId = associatedPeerId + } + if let settings = postbox.peerNotificationSettingsTable.getEffective(notificationPeerId) { + notificationSettings[peerId] = settings + } + } + + var updated = false + if self.notificationSettings.count != notificationSettings.count { + updated = true + } else { + for (key, value) in self.notificationSettings { + if let other = notificationSettings[key] { + if !other.isEqual(to: value) { + updated = true + break + } + } else { + updated = true + break + } + } + } + + if updated { + self.notificationSettings = notificationSettings + return true + } else { + return false + } + } func immutableView() -> PostboxView { return PeerNotificationSettingsView(self) diff --git a/submodules/Postbox/Sources/PeerPresencesView.swift b/submodules/Postbox/Sources/PeerPresencesView.swift index 4f280d944b..51f4a32c03 100644 --- a/submodules/Postbox/Sources/PeerPresencesView.swift +++ b/submodules/Postbox/Sources/PeerPresencesView.swift @@ -26,6 +26,40 @@ final class MutablePeerPresencesView: MutablePostboxView { return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + var presences: [PeerId: PeerPresence] = [:] + + for id in self.ids { + if let presence = postbox.peerPresenceTable.get(id) { + presences[id] = presence + } + } + + var updated = false + if self.presences.count != presences.count { + updated = true + } else { + for (key, value) in self.presences { + if let other = presences[key] { + if !other.isEqual(to: value) { + updated = true + break + } + } else { + updated = true + break + } + } + } + + if updated { + self.presences = presences + return true + } else { + return false + } + } func immutableView() -> PostboxView { return PeerPresencesView(self) diff --git a/submodules/Postbox/Sources/PeerView.swift b/submodules/Postbox/Sources/PeerView.swift index 9fb5046de6..6db9f56c81 100644 --- a/submodules/Postbox/Sources/PeerView.swift +++ b/submodules/Postbox/Sources/PeerView.swift @@ -253,6 +253,10 @@ final class MutablePeerView: MutablePostboxView { return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return PeerView(self) diff --git a/submodules/Postbox/Sources/PendingMessageActionsSummaryView.swift b/submodules/Postbox/Sources/PendingMessageActionsSummaryView.swift index dbfa7c7e56..79161a526d 100644 --- a/submodules/Postbox/Sources/PendingMessageActionsSummaryView.swift +++ b/submodules/Postbox/Sources/PendingMessageActionsSummaryView.swift @@ -16,6 +16,10 @@ final class MutablePendingMessageActionsSummaryView: MutablePostboxView { } return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return PendingMessageActionsSummaryView(self) diff --git a/submodules/Postbox/Sources/PendingMessageActionsView.swift b/submodules/Postbox/Sources/PendingMessageActionsView.swift index 87662edaef..1b24f63f63 100644 --- a/submodules/Postbox/Sources/PendingMessageActionsView.swift +++ b/submodules/Postbox/Sources/PendingMessageActionsView.swift @@ -38,6 +38,10 @@ final class MutablePendingMessageActionsView: MutablePostboxView { } return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return PendingMessageActionsView(self) diff --git a/submodules/Postbox/Sources/PendingPeerNotificationSettingsView.swift b/submodules/Postbox/Sources/PendingPeerNotificationSettingsView.swift index dfc7e22d22..d7da2420e9 100644 --- a/submodules/Postbox/Sources/PendingPeerNotificationSettingsView.swift +++ b/submodules/Postbox/Sources/PendingPeerNotificationSettingsView.swift @@ -25,6 +25,10 @@ final class MutablePendingPeerNotificationSettingsView: MutablePostboxView { } return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } func immutableView() -> PostboxView { return PendingPeerNotificationSettingsView(self) diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index ea1b24e9da..321edd1ef9 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -2212,7 +2212,7 @@ final class PostboxImpl { } fileprivate func setPeerChatState(_ id: PeerId, state: PeerChatState) { - self.peerChatStateTable.set(id, state: state) + self.peerChatStateTable.set(id, state: CodableEntry(legacyValue: state)) self.currentUpdatedPeerChatStates.insert(id) } diff --git a/submodules/Postbox/Sources/PostboxView.swift b/submodules/Postbox/Sources/PostboxView.swift index effaf9752c..710fb0b21a 100644 --- a/submodules/Postbox/Sources/PostboxView.swift +++ b/submodules/Postbox/Sources/PostboxView.swift @@ -5,6 +5,7 @@ public protocol PostboxView { protocol MutablePostboxView { func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool func immutableView() -> PostboxView } @@ -24,6 +25,16 @@ final class CombinedMutableView { } return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + var updated = false + for (_, view) in self.views { + if view.refreshDueToExternalTransaction(postbox: postbox) { + updated = true + } + } + return updated + } func immutableView() -> CombinedView { var result: [PostboxViewKey: PostboxView] = [:] diff --git a/submodules/Postbox/Sources/PreferencesEntry.swift b/submodules/Postbox/Sources/PreferencesEntry.swift index a38f5cae4b..125a6ed5ae 100644 --- a/submodules/Postbox/Sources/PreferencesEntry.swift +++ b/submodules/Postbox/Sources/PreferencesEntry.swift @@ -13,11 +13,37 @@ public final class CodableEntry: Equatable { self.data = encoder.makeData() } + public init(legacyValue: PostboxCoding) { + let encoder = PostboxEncoder() + encoder.encodeRootObject(legacyValue) + self.data = encoder.makeData() + } + public func get(_ type: T.Type) -> T? { let decoder = PostboxDecoder(buffer: MemoryBuffer(data: self.data)) return decoder.decode(T.self, forKey: "_") } + public func getLegacy(_ type: T.Type) -> T? { + let decoder = PostboxDecoder(buffer: MemoryBuffer(data: self.data)) + let object = decoder.decodeRootObject() + if let object = object as? T { + return object + } else { + return nil + } + } + + public func getLegacy() -> PostboxCoding? { + let decoder = PostboxDecoder(buffer: MemoryBuffer(data: self.data)) + let object = decoder.decodeRootObject() + if let object = object { + return object + } else { + return nil + } + } + public static func ==(lhs: CodableEntry, rhs: CodableEntry) -> Bool { return lhs.data == rhs.data } diff --git a/submodules/Postbox/Sources/PreferencesView.swift b/submodules/Postbox/Sources/PreferencesView.swift index 1fa4b3f348..faec5ead7c 100644 --- a/submodules/Postbox/Sources/PreferencesView.swift +++ b/submodules/Postbox/Sources/PreferencesView.swift @@ -44,6 +44,21 @@ final class MutablePreferencesView: MutablePostboxView { return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + var values: [ValueBoxKey: PreferencesEntry] = [:] + for key in self.keys { + if let value = postbox.preferencesTable.get(key: key) { + values[key] = value + } + } + if self.values != values { + self.values = values + return true + } else { + return false + } + } func immutableView() -> PostboxView { return PreferencesView(self) diff --git a/submodules/Postbox/Sources/SynchronizeGroupMessageStatsView.swift b/submodules/Postbox/Sources/SynchronizeGroupMessageStatsView.swift index a2865e2b50..af39f00534 100644 --- a/submodules/Postbox/Sources/SynchronizeGroupMessageStatsView.swift +++ b/submodules/Postbox/Sources/SynchronizeGroupMessageStatsView.swift @@ -26,6 +26,16 @@ final class MutableSynchronizeGroupMessageStatsView: MutablePostboxView { } return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + let groupsAndNamespaces = postbox.synchronizeGroupMessageStatsTable.get() + if self.groupsAndNamespaces != groupsAndNamespaces { + self.groupsAndNamespaces = groupsAndNamespaces + return true + } else { + return false + } + } func immutableView() -> PostboxView { return SynchronizeGroupMessageStatsView(self) diff --git a/submodules/Postbox/Sources/TopChatMessageView.swift b/submodules/Postbox/Sources/TopChatMessageView.swift index 9f276873f9..d348c6dfb1 100644 --- a/submodules/Postbox/Sources/TopChatMessageView.swift +++ b/submodules/Postbox/Sources/TopChatMessageView.swift @@ -29,6 +29,41 @@ final class MutableTopChatMessageView: MutablePostboxView { return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + var messages: [PeerId: Message] = [:] + + for peerId in self.peerIds { + if let index = postbox.chatListIndexTable.get(peerId: peerId).topMessageIndex { + messages[peerId] = postbox.getMessage(index.id) + } + } + + var updated = false + + if self.messages.count != messages.count { + updated = true + } else { + for (key, value) in self.messages { + if let other = messages[key] { + if other.stableId != value.stableId || other.stableVersion != value.stableVersion { + updated = true + break + } + } else { + updated = true + break + } + } + } + + if updated { + self.messages = messages + return true + } else { + return false + } + } func immutableView() -> PostboxView { return TopChatMessageView(self) diff --git a/submodules/Postbox/Sources/UnreadMessageCountsView.swift b/submodules/Postbox/Sources/UnreadMessageCountsView.swift index 8fe50bc914..8e2d19a12b 100644 --- a/submodules/Postbox/Sources/UnreadMessageCountsView.swift +++ b/submodules/Postbox/Sources/UnreadMessageCountsView.swift @@ -6,10 +6,42 @@ public enum UnreadMessageCountsItem: Equatable { case peer(PeerId) } -private enum MutableUnreadMessageCountsItemEntry { +private enum MutableUnreadMessageCountsItemEntry: Equatable { case total((ValueBoxKey, PreferencesEntry?)?, ChatListTotalUnreadState) case totalInGroup(PeerGroupId, ChatListTotalUnreadState) case peer(PeerId, CombinedPeerReadState?) + + static func ==(lhs: MutableUnreadMessageCountsItemEntry, rhs: MutableUnreadMessageCountsItemEntry) -> Bool { + switch lhs { + case let .total(lhsKeyAndEntry, lhsUnreadState): + if case let .total(rhsKeyAndEntry, rhsUnreadState) = rhs { + if lhsKeyAndEntry?.0 != rhsKeyAndEntry?.0 { + return false + } + if lhsKeyAndEntry?.1 != rhsKeyAndEntry?.1 { + return false + } + if lhsUnreadState != rhsUnreadState { + return false + } + return true + } else { + return false + } + case let .totalInGroup(groupId, state): + if case .totalInGroup(groupId, state) = rhs { + return true + } else { + return false + } + case let .peer(peerId, readState): + if case .peer(peerId, readState) = rhs { + return true + } else { + return false + } + } + } } public enum UnreadMessageCountsItemEntry { @@ -19,9 +51,12 @@ public enum UnreadMessageCountsItemEntry { } final class MutableUnreadMessageCountsView: MutablePostboxView { + private let items: [UnreadMessageCountsItem] fileprivate var entries: [MutableUnreadMessageCountsItemEntry] init(postbox: PostboxImpl, items: [UnreadMessageCountsItem]) { + self.items = items + self.entries = items.map { item in switch item { case let .total(preferencesKey): @@ -80,6 +115,25 @@ final class MutableUnreadMessageCountsView: MutablePostboxView { return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + let entries: [MutableUnreadMessageCountsItemEntry] = self.items.map { item -> MutableUnreadMessageCountsItemEntry in + switch item { + case let .total(preferencesKey): + return .total(preferencesKey.flatMap({ ($0, postbox.preferencesTable.get(key: $0)) }), postbox.messageHistoryMetadataTable.getTotalUnreadState(groupId: .root)) + case let .totalInGroup(groupId): + return .totalInGroup(groupId, postbox.messageHistoryMetadataTable.getTotalUnreadState(groupId: groupId)) + case let .peer(peerId): + return .peer(peerId, postbox.readStateTable.getCombinedState(peerId)) + } + } + if self.entries != entries { + self.entries = entries + return true + } else { + return false + } + } func immutableView() -> PostboxView { return UnreadMessageCountsView(self) @@ -151,6 +205,16 @@ final class MutableCombinedReadStateView: MutablePostboxView { return updated } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + let state = postbox.readStateTable.getCombinedState(self.peerId) + if state != self.state { + self.state = state + return true + } else { + return false + } + } func immutableView() -> PostboxView { return CombinedReadStateView(self) diff --git a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift index 11f8e2384c..3c0b2ef2c5 100644 --- a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift +++ b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift @@ -62,7 +62,7 @@ private final class BubbleSettingsControllerNode: ASDisplayNode, UIScrollViewDel self.scrollNode = ASScrollNode() - self.chatBackgroundNode = WallpaperBackgroundNode(context: context) + self.chatBackgroundNode = createWallpaperBackgroundNode(context: context) self.chatBackgroundNode.displaysAsynchronously = false self.messagesContainerNode = ASDisplayNode() diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift index f9acade286..6db765af58 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift @@ -132,7 +132,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { return { item, params, neighbors in if currentBackgroundNode == nil { - currentBackgroundNode = WallpaperBackgroundNode(context: item.context) + currentBackgroundNode = createWallpaperBackgroundNode(context: item.context) } currentBackgroundNode?.update(wallpaper: item.wallpaper) currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners) diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index a60c77180c..ea3fb677f8 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -74,7 +74,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView self.pageControlNode = PageControlNode(dotSpacing: 7.0, dotColor: .white, inactiveDotColor: UIColor.white.withAlphaComponent(0.4)) self.chatListBackgroundNode = ASDisplayNode() - self.chatBackgroundNode = WallpaperBackgroundNode(context: context) + self.chatBackgroundNode = createWallpaperBackgroundNode(context: context) self.chatBackgroundNode.displaysAsynchronously = false self.messagesContainerNode = ASDisplayNode() diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 85ef7f6e27..08afc2236b 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -285,7 +285,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate self.backgroundContainerNode = ASDisplayNode() self.backgroundContainerNode.clipsToBounds = true self.backgroundWrapperNode = ASDisplayNode() - self.backgroundNode = WallpaperBackgroundNode(context: context) + self.backgroundNode = createWallpaperBackgroundNode(context: context) self.messagesContainerNode = ASDisplayNode() self.messagesContainerNode.clipsToBounds = true @@ -1354,7 +1354,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate @objc private func playPressed() { if self.state.backgroundColors.count >= 3 || self.state.messagesColors.count >= 3 { - self.backgroundNode.animateEvent(transition: .animated(duration: 0.5, curve: .spring)) + self.backgroundNode.animateEvent(transition: .animated(duration: 0.5, curve: .spring), extendAnimation: false) } else { self.updateState({ state in var state = state diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 81426ab556..7cb2af7755 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -107,7 +107,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { self.messagesContainerNode.clipsToBounds = true self.messagesContainerNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) - self.instantChatBackgroundNode = WallpaperBackgroundNode(context: context) + self.instantChatBackgroundNode = createWallpaperBackgroundNode(context: context) self.instantChatBackgroundNode.displaysAsynchronously = false self.ready.set(.single(true)) @@ -121,7 +121,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { self.blurredNode = BlurredImageNode() self.blurredNode.blurView.contentMode = .scaleAspectFill - self.wallpaperNode = WallpaperBackgroundNode(context: context) + self.wallpaperNode = createWallpaperBackgroundNode(context: context) self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.previewTheme, strings: self.presentationData.strings, doneButtonType: .set) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift index 464280933b..064fa3e245 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift @@ -138,7 +138,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { return { item, params, neighbors in if currentBackgroundNode == nil { - currentBackgroundNode = WallpaperBackgroundNode(context: item.context) + currentBackgroundNode = createWallpaperBackgroundNode(context: item.context) } currentBackgroundNode?.update(wallpaper: item.wallpaper) currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.componentTheme, bubbleCorners: item.chatBubbleCorners) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 55a93b4304..8d5a1921dc 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -136,7 +136,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.wrapperNode = ASDisplayNode() self.imageNode = TransformImageNode() self.imageNode.contentAnimations = .subsequentUpdates - self.nativeNode = WallpaperBackgroundNode(context: context) + self.nativeNode = createWallpaperBackgroundNode(context: context) self.cropNode = WallpaperCropNode() self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6)) self.statusNode.frame = CGRect(x: 0.0, y: 0.0, width: progressDiameter, height: progressDiameter) @@ -812,7 +812,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { switch wallpaper { case let .gradient(gradient): if gradient.colors.count >= 3 { - self.nativeNode.animateEvent(transition: .animated(duration: 0.5, curve: .spring)) + self.nativeNode.animateEvent(transition: .animated(duration: 0.5, curve: .spring), extendAnimation: false) } else { let rotation = gradient.settings.rotation ?? 0 self.requestRotateGradient?((rotation + 90) % 360) @@ -820,7 +820,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { case let .file(file): if file.isPattern { if file.settings.colors.count >= 3 { - self.nativeNode.animateEvent(transition: .animated(duration: 0.5, curve: .spring)) + self.nativeNode.animateEvent(transition: .animated(duration: 0.5, curve: .spring), extendAnimation: false) } else { let rotation = file.settings.rotation ?? 0 self.requestRotateGradient?((rotation + 90) % 360) diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 5af7a8504e..424eb02b38 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -964,7 +964,7 @@ final class MessageStoryRenderer { self.containerNode = ASDisplayNode() - self.instantChatBackgroundNode = WallpaperBackgroundNode(context: context) + self.instantChatBackgroundNode = createWallpaperBackgroundNode(context: context) self.instantChatBackgroundNode.displaysAsynchronously = false self.messagesContainerNode = ASDisplayNode() diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift index bdbf6a3984..ccee06a92a 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift @@ -181,8 +181,12 @@ private func synchronizeMarkAllUnseen(transaction: Transaction, postbox: Postbox ) |> mapToSignal { resultId -> Signal in if let resultId = resultId { - let _ = currentMaxId.swap(resultId) - return .complete() + let previous = currentMaxId.swap(resultId) + if previous == resultId { + return .fail(.done) + } else { + return .complete() + } } else { return .fail(.done) } diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index e90da4c911..e2ff733bca 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -1174,17 +1174,7 @@ private func extractAccountManagerState(records: AccountRecordsView mapToSignal { context -> Signal in - if let context = context { - return context.applicationBadge - } else { - return .single(0) - } - } - |> deliverOnMainQueue).start(next: { count in - UIApplication.shared.applicationIconBadgeNumber = Int(count) - })) + self.resetBadge() if #available(iOS 9.1, *) { self.quickActionsDisposable.set((self.context.get() @@ -1281,6 +1271,20 @@ private func extractAccountManagerState(records: AccountRecordsView mapToSignal { context -> Signal in + if let context = context { + return context.applicationBadge + } else { + return .single(0) + } + } + |> deliverOnMainQueue).start(next: { count in + UIApplication.shared.applicationIconBadgeNumber = Int(count) + })) + } + func applicationWillResignActive(_ application: UIApplication) { self.isActiveValue = false self.isActivePromise.set(false) @@ -1361,6 +1365,8 @@ private func extractAccountManagerState(records: AccountRecordsView UIImage? { return context.generateImage() } -public final class WallpaperBackgroundNode: ASDisplayNode { - public final class BubbleBackgroundNode: ASDisplayNode { - public enum BubbleType { - case incoming - case outgoing - case free - } +public enum WallpaperBubbleType { + case incoming + case outgoing + case free +} - private let bubbleType: BubbleType +public protocol WallpaperBubbleBackgroundNode: ASDisplayNode { + var frame: CGRect { get set } + + func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) + func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) + func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) + func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) +} + +public protocol WallpaperBackgroundNode: ASDisplayNode { + var isReady: Signal { get } + var rotation: CGFloat { get set } + + func update(wallpaper: TelegramWallpaper) + func _internalUpdateIsSettingUpWallpaper() + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) + func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) + func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) + func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool + func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? +} + +final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode { + final class BubbleBackgroundNodeImpl: ASDisplayNode, WallpaperBubbleBackgroundNode { + private let bubbleType: WallpaperBubbleType private let contentNode: ASImageNode private var cleanWallpaperNode: ASDisplayNode? private var gradientWallpaperNode: GradientBackgroundNode.CloneNode? - private weak var backgroundNode: WallpaperBackgroundNode? - private var index: SparseBag.Index? + private weak var backgroundNode: WallpaperBackgroundNodeImpl? + private var index: SparseBag.Index? private var currentLayout: (rect: CGRect, containerSize: CGSize)? - public override var frame: CGRect { + override var frame: CGRect { didSet { if oldValue.size != self.bounds.size { self.contentNode.frame = self.bounds @@ -59,7 +81,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { } } - init(backgroundNode: WallpaperBackgroundNode, bubbleType: BubbleType) { + init(backgroundNode: WallpaperBackgroundNodeImpl, bubbleType: WallpaperBubbleType) { self.backgroundNode = backgroundNode self.bubbleType = bubbleType @@ -210,7 +232,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { } } - public func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) { + func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) { self.currentLayout = (rect, containerSize) let shiftedContentsRect = CGRect(origin: CGPoint(x: rect.minX / containerSize.width, y: rect.minY / containerSize.height), size: CGSize(width: rect.width / containerSize.width, height: rect.height / containerSize.height)) @@ -233,7 +255,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { } } - public func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) { + func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) { self.currentLayout = (rect, containerSize) let shiftedContentsRect = CGRect(origin: CGPoint(x: rect.minX / containerSize.width, y: rect.minY / containerSize.height), size: CGSize(width: rect.width / containerSize.width, height: rect.height / containerSize.height)) @@ -250,7 +272,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { } } - public func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { guard let (_, containerSize) = self.currentLayout else { return } @@ -267,7 +289,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { } } - public func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { + func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { guard let (_, containerSize) = self.currentLayout else { return } @@ -285,9 +307,9 @@ public final class WallpaperBackgroundNode: ASDisplayNode { } private final class BubbleBackgroundNodeReference { - weak var node: BubbleBackgroundNode? + weak var node: BubbleBackgroundNodeImpl? - init(node: BubbleBackgroundNode) { + init(node: BubbleBackgroundNodeImpl) { self.node = node } } @@ -303,6 +325,8 @@ public final class WallpaperBackgroundNode: ASDisplayNode { private let patternImageNode: ASImageNode private var isGeneratingPatternImage: Bool = false + private let bakedBackgroundView: UIImageView + private var validLayout: CGSize? private var wallpaper: TelegramWallpaper? private var isSettingUpWallpaper: Bool = false @@ -367,7 +391,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { } } - public var rotation: CGFloat = 0.0 { + var rotation: CGFloat = 0.0 { didSet { var fromValue: CGFloat = 0.0 if let value = (self.layer.value(forKeyPath: "transform.rotation.z") as? NSNumber)?.floatValue { @@ -400,11 +424,11 @@ public final class WallpaperBackgroundNode: ASDisplayNode { private static var cachedSharedPattern: (PatternKey, UIImage)? private let _isReady = ValuePromise(false, ignoreRepeated: true) - public var isReady: Signal { + var isReady: Signal { return self._isReady.get() } - public init(context: AccountContext, useSharedAnimationPhase: Bool = false) { + init(context: AccountContext, useSharedAnimationPhase: Bool) { self.context = context self.useSharedAnimationPhase = useSharedAnimationPhase self.imageContentMode = .scaleAspectFill @@ -413,6 +437,9 @@ public final class WallpaperBackgroundNode: ASDisplayNode { self.contentNode.contentMode = self.imageContentMode self.patternImageNode = ASImageNode() + + self.bakedBackgroundView = UIImageView() + self.bakedBackgroundView.isHidden = true super.init() @@ -420,6 +447,8 @@ public final class WallpaperBackgroundNode: ASDisplayNode { self.contentNode.frame = self.bounds self.addSubnode(self.contentNode) self.addSubnode(self.patternImageNode) + + //self.view.addSubview(self.bakedBackgroundView) } deinit { @@ -428,7 +457,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { self.imageDisposable.dispose() } - public func update(wallpaper: TelegramWallpaper) { + func update(wallpaper: TelegramWallpaper) { if self.wallpaper == wallpaper { return } @@ -539,7 +568,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { } } - public func _internalUpdateIsSettingUpWallpaper() { + func _internalUpdateIsSettingUpWallpaper() { self.isSettingUpWallpaper = true } @@ -622,7 +651,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { self.validPatternGeneratedImage = nil self.validPatternImage = nil - if let cachedValidPatternImage = WallpaperBackgroundNode.cachedValidPatternImage, cachedValidPatternImage.generated.wallpaper == wallpaper { + if let cachedValidPatternImage = WallpaperBackgroundNodeImpl.cachedValidPatternImage, cachedValidPatternImage.generated.wallpaper == wallpaper { self.validPatternImage = ValidPatternImage(wallpaper: cachedValidPatternImage.generated.wallpaper, generate: cachedValidPatternImage.generate) } else { func reference(for resource: EngineMediaResource, media: EngineMedia) -> MediaResourceReference { @@ -688,7 +717,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { if self.validPatternGeneratedImage != updatedGeneratedImage { self.validPatternGeneratedImage = updatedGeneratedImage - if let cachedValidPatternImage = WallpaperBackgroundNode.cachedValidPatternImage, cachedValidPatternImage.generated == updatedGeneratedImage { + if let cachedValidPatternImage = WallpaperBackgroundNodeImpl.cachedValidPatternImage, cachedValidPatternImage.generated == updatedGeneratedImage { self.patternImageNode.image = cachedValidPatternImage.image self.updatePatternPresentation() } else { @@ -700,7 +729,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { self.updatePatternPresentation() if self.useSharedAnimationPhase { - WallpaperBackgroundNode.cachedValidPatternImage = CachedValidPatternImage(generate: validPatternImage.generate, generated: updatedGeneratedImage, image: image) + WallpaperBackgroundNodeImpl.cachedValidPatternImage = CachedValidPatternImage(generate: validPatternImage.generate, generated: updatedGeneratedImage, image: image) } } else { self.updatePatternPresentation() @@ -721,7 +750,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { strongSelf.updatePatternPresentation() if let image = image, strongSelf.useSharedAnimationPhase { - WallpaperBackgroundNode.cachedValidPatternImage = CachedValidPatternImage(generate: validPatternImage.generate, generated: updatedGeneratedImage, image: image) + WallpaperBackgroundNodeImpl.cachedValidPatternImage = CachedValidPatternImage(generate: validPatternImage.generate, generated: updatedGeneratedImage, image: image) } } } @@ -743,7 +772,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { transition.updateFrame(node: self.patternImageNode, frame: CGRect(origin: CGPoint(), size: size)) } - public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { let isFirstLayout = self.validLayout == nil self.validLayout = size @@ -767,12 +796,25 @@ public final class WallpaperBackgroundNode: ASDisplayNode { } } - public func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool = false) { + func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) { self.gradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation) self.outgoingBubbleGradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation) } - public func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) { + private func updateBakedBackground() { + guard let size = self.validLayout else { + return + } + let context = DrawingContext(size: size, scale: UIScreenScale, opaque: true) + + context.withContext { context in + context.clear(CGRect(origin: CGPoint(), size: size)) + } + + self.bakedBackgroundView.image = context.generateImage() + } + + func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) { if self.bubbleTheme !== bubbleTheme || self.bubbleCorners != bubbleCorners { self.bubbleTheme = bubbleTheme self.bubbleCorners = bubbleCorners @@ -801,7 +843,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { } } - public func hasBubbleBackground(for type: WallpaperBackgroundNode.BubbleBackgroundNode.BubbleType) -> Bool { + func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool { guard let bubbleTheme = self.bubbleTheme, let bubbleCorners = self.bubbleCorners else { return false } @@ -846,12 +888,138 @@ public final class WallpaperBackgroundNode: ASDisplayNode { return false } - public func makeBubbleBackground(for type: WallpaperBackgroundNode.BubbleBackgroundNode.BubbleType) -> WallpaperBackgroundNode.BubbleBackgroundNode? { + func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? { if !self.hasBubbleBackground(for: type) { return nil } - let node = WallpaperBackgroundNode.BubbleBackgroundNode(backgroundNode: self, bubbleType: type) + let node = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: type) node.updateContents() return node } } + +final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroundNode { + final class SharedStorage { + } + + private class WallpaperComponentView: UIView { + let updated: () -> Void + + init(updated: @escaping () -> Void) { + self.updated = updated + + super.init(frame: CGRect()) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + + private final class WallpaperGradiendComponentView: WallpaperComponentView { + struct Spec { + var colors: [UInt32] + } + } + + private final class WallpaperPatternComponentView: WallpaperComponentView { + struct Spec { + } + } + + private let context: AccountContext + private let storage: SharedStorage + + private let staticView: UIImageView + private var gradient: WallpaperGradiendComponentView? + private var pattern: WallpaperPatternComponentView? + + private let _isReady = ValuePromise(false, ignoreRepeated: true) + var isReady: Signal { + return self._isReady.get() + } + + var rotation: CGFloat = 0.0 { + didSet { + } + } + + init(context: AccountContext, storage: SharedStorage?) { + self.context = context + self.storage = storage ?? SharedStorage() + + self.staticView = UIImageView() + + super.init() + + self.view.addSubview(self.staticView) + } + + func update(wallpaper: TelegramWallpaper) { + var gradientSpec: WallpaperGradiendComponentView.Spec? + + switch wallpaper { + case let .builtin(wallpaperSettings): + let _ = wallpaperSettings + case let .color(color): + let _ = color + case let .gradient(gradient): + if gradient.colors.count >= 3 { + gradientSpec = WallpaperGradiendComponentView.Spec(colors: gradient.colors) + } + case let .image(representations, settings): + let _ = representations + let _ = settings + case let .file(file): + if file.settings.colors.count >= 3 { + gradientSpec = WallpaperGradiendComponentView.Spec(colors: file.settings.colors) + } + } + + if let gradientSpec = gradientSpec { + let gradient: WallpaperGradiendComponentView + if let current = self.gradient { + gradient = current + } else { + gradient = WallpaperGradiendComponentView(updated: { [weak self] in + self?.componentsUpdated() + }) + } + let _ = gradient + let _ = gradientSpec + } else if let gradient = self.gradient { + self.gradient = nil + gradient.removeFromSuperview() + } + } + + private func componentsUpdated() { + } + + func _internalUpdateIsSettingUpWallpaper() { + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + + } + + func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) { + + } + + func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) { + + } + + func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool { + return false + } + + func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? { + return nil + } +} + +public func createWallpaperBackgroundNode(context: AccountContext, useSharedAnimationPhase: Bool = false) -> WallpaperBackgroundNode { + return WallpaperBackgroundNodeImpl(context: context, useSharedAnimationPhase: useSharedAnimationPhase) +}