diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index dc8c198c7e..deffdc64bd 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14383,7 +14383,7 @@ Sorry for the inconvenience."; "ChannelMessages.PriceSectionTitle" = "PRICE FOR EACH MESSAGE"; "ChannelMessages.PriceSectionFooter" = "You will receive 85% of the selected fee for each incoming message."; -"ChatList.MonoforumLabel" = "MESSAGES"; +"ChatList.MonoforumLabel" = "DIRECT"; "ChatList.MonoforumEmptyText" = "No messages here yet..."; "Chat.InlineTopicMenu.Reorder" = "Reorder"; @@ -14400,6 +14400,7 @@ Sorry for the inconvenience."; "Chat.EmptyStateMonoforum.Text" = "Send a direct message to the administrator of **%@**."; "Chat.EmptyStateMonoforumPaid.Text" = "**%1$@** charges **%2$@**\nper message to its admin."; +"Chat.Monoforum.Subtitle" = "Direct messages"; "Monoforum.NameFormat" = "%@ Messages"; diff --git a/submodules/Display/Source/TransformImageNode.swift b/submodules/Display/Source/TransformImageNode.swift index 4e06c3ab48..5d17ee0b23 100644 --- a/submodules/Display/Source/TransformImageNode.swift +++ b/submodules/Display/Source/TransformImageNode.swift @@ -97,22 +97,14 @@ open class TransformImageNode: ASDisplayNode { self.disposable.set((result |> deliverOnMainQueue).start(next: { [weak self] next in let apply: () -> Void = { if let strongSelf = self { + var animateFromContents: Any? + if strongSelf.contents == nil { if strongSelf.contentAnimations.contains(.firstUpdate) && !attemptSynchronously { strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) } } else if strongSelf.contentAnimations.contains(.subsequentUpdates) { - let tempLayer = CALayer() - if strongSelf.captureProtected { - setLayerDisableScreenshots(tempLayer, strongSelf.captureProtected) - } - tempLayer.frame = strongSelf.bounds - tempLayer.contentsGravity = strongSelf.layer.contentsGravity - tempLayer.contents = strongSelf.contents - strongSelf.layer.addSublayer(tempLayer) - tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in - tempLayer?.removeFromSuperlayer() - }) + animateFromContents = strongSelf.contents } var imageUpdate: UIImage? @@ -129,6 +121,23 @@ open class TransformImageNode: ASDisplayNode { if let imageUpdated = strongSelf.imageUpdated { imageUpdated(imageUpdate) } + + if let animateFromContents { + let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear) + transition.animateContents(layer: strongSelf.layer, from: animateFromContents) + + /*let tempLayer = CALayer() + if strongSelf.captureProtected { + setLayerDisableScreenshots(tempLayer, strongSelf.captureProtected) + } + tempLayer.frame = strongSelf.bounds + tempLayer.contentsGravity = strongSelf.layer.contentsGravity + tempLayer.contents = animateFromContents + strongSelf.layer.addSublayer(tempLayer) + tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in + tempLayer?.removeFromSuperlayer() + })*/ + } } } if dispatchOnDisplayLink && !attemptSynchronously { @@ -168,6 +177,34 @@ open class TransformImageNode: ASDisplayNode { } } + public func asyncLayoutWithAnimation() -> (TransformImageArguments) -> ((ListViewItemUpdateAnimation) -> Void) { + let currentTransform = self.currentTransform + let currentArguments = self.currentArguments + return { [weak self] arguments in + let updatedImage: UIImage? + + if currentArguments != arguments { + updatedImage = currentTransform?(arguments)?.generateImage() + } else { + updatedImage = nil + } + return { animation in + guard let self else { + return + } + if let image = updatedImage { + self.contents = image.cgImage + self.image = image + self.currentArguments = arguments + if let _ = self.overlayColor { + self.applyOverlayColor(animated: false) + } + } + self.argumentsPromise.set(arguments) + } + } + } + public class func asyncLayout(_ maybeNode: TransformImageNode?) -> (TransformImageArguments) -> (() -> TransformImageNode) { return { arguments in let node: TransformImageNode diff --git a/submodules/MediaPlayer/Sources/MediaPlayerNode.swift b/submodules/MediaPlayer/Sources/MediaPlayerNode.swift index 7b1273f115..c8b563b5b8 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayerNode.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayerNode.swift @@ -402,7 +402,7 @@ public final class MediaPlayerNode: ASDisplayNode { } } - private func updateLayout() { + public func updateLayout() { let bounds = self.bounds if bounds.isEmpty { return diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 3938669792..1bbecf6485 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -858,7 +858,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr public func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ mediaIndex: Int?, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) { let currentMessage = self.message let currentMedia = self.media - let imageLayout = self.imageNode.asyncLayout() + let imageLayout = self.imageNode.asyncLayoutWithAnimation() let statusLayout = self.dateAndStatusNode.asyncLayout() let currentVideoNode = self.videoNode @@ -1894,7 +1894,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr timestampMaskView.image = strongSelf.generateTimestampMaskImage(corners: arguments.corners) } strongSelf.currentImageArguments = arguments - imageApply() + imageApply(transition) if let statusApply = statusApply { let dateAndStatusFrame = CGRect(origin: CGPoint(x: cleanImageFrame.width - layoutConstants.image.statusInsets.right - statusSize.width, y: cleanImageFrame.height - layoutConstants.image.statusInsets.bottom - statusSize.height), size: statusSize) @@ -2084,8 +2084,13 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr strongSelf.imageNode.cornerRadius = 0.0 } - videoNode.updateLayout(size: arguments.drawingSize, transition: .immediate) - videoNode.frame = CGRect(origin: CGPoint(), size: imageFrame.size) + if videoNode.bounds.isEmpty { + videoNode.updateLayout(size: arguments.drawingSize, transition: .immediate) + videoNode.frame = CGRect(origin: CGPoint(), size: imageFrame.size) + } else { + videoNode.updateLayout(size: arguments.drawingSize, transition: transition.transition) + transition.animator.updateFrame(layer: videoNode.layer, frame: CGRect(origin: CGPoint(), size: imageFrame.size), completion: nil) + } if strongSelf.visibility && strongSelf.internallyVisible && !presentationData.isPreview { if !videoNode.canAttachContent { diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift index a686733ac9..1c2ecc3235 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift @@ -1359,7 +1359,7 @@ public final class ChatSideTopicsPanel: Component { maximumNumberOfLines: 2 )), environment: {}, - containerSize: CGSize(width: 200.0, height: 200.0) + containerSize: CGSize(width: 400.0, height: 200.0) ) let contentSize: CGFloat = leftInset + rightInset + titleSize.height diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 5a8b4b2d42..6141c84cfd 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -68,6 +68,8 @@ public enum ChatTitleContent: Equatable { return false } if lhs.peerPresences.count != rhs.peerPresences.count { + return false + } else { for (key, value) in lhs.peerPresences { if let rhsValue = rhs.peerPresences[key] { if !value.isEqual(to: rhsValue) { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift index 332aa07e15..4cdf6e8435 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift @@ -397,21 +397,30 @@ extension ChatControllerImpl { self.navigationActionDisposable.set((peerView.get() |> take(1) |> deliverOnMainQueue).startStrict(next: { [weak self] peerView in - if let strongSelf = self, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && !strongSelf.presentationInterfaceState.isNotAccessible { - - if peer.id == strongSelf.context.account.peerId { - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) { - strongSelf.effectiveNavigationController?.pushViewController(infoController) + guard let self else { + return + } + guard var peer = peerView.peers[peerView.peerId] else { + return + } + if let channel = peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] { + peer = mainPeer + } + + if peer.restrictionText(platform: "ios", contentSettings: self.context.currentContentSettings.with { $0 }) == nil && !self.presentationInterfaceState.isNotAccessible { + if peer.id == self.context.account.peerId { + if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer, let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) { + self.effectiveNavigationController?.pushViewController(infoController) } } else { var expandAvatar = expandAvatar if peer.smallProfileImage == nil { expandAvatar = false } - if let validLayout = strongSelf.validLayout, validLayout.deviceMetrics.type == .tablet { + if let validLayout = self.validLayout, validLayout.deviceMetrics.type == .tablet { expandAvatar = false } - let mode: PeerInfoControllerMode + let mode: PeerInfoControllerMode switch section { case .groupsInCommon: mode = .groupsInCommon @@ -420,12 +429,12 @@ extension ChatControllerImpl { default: mode = .generic } - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: true, requestsContext: strongSelf.contentData?.inviteRequestsContext) { - strongSelf.effectiveNavigationController?.pushViewController(infoController) + if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: true, requestsContext: self.contentData?.inviteRequestsContext) { + self.effectiveNavigationController?.pushViewController(infoController) } } - let _ = strongSelf.dismissPreviewing?(false) + let _ = self.dismissPreviewing?(false) } })) case .replyThread: diff --git a/submodules/TelegramUI/Sources/ChatControllerContentData.swift b/submodules/TelegramUI/Sources/ChatControllerContentData.swift index 66080bef0a..55d1f8ff3f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerContentData.swift +++ b/submodules/TelegramUI/Sources/ChatControllerContentData.swift @@ -548,9 +548,9 @@ extension ChatControllerImpl { strongSelf.state.chatTitleContent = .custom(strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false) } else if let channel = peer as? TelegramChannel, channel.isMonoForum { if let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] { - strongSelf.state.chatTitleContent = .custom(mainPeer.debugDisplayTitle, "Direct messages", false) + strongSelf.state.chatTitleContent = .custom(mainPeer.debugDisplayTitle, strings.Chat_Monoforum_Subtitle, true) } else { - strongSelf.state.chatTitleContent = .custom(channel.debugDisplayTitle, nil, false) + strongSelf.state.chatTitleContent = .custom(channel.debugDisplayTitle, nil, true) } } else { strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 0769d003c6..397195770e 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -2552,17 +2552,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if self.leftPanel != nil || dismissedLeftPanel != nil { titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -titleTopicsAccessoryPanelFrame.height, transition: .immediate) } - - let topPanelTransition = ComponentTransition(transition) - /*switch topPanelTransition.animation { - case let .curve(duration, _): - topPanelTransition = topPanelTransition.withAnimation(.curve(duration: duration, curve: ComponentTransition.Animation.Curve(ChatMessageTransitionNodeImpl.verticalAnimationCurve))) - default: - break - }*/ - topPanelTransition.setFrame(view: titleTopicsAccessoryPanelNode.view, frame: titleTopicsAccessoryPanelFrame) - titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: 0.0, transition: topPanelTransition) + ComponentTransition(transition).setFrame(view: titleTopicsAccessoryPanelNode.view, frame: titleTopicsAccessoryPanelFrame) + titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: 0.0, transition: ComponentTransition(transition)) } else { let previousFrame = titleTopicsAccessoryPanelNode.frame titleTopicsAccessoryPanelNode.frame = titleTopicsAccessoryPanelFrame diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 57c3f0e91e..6b3cff7b36 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -681,6 +681,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto private var loadedMessagesFromCachedDataDisposable: Disposable? + private var isSettingTopReplyThreadMessageShown: Bool = false let isTopReplyThreadMessageShown = ValuePromise(false, ignoreRepeated: true) private var topVisibleMessageRangeValueInitialized: Bool = false @@ -3178,8 +3179,15 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } } - - self.isTopReplyThreadMessageShown.set(isTopReplyThreadMessageShownValue) + if !self.isSettingTopReplyThreadMessageShown { + self.isSettingTopReplyThreadMessageShown = true + self.isTopReplyThreadMessageShown.set(isTopReplyThreadMessageShownValue) + self.isSettingTopReplyThreadMessageShown = false + } else { + #if DEBUG + print("Ignore repeated isTopReplyThreadMessageShown update") + #endif + } self.updateTopVisibleMessageRange(topVisibleMessageRange) let _ = self.visibleMessageRange.swap(topVisibleMessageRange.flatMap { range in return VisibleMessageRange(lowerBound: range.lowerBound, upperBound: range.upperBound) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index e7e6d16bc3..a0e51dbbc2 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1591,7 +1591,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } } - if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, !(message.media.first is TelegramMediaAction), !isReplyThreadHead, !isMigrated { + if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, !channel.isMonoForum, !(message.media.first is TelegramMediaAction), !isReplyThreadHead, !isMigrated { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in @@ -1893,8 +1893,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState var canViewStats = false var canViewAuthor = false - if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum { - if message.effectivelyIncoming(context.account.peerId) { + if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let associatedPeerId = channel.associatedPeerId { + if message.effectivelyIncoming(context.account.peerId), message.author?.id == associatedPeerId { canViewAuthor = true } } else if let messageReadStatsAreHidden = infoSummaryData.messageReadStatsAreHidden, !messageReadStatsAreHidden { diff --git a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift index e8b67bd497..d4c389c80f 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift @@ -690,22 +690,34 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent if let dimensions = self.dimensions { let imageSize = CGSize(width: floor(dimensions.width / 2.0), height: floor(dimensions.height / 2.0)) - let makeLayout = self.imageNode.asyncLayout() + let makeLayout = self.imageNode.asyncLayoutWithAnimation() let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: self.fileReference.media.isInstantVideo ? .clear : self.placeholderColor)) - applyLayout() + let mappedAnimation: ListViewItemUpdateAnimation + if case let .animated(duration, curve) = transition { + mappedAnimation = .System(duration: duration, transition: ControlledTransition(duration: duration, curve: curve, interactive: false)) + } else { + mappedAnimation = .None + } + applyLayout(mappedAnimation) } transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(), size: size)) let fromFrame = self.playerNode.frame let toFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0) if case let .animated(duration, curve) = transition, fromFrame != toFrame, !fromFrame.width.isZero, !fromFrame.height.isZero, !toFrame.width.isZero, !toFrame.height.isZero { - self.playerNode.frame = toFrame - transition.animatePosition(node: self.playerNode, from: CGPoint(x: fromFrame.center.x - toFrame.center.x, y: fromFrame.center.y - toFrame.center.y)) + let _ = duration + let _ = curve + self.playerNode.position = toFrame.center + self.playerNode.bounds = CGRect(origin: CGPoint(), size: toFrame.size) + self.playerNode.updateLayout() + transition.animatePosition(node: self.playerNode, from: CGPoint(x: fromFrame.center.x, y: fromFrame.center.y)) let transform = CATransform3DScale(CATransform3DIdentity, fromFrame.width / toFrame.width, fromFrame.height / toFrame.height, 1.0) self.playerNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration) } else { - transition.updateFrame(node: self.playerNode, frame: toFrame) + transition.updatePosition(node: self.playerNode, position: toFrame.center) + transition.updateBounds(node: self.playerNode, bounds: CGRect(origin: CGPoint(), size: toFrame.size)) + self.playerNode.updateLayout() } if let thumbnailNode = self.thumbnailNode { transition.updateFrame(node: thumbnailNode, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0))