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 {