From 9f69dd90123a4ff350047850028a171bb3bc421a Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 29 Nov 2022 20:57:59 +0400 Subject: [PATCH 1/3] Keep keyboard static during authorization sequence on iOS 16 --- ...orizationSequenceCodeEntryController.swift | 27 +++++++++++++++++++ ...ationSequenceCodeEntryControllerNode.swift | 10 +++++++ ...ationSequencePasswordEntryController.swift | 4 +++ ...uthorizationSequenceSignUpController.swift | 4 +++ ...rizationSequenceSignUpControllerNode.swift | 11 ++++++++ .../Sources/Node/ChatListNode.swift | 2 +- .../Sources/Node/ChatListNodeEntries.swift | 4 +-- .../Sources/PeerInfo/PeerInfoData.swift | 2 +- .../Sources/WebSearchController.swift | 6 +---- 9 files changed, 61 insertions(+), 9 deletions(-) diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift index c791af5863..46a82494b3 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Display import AsyncDisplayKit +import SwiftSignalKit import TelegramCore import TelegramPresentationData import ProgressNavigationButtonNode @@ -120,6 +121,10 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + if let navigationController = self.navigationController as? NavigationController, let layout = self.validLayout { + addTemporaryKeyboardSnapshotView(navigationController: navigationController, parentView: self.view, layout: layout) + } + self.controllerNode.activateInput() } @@ -219,3 +224,25 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { self.controllerNode.updateCode("\(code)") } } + +func addTemporaryKeyboardSnapshotView(navigationController: NavigationController, parentView: UIView, layout: ContainerViewLayout) { + if case .compact = layout.metrics.widthClass, let statusBarHost = navigationController.statusBarHost { + if let keyboardView = statusBarHost.keyboardView { + if let snapshotView = keyboardView.snapshotView(afterScreenUpdates: false) { + keyboardView.layer.removeAllAnimations() + UIView.performWithoutAnimation { + snapshotView.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - snapshotView.frame.size.height), size: snapshotView.frame.size) + if let keyboardWindow = statusBarHost.keyboardWindow { + keyboardWindow.addSubview(snapshotView) + } + + Queue.mainQueue().after(0.45, { + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + }) + } + } + } + } +} diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift index 1b99dceaa9..e2e7840121 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift @@ -74,6 +74,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.codeInputView.alpha = self.inProgress ? 0.6 : 1.0 } } + + private let appearanceTimestamp = CACurrentMediaTime() init(strings: PresentationStrings, theme: PresentationTheme) { self.strings = strings @@ -270,8 +272,16 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + let previousInputHeight = self.layoutArguments?.0.inputHeight ?? 0.0 + let newInputHeight = layout.inputHeight ?? 0.0 + self.layoutArguments = (layout, navigationBarHeight) + var layout = layout + if CACurrentMediaTime() - self.appearanceTimestamp < 2.0, newInputHeight < previousInputHeight { + layout = layout.withUpdatedInputHeight(previousInputHeight) + } + let maximumWidth: CGFloat = min(430.0, layout.size.width) let inset: CGFloat = 24.0 diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryController.swift index b7c2bd1f6c..b4ba03969a 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryController.swift @@ -89,6 +89,10 @@ final class AuthorizationSequencePasswordEntryController: ViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + if let navigationController = self.navigationController as? NavigationController, let layout = self.validLayout { + addTemporaryKeyboardSnapshotView(navigationController: navigationController, parentView: self.view, layout: layout) + } + self.controllerNode.activateInput() } diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift index cf0dded3dd..c65fa59016 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift @@ -130,6 +130,10 @@ final class AuthorizationSequenceSignUpController: ViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + if let navigationController = self.navigationController as? NavigationController, let layout = self.validLayout { + addTemporaryKeyboardSnapshotView(navigationController: navigationController, parentView: self.view, layout: layout) + } + self.controllerNode.activateInput() } diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift index 660d80a0eb..d689128673 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import AsyncDisplayKit import Display +import SwiftSignalKit import TelegramPresentationData import TextFormat import Markdown @@ -40,6 +41,8 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel private var layoutArguments: (ContainerViewLayout, CGFloat)? + private let appearanceTimestamp = CACurrentMediaTime() + var currentName: (String, String) { return (self.firstNameField.textField.text ?? "", self.lastNameField.textField.text ?? "") } @@ -209,8 +212,16 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + let previousInputHeight = self.layoutArguments?.0.inputHeight ?? 0.0 + let newInputHeight = layout.inputHeight ?? 0.0 + self.layoutArguments = (layout, navigationBarHeight) + var layout = layout + if CACurrentMediaTime() - self.appearanceTimestamp < 2.0, newInputHeight < previousInputHeight { + layout = layout.withUpdatedInputHeight(previousInputHeight) + } + let maximumWidth: CGFloat = min(430.0, layout.size.width) var insets = layout.insets(options: [.statusBar]) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index b01c44204a..ec49e37e88 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -2297,7 +2297,7 @@ public final class ChatListNode: ListView { isEmpty = true loop1: for entry in transition.chatListView.filteredEntries { switch entry { - case .GroupReferenceEntry, .HeaderEntry, .HoleEntry: + case .HeaderEntry, .HoleEntry: break default: if case .ArchiveIntro = entry { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index f9cf05d8b5..571ccd6a23 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -612,7 +612,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState autoremoveTimeout: item.item.autoremoveTimeout, forumTopicData: item.item.forumTopicData, topForumTopicItems: item.item.topForumTopicItems, - revealed: threadId == 1 && (state.hiddenItemShouldBeTemporaryRevealed || state.editing) + revealed: state.hiddenItemShouldBeTemporaryRevealed || state.editing ))) if pinningIndex != 0 { pinningIndex -= 1 @@ -632,7 +632,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState message: groupReference.topMessage, editing: state.editing, unreadCount: groupReference.unreadCount, - revealed: state.hiddenItemShouldBeTemporaryRevealed, + revealed: state.hiddenItemShouldBeTemporaryRevealed || view.items.isEmpty, hiddenByDefault: hideArchivedFolderByDefault )) if pinningIndex != 0 { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index 555b558c5b..206becc8db 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -1151,7 +1151,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro } var canReport = true - if channel.isVerified || channel.adminRights != nil || channel.flags.contains(.isCreator) { + if channel.adminRights != nil || channel.flags.contains(.isCreator) { canReport = false } diff --git a/submodules/WebSearchUI/Sources/WebSearchController.swift b/submodules/WebSearchUI/Sources/WebSearchController.swift index 45f9b4697e..79695f3c85 100644 --- a/submodules/WebSearchUI/Sources/WebSearchController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchController.swift @@ -413,11 +413,7 @@ public final class WebSearchController: ViewController { } } - private func updateSearchQuery(_ query: String) { - if !query.isEmpty { - let _ = addRecentWebSearchQuery(engine: self.context.engine, string: query).start() - } - + private func updateSearchQuery(_ query: String) { let scope: Signal switch self.mode { case .media: From fd341053a6d9c042e5d63d44fb926b761c66b09d Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 2 Dec 2022 00:55:53 +0400 Subject: [PATCH 2/3] Various fixes --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 + .../Sources/Node/ChatListItem.swift | 4 +- .../Items/UniversalVideoGalleryItem.swift | 2 +- .../Sources/AppIconsDemoComponent.swift | 15 +++- .../SyncCore/SyncCore_CachedUserData.swift | 52 +++++++++---- .../TelegramEngine/Data/PeersData.swift | 6 +- .../Peers/UpdateCachedPeerData.swift | 2 +- .../Resources/PresentationResourceKey.swift | 3 + .../Resources/PresentationResourcesChat.swift | 18 +++++ .../Sources/ChatAvatarNavigationNode.swift | 4 +- .../Sources/ChatEntityKeyboardInputNode.swift | 6 +- .../ChatMessageAnimatedStickerItemNode.swift | 78 +++++++++---------- .../Sources/ChatMessageBubbleItemNode.swift | 9 ++- .../Sources/ChatMessageDateHeader.swift | 4 +- .../Sources/ChatMessageStickerItemNode.swift | 76 +++++++++--------- .../Sources/ChatMessageThreadInfoNode.swift | 24 +++--- .../Sources/OverlayUniversalVideoNode.swift | 19 ++++- 17 files changed, 203 insertions(+), 121 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 3fbd752d8c..31cb718f0e 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8405,6 +8405,8 @@ Sorry for the inconvenience."; "GlobalAutodeleteSettings.AttemptDisabledGenericSelection" = "You can't enable auto-delete in this chat."; "EmojiSearch.SearchEmojiPlaceholder" = "Search Emoji"; +"StickersSearch.SearchStickersPlaceholder" = "Search Stickers"; +"GifSearch.SearchGifPlaceholder" = "Search GIFs"; "MessageTimer.LargeShortSeconds_1" = "%@s"; "MessageTimer.LargeShortSeconds_2" = "%@s"; diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 863b0f9951..8f7b888ce7 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1295,8 +1295,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { return } let cachedPeerData = peerView.cachedData - if let cachedPeerData = cachedPeerData as? CachedUserData { - if let photo = cachedPeerData.photo, let video = smallestVideoRepresentation(photo.videoRepresentations), let peerReference = PeerReference(peer._asPeer()) { + if let cachedPeerData = cachedPeerData as? CachedUserData, case let .known(maybePhoto) = cachedPeerData.photo { + if let photo = maybePhoto, let video = smallestVideoRepresentation(photo.videoRepresentations), let peerReference = PeerReference(peer._asPeer()) { let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value() let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: false) diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 8ecb9adaed..b061a38700 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -517,7 +517,7 @@ private final class PictureInPictureContentImpl: NSObject, PictureInPictureConte guard let status = self.status else { return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0))) } - return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: status.duration, preferredTimescale: CMTimeScale(30.0))) + return CMTimeRange(start: CMTime(seconds: status.timestamp, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: status.duration, preferredTimescale: CMTimeScale(30.0))) } public func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool { diff --git a/submodules/PremiumUI/Sources/AppIconsDemoComponent.swift b/submodules/PremiumUI/Sources/AppIconsDemoComponent.swift index 996c825f4e..7c0fb63798 100644 --- a/submodules/PremiumUI/Sources/AppIconsDemoComponent.swift +++ b/submodules/PremiumUI/Sources/AppIconsDemoComponent.swift @@ -104,7 +104,9 @@ final class AppIconsDemoComponent: Component { position = CGPoint(x: availableSize.width * 0.5, y: availableSize.height * 0.5) } - view.center = position.offsetBy(dx: availableSize.width / 2.0, dy: 0.0) + if !self.animating { + view.center = position.offsetBy(dx: availableSize.width / 2.0, dy: 0.0) + } i += 1 } @@ -126,7 +128,10 @@ final class AppIconsDemoComponent: Component { return availableSize } + private var animating = false func animateIn(availableSize: CGSize) { + self.animating = true + var i = 0 for view in self.imageViews { let from: CGPoint @@ -146,9 +151,17 @@ final class AppIconsDemoComponent: Component { delay = 0.0 } + let initialPosition = view.layer.position + view.layer.position = initialPosition.offsetBy(dx: from.x, dy: from.y) + Queue.mainQueue().after(delay) { + view.layer.position = initialPosition view.layer.animateScale(from: 3.0, to: 1.0, duration: 0.5, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring) view.layer.animatePosition(from: from, to: CGPoint(), duration: 0.5, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + + if i == 2 { + self.animating = false + } } i += 1 diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift index 8e7087c4f8..49120073d0 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift @@ -50,6 +50,34 @@ public enum CachedPeerAutoremoveTimeout: Equatable, PostboxCoding { } } +public enum CachedPeerProfilePhoto: Equatable, PostboxCoding { + case unknown + case known(TelegramMediaImage?) + + public init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("_v", orElse: 0) { + case 1: + self = .known(decoder.decodeObjectForKey("v", decoder: { TelegramMediaImage(decoder: $0) }) as? TelegramMediaImage) + default: + self = .unknown + } + } + + public func encode(_ encoder: PostboxEncoder) { + switch self { + case .unknown: + encoder.encodeInt32(0, forKey: "_v") + case let .known(value): + encoder.encodeInt32(1, forKey: "_v") + if let value = value { + encoder.encodeObject(value, forKey: "v") + } else { + encoder.encodeNil(forKey: "v") + } + } + } +} + public struct CachedPremiumGiftOption: Equatable, PostboxCoding { public let months: Int32 public let currency: String @@ -123,7 +151,7 @@ public final class CachedUserData: CachedPeerData { public let hasScheduledMessages: Bool public let autoremoveTimeout: CachedPeerAutoremoveTimeout public let themeEmoticon: String? - public let photo: TelegramMediaImage? + public let photo: CachedPeerProfilePhoto public let premiumGiftOptions: [CachedPremiumGiftOption] public let voiceMessagesAvailable: Bool @@ -145,14 +173,14 @@ public final class CachedUserData: CachedPeerData { self.hasScheduledMessages = false self.autoremoveTimeout = .unknown self.themeEmoticon = nil - self.photo = nil + self.photo = .unknown self.premiumGiftOptions = [] self.voiceMessagesAvailable = true self.peerIds = Set() self.messageIds = Set() } - public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?, photo: TelegramMediaImage?, premiumGiftOptions: [CachedPremiumGiftOption], voiceMessagesAvailable: Bool) { + public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?, photo: CachedPeerProfilePhoto, premiumGiftOptions: [CachedPremiumGiftOption], voiceMessagesAvailable: Bool) { self.about = about self.botInfo = botInfo self.peerStatusSettings = peerStatusSettings @@ -204,12 +232,8 @@ public final class CachedUserData: CachedPeerData { self.autoremoveTimeout = decoder.decodeObjectForKey("artv", decoder: CachedPeerAutoremoveTimeout.init(decoder:)) as? CachedPeerAutoremoveTimeout ?? .unknown self.themeEmoticon = decoder.decodeOptionalStringForKey("te") - if let photo = decoder.decodeObjectForKey("ph", decoder: { TelegramMediaImage(decoder: $0) }) as? TelegramMediaImage { - self.photo = photo - } else { - self.photo = nil - } - + self.photo = decoder.decodeObjectForKey("phv", decoder: CachedPeerProfilePhoto.init(decoder:)) as? CachedPeerProfilePhoto ?? .unknown + self.premiumGiftOptions = decoder.decodeObjectArrayWithDecoderForKey("pgo") as [CachedPremiumGiftOption] self.voiceMessagesAvailable = decoder.decodeInt32ForKey("vma", orElse: 0) != 0 @@ -261,12 +285,8 @@ public final class CachedUserData: CachedPeerData { encoder.encodeNil(forKey: "te") } - if let photo = self.photo { - encoder.encodeObject(photo, forKey: "ph") - } else { - encoder.encodeNil(forKey: "ph") - } - + encoder.encodeObject(self.photo, forKey: "phv") + encoder.encodeObjectArray(self.premiumGiftOptions, forKey: "pgo") encoder.encodeInt32(self.voiceMessagesAvailable ? 1 : 0, forKey: "vma") } @@ -338,7 +358,7 @@ public final class CachedUserData: CachedPeerData { return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) } - public func withUpdatedPhoto(_ photo: TelegramMediaImage?) -> CachedUserData { + public func withUpdatedPhoto(_ photo: CachedPeerProfilePhoto) -> CachedUserData { return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index 82ef129524..dca233f9f0 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -739,7 +739,11 @@ public extension TelegramEngine.EngineData.Item { preconditionFailure() } if let cachedData = view.cachedPeerData as? CachedUserData { - return .known(cachedData.photo) + if case let .known(value) = cachedData.photo { + return .known(value) + } else { + return .unknown + } } else if let cachedData = view.cachedPeerData as? CachedGroupData { return .known(cachedData.photo) } else if let cachedData = view.cachedPeerData as? CachedChannelData { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 0b52bb97cc..473c8f4620 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -267,7 +267,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee return previous.withUpdatedAbout(userFullAbout).withUpdatedBotInfo(botInfo).withUpdatedCommonGroupCount(userFullCommonChatsCount).withUpdatedIsBlocked(isBlocked).withUpdatedVoiceCallsAvailable(voiceCallsAvailable).withUpdatedVideoCallsAvailable(videoCallsAvailable).withUpdatedCallsPrivate(callsPrivate).withUpdatedCanPinMessages(canPinMessages).withUpdatedPeerStatusSettings(peerStatusSettings).withUpdatedPinnedMessageId(pinnedMessageId).withUpdatedHasScheduledMessages(hasScheduledMessages) .withUpdatedAutoremoveTimeout(autoremoveTimeout) .withUpdatedThemeEmoticon(userFullThemeEmoticon) - .withUpdatedPhoto(photo) + .withUpdatedPhoto(.known(photo)) .withUpdatedPremiumGiftOptions(premiumGiftOptions) .withUpdatedVoiceMessagesAvailable(voiceMessagesAvailable) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 747e48d983..0d14db4cdb 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -290,6 +290,9 @@ public enum PresentationResourceKey: Int32 { case chatKeyboardActionButtonWebAppIcon case chatGeneralThreadIcon + case chatGeneralThreadIncomingIcon + case chatGeneralThreadOutgoingIcon + case chatGeneralThreadFreeIcon case uploadToneIcon } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index f377670c0e..4a5b65b93a 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1325,4 +1325,22 @@ public struct PresentationResourcesChat { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Info/GeneralIcon"), color: theme.rootController.navigationBar.controlColor) }) } + + public static func chatGeneralThreadIncomingIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatGeneralThreadIncomingIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: theme.chat.message.incoming.accentTextColor) + }) + } + + public static func chatGeneralThreadOutgoingIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatGeneralThreadOutgoingIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: theme.chat.message.outgoing.accentTextColor) + }) + } + + public static func chatGeneralThreadFreeIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatGeneralThreadFreeIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: theme.chat.message.mediaOverlayControlColors.foregroundColor) + }) + } } diff --git a/submodules/TelegramUI/Sources/ChatAvatarNavigationNode.swift b/submodules/TelegramUI/Sources/ChatAvatarNavigationNode.swift index 8c70e59df1..39b4f42154 100644 --- a/submodules/TelegramUI/Sources/ChatAvatarNavigationNode.swift +++ b/submodules/TelegramUI/Sources/ChatAvatarNavigationNode.swift @@ -123,8 +123,8 @@ final class ChatAvatarNavigationNode: ASDisplayNode { return } let cachedPeerData = peerView.cachedData - if let cachedPeerData = cachedPeerData as? CachedUserData { - if let photo = cachedPeerData.photo, let video = smallestVideoRepresentation(photo.videoRepresentations), let peerReference = PeerReference(peer._asPeer()) { + if let cachedPeerData = cachedPeerData as? CachedUserData, case let .known(maybePhoto) = cachedPeerData.photo { + if let photo = maybePhoto, let video = smallestVideoRepresentation(photo.videoRepresentations), let peerReference = PeerReference(peer._asPeer()) { let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value() let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) let videoContent = NativeVideoContent(id: .profileVideo(videoId, "header"), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: false) diff --git a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift index 4bfd915334..efdbdffdc1 100644 --- a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift @@ -501,6 +501,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } return EmojiPagerContentComponent( id: "stickers", context: context, @@ -537,7 +538,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { itemLayoutType: .detailed, itemContentUniqueId: nil, warpContentsOnEdges: false, - displaySearchWithPlaceholder: "Search Stickers", + displaySearchWithPlaceholder: presentationData.strings.StickersSearch_SearchStickersPlaceholder, searchInitiallyHidden: false, searchIsPlaceholderOnly: true, emptySearchResults: nil, @@ -748,6 +749,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { return !savedGifs.isEmpty } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } let gifItems: Signal switch subject { case .recent: @@ -769,7 +771,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { items: items, isLoading: false, loadMoreToken: nil, - displaySearchWithPlaceholder: "Search GIFs", + displaySearchWithPlaceholder: presentationData.strings.GifSearch_SearchGifPlaceholder, searchInitiallyHidden: false ) ) diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 3f4ce2db16..e69704750e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -1186,6 +1186,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } + var replyMessage: Message? for attribute in item.message.attributes { if let attribute = attribute as? InlineBotMessageAttribute { var inlineBotNameString: String? @@ -1205,51 +1206,48 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { viaBotApply = viaBotLayout(TextNodeLayoutArguments(attributedString: botString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0, availableContentWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) } } - - if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - var hasReply = true - - if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId { - hasReply = false - } - - if case .peer = item.chatLocation, replyMessage.threadId != nil, case let .peer(peerId) = item.chatLocation, peerId == replyMessage.id.peerId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) { - if let threadId = item.message.threadId, Int64(replyMessage.id.id) == threadId { - hasReply = false - } - - threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments( - presentationData: item.presentationData, - strings: item.presentationData.strings, - context: item.context, - controllerInteraction: item.controllerInteraction, - type: .standalone, - message: replyMessage, - parentMessage: item.message, - constrainedSize: CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude), - animationCache: item.controllerInteraction.presentationContext.animationCache, - animationRenderer: item.controllerInteraction.presentationContext.animationRenderer - )) - } - - if hasReply { - replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments( - presentationData: item.presentationData, - strings: item.presentationData.strings, - context: item.context, - type: .standalone, - message: replyMessage, - parentMessage: item.message, - constrainedSize: CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude), - animationCache: item.controllerInteraction.presentationContext.animationCache, - animationRenderer: item.controllerInteraction.presentationContext.animationRenderer - )) - } + + if let replyAttribute = attribute as? ReplyMessageAttribute { + replyMessage = item.message.associatedMessages[replyAttribute.messageId] } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty { replyMarkup = attribute } } + var hasReply = replyMessage != nil + if case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), item.message.associatedThreadInfo != nil { + if let threadId = item.message.threadId, let replyMessage = replyMessage, Int64(replyMessage.id.id) == threadId { + hasReply = false + } + + threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments( + presentationData: item.presentationData, + strings: item.presentationData.strings, + context: item.context, + controllerInteraction: item.controllerInteraction, + type: .standalone, + threadId: item.message.threadId ?? 1, + parentMessage: item.message, + constrainedSize: CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude), + animationCache: item.controllerInteraction.presentationContext.animationCache, + animationRenderer: item.controllerInteraction.presentationContext.animationRenderer + )) + } + + if let replyMessage = replyMessage, hasReply { + replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments( + presentationData: item.presentationData, + strings: item.presentationData.strings, + context: item.context, + type: .standalone, + message: replyMessage, + parentMessage: item.message, + constrainedSize: CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude), + animationCache: item.controllerInteraction.presentationContext.animationCache, + animationRenderer: item.controllerInteraction.presentationContext.animationRenderer + )) + } + if item.message.id.peerId != item.context.account.peerId && !item.message.id.peerId.isReplies { for attribute in item.message.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index a0a4a15018..847fecda52 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -1737,6 +1737,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode if replyMessage != nil { displayHeader = true } + if !displayHeader, case .peer = item.chatLocation, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), item.message.associatedThreadInfo != nil { + displayHeader = true + } } let firstNodeTopPosition: ChatMessageBubbleRelativePosition @@ -1963,8 +1966,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } var hasReply = replyMessage != nil - if !isInstantVideo, let replyMessage = replyMessage, replyMessage.threadId != nil, case let .peer(peerId) = item.chatLocation, peerId == replyMessage.id.peerId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), item.message.associatedThreadInfo != nil { - if let threadId = item.message.threadId, Int64(replyMessage.id.id) == threadId { + if !isInstantVideo, case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), item.message.associatedThreadInfo != nil { + if let threadId = item.message.threadId, let replyMessage = replyMessage, Int64(replyMessage.id.id) == threadId { hasReply = false } @@ -1980,7 +1983,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode context: item.context, controllerInteraction: item.controllerInteraction, type: .bubble(incoming: incoming), - message: replyMessage, + threadId: item.message.threadId ?? 1, parentMessage: item.message, constrainedSize: CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude), animationCache: item.controllerInteraction.presentationContext.animationCache, diff --git a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift index 151de3f2f1..17c792ff9c 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift @@ -531,8 +531,8 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { return } let cachedPeerData = peerView.cachedData - if let cachedPeerData = cachedPeerData as? CachedUserData { - if let photo = cachedPeerData.photo, let video = photo.videoRepresentations.last, let peerReference = PeerReference(peer) { + if let cachedPeerData = cachedPeerData as? CachedUserData, case let .known(maybePhoto) = cachedPeerData.photo { + if let photo = maybePhoto, let video = photo.videoRepresentations.last, let peerReference = PeerReference(peer) { let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value() let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) let videoContent = NativeVideoContent(id: .profileVideo(videoId, "\(Int32.random(in: 0 ..< Int32.max))"), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: false) diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 104d177ce0..c560b681dc 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -614,6 +614,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } + var replyMessage: Message? for attribute in item.message.attributes { if let attribute = attribute as? InlineBotMessageAttribute { var inlineBotNameString: String? @@ -634,49 +635,48 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - var hasReply = true - - if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId { - hasReply = false - } - - if case .peer = item.chatLocation, replyMessage.threadId != nil, case let .peer(peerId) = item.chatLocation, peerId == replyMessage.id.peerId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) { - if let threadId = item.message.threadId, Int64(replyMessage.id.id) == threadId { - hasReply = false - } - threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments( - presentationData: item.presentationData, - strings: item.presentationData.strings, - context: item.context, - controllerInteraction: item.controllerInteraction, - type: .standalone, - message: replyMessage, - parentMessage: item.message, - constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), - animationCache: item.controllerInteraction.presentationContext.animationCache, - animationRenderer: item.controllerInteraction.presentationContext.animationRenderer - )) - } - - if hasReply { - replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments( - presentationData: item.presentationData, - strings: item.presentationData.strings, - context: item.context, - type: .standalone, - message: replyMessage, - parentMessage: item.message, - constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), - animationCache: item.controllerInteraction.presentationContext.animationCache, - animationRenderer: item.controllerInteraction.presentationContext.animationRenderer - )) - } + + if let replyAttribute = attribute as? ReplyMessageAttribute { + replyMessage = item.message.associatedMessages[replyAttribute.messageId] } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty { replyMarkup = attribute } } + var hasReply = replyMessage != nil + if case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), item.message.associatedThreadInfo != nil { + if let threadId = item.message.threadId, let replyMessage = replyMessage, Int64(replyMessage.id.id) == threadId { + hasReply = false + } + + threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments( + presentationData: item.presentationData, + strings: item.presentationData.strings, + context: item.context, + controllerInteraction: item.controllerInteraction, + type: .standalone, + threadId: item.message.threadId ?? 1, + parentMessage: item.message, + constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), + animationCache: item.controllerInteraction.presentationContext.animationCache, + animationRenderer: item.controllerInteraction.presentationContext.animationRenderer + )) + } + + if let replyMessage = replyMessage, hasReply { + replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments( + presentationData: item.presentationData, + strings: item.presentationData.strings, + context: item.context, + type: .standalone, + message: replyMessage, + parentMessage: item.message, + constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), + animationCache: item.controllerInteraction.presentationContext.animationCache, + animationRenderer: item.controllerInteraction.presentationContext.animationRenderer + )) + } + if item.message.id.peerId != item.context.account.peerId && !item.message.id.peerId.isReplies { for attribute in item.message.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { diff --git a/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift index f798f66cbe..3fe396bfb1 100644 --- a/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift @@ -183,7 +183,7 @@ class ChatMessageThreadInfoNode: ASDisplayNode { let context: AccountContext let controllerInteraction: ChatControllerInteraction let type: ChatMessageThreadInfoType - let message: Message + let threadId: Int64 let parentMessage: Message let constrainedSize: CGSize let animationCache: AnimationCache? @@ -195,7 +195,7 @@ class ChatMessageThreadInfoNode: ASDisplayNode { context: AccountContext, controllerInteraction: ChatControllerInteraction, type: ChatMessageThreadInfoType, - message: Message, + threadId: Int64, parentMessage: Message, constrainedSize: CGSize, animationCache: AnimationCache?, @@ -206,7 +206,7 @@ class ChatMessageThreadInfoNode: ASDisplayNode { self.context = context self.controllerInteraction = controllerInteraction self.type = type - self.message = message + self.threadId = threadId self.parentMessage = parentMessage self.constrainedSize = constrainedSize self.animationCache = animationCache @@ -318,7 +318,6 @@ class ChatMessageThreadInfoNode: ASDisplayNode { var topicIconId: Int64? var topicIconColor: Int32 = 0 if let _ = arguments.parentMessage.threadId, let channel = arguments.parentMessage.peers[arguments.parentMessage.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), let threadInfo = arguments.parentMessage.associatedThreadInfo { - topicTitle = threadInfo.title topicIconId = threadInfo.icon topicIconColor = threadInfo.iconColor @@ -327,9 +326,10 @@ class ChatMessageThreadInfoNode: ASDisplayNode { let backgroundColor: UIColor let textColor: UIColor let arrowIcon: UIImage? + let generalThreadIcon: UIImage? switch arguments.type { case let .bubble(incoming): - if topicIconId == nil, topicIconColor != 0, incoming { + if topicIconId == nil, topicIconColor != 0, incoming, arguments.threadId != 1 { let colors = topicIconColors(for: topicIconColor) backgroundColor = UIColor(rgb: colors.0.last ?? 0x000000) textColor = UIColor(rgb: colors.1.first ?? 0x000000) @@ -345,13 +345,15 @@ class ChatMessageThreadInfoNode: ASDisplayNode { arrowIcon = PresentationResourcesChat.chatBubbleArrowOutgoingImage(arguments.presentationData.theme.theme) } } + generalThreadIcon = incoming ? PresentationResourcesChat.chatGeneralThreadIncomingIcon(arguments.presentationData.theme.theme) : PresentationResourcesChat.chatGeneralThreadOutgoingIcon(arguments.presentationData.theme.theme) case .standalone: - textColor = .white + textColor = arguments.presentationData.theme.theme.chat.message.mediaOverlayControlColors.foregroundColor backgroundColor = .white arrowIcon = PresentationResourcesChat.chatBubbleArrowFreeImage(arguments.presentationData.theme.theme) + generalThreadIcon = PresentationResourcesChat.chatGeneralThreadFreeIcon(arguments.presentationData.theme.theme) } - let placeholderColor: UIColor = arguments.message.effectivelyIncoming(arguments.context.account.peerId) ? arguments.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : arguments.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor + let placeholderColor: UIColor = arguments.parentMessage.effectivelyIncoming(arguments.context.account.peerId) ? arguments.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : arguments.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor let text = NSAttributedString(string: topicTitle, font: textFont, textColor: textColor) @@ -390,9 +392,7 @@ class ChatMessageThreadInfoNode: ASDisplayNode { } node.pressed = { - if let threadId = arguments.message.threadId { - arguments.controllerInteraction.navigateToThreadMessage(arguments.parentMessage.id.peerId, threadId, arguments.parentMessage.id) - } + arguments.controllerInteraction.navigateToThreadMessage(arguments.parentMessage.id.peerId, arguments.threadId, arguments.parentMessage.id) } if node.lineRects != lineRects { @@ -480,7 +480,9 @@ class ChatMessageThreadInfoNode: ASDisplayNode { } let titleTopicIconContent: EmojiStatusComponent.Content - if let fileId = topicIconId, fileId != 0 { + if arguments.threadId == 1 { + titleTopicIconContent = .image(image: generalThreadIcon) + } else if let fileId = topicIconId, fileId != 0 { titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: arguments.presentationData.theme.theme.list.mediaPlaceholderColor, themeColor: arguments.presentationData.theme.theme.list.itemAccentColor, loopMode: .count(1)) } else { titleTopicIconContent = .topic(title: String(topicTitle.prefix(1)), color: topicIconColor, size: CGSize(width: 22.0, height: 22.0)) diff --git a/submodules/TelegramUniversalVideoContent/Sources/OverlayUniversalVideoNode.swift b/submodules/TelegramUniversalVideoContent/Sources/OverlayUniversalVideoNode.swift index a4f0aa66e3..7f1670691c 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/OverlayUniversalVideoNode.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/OverlayUniversalVideoNode.swift @@ -8,6 +8,7 @@ import Postbox import TelegramAudio import AccountContext import AVKit +import UniversalMediaPlayer public final class OverlayUniversalVideoNode: OverlayMediaItemNode, AVPictureInPictureSampleBufferPlaybackDelegate { public let content: UniversalVideoContent @@ -37,6 +38,9 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode, AVPictureInP public var customClose: (() -> Void)? public var controlsAreShowingUpdated: ((Bool) -> Void)? + private var statusDisposable: Disposable? + private var status: MediaPlayerStatus? + public init(postbox: Postbox, audioSession: ManagedAudioSession, manager: UniversalVideoManager, content: UniversalVideoContent, shouldBeDismissed: Signal = .single(false), expand: @escaping () -> Void, close: @escaping () -> Void) { self.content = content self.defaultExpand = expand @@ -124,6 +128,16 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode, AVPictureInP strongSelf.dismiss() closeImpl?() }) + + self.statusDisposable = (self.videoNode.status + |> deliverOnMainQueue).start(next: { [weak self] status in + self?.status = status + }) + } + + deinit { + self.shouldBeDismissedDisposable?.dispose() + self.statusDisposable?.dispose() } override public func didLoad() { @@ -194,7 +208,10 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode, AVPictureInP } public func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange { - return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: 10.0, preferredTimescale: CMTimeScale(30.0))) + guard let status = self.status else { + return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0))) + } + return CMTimeRange(start: CMTime(seconds: status.timestamp, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: status.duration, preferredTimescale: CMTimeScale(30.0))) } public func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool { From c6788d923bd9941b43fda129f0c28855e3044ef5 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 2 Dec 2022 14:35:53 +0400 Subject: [PATCH 3/3] Various fixes --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 ++ submodules/Display/Source/TextNode.swift | 27 +++++++++++++++++++ .../Peers/ChannelAdminEventLogs.swift | 12 +++++---- .../ChatRecentActionsHistoryTransition.swift | 9 ++++++- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 31cb718f0e..68cb922bcb 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8261,6 +8261,8 @@ Sorry for the inconvenience."; "Channel.AdminLog.TopicRenamedWithRemovedIcon" = "%1$@ renamed topic %2$@ to %3$@ and removed icon"; "Channel.AdminLog.TopicChangedIcon" = "%1$@ changed topic %2$@ icon to %3$@"; "Channel.AdminLog.TopicRemovedIcon" = "%1$@ removed topic %2$@ icon"; +"Channel.AdminLog.TopicUnhidden" = "%1$@ unhid topic %2$@"; +"Channel.AdminLog.TopicHidden" = "%1$@ hid topic %2$@"; "Attachment.Pasteboard" = "Clipboard"; "Attachment.DiscardPasteboardAlertText" = "Discard pasted items?"; diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index 933a8590c8..f514c9866c 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -1411,10 +1411,14 @@ open class TextNode: ASDisplayNode { context.setAllowsFontSubpixelQuantization(true) context.setShouldSubpixelQuantizeFonts(true) + var blendMode: CGBlendMode = .normal + var clearRects: [CGRect] = [] if let layout = parameters as? TextNodeLayout { if !isRasterizing || layout.backgroundColor != nil { context.setBlendMode(.copy) + blendMode = .copy + context.setFillColor((layout.backgroundColor ?? UIColor.clear).cgColor) context.fill(bounds) } @@ -1426,6 +1430,8 @@ open class TextNode: ASDisplayNode { if let (textStrokeColor, textStrokeWidth) = layout.textStroke { context.setBlendMode(.normal) + blendMode = .normal + context.setLineCap(.round) context.setLineJoin(.round) context.setStrokeColor(textStrokeColor.cgColor) @@ -1487,7 +1493,28 @@ open class TextNode: ASDisplayNode { if attributes["Attribute__EmbeddedItem"] != nil { continue } + + var fixCoupleEmoji = false + if glyphCount == 2, let font = attributes["NSFont"] as? UIFont, font.fontName.contains("ColorEmoji"), let string = layout.attributedString { + let range = CTRunGetStringRange(run) + let substring = string.attributedSubstring(from: NSMakeRange(range.location, range.length)).string + + let heart = Unicode.Scalar(0x2764)! + let man = Unicode.Scalar(0x1F468)! + let woman = Unicode.Scalar(0x1F469)! + + if substring.unicodeScalars.contains(heart) && (substring.unicodeScalars.contains(man) || substring.unicodeScalars.contains(woman)) { + fixCoupleEmoji = true + } + } + + if fixCoupleEmoji { + context.setBlendMode(.normal) + } CTRunDraw(run, context, CFRangeMake(0, glyphCount)) + if fixCoupleEmoji { + context.setBlendMode(blendMode) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift index 3969bbc49b..9213f93729 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift @@ -35,10 +35,12 @@ public enum AdminLogEventAction { public struct ForumTopicInfo { public var info: EngineMessageHistoryThread.Info public var isClosed: Bool + public var isHidden: Bool - public init(info: EngineMessageHistoryThread.Info, isClosed: Bool) { + public init(info: EngineMessageHistoryThread.Info, isClosed: Bool, isHidden: Bool) { self.info = info self.isClosed = isClosed + self.isHidden = isHidden } } @@ -302,17 +304,17 @@ func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: PeerId, m let prevInfo: AdminLogEventAction.ForumTopicInfo switch prevTopic { case let .forumTopic(flags, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _): - prevInfo = AdminLogEventAction.ForumTopicInfo(info: EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor), isClosed: (flags & (1 << 2)) != 0) + prevInfo = AdminLogEventAction.ForumTopicInfo(info: EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor), isClosed: (flags & (1 << 2)) != 0, isHidden: (flags & (1 << 6)) != 0) case .forumTopicDeleted: - prevInfo = AdminLogEventAction.ForumTopicInfo(info: EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0), isClosed: false) + prevInfo = AdminLogEventAction.ForumTopicInfo(info: EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0), isClosed: false, isHidden: false) } let newInfo: AdminLogEventAction.ForumTopicInfo switch newTopic { case let .forumTopic(flags, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _): - newInfo = AdminLogEventAction.ForumTopicInfo(info: EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor), isClosed: (flags & (1 << 2)) != 0) + newInfo = AdminLogEventAction.ForumTopicInfo(info: EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor), isClosed: (flags & (1 << 2)) != 0, isHidden: (flags & (1 << 6)) != 0) case .forumTopicDeleted: - newInfo = AdminLogEventAction.ForumTopicInfo(info: EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0), isClosed: false) + newInfo = AdminLogEventAction.ForumTopicInfo(info: EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0), isClosed: false, isHidden: false) } action = .editTopic(prevInfo: prevInfo, newInfo: newInfo) diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift index ecaa5f3354..288b6ddd8d 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift @@ -1714,7 +1714,14 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { "Channel.AdminLog.TopicRemovedIcon" = "%1$@ removed topic %2$@ icon";*/ let authorTitle: String = author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "" - if prevInfo.isClosed != newInfo.isClosed { + if prevInfo.isHidden != newInfo.isHidden { + appendAttributedText(text: newInfo.isHidden ? self.presentationData.strings.Channel_AdminLog_TopicHidden(authorTitle, newInfo.info.title) : self.presentationData.strings.Channel_AdminLog_TopicUnhidden(authorTitle, newInfo.info.title), generateEntities: { index in + if index == 0, let author = author { + return [.TextMention(peerId: author.id)] + } + return [] + }, to: &text, entities: &entities) + } else if prevInfo.isClosed != newInfo.isClosed { appendAttributedText(text: newInfo.isClosed ? self.presentationData.strings.Channel_AdminLog_TopicClosed(authorTitle, newInfo.info.title) : self.presentationData.strings.Channel_AdminLog_TopicReopened(authorTitle, newInfo.info.title), generateEntities: { index in if index == 0, let author = author { return [.TextMention(peerId: author.id)]