From 39b395c2fab5fdad070905f5a7777087b9a58bec Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 19 Jun 2023 23:59:38 +0300 Subject: [PATCH 01/13] Stories --- .../Sources/AccountContext.swift | 1 + .../Sources/ChatListController.swift | 4 +- .../Sources/ChatListControllerNode.swift | 2 + .../Sources/ContactsControllerNode.swift | 2 + .../Navigation/NavigationContainer.swift | 3 + .../Display/Source/ViewController.swift | 2 + .../Sources/TelegramBaseController.swift | 99 ++-- .../Sources/ChatListNavigationBar.swift | 45 +- .../MessageInputActionButtonComponent.swift | 2 +- .../Sources/PeerInfoStoryPaneNode.swift | 21 + .../Stories/StoryContainerScreen/BUILD | 5 + .../Sources/StoryContainerScreen.swift | 20 +- .../StoryContentCaptionComponent.swift | 205 +++++++- .../StoryItemSetContainerComponent.swift | 482 ++++++------------ ...StoryItemSetContainerViewSendMessage.swift | 260 ++++++++++ .../Sources/StoryChatContent.swift | 1 + .../Sources/StoryItemContentComponent.swift | 2 +- .../Sources/StoryPeerListComponent.swift | 3 +- .../Sources/StoryPeerListItemComponent.swift | 5 +- .../IconForwardSend.imageset/Contents.json | 12 + .../arrowshape_30.pdf | 167 ++++++ .../TelegramUI/Sources/OpenChatMessage.swift | 3 + .../Sources/SharedAccountContext.swift | 5 + .../WebpagePreviewAccessoryPanelNode.swift | 3 + 24 files changed, 975 insertions(+), 379 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForwardSend.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForwardSend.imageset/arrowshape_30.pdf diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 2898742a5b..12c58da368 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -867,6 +867,7 @@ public protocol SharedAccountContext: AnyObject { func makeStorageManagementController(context: AccountContext) -> ViewController func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, send: @escaping (AnyMediaReference) -> Void) -> AttachmentFileController func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? + func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String) -> ViewController func navigateToChatController(_ params: NavigateToChatControllerParams) func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController) func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 2c78eeda34..bb4dbbabe1 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -218,6 +218,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController super.init(context: context, navigationBarPresentationData: nil, mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: groupCallPanelSource) + self.accessoryPanelContainer = ASDisplayNode() + self.tabBarItemContextActionType = .always self.automaticallyControlPresentationContextLayout = false @@ -2275,7 +2277,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController var primaryContent: ChatListHeaderComponent.Content? if let primaryContext = self.primaryContext { var backTitle: String? - if let previousItem = self.navigationBar?.previousItem { + if let previousItem = self.previousItem { switch previousItem { case let .item(item): backTitle = item.title ?? self.presentationData.strings.Common_Back diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 9930d5ccc4..7c2950ded0 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -1884,6 +1884,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { uploadProgress: self.controller?.storyUploadProgress, tabsNode: tabsNode, tabsNodeIsSearch: tabsNodeIsSearch, + accessoryPanelContainer: self.controller?.accessoryPanelContainer, + accessoryPanelContainerHeight: self.controller?.accessoryPanelContainerHeight ?? 0.0, activateSearch: { [weak self] searchContentNode in guard let self, let controller = self.controller else { return diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 0129bd72a6..7274455207 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -373,6 +373,8 @@ final class ContactsControllerNode: ASDisplayNode { uploadProgress: nil, tabsNode: tabsNode, tabsNodeIsSearch: tabsNodeIsSearch, + accessoryPanelContainer: nil, + accessoryPanelContainerHeight: 0.0, activateSearch: { [weak self] searchContentNode in guard let self else { return diff --git a/submodules/Display/Source/Navigation/NavigationContainer.swift b/submodules/Display/Source/Navigation/NavigationContainer.swift index e317248156..683547b170 100644 --- a/submodules/Display/Source/Navigation/NavigationContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationContainer.swift @@ -325,11 +325,14 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega if i == 0 { if canBeClosed { controllers[i].transitionNavigationBar?.previousItem = .close + controllers[i].previousItem = .close } else { controllers[i].transitionNavigationBar?.previousItem = nil + controllers[i].previousItem = nil } } else { controllers[i].transitionNavigationBar?.previousItem = .item(controllers[i - 1].navigationItem) + controllers[i].previousItem = .item(controllers[i - 1].navigationItem) } } diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index b7745a3820..5eaecf3ab4 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -155,6 +155,8 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { return self.prefersOnScreenNavigationHidden } + public internal(set) var previousItem: NavigationPreviousAction? + open var navigationPresentation: ViewControllerNavigationPresentation = .default open var _presentedInModal: Bool = false diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index ed3eeee0b1..2c89594ae0 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -61,6 +61,9 @@ private func presentLiveLocationController(context: AccountContext, peerId: Peer open class TelegramBaseController: ViewController, KeyShortcutResponder { private let context: AccountContext + public var accessoryPanelContainer: ASDisplayNode? + public private(set) var accessoryPanelContainerHeight: CGFloat = 0.0 + public let mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility public let locationBroadcastPanelSource: LocationBroadcastPanelSource public let groupCallPanelSource: GroupCallPanelSource @@ -76,7 +79,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { public var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)? public var tempVoicePlaylistCurrentItem: SharedMediaPlaylistItem? - public var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)? + public private(set) var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)? private var locationBroadcastMode: LocationBroadcastNavigationAccessoryPanelMode? private var locationBroadcastPeers: [EnginePeer]? @@ -84,7 +87,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { private var locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel? private var groupCallPanelData: GroupCallPanelData? - private var groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel? + public private(set) var groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel? private var dismissingPanel: ASDisplayNode? @@ -96,14 +99,16 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { override open var additionalNavigationBarHeight: CGFloat { var height: CGFloat = 0.0 - if let _ = self.groupCallAccessoryPanel { - height += 50.0 - } - if let _ = self.mediaAccessoryPanel { - height += MediaNavigationAccessoryHeaderNode.minimizedHeight - } - if let _ = self.locationBroadcastAccessoryPanel { - height += MediaNavigationAccessoryHeaderNode.minimizedHeight + if self.accessoryPanelContainer == nil { + if let _ = self.groupCallAccessoryPanel { + height += 50.0 + } + if let _ = self.mediaAccessoryPanel { + height += MediaNavigationAccessoryHeaderNode.minimizedHeight + } + if let _ = self.locationBroadcastAccessoryPanel { + height += MediaNavigationAccessoryHeaderNode.minimizedHeight + } } return height } @@ -401,16 +406,38 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { super.containerLayoutUpdated(layout, transition: transition) let navigationHeight = super.navigationLayout(layout: layout).navigationFrame.height - self.additionalNavigationBarHeight -// if !self.displayNavigationBar { -// navigationHeight = 0.0 -// } + + let mediaAccessoryPanelHidden: Bool + switch self.mediaAccessoryPanelVisibility { + case .always: + mediaAccessoryPanelHidden = false + case .none: + mediaAccessoryPanelHidden = true + case let .specific(size): + mediaAccessoryPanelHidden = size != layout.metrics.widthClass + } var additionalHeight: CGFloat = 0.0 + var panelStartY: CGFloat = 0.0 + if self.accessoryPanelContainer == nil { + var negativeHeight: CGFloat = 0.0 + if let _ = self.groupCallPanelData { + negativeHeight += 50.0 + } + if let _ = self.locationBroadcastPeers, let _ = self.locationBroadcastMode { + negativeHeight += MediaNavigationAccessoryHeaderNode.minimizedHeight + } + if let _ = self.playlistStateAndType, !mediaAccessoryPanelHidden { + negativeHeight += MediaNavigationAccessoryHeaderNode.minimizedHeight + } + panelStartY = navigationHeight.isZero ? (-negativeHeight) : (navigationHeight + additionalHeight + UIScreenPixel) + } if let groupCallPanelData = self.groupCallPanelData { let panelHeight: CGFloat = 50.0 - let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight)) + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight)) additionalHeight += panelHeight + panelStartY += panelHeight let groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel if let current = self.groupCallAccessoryPanel { @@ -429,7 +456,11 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { activeCall: EngineGroupCallDescription(id: groupCallPanelData.info.id, accessHash: groupCallPanelData.info.accessHash, title: groupCallPanelData.info.title, scheduleTimestamp: groupCallPanelData.info.scheduleTimestamp, subscribedToScheduled: groupCallPanelData.info.subscribedToScheduled, isStream: groupCallPanelData.info.isStream) ) }) - self.navigationBar?.additionalContentNode.addSubnode(groupCallAccessoryPanel) + if let accessoryPanelContainer = self.accessoryPanelContainer { + accessoryPanelContainer.addSubnode(groupCallAccessoryPanel) + } else { + self.navigationBar?.additionalContentNode.addSubnode(groupCallAccessoryPanel) + } self.groupCallAccessoryPanel = groupCallAccessoryPanel groupCallAccessoryPanel.frame = panelFrame @@ -452,8 +483,9 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { if let locationBroadcastPeers = self.locationBroadcastPeers, let locationBroadcastMode = self.locationBroadcastMode { let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight - let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight)) + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight)) additionalHeight += panelHeight + panelStartY += panelHeight let locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel if let current = self.locationBroadcastAccessoryPanel { @@ -576,7 +608,11 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } }) - self.navigationBar?.additionalContentNode.addSubnode(locationBroadcastAccessoryPanel) + if let accessoryPanelContainer = self.accessoryPanelContainer { + accessoryPanelContainer.addSubnode(locationBroadcastAccessoryPanel) + } else { + self.navigationBar?.additionalContentNode.addSubnode(locationBroadcastAccessoryPanel) + } self.locationBroadcastAccessoryPanel = locationBroadcastAccessoryPanel locationBroadcastAccessoryPanel.frame = panelFrame @@ -607,19 +643,12 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { } } - let mediaAccessoryPanelHidden: Bool - switch self.mediaAccessoryPanelVisibility { - case .always: - mediaAccessoryPanelHidden = false - case .none: - mediaAccessoryPanelHidden = true - case let .specific(size): - mediaAccessoryPanelHidden = size != layout.metrics.widthClass - } - if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType, !mediaAccessoryPanelHidden { let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight - let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight)), size: CGSize(width: layout.size.width, height: panelHeight)) + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight)) + additionalHeight += panelHeight + panelStartY += panelHeight + if let (mediaAccessoryPanel, mediaType) = self.mediaAccessoryPanel, mediaType == type { transition.updateFrame(layer: mediaAccessoryPanel.layer, frame: panelFrame) mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: transition) @@ -848,9 +877,17 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { } mediaAccessoryPanel.frame = panelFrame if let dismissingPanel = self.dismissingPanel { - self.navigationBar?.additionalContentNode.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel) + if let accessoryPanelContainer = self.accessoryPanelContainer { + accessoryPanelContainer.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel) + } else { + self.navigationBar?.additionalContentNode.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel) + } } else { - self.navigationBar?.additionalContentNode.addSubnode(mediaAccessoryPanel) + if let accessoryPanelContainer = self.accessoryPanelContainer { + accessoryPanelContainer.addSubnode(mediaAccessoryPanel) + } else { + self.navigationBar?.additionalContentNode.addSubnode(mediaAccessoryPanel) + } } self.mediaAccessoryPanel = (mediaAccessoryPanel, type) mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: .immediate) @@ -889,6 +926,8 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { self.suspendedNavigationBarLayout = suspendedNavigationBarLayout self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition) } + + self.accessoryPanelContainerHeight = additionalHeight } open var keyShortcuts: [KeyShortcut] { diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift index dd9d24e35f..18ea03cb0a 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift @@ -25,6 +25,8 @@ public final class ChatListNavigationBar: Component { public let uploadProgress: Float? public let tabsNode: ASDisplayNode? public let tabsNodeIsSearch: Bool + public let accessoryPanelContainer: ASDisplayNode? + public let accessoryPanelContainerHeight: CGFloat public let activateSearch: (NavigationBarSearchContentNode) -> Void public let openStatusSetup: (UIView) -> Void @@ -44,6 +46,8 @@ public final class ChatListNavigationBar: Component { uploadProgress: Float?, tabsNode: ASDisplayNode?, tabsNodeIsSearch: Bool, + accessoryPanelContainer: ASDisplayNode?, + accessoryPanelContainerHeight: CGFloat, activateSearch: @escaping (NavigationBarSearchContentNode) -> Void, openStatusSetup: @escaping (UIView) -> Void ) { @@ -62,6 +66,8 @@ public final class ChatListNavigationBar: Component { self.uploadProgress = uploadProgress self.tabsNode = tabsNode self.tabsNodeIsSearch = tabsNodeIsSearch + self.accessoryPanelContainer = accessoryPanelContainer + self.accessoryPanelContainerHeight = accessoryPanelContainerHeight self.activateSearch = activateSearch self.openStatusSetup = openStatusSetup } @@ -112,6 +118,12 @@ public final class ChatListNavigationBar: Component { if lhs.tabsNodeIsSearch != rhs.tabsNodeIsSearch { return false } + if lhs.accessoryPanelContainer !== rhs.accessoryPanelContainer { + return false + } + if lhs.accessoryPanelContainerHeight != rhs.accessoryPanelContainerHeight { + return false + } return true } @@ -199,7 +211,7 @@ public final class ChatListNavigationBar: Component { } public func applyScroll(offset: CGFloat, allowAvatarsExpansion: Bool, forceUpdate: Bool = false, transition: Transition) { - if self.currentAllowAvatarsExpansion != allowAvatarsExpansion, allowAvatarsExpansion { + if self.currentAllowAvatarsExpansion != allowAvatarsExpansion, allowAvatarsExpansion, !transition.animation.isImmediate { self.addStoriesUnlockedAnimation(duration: 0.3, animateScrollUnlocked: false) } @@ -317,6 +329,9 @@ public final class ChatListNavigationBar: Component { if component.tabsNode != nil { searchFrame.origin.y -= 40.0 } + if !component.isSearchActive { + searchFrame.origin.y -= component.accessoryPanelContainerHeight + } let clippedSearchOffset = max(0.0, min(clippedScrollOffset - effectiveStoriesOffsetDistance, searchOffsetDistance)) let searchOffsetFraction = clippedSearchOffset / searchOffsetDistance @@ -407,10 +422,15 @@ public final class ChatListNavigationBar: Component { } var tabsFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: 46.0)) + if !component.isSearchActive { + tabsFrame.origin.y -= component.accessoryPanelContainerHeight + } if component.tabsNode != nil { tabsFrame.origin.y -= 46.0 } + let accessoryPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height - component.accessoryPanelContainerHeight), size: CGSize(width: visibleSize.width, height: component.accessoryPanelContainerHeight)) + if let disappearingTabsView = self.disappearingTabsView { disappearingTabsView.layer.anchorPoint = CGPoint() transition.setFrameWithAdditivePosition(view: disappearingTabsView, frame: tabsFrame.offsetBy(dx: 0.0, dy: self.disappearingTabsViewSearch ? (-currentLayout.size.height + 2.0) : 0.0)) @@ -441,6 +461,23 @@ public final class ChatListNavigationBar: Component { tabsNodeTransition.setFrameWithAdditivePosition(view: tabsNode.view, frame: tabsFrame.offsetBy(dx: 0.0, dy: component.tabsNodeIsSearch ? (-currentLayout.size.height + 2.0) : 0.0)) } + + if let accessoryPanelContainer = component.accessoryPanelContainer { + var tabsNodeTransition = transition + if accessoryPanelContainer.view.superview !== self { + accessoryPanelContainer.view.layer.anchorPoint = CGPoint() + tabsNodeTransition = .immediate + accessoryPanelContainer.view.alpha = 1.0 + self.addSubview(accessoryPanelContainer.view) + if !transition.animation.isImmediate { + accessoryPanelContainer.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } else { + transition.setAlpha(view: accessoryPanelContainer.view, alpha: 1.0) + } + + tabsNodeTransition.setFrameWithAdditivePosition(view: accessoryPanelContainer.view, frame: accessoryPanelContainerFrame) + } } public func updateStoryUploadProgress(storyUploadProgress: Float?) { @@ -464,6 +501,8 @@ public final class ChatListNavigationBar: Component { uploadProgress: storyUploadProgress, tabsNode: component.tabsNode, tabsNodeIsSearch: component.tabsNodeIsSearch, + accessoryPanelContainer: component.accessoryPanelContainer, + accessoryPanelContainerHeight: component.accessoryPanelContainerHeight, activateSearch: component.activateSearch, openStatusSetup: component.openStatusSetup ) @@ -547,6 +586,10 @@ public final class ChatListNavigationBar: Component { contentHeight += 40.0 } + if component.accessoryPanelContainer != nil && !component.isSearchActive { + contentHeight += component.accessoryPanelContainerHeight + } + let size = CGSize(width: availableSize.width, height: contentHeight) self.currentLayout = CurrentLayout(size: size) diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift index 2cd2bd451d..0c7df6c5db 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift @@ -16,7 +16,7 @@ private extension MessageInputActionButtonComponent.Mode { case .attach: return "Chat/Input/Text/IconAttachment" case .forward: - return "Chat/Input/Text/IconForward" + return "Chat/Input/Text/IconForwardSend" default: return nil } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index 11faf768e3..f7f6894b5d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -889,6 +889,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr private var presentationData: PresentationData private var presentationDataDisposable: Disposable? + + private weak var pendingOpenListContext: PeerStoryListContentContextImpl? public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, navigationController: @escaping () -> NavigationController?, listContext: PeerStoryListContext?) { self.context = context @@ -952,6 +954,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } } + if self.pendingOpenListContext != nil { + return + } + //TODO:selection let listContext = PeerStoryListContentContextImpl( context: self.context, @@ -959,6 +965,9 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr listContext: self.listSource, initialId: item.story.id ) + self.pendingOpenListContext = listContext + self.itemGrid.isUserInteractionEnabled = false + let _ = (listContext.state |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in @@ -966,6 +975,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr return } + guard let pendingOpenListContext = self.pendingOpenListContext, pendingOpenListContext === listContext else { + return + } + self.pendingOpenListContext = nil + self.itemGrid.isUserInteractionEnabled = true + var transitionIn: StoryContainerScreen.TransitionIn? let story = item.story @@ -1016,6 +1031,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr }, updateView: { view, state, transition in (view as? ItemTransitionView)?.update(state: state, transition: transition) + }, + insertCloneTransitionView: { [weak self] view in + guard let self else { + return + } + self.view.insertSubview(view, aboveSubview: self.itemGrid.view) } ), destinationRect: self.itemGrid.view.convert(itemRect, to: self.view), diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index 928b85104d..1ed9e6218d 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -56,6 +56,11 @@ swift_library( "//submodules/TelegramStringFormatting", "//submodules/ShimmerEffect", "//submodules/ImageCompression", + "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/InvisibleInkDustNode", + "//submodules/PresentationDataUtils", + "//submodules/UrlEscaping", + "//submodules/OverlayStatusController", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index ade16dd8de..6680a14df7 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -851,7 +851,7 @@ private final class StoryContainerScreenComponent: Component { } let itemFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - itemSetContainerSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - itemSetContainerSize.height) / 2.0)), size: itemSetContainerSize) - if let itemSetComponentView = itemSetView.view.view { + if let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View { if itemSetView.superview == nil { self.addSubview(itemSetView) } @@ -866,6 +866,10 @@ private final class StoryContainerScreenComponent: Component { itemSetTransition.setBounds(view: itemSetView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) itemSetTransition.setSublayerTransform(view: itemSetView, transform: CATransform3DMakeScale(dismissPanScale, dismissPanScale, 1.0)) + itemSetTransition.setPosition(view: itemSetComponentView.transitionCloneContainerView, position: itemFrame.center.offsetBy(dx: 0.0, dy: dismissPanOffset)) + itemSetTransition.setBounds(view: itemSetComponentView.transitionCloneContainerView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) + itemSetTransition.setSublayerTransform(view: itemSetComponentView.transitionCloneContainerView, transform: CATransform3DMakeScale(dismissPanScale, dismissPanScale, 1.0)) + itemSetTransition.setPosition(view: itemSetComponentView, position: CGRect(origin: CGPoint(), size: itemFrame.size).center) itemSetTransition.setBounds(view: itemSetComponentView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) @@ -1037,13 +1041,16 @@ public class StoryContainerScreen: ViewControllerComponentContainer { public final class TransitionView { public let makeView: () -> UIView public let updateView: (UIView, TransitionState, Transition) -> Void + public let insertCloneTransitionView: ((UIView) -> Void)? public init( makeView: @escaping () -> UIView, - updateView: @escaping (UIView, TransitionState, Transition) -> Void + updateView: @escaping (UIView, TransitionState, Transition) -> Void, + insertCloneTransitionView: ((UIView) -> Void)? ) { self.makeView = makeView self.updateView = updateView + self.insertCloneTransitionView = insertCloneTransitionView } } @@ -1092,6 +1099,7 @@ public class StoryContainerScreen: ViewControllerComponentContainer { } private let context: AccountContext + private var didAnimateIn: Bool = false private var isDismissed: Bool = false private let focusedItemPromise = Promise(nil) @@ -1141,8 +1149,12 @@ public class StoryContainerScreen: ViewControllerComponentContainer { self.view.disablesInteractiveModalDismiss = true - if let componentView = self.node.hostView.componentView as? StoryContainerScreenComponent.View { - componentView.animateIn() + if !self.didAnimateIn { + self.didAnimateIn = true + + if let componentView = self.node.hostView.componentView as? StoryContainerScreenComponent.View { + componentView.animateIn() + } } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift index b72b2123e1..082c3c9d6c 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift @@ -3,8 +3,23 @@ import UIKit import Display import ComponentFlow import MultilineTextComponent +import AccountContext +import TelegramCore +import TextNodeWithEntities +import TextFormat +import InvisibleInkDustNode +import UrlEscaping final class StoryContentCaptionComponent: Component { + enum Action { + case url(url: String, concealed: Bool) + case textMention(String) + case peerMention(peerId: EnginePeer.Id, mention: String) + case hashtag(String?, String) + case bankCard(String) + case customEmoji(TelegramMediaFile) + } + final class ExternalState { fileprivate(set) var isExpanded: Bool = false @@ -25,20 +40,38 @@ final class StoryContentCaptionComponent: Component { } let externalState: ExternalState + let context: AccountContext let text: String + let entities: [MessageTextEntity] + let action: (Action) -> Void init( externalState: ExternalState, - text: String + context: AccountContext, + text: String, + entities: [MessageTextEntity], + action: @escaping (Action) -> Void ) { self.externalState = externalState + self.context = context self.text = text + self.entities = entities + self.action = action } static func ==(lhs: StoryContentCaptionComponent, rhs: StoryContentCaptionComponent) -> Bool { + if lhs.externalState !== rhs.externalState { + return false + } + if lhs.context !== rhs.context { + return false + } if lhs.text != rhs.text { return false } + if lhs.entities != rhs.entities { + return false + } return true } @@ -70,7 +103,9 @@ final class StoryContentCaptionComponent: Component { private let shadowGradientLayer: SimpleGradientLayer private let shadowPlainLayer: SimpleLayer - private let text = ComponentView() + private var textNode: TextNodeWithEntities? + private var linkHighlightingNode: LinkHighlightingNode? + private var dustNode: InvisibleInkDustNode? private var component: StoryContentCaptionComponent? private weak var state: EmptyComponentState? @@ -133,10 +168,10 @@ final class StoryContentCaptionComponent: Component { if !self.bounds.contains(point) { return nil } - if let textView = self.text.view { + if let textView = self.textNode?.textNode.view { let textLocalPoint = self.convert(point, to: textView) if textLocalPoint.y >= -7.0 { - return self.scrollView + return textView } } @@ -205,6 +240,103 @@ final class StoryContentCaptionComponent: Component { } } + @objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + switch recognizer.state { + case .ended: + if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, let component = self.component, let textNode = self.textNode { + switch gesture { + case .tap: + let titleFrame = textNode.textNode.view.bounds + if titleFrame.contains(location) { + if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) { + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(self.dustNode?.isRevealed ?? true) { + return + } else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { + var concealed = true + if let (attributeText, fullText) = textNode.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) { + concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText) + } + component.action(.url(url: url, concealed: concealed)) + return + } else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { + component.action(.peerMention(peerId: peerMention.peerId, mention: peerMention.mention)) + return + } else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { + component.action(.textMention(peerName)) + return + } else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag { + component.action(.hashtag(hashtag.peerName, hashtag.hashtag)) + return + } else if let bankCard = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BankCard)] as? String { + component.action(.bankCard(bankCard)) + return + } else if let emoji = attributes[NSAttributedString.Key(rawValue: ChatTextInputAttributes.customEmoji.rawValue)] as? ChatTextInputTextCustomEmojiAttribute, let file = emoji.file { + component.action(.customEmoji(file)) + return + } + } + } + + self.expand(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + default: + break + } + } + default: + break + } + } + + private func updateTouchesAtPoint(_ point: CGPoint?) { + guard let textNode = self.textNode else { + return + } + var rects: [CGRect]? + var spoilerRects: [CGRect]? + if let point = point { + let textNodeFrame = textNode.textNode.bounds + if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { + let possibleNames: [String] = [ + TelegramTextAttributes.URL, + TelegramTextAttributes.PeerMention, + TelegramTextAttributes.PeerTextMention, + TelegramTextAttributes.BotCommand, + TelegramTextAttributes.Hashtag, + TelegramTextAttributes.Timecode, + TelegramTextAttributes.BankCard + ] + for name in possibleNames { + if let _ = attributes[NSAttributedString.Key(rawValue: name)] { + rects = textNode.textNode.attributeRects(name: name, at: index) + break + } + } + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)] { + spoilerRects = textNode.textNode.attributeRects(name: TelegramTextAttributes.Spoiler, at: index) + } + } + } + + if let spoilerRects = spoilerRects, !spoilerRects.isEmpty, let dustNode = self.dustNode, !dustNode.isRevealed { + } else if let rects = rects { + let linkHighlightingNode: LinkHighlightingNode + if let current = self.linkHighlightingNode { + linkHighlightingNode = current + } else { + linkHighlightingNode = LinkHighlightingNode(color: UIColor(white: 1.0, alpha: 0.5)) + self.linkHighlightingNode = linkHighlightingNode + self.scrollView.insertSubview(linkHighlightingNode.view, belowSubview: textNode.textNode.view) + } + linkHighlightingNode.frame = textNode.textNode.view.frame + linkHighlightingNode.updateRects(rects) + } else if let linkHighlightingNode = self.linkHighlightingNode { + self.linkHighlightingNode = nil + linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in + linkHighlightingNode?.removeFromSupernode() + }) + } + } + func update(component: StoryContentCaptionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.ignoreExternalState = true @@ -213,29 +345,64 @@ final class StoryContentCaptionComponent: Component { let sideInset: CGFloat = 16.0 let verticalInset: CGFloat = 7.0 - let textContainerSize = CGSize(width: availableSize.width - sideInset * 2.0 - 50.0, height: availableSize.height - verticalInset * 2.0) + let textContainerSize = CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height - verticalInset * 2.0) - let textSize = self.text.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.text, font: Font.regular(16.0), textColor: .white)), - maximumNumberOfLines: 0 - )), - environment: {}, - containerSize: textContainerSize + let attributedText = stringWithAppliedEntities( + component.text, + entities: component.entities, + baseColor: .white, + linkColor: .white, + baseFont: Font.regular(16.0), + linkFont: Font.regular(16.0), + boldFont: Font.semibold(16.0), + italicFont: Font.italic(16.0), + boldItalicFont: Font.semiboldItalic(16.0), + fixedFont: Font.monospace(16.0), + blockQuoteFont: Font.monospace(16.0), + message: nil ) + let makeLayout = TextNodeWithEntities.asyncLayout(self.textNode) + let textLayout = makeLayout(TextNodeLayoutArguments( + attributedString: attributedText, + maximumNumberOfLines: 0, + truncationType: .end, + constrainedSize: textContainerSize + )) + let maxHeight: CGFloat = 50.0 - let visibleTextHeight = min(maxHeight, textSize.height) - let textOverflowHeight: CGFloat = textSize.height - visibleTextHeight + let visibleTextHeight = min(maxHeight, textLayout.0.size.height) + let textOverflowHeight: CGFloat = textLayout.0.size.height - visibleTextHeight let scrollContentSize = CGSize(width: availableSize.width, height: availableSize.height + textOverflowHeight) - if let textView = self.text.view { - if textView.superview == nil { - self.scrollView.addSubview(textView) + let textNode = textLayout.1(TextNodeWithEntities.Arguments( + context: component.context, + cache: component.context.animationCache, + renderer: component.context.animationRenderer, + placeholderColor: UIColor(white: 0.2, alpha: 1.0), attemptSynchronous: true + )) + if self.textNode !== textNode { + self.textNode?.textNode.view.removeFromSuperview() + + self.textNode = textNode + if textNode.textNode.view.superview == nil { + self.scrollView.addSubview(textNode.textNode.view) + + let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) + recognizer.tapActionAtPoint = { point in + return .waitForSingleTap + } + recognizer.highlight = { [weak self] point in + guard let self else { + return + } + self.updateTouchesAtPoint(point) + } + textNode.textNode.view.addGestureRecognizer(recognizer) } - textView.frame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - visibleTextHeight - verticalInset), size: textSize) } + + textNode.textNode.frame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - visibleTextHeight - verticalInset), size: textLayout.0.size) self.itemLayout = ItemLayout( containerSize: availableSize, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 38752ed02a..8a8198194c 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -6,7 +6,6 @@ import AppBundle import ComponentDisplayAdapters import ReactionSelectionNode import EntityKeyboard -import StoryFooterPanelComponent import MessageInputPanelComponent import TelegramPresentationData import SwiftSignalKit @@ -22,6 +21,7 @@ import ImageCompression import ShareWithPeersScreen import PlainButtonComponent import TooltipUI +import PresentationDataUtils public final class StoryItemSetContainerComponent: Component { public final class ExternalState { @@ -242,7 +242,6 @@ public final class StoryItemSetContainerComponent: Component { var captionItem: CaptionItem? let inputPanel = ComponentView() - let footerPanel = ComponentView() let inputPanelExternalState = MessageInputPanelComponent.ExternalState() var displayViewList: Bool = false @@ -274,6 +273,8 @@ public final class StoryItemSetContainerComponent: Component { private weak var voiceMessagesRestrictedTooltipController: TooltipController? + let transitionCloneContainerView: UIView + override init(frame: CGRect) { self.sendMessageContext = StoryItemSetContainerSendMessage() @@ -294,6 +295,8 @@ public final class StoryItemSetContainerComponent: Component { self.closeButton = HighlightableButton() self.closeButtonIconView = UIImageView() + self.transitionCloneContainerView = UIView() + super.init(frame: frame) self.scrollView.delaysContentTouches = false @@ -648,8 +651,10 @@ public final class StoryItemSetContainerComponent: Component { return } if component.slice.peer.id == component.context.account.peerId { - self.displayViewList = true - self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + if let views = component.slice.item.storyItem.views, !views.seenPeers.isEmpty { + self.displayViewList = true + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + } } else { if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View { inputPanelView.activateInput() @@ -670,16 +675,6 @@ public final class StoryItemSetContainerComponent: Component { ) inputPanelView.layer.animateAlpha(from: 0.0, to: inputPanelView.alpha, duration: 0.28) } - if let footerPanelView = self.footerPanel.view { - footerPanelView.layer.animatePosition( - from: CGPoint(x: 0.0, y: self.bounds.height - footerPanelView.frame.minY), - to: CGPoint(), - duration: 0.3, - timingFunction: kCAMediaTimingFunctionSpring, - additive: true - ) - footerPanelView.layer.animateAlpha(from: 0.0, to: footerPanelView.alpha, duration: 0.28) - } if let viewListView = self.viewList?.view.view { viewListView.layer.animatePosition( from: CGPoint(x: 0.0, y: self.bounds.height - self.contentContainerView.frame.maxY), @@ -749,9 +744,7 @@ public final class StoryItemSetContainerComponent: Component { } func animateOut(transitionOut: StoryContainerScreen.TransitionOut, completion: @escaping () -> Void) { - self.closeButton.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - completion() - }) + var cleanups: [() -> Void] = [] if let inputPanelView = self.inputPanel.view { inputPanelView.layer.animatePosition( @@ -764,17 +757,6 @@ public final class StoryItemSetContainerComponent: Component { ) inputPanelView.layer.animateAlpha(from: inputPanelView.alpha, to: 0.0, duration: 0.3, removeOnCompletion: false) } - if let footerPanelView = self.footerPanel.view { - footerPanelView.layer.animatePosition( - from: CGPoint(), - to: CGPoint(x: 0.0, y: self.bounds.height - footerPanelView.frame.minY), - duration: 0.3, - timingFunction: kCAMediaTimingFunctionSpring, - removeOnCompletion: false, - additive: true - ) - footerPanelView.layer.animateAlpha(from: footerPanelView.alpha, to: 0.0, duration: 0.3, removeOnCompletion: false) - } if let viewListView = self.viewList?.view.view { viewListView.layer.animatePosition( from: CGPoint(), @@ -811,39 +793,75 @@ public final class StoryItemSetContainerComponent: Component { if let rightInfoView = self.rightInfoItem?.view.view { if transitionOut.destinationIsAvatar { let transitionView = transitionOut.transitionView - let transitionViewImpl = transitionView?.makeView() - if let transitionViewImpl { - self.insertSubview(transitionViewImpl, aboveSubview: self.contentContainerView) + + var transitionViewsImpl: [UIView] = [] + + if let transitionViewImpl = transitionView?.makeView() { + transitionViewsImpl.append(transitionViewImpl) + + let transitionSourceContainerView = UIView(frame: self.bounds) + transitionSourceContainerView.isUserInteractionEnabled = false + self.insertSubview(transitionSourceContainerView, aboveSubview: self.contentContainerView) + + transitionSourceContainerView.addSubview(transitionViewImpl) + + if let insertCloneTransitionView = transitionView?.insertCloneTransitionView { + if let transitionCloneViewImpl = transitionView?.makeView() { + transitionViewsImpl.append(transitionCloneViewImpl) + + let transitionCloneContainerView = self.transitionCloneContainerView + transitionCloneContainerView.isUserInteractionEnabled = false + insertCloneTransitionView(transitionCloneContainerView) + transitionCloneContainerView.frame = transitionCloneContainerView.convert(self.convert(self.bounds, to: nil), from: nil) + + transitionCloneContainerView.addSubview(transitionCloneViewImpl) + + transitionSourceContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, removeOnCompletion: false) + transitionCloneContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12) + + cleanups.append({ [weak transitionCloneContainerView] in + transitionCloneContainerView?.removeFromSuperview() + }) + } + } let rightInfoSourceFrame = rightInfoView.convert(rightInfoView.bounds, to: self) let positionKeyframes: [CGPoint] = generateParabollicMotionKeyframes(from: sourceLocalFrame.center, to: rightInfoSourceFrame.center, elevation: 0.0, duration: 0.3, curve: .spring, reverse: true) - transitionViewImpl.frame = rightInfoSourceFrame - transitionViewImpl.alpha = 0.0 - transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState( - sourceSize: rightInfoSourceFrame.size, - destinationSize: sourceLocalFrame.size, - progress: 0.0 - ), .immediate) + for transitionViewImpl in transitionViewsImpl { + transitionViewImpl.frame = rightInfoSourceFrame + transitionViewImpl.alpha = 0.0 + transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState( + sourceSize: rightInfoSourceFrame.size, + destinationSize: sourceLocalFrame.size, + progress: 0.0 + ), .immediate) + } let transition = Transition(animation: .curve(duration: 0.3, curve: .spring)) - transitionViewImpl.alpha = 1.0 - transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + for transitionViewImpl in transitionViewsImpl { + transitionViewImpl.alpha = 1.0 + transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + } rightInfoView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame) + for transitionViewImpl in transitionViewsImpl { + transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame) + } - transitionViewImpl.layer.position = positionKeyframes[positionKeyframes.count - 1] - transitionViewImpl.layer.animateKeyframes(values: positionKeyframes.map { NSValue(cgPoint: $0) }, duration: 0.3, keyPath: "position", removeOnCompletion: false, additive: false) - transitionViewImpl.layer.animateBounds(from: CGRect(origin: CGPoint(), size: rightInfoSourceFrame.size), to: CGRect(origin: CGPoint(), size: sourceLocalFrame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - - transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState( - sourceSize: rightInfoSourceFrame.size, - destinationSize: sourceLocalFrame.size, - progress: 1.0 - ), transition) + for transitionViewImpl in transitionViewsImpl { + transitionViewImpl.layer.position = positionKeyframes[positionKeyframes.count - 1] + transitionViewImpl.layer.animateKeyframes(values: positionKeyframes.map { NSValue(cgPoint: $0) }, duration: 0.3, keyPath: "position", removeOnCompletion: false, additive: false) + transitionViewImpl.layer.animateBounds(from: CGRect(origin: CGPoint(), size: rightInfoSourceFrame.size), to: CGRect(origin: CGPoint(), size: sourceLocalFrame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + + transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState( + sourceSize: rightInfoSourceFrame.size, + destinationSize: sourceLocalFrame.size, + progress: 1.0 + ), transition) + } } let positionKeyframes: [CGPoint] = generateParabollicMotionKeyframes(from: innerSourceLocalFrame.center, to: rightInfoView.layer.position, elevation: 0.0, duration: 0.3, curve: .spring, reverse: true) @@ -867,30 +885,63 @@ public final class StoryItemSetContainerComponent: Component { if !transitionOut.destinationIsAvatar { let transitionView = transitionOut.transitionView - let transitionViewImpl = transitionView?.makeView() - if let transitionViewImpl { - self.insertSubview(transitionViewImpl, belowSubview: self.contentContainerView) + + var transitionViewsImpl: [UIView] = [] + + if let transitionViewImpl = transitionView?.makeView() { + transitionViewsImpl.append(transitionViewImpl) - transitionViewImpl.frame = contentSourceFrame - transitionViewImpl.alpha = 0.0 - transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState( - sourceSize: contentSourceFrame.size, - destinationSize: sourceLocalFrame.size, - progress: 0.0 - ), .immediate) + let transitionSourceContainerView = UIView(frame: self.bounds) + transitionSourceContainerView.isUserInteractionEnabled = false + self.insertSubview(transitionSourceContainerView, belowSubview: self.contentContainerView) + + transitionSourceContainerView.addSubview(transitionViewImpl) + + if let insertCloneTransitionView = transitionView?.insertCloneTransitionView { + if let transitionCloneViewImpl = transitionView?.makeView() { + transitionViewsImpl.append(transitionCloneViewImpl) + + let transitionCloneContainerView = self.transitionCloneContainerView + transitionCloneContainerView.isUserInteractionEnabled = false + insertCloneTransitionView(transitionCloneContainerView) + + transitionCloneContainerView.addSubview(transitionCloneViewImpl) + + transitionSourceContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, removeOnCompletion: false) + transitionCloneContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12) + + cleanups.append({ [weak transitionCloneContainerView] in + transitionCloneContainerView?.removeFromSuperview() + }) + } + } + + for transitionViewImpl in transitionViewsImpl { + transitionViewImpl.frame = contentSourceFrame + transitionViewImpl.alpha = 0.0 + transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState( + sourceSize: contentSourceFrame.size, + destinationSize: sourceLocalFrame.size, + progress: 0.0 + ), .immediate) + } let transition = Transition(animation: .curve(duration: 0.3, curve: .spring)) - transitionViewImpl.alpha = 1.0 - transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + for transitionViewImpl in transitionViewsImpl { + transitionViewImpl.alpha = 1.0 + transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + } self.contentContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame) - transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState( - sourceSize: contentSourceFrame.size, - destinationSize: sourceLocalFrame.size, - progress: 1.0 - ), transition) + for transitionViewImpl in transitionViewsImpl { + transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame) + transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState( + sourceSize: contentSourceFrame.size, + destinationSize: sourceLocalFrame.size, + progress: 1.0 + ), transition) + } } } @@ -919,6 +970,14 @@ public final class StoryItemSetContainerComponent: Component { visibleItemView.layer.animateScale(from: 1.0, to: innerScale, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) } } + + self.closeButton.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + for cleanup in cleanups { + cleanup() + } + cleanups.removeAll() + completion() + }) } func update(component: StoryItemSetContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { @@ -1112,238 +1171,6 @@ public final class StoryItemSetContainerComponent: Component { containerSize: CGSize(width: inputPanelAvailableWidth, height: 200.0) ) - /*let footerPanelSize = self.footerPanel.update( - transition: transition, - component: AnyComponent(StoryFooterPanelComponent( - context: component.context, - storyItem: currentItem?.storyItem, - expandViewStats: { [weak self] in - guard let self else { - return - } - - if !self.displayViewList { - self.displayViewList = true - self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) - } - }, - deleteAction: { [weak self] in - guard let self, let component = self.component else { - return - } - - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - let actionSheet = ActionSheetController(presentationData: presentationData) - - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: "Delete", color: .destructive, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - - guard let self, let component = self.component else { - return - } - component.delete() - - /*if let currentSlice = self.currentSlice, let index = currentSlice.items.firstIndex(where: { $0.id == focusedItemId }) { - let item = currentSlice.items[index] - - if currentSlice.items.count == 1 { - component.navigateToItemSet(.next) - } else { - var nextIndex: Int = index + 1 - if nextIndex >= currentSlice.items.count { - nextIndex = currentSlice.items.count - 1 - } - self.focusedItemId = currentSlice.items[nextIndex].id - - currentSlice.items[nextIndex].markAsSeen?() - - self.state?.updated(transition: .immediate) - } - - item.delete?() - }*/ - }) - ]), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ]) - ]) - - actionSheet.dismissed = { [weak self] _ in - guard let self else { - return - } - self.actionSheet = nil - self.updateIsProgressPaused() - } - self.actionSheet = actionSheet - self.updateIsProgressPaused() - - component.presentController(actionSheet) - }, - moreAction: { [weak self] sourceView, gesture in - guard let self, let component = self.component, let controller = component.controller() else { - return - } - - var items: [ContextMenuItem] = [] - - let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0 - - let privacyText: String - switch component.slice.item.storyItem.privacy?.base { - case .closeFriends: - if additionalCount != 0 { - privacyText = "Close Friends (+\(additionalCount)" - } else { - privacyText = "Close Friends" - } - case .contacts: - if additionalCount != 0 { - privacyText = "Contacts (+\(additionalCount)" - } else { - privacyText = "Contacts" - } - case .nobody: - if additionalCount != 0 { - if additionalCount == 1 { - privacyText = "\(additionalCount) Person" - } else { - privacyText = "\(additionalCount) People" - } - } else { - privacyText = "Only Me" - } - default: - privacyText = "Everyone" - } - - items.append(.action(ContextMenuActionItem(text: "Who can see", textLayout: .secondLineWithValue(privacyText), icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self else { - return - } - self.openItemPrivacySettings() - }))) - - items.append(.action(ContextMenuActionItem(text: "Edit Story", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self else { - return - } - self.openStoryEditing() - }))) - - items.append(.separator) - - component.controller()?.forEachController { c in - if let c = c as? UndoOverlayController { - c.dismiss() - } - return true - } - - items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? "Remove from profile" : "Save to profile", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Chat/Context Menu/Check" : "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self, let component = self.component else { - return - } - - let _ = component.context.engine.messages.updateStoriesArePinned(ids: [component.slice.item.storyItem.id: component.slice.item.storyItem], isPinned: !component.slice.item.storyItem.isPinned).start() - - if component.slice.item.storyItem.isPinned { - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - self.component?.presentController(UndoOverlayController( - presentationData: presentationData, - content: .info(title: nil, text: "Story removed from your profile", timeout: nil), - elevatedLayout: false, - animateInAsReplacement: false, - action: { _ in return false } - )) - } else { - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - self.component?.presentController(UndoOverlayController( - presentationData: presentationData, - content: .info(title: "Story saved to your profile", text: "Saved stories can be viewed by others on your profile until you remove them.", timeout: nil), - elevatedLayout: false, - animateInAsReplacement: false, - action: { _ in return false } - )) - } - }))) - items.append(.action(ContextMenuActionItem(text: "Save image", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) - }, action: { _, a in - a(.default) - }))) - - if component.slice.item.storyItem.isPublic { - items.append(.action(ContextMenuActionItem(text: "Copy link", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self, let component = self.component else { - return - } - - let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id) - |> deliverOnMainQueue).start(next: { [weak self] link in - guard let self, let component = self.component else { - return - } - if let link { - UIPasteboard.general.string = link - - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - component.presentController(UndoOverlayController( - presentationData: presentationData, - content: .linkCopied(text: "Link copied."), - elevatedLayout: false, - animateInAsReplacement: false, - action: { _ in return false } - )) - } - }) - }))) - items.append(.action(ContextMenuActionItem(text: "Share", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) - }, action: { _, a in - a(.default) - }))) - } - - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - contextController.dismissed = { [weak self] in - guard let self else { - return - } - self.contextController = nil - self.updateIsProgressPaused() - } - self.contextController = contextController - self.updateIsProgressPaused() - controller.present(contextController, in: .window(.root)) - } - )), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: 200.0) - )*/ - let bottomContentInsetWithoutInput = bottomContentInset var viewListInset: CGFloat = 0.0 @@ -1379,8 +1206,10 @@ public final class StoryItemSetContainerComponent: Component { let outerExpansionFraction: CGFloat if self.displayViewList { outerExpansionFraction = 1.0 - } else { + } else if let views = component.slice.item.storyItem.views, !views.seenPeers.isEmpty { outerExpansionFraction = component.verticalPanFraction + } else { + outerExpansionFraction = 0.0 } viewList.view.parentState = state @@ -1762,7 +1591,7 @@ public final class StoryItemSetContainerComponent: Component { if let view = currentCenterInfoItem.view.view { var animateIn = false if view.superview == nil { - self.contentContainerView.addSubview(view) + self.contentContainerView.insertSubview(view, belowSubview: self.closeButton) animateIn = true } transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: centerInfoItemSize)) @@ -1857,7 +1686,38 @@ public final class StoryItemSetContainerComponent: Component { transition: captionItemTransition, component: AnyComponent(StoryContentCaptionComponent( externalState: captionItem.externalState, - text: component.slice.item.storyItem.text + context: component.context, + text: component.slice.item.storyItem.text, + entities: component.slice.item.storyItem.entities, + action: { [weak self] action in + guard let self, let component = self.component else { + return + } + switch action { + case let .url(url, concealed): + openUserGeneratedUrl(context: component.context, peerId: component.slice.peer.id, url: url, concealed: concealed, skipUrlAuth: false, skipConcealedAlert: false, present: { [weak self] c in + guard let self, let component = self.component, let controller = component.controller() else { + return + } + controller.present(c, in: .window(.root)) + }, openResolved: { [weak self] resolved in + guard let self else { + return + } + self.sendMessageContext.openResolved(view: self, result: resolved, forceExternal: false, concealed: concealed) + }) + case let .textMention(value): + self.sendMessageContext.openPeerMention(view: self, name: value) + case let .peerMention(peerId, _): + self.sendMessageContext.openPeerMention(view: self, peerId: peerId) + case let .hashtag(username, value): + self.sendMessageContext.openHashtag(view: self, hashtag: value, peerName: username) + case let .bankCard(value): + let _ = value + case .customEmoji: + break + } + } )), environment: {}, containerSize: CGSize(width: availableSize.width, height: contentFrame.height) @@ -2109,22 +1969,6 @@ public final class StoryItemSetContainerComponent: Component { } } - /*var footerPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - inputPanelBottomInset - footerPanelSize.height), size: footerPanelSize) - var footerPanelAlpha: CGFloat = (focusedItem?.isMy == true && !self.displayViewList) ? 1.0 : 0.0 - if case .regular = component.metrics.widthClass { - footerPanelAlpha *= component.visibilityFraction - } - if self.displayViewList { - footerPanelFrame.origin.y += footerPanelSize.height - } - if let footerPanelView = self.footerPanel.view { - if footerPanelView.superview == nil { - self.addSubview(footerPanelView) - } - transition.setFrame(view: footerPanelView, frame: footerPanelFrame) - transition.setAlpha(view: footerPanelView, alpha: footerPanelAlpha) - }*/ - let bottomGradientHeight = inputPanelSize.height + 32.0 transition.setFrame(layer: self.bottomContentGradientLayer, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: availableSize.height - component.inputHeight - bottomGradientHeight), size: CGSize(width: contentFrame.width, height: bottomGradientHeight))) //transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: inputPanelIsOverlay ? 1.0 : 0.0) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 04039aa915..4e8e8fab19 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -32,6 +32,8 @@ import TelegramPresentationData import ShareController import ChatPresentationInterfaceState import Postbox +import OverlayStatusController +import PresentationDataUtils final class StoryItemSetContainerSendMessage { weak var attachmentController: AttachmentController? @@ -46,6 +48,8 @@ final class StoryItemSetContainerSendMessage { var videoRecorder = Promise() let controllerNavigationDisposable = MetaDisposable() let enqueueMediaMessageDisposable = MetaDisposable() + let navigationActionDisposable = MetaDisposable() + let resolvePeerByNameDisposable = MetaDisposable() private(set) var isMediaRecordingLocked: Bool = false var wasRecordingDismissed: Bool = false @@ -53,6 +57,8 @@ final class StoryItemSetContainerSendMessage { deinit { self.controllerNavigationDisposable.dispose() self.enqueueMediaMessageDisposable.dispose() + self.navigationActionDisposable.dispose() + self.resolvePeerByNameDisposable.dispose() } func performSendMessageAction( @@ -1797,4 +1803,258 @@ final class StoryItemSetContainerSendMessage { let _ = (legacyAssetPickerEnqueueMessages(context: component.context, account: component.context.account, signals: signals) |> deliverOnMainQueue).start() } + + func openResolved(view: StoryItemSetContainerComponent.View, result: ResolvedUrl, forceExternal: Bool = false, concealed: Bool = false) { + guard let component = view.component, let navigationController = component.controller()?.navigationController as? NavigationController else { + return + } + let peerId = component.slice.peer.id + component.context.sharedContext.openResolvedUrl(result, context: component.context, urlContext: .chat(peerId: peerId, updatedPresentationData: nil), navigationController: navigationController, forceExternal: forceExternal, openPeer: { [weak self, weak view] peerId, navigation in + guard let self, let view, let component = view.component, let controller = component.controller() as? StoryContainerScreen else { + return + } + + controller.dismissWithoutTransitionOut() + + switch navigation { + case let .chat(_, subject, peekData): + if let navigationController = controller.navigationController as? NavigationController { + if case let .channel(channel) = peerId, channel.flags.contains(.isForum) { + component.context.sharedContext.navigateToForumChannel(context: component.context, peerId: peerId.id, navigationController: navigationController) + } else { + component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peerId), subject: subject, keepStack: .always, peekData: peekData, pushController: { [weak controller, weak navigationController] chatController, animated, completion in + guard let controller, let navigationController else { + return + } + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(where: { $0 === controller }) { + viewControllers.insert(chatController, at: index) + } else { + viewControllers.append(chatController) + } + navigationController.setViewControllers(viewControllers, animated: animated) + })) + } + } + case .info: + self.navigationActionDisposable.set((component.context.account.postbox.loadedPeerWithId(peerId.id) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak view] peer in + guard let view, let component = view.component else { + return + } + if peer.restrictionText(platform: "ios", contentSettings: component.context.currentContentSettings.with { $0 }) == nil { + if let infoController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + component.controller()?.push(infoController) + } + } + })) + case let .withBotStartPayload(startPayload): + if let navigationController = controller.navigationController as? NavigationController { + component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peerId), botStart: startPayload, keepStack: .always)) + } + case let .withAttachBot(attachBotStart): + if let navigationController = controller.navigationController as? NavigationController { + component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peerId), attachBotStart: attachBotStart)) + } + default: + break + } + }, + sendFile: nil, + sendSticker: nil, + requestMessageActionUrlAuth: nil, + joinVoiceChat: nil, + present: { [weak view] c, a in + guard let view, let component = view.component, let controller = component.controller() else { + return + } + controller.present(c, in: .window(.root), with: a) + }, dismissInput: { [weak view] in + guard let view else { + return + } + view.endEditing(true) + }, + contentContext: nil + ) + } + + func navigateToMessage(view: StoryItemSetContainerComponent.View, messageId: EngineMessage.Id, completion: (() -> Void)?) { + guard let component = view.component else { + return + } + let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)) + |> deliverOnMainQueue).start(next: { [weak view] peer in + guard let view, let component = view.component, let controller = component.controller(), let peer = peer else { + return + } + if let navigationController = controller.navigationController as? NavigationController { + component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: true, timecode: nil))) + } + completion?() + }) + } + + func openPeerMention(view: StoryItemSetContainerComponent.View, name: String, navigation: ChatControllerInteractionNavigateToPeer = .default, sourceMessageId: MessageId? = nil) { + guard let component = view.component, let parentController = component.controller() else { + return + } + let disposable = self.resolvePeerByNameDisposable + var resolveSignal = component.context.engine.peers.resolvePeerByName(name: name, ageLimit: 10) + + var cancelImpl: (() -> Void)? + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let progressSignal = Signal { [weak parentController] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + parentController?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + resolveSignal = resolveSignal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + cancelImpl = { [weak self] in + guard let self else { + return + } + self.resolvePeerByNameDisposable.set(nil) + } + disposable.set((resolveSignal + |> take(1) + |> mapToSignal { peer -> Signal in + return .single(peer?._asPeer()) + } + |> deliverOnMainQueue).start(next: { [weak view] peer in + guard let view, let component = view.component else { + return + } + if let peer = peer { + var navigation = navigation + if case .default = navigation { + if let peer = peer as? TelegramUser, peer.botInfo != nil { + navigation = .chat(textInputState: nil, subject: nil, peekData: nil) + } + } + self.openResolved(view: view, result: .peer(peer, navigation)) + } else { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + component.controller()?.present(textAlertController(context: component.context, updatedPresentationData: nil, title: nil, text: presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + } + })) + } + + func openHashtag(view: StoryItemSetContainerComponent.View, hashtag: String, peerName: String?) { + guard let component = view.component, let parentController = component.controller() else { + return + } + + let peerId = component.slice.peer.id + + var resolveSignal: Signal + if let peerName = peerName { + resolveSignal = component.context.engine.peers.resolvePeerByName(name: peerName) + |> mapToSignal { peer -> Signal in + if let peer = peer { + return .single(peer._asPeer()) + } else { + return .single(nil) + } + } + } else { + resolveSignal = component.context.account.postbox.loadedPeerWithId(peerId) + |> map(Optional.init) + } + var cancelImpl: (() -> Void)? + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let progressSignal = Signal { [weak parentController] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + parentController?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + resolveSignal = resolveSignal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + cancelImpl = { [weak self] in + guard let self else { + return + } + self.resolvePeerByNameDisposable.set(nil) + } + self.resolvePeerByNameDisposable.set((resolveSignal + |> deliverOnMainQueue).start(next: { [weak view] peer in + guard let view, let component = view.component else { + return + } + guard let navigationController = component.controller()?.navigationController as? NavigationController else { + return + } + if !hashtag.isEmpty { + let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer.flatMap(EnginePeer.init), query: hashtag) + navigationController.pushViewController(searchController) + } + })) + } + + func openPeerMention(view: StoryItemSetContainerComponent.View, peerId: EnginePeer.Id) { + guard let component = view.component else { + return + } + let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { [weak self, weak view] peer in + guard let self, let view, let peer else { + return + } + self.openPeer(view: view, peer: peer) + }) + } + + func openPeer(view: StoryItemSetContainerComponent.View, peer: EnginePeer, expandAvatar: Bool = false, peerTypes: ReplyMarkupButtonAction.PeerTypes? = nil) { + guard let component = view.component else { + return + } + + let peerSignal: Signal = component.context.account.postbox.loadedPeerWithId(peer.id) |> map(Optional.init) + self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak view] peer in + guard let view, let component = view.component, let peer else { + return + } + let mode: PeerInfoControllerMode = .generic + var expandAvatar = expandAvatar + if peer.smallProfileImage == nil { + expandAvatar = false + } + if component.metrics.widthClass == .regular { + expandAvatar = false + } + if let infoController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: false, requestsContext: nil) { + component.controller()?.push(infoController) + } + })) + } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift index 3872f92bd5..acbcf89799 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift @@ -1073,6 +1073,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { listContext.state, self.focusedIdUpdated.get() ) + //|> delay(0.4, queue: .mainQueue()) |> deliverOnMainQueue).start(next: { [weak self] peerAndVoiceMessages, state, _ in guard let self else { return diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift index d575b7b4b9..5500e1de51 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift @@ -148,7 +148,7 @@ final class StoryItemContentComponent: Component { return } - if case let .file(file) = currentMessageMedia, let peerReference = PeerReference(component.peer._asPeer()) { + if case let .file(file) = currentMessageMedia, let peerReference = PeerReference(component.peer._asPeer()), !"".isEmpty { if self.videoNode == nil { let videoNode = UniversalVideoNode( postbox: component.context.account.postbox, diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 5de648bb72..2e2c42c4fa 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -240,7 +240,8 @@ public final class StoryPeerListComponent: Component { }, updateView: { view, state, transition in (view as? StoryPeerListItemComponent.TransitionView)?.update(state: state, transition: transition) - } + }, + insertCloneTransitionView: nil )) } } diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift index 65809ecbaa..973fa03cc4 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift @@ -697,8 +697,9 @@ public final class StoryPeerListItemComponent: Component { var titleTransition = transition if previousComponent?.ringAnimation != nil && component.ringAnimation == nil { - if let titleView = self.title.view, let snapshotView = titleView.snapshotContentTree() { - titleView.superview?.addSubview(snapshotView) + if let titleView = self.title.view, let snapshotView = titleView.snapshotView(afterScreenUpdates: false) { + self.button.addSubview(snapshotView) + snapshotView.frame = titleView.frame snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() }) diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForwardSend.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForwardSend.imageset/Contents.json new file mode 100644 index 0000000000..5c235cd01d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForwardSend.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "arrowshape_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForwardSend.imageset/arrowshape_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForwardSend.imageset/arrowshape_30.pdf new file mode 100644 index 0000000000..752e9f0376 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForwardSend.imageset/arrowshape_30.pdf @@ -0,0 +1,167 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 3.866699 4.701172 cm +0.000000 0.000000 0.000000 scn +11.694376 14.486328 m +11.694376 13.656328 l +12.152772 13.656328 12.524376 14.027931 12.524376 14.486328 c +11.694376 14.486328 l +h +11.694376 6.236328 m +12.524376 6.236328 l +12.524376 6.456457 12.436930 6.667571 12.281274 6.823226 c +12.125619 6.978882 11.914505 7.066328 11.694376 7.066328 c +11.694376 6.236328 l +h +1.306852 1.959093 m +0.679964 2.503071 l +1.306852 1.959093 l +h +0.098354 2.362061 m +-0.727694 2.442957 l +0.098354 2.362061 l +h +13.546710 1.785391 m +12.979038 2.390907 l +13.546710 1.785391 l +h +21.838385 11.163817 m +22.406055 11.769334 l +21.838385 11.163817 l +h +21.838385 9.558837 m +22.406055 8.953321 l +21.838385 9.558837 l +h +12.524376 14.486328 m +12.524376 18.134773 l +10.864376 18.134773 l +10.864376 14.486328 l +12.524376 14.486328 l +h +12.979040 18.331747 m +21.270714 10.558302 l +22.406055 11.769334 l +14.114382 19.542780 l +12.979040 18.331747 l +h +21.270712 10.164352 m +12.979038 2.390907 l +14.114381 1.179874 l +22.406055 8.953321 l +21.270712 10.164352 l +h +12.524376 2.587883 m +12.524376 6.236328 l +10.864376 6.236328 l +10.864376 2.587883 l +12.524376 2.587883 l +h +11.694376 7.066328 m +5.780328 7.066328 2.387692 4.471083 0.679964 2.503071 c +1.933740 1.415115 l +3.380624 3.082527 6.338683 5.406328 11.694376 5.406328 c +11.694376 7.066328 l +h +0.924402 2.281166 m +1.118631 4.264515 1.688284 7.135999 3.270899 9.490906 c +4.820140 11.796155 7.367803 13.656328 11.694376 13.656328 c +11.694376 15.316328 l +6.783209 15.316328 3.732595 13.153936 1.893128 10.416837 c +0.087034 7.729396 -0.522455 4.538746 -0.727694 2.442957 c +0.924402 2.281166 l +h +0.679964 2.503071 m +0.721193 2.550583 0.766227 2.565825 0.782870 2.569241 c +0.790647 2.570837 0.793910 2.570457 0.793002 2.570513 c +0.792788 2.570526 0.792269 2.570576 0.791612 2.570700 c +0.790952 2.570822 0.790647 2.570927 0.790832 2.570868 c +0.791008 2.570812 0.792187 2.570429 0.794370 2.569412 c +0.796545 2.568398 0.800247 2.566505 0.805242 2.563288 c +0.814777 2.557144 0.832552 2.543856 0.852494 2.519463 c +0.873059 2.494308 0.894952 2.458349 0.909617 2.412121 c +0.924611 2.364859 0.928116 2.319090 0.924402 2.281166 c +-0.727694 2.442957 l +-0.807448 1.628555 -0.199440 1.144114 0.287034 0.989164 c +0.769746 0.835413 1.467871 0.878242 1.933740 1.415115 c +0.679964 2.503071 l +h +12.979038 2.390907 m +12.806601 2.229246 12.524376 2.351511 12.524376 2.587883 c +10.864376 2.587883 l +10.864376 0.898287 12.881754 0.024286 14.114381 1.179874 c +12.979038 2.390907 l +h +21.270714 10.558302 m +21.384493 10.451633 21.384495 10.271024 21.270712 10.164352 c +22.406055 8.953321 l +23.219378 9.715812 23.219381 11.006841 22.406055 11.769334 c +21.270714 10.558302 l +h +12.524376 18.134773 m +12.524376 18.371141 12.806600 18.493410 12.979040 18.331747 c +14.114382 19.542780 l +12.881757 20.698366 10.864376 19.824371 10.864376 18.134773 c +12.524376 18.134773 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 2921 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000003011 00000 n +0000003034 00000 n +0000003207 00000 n +0000003281 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3340 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index cac2031ec8..328916515b 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -91,6 +91,9 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { let fromScale: CGFloat = 1.0 let scale = toScale.interpolate(to: fromScale, amount: state.progress) transition.setTransform(view: view, transform: CATransform3DMakeScale(scale, scale, 1.0)) + }, + insertCloneTransitionView: { view in + params.addToTransitionSurface(view) } ), destinationRect: selectedTransitionNode.1, diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 772e939e54..b50af35ce4 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -37,6 +37,7 @@ import TextFormat import ChatTextLinkEditUI import AttachmentTextInputPanelNode import ChatEntityKeyboardInputNode +import HashtagSearchUI private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -1718,6 +1719,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { return inputPanelNode } + public func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String) -> ViewController { + return HashtagSearchController(context: context, peer: peer, query: query) + } + public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController { let mappedSource: PremiumSource switch source { diff --git a/submodules/TelegramUI/Sources/WebpagePreviewAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/WebpagePreviewAccessoryPanelNode.swift index 758aa60e85..0257a9b038 100644 --- a/submodules/TelegramUI/Sources/WebpagePreviewAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/WebpagePreviewAccessoryPanelNode.swift @@ -140,6 +140,9 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode { text = strings.Message_Theme } else if content.type == "video" { text = stringForMediaKind(.video, strings: self.strings).0.string + } else if content.type == "telegram_story" { + //TODO:localize + text = "Story" } else if let _ = content.image { text = stringForMediaKind(.image, strings: self.strings).0.string } From d5acba215b919ec4aac74782c0e9b08e7a103f7e Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 20 Jun 2023 09:36:50 +0300 Subject: [PATCH 02/13] Remove debugging --- .../Sources/SparseItemGrid.swift | 98 ++++++++++--------- .../Sources/StoryItemContentComponent.swift | 2 +- 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift index d9514e51ec..0af9264400 100644 --- a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift +++ b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift @@ -951,63 +951,65 @@ public final class SparseItemGrid: ASDisplayNode { var updateLayers: [SparseItemGridDisplayItem] = [] let visibleRange = layout.visibleItemRange(for: visibleBounds, count: items.count) - for index in visibleRange.minIndex ... visibleRange.maxIndex { - if let item = items.item(at: index) { - let itemFrame = layout.frame(at: index) - - let itemLayer: VisibleItem - if let current = self.visibleItems[item.id] { - itemLayer = current - updateLayers.append(itemLayer) - } else { - itemLayer = VisibleItem(layer: items.itemBinding.createLayer(), view: items.itemBinding.createView()) - self.visibleItems[item.id] = itemLayer - - bindItems.append(item) - bindLayers.append(itemLayer) - - if let layer = itemLayer.layer { - self.scrollView.layer.addSublayer(layer) - } else if let view = itemLayer.view { - self.scrollView.addSubview(view) + if visibleRange.maxIndex >= visibleRange.minIndex { + for index in visibleRange.minIndex ... visibleRange.maxIndex { + if let item = items.item(at: index) { + let itemFrame = layout.frame(at: index) + + let itemLayer: VisibleItem + if let current = self.visibleItems[item.id] { + itemLayer = current + updateLayers.append(itemLayer) + } else { + itemLayer = VisibleItem(layer: items.itemBinding.createLayer(), view: items.itemBinding.createView()) + self.visibleItems[item.id] = itemLayer + + bindItems.append(item) + bindLayers.append(itemLayer) + + if let layer = itemLayer.layer { + self.scrollView.layer.addSublayer(layer) + } else if let view = itemLayer.view { + self.scrollView.addSubview(view) + } } - } - - if itemLayer.needsShimmer { + + if itemLayer.needsShimmer { + let placeholderLayer: SparseItemGridShimmerLayer + if let current = itemLayer.shimmerLayer { + placeholderLayer = current + } else { + placeholderLayer = items.itemBinding.createShimmerLayer() ?? Shimmer.Layer() + self.scrollView.layer.insertSublayer(placeholderLayer, at: 0) + itemLayer.shimmerLayer = placeholderLayer + } + + placeholderLayer.frame = itemFrame + self.shimmer.update(colors: shimmerColors, layer: placeholderLayer, containerSize: layout.containerLayout.size, frame: itemFrame.offsetBy(dx: 0.0, dy: -visibleBounds.minY)) + placeholderLayer.update(size: itemFrame.size) + } else if let placeholderLayer = itemLayer.shimmerLayer { + itemLayer.shimmerLayer = nil + placeholderLayer.removeFromSuperlayer() + } + + validIds.insert(item.id) + + itemLayer.frame = itemFrame + } else { let placeholderLayer: SparseItemGridShimmerLayer - if let current = itemLayer.shimmerLayer { - placeholderLayer = current + if self.visiblePlaceholders.count > usedPlaceholderCount { + placeholderLayer = self.visiblePlaceholders[usedPlaceholderCount] } else { placeholderLayer = items.itemBinding.createShimmerLayer() ?? Shimmer.Layer() - self.scrollView.layer.insertSublayer(placeholderLayer, at: 0) - itemLayer.shimmerLayer = placeholderLayer + self.scrollView.layer.addSublayer(placeholderLayer) + self.visiblePlaceholders.append(placeholderLayer) } - + let itemFrame = layout.frame(at: index) placeholderLayer.frame = itemFrame self.shimmer.update(colors: shimmerColors, layer: placeholderLayer, containerSize: layout.containerLayout.size, frame: itemFrame.offsetBy(dx: 0.0, dy: -visibleBounds.minY)) placeholderLayer.update(size: itemFrame.size) - } else if let placeholderLayer = itemLayer.shimmerLayer { - itemLayer.shimmerLayer = nil - placeholderLayer.removeFromSuperlayer() + usedPlaceholderCount += 1 } - - validIds.insert(item.id) - - itemLayer.frame = itemFrame - } else { - let placeholderLayer: SparseItemGridShimmerLayer - if self.visiblePlaceholders.count > usedPlaceholderCount { - placeholderLayer = self.visiblePlaceholders[usedPlaceholderCount] - } else { - placeholderLayer = items.itemBinding.createShimmerLayer() ?? Shimmer.Layer() - self.scrollView.layer.addSublayer(placeholderLayer) - self.visiblePlaceholders.append(placeholderLayer) - } - let itemFrame = layout.frame(at: index) - placeholderLayer.frame = itemFrame - self.shimmer.update(colors: shimmerColors, layer: placeholderLayer, containerSize: layout.containerLayout.size, frame: itemFrame.offsetBy(dx: 0.0, dy: -visibleBounds.minY)) - placeholderLayer.update(size: itemFrame.size) - usedPlaceholderCount += 1 } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift index 5500e1de51..d575b7b4b9 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift @@ -148,7 +148,7 @@ final class StoryItemContentComponent: Component { return } - if case let .file(file) = currentMessageMedia, let peerReference = PeerReference(component.peer._asPeer()), !"".isEmpty { + if case let .file(file) = currentMessageMedia, let peerReference = PeerReference(component.peer._asPeer()) { if self.videoNode == nil { let videoNode = UniversalVideoNode( postbox: component.context.account.postbox, From 47bbfba78d828105497c781617199720f9598d1f Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 20 Jun 2023 10:05:01 +0300 Subject: [PATCH 03/13] Stories --- .../Sources/StoryContainerScreen.swift | 55 ++++++++----------- .../StoryItemSetContainerComponent.swift | 33 ++++++----- .../Sources/StorySetIndicatorComponent.swift | 10 +++- .../Sources/PeerInfo/PeerInfoScreen.swift | 4 +- 4 files changed, 53 insertions(+), 49 deletions(-) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index 6680a14df7..957486ffc0 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -171,6 +171,8 @@ private final class StoryContainerScreenComponent: Component { private var verticalPanState: ItemSetPanState? private var isHoldingTouch: Bool = false + private var transitionCloneMasterView: UIView + private var isAnimatingOut: Bool = false private var didAnimateOut: Bool = false @@ -184,8 +186,15 @@ private final class StoryContainerScreenComponent: Component { self.backgroundEffectView = BlurredBackgroundView(color: UIColor(rgb: 0x000000, alpha: 0.9), enableBlur: true) self.backgroundEffectView.layer.zPosition = -1001.0 + let transitionCloneMasterView = UIView() + transitionCloneMasterView.isHidden = true + transitionCloneMasterView.isUserInteractionEnabled = false + self.transitionCloneMasterView = transitionCloneMasterView + super.init(frame: frame) + self.addSubview(transitionCloneMasterView) + self.layer.addSublayer(self.backgroundLayer) let horizontalPanRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in @@ -528,7 +537,12 @@ private final class StoryContainerScreenComponent: Component { let transitionOutCompleted = transitionOut.completed let focusedItemPromise = component.focusedItemPromise - itemSetComponentView.animateOut(transitionOut: transitionOut, completion: { + + let transitionCloneMasterView = self.transitionCloneMasterView + transitionCloneMasterView.isHidden = false + self.transitionCloneMasterView = UIView() + + itemSetComponentView.animateOut(transitionOut: transitionOut, transitionCloneMasterView: transitionCloneMasterView, completion: { completion() transitionOutCompleted() focusedItemPromise.set(.single(nil)) @@ -556,37 +570,6 @@ private final class StoryContainerScreenComponent: Component { self.didAnimateOut = true } - private func updatePreloads() { - /*var validIds: [AnyHashable] = [] - if let currentSlice = self.currentSlice, let focusedItemId = self.focusedItemId, let currentIndex = currentSlice.items.firstIndex(where: { $0.id == focusedItemId }) { - for i in 0 ..< 2 { - var nextIndex: Int = currentIndex + 1 + i - nextIndex = max(0, min(nextIndex, currentSlice.items.count - 1)) - if nextIndex != currentIndex { - let nextItem = currentSlice.items[nextIndex] - - validIds.append(nextItem.id) - if self.preloadContexts[nextItem.id] == nil { - if let signal = nextItem.preload { - self.preloadContexts[nextItem.id] = signal.start() - } - } - } - } - } - - var removeIds: [AnyHashable] = [] - for (id, disposable) in self.preloadContexts { - if !validIds.contains(id) { - removeIds.append(id) - disposable.dispose() - } - } - for id in removeIds { - self.preloadContexts.removeValue(forKey: id) - }*/ - } - func update(component: StoryContainerScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { if self.didAnimateOut { return availableSize @@ -623,6 +606,8 @@ private final class StoryContainerScreenComponent: Component { self.component = component self.state = state + transition.setFrame(view: self.transitionCloneMasterView, frame: CGRect(origin: CGPoint(), size: availableSize)) + transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(), size: availableSize)) transition.setFrame(view: self.backgroundEffectView, frame: CGRect(origin: CGPoint(), size: availableSize)) @@ -860,6 +845,8 @@ private final class StoryContainerScreenComponent: Component { itemSetComponentView.layer.isDoubleSided = false itemSetView.addSubview(itemSetComponentView) itemSetView.layer.addSublayer(itemSetView.tintLayer) + + self.transitionCloneMasterView.addSubview(itemSetComponentView.transitionCloneContainerView) } itemSetTransition.setPosition(view: itemSetView, position: itemFrame.center.offsetBy(dx: 0.0, dy: dismissPanOffset)) @@ -986,6 +973,10 @@ private final class StoryContainerScreenComponent: Component { if !validIds.contains(id) { removedIds.append(id) itemSetView.removeFromSuperview() + + if let view = itemSetView.view.view as? StoryItemSetContainerComponent.View { + view.transitionCloneContainerView.removeFromSuperview() + } } } for id in removedIds { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index d5cd3dfdab..4e979c1f02 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -743,7 +743,7 @@ public final class StoryItemSetContainerComponent: Component { } } - func animateOut(transitionOut: StoryContainerScreen.TransitionOut, completion: @escaping () -> Void) { + func animateOut(transitionOut: StoryContainerScreen.TransitionOut, transitionCloneMasterView: UIView, completion: @escaping () -> Void) { var cleanups: [() -> Void] = [] if let inputPanelView = self.inputPanel.view { @@ -809,18 +809,20 @@ public final class StoryItemSetContainerComponent: Component { if let transitionCloneViewImpl = transitionView?.makeView() { transitionViewsImpl.append(transitionCloneViewImpl) - let transitionCloneContainerView = self.transitionCloneContainerView - transitionCloneContainerView.isUserInteractionEnabled = false - insertCloneTransitionView(transitionCloneContainerView) - transitionCloneContainerView.frame = transitionCloneContainerView.convert(self.convert(self.bounds, to: nil), from: nil) + transitionCloneMasterView.isUserInteractionEnabled = false + let transitionCloneMasterGlobalFrame = transitionCloneMasterView.convert(transitionCloneMasterView.bounds, to: nil) + insertCloneTransitionView(transitionCloneMasterView) + if let newParentView = transitionCloneMasterView.superview { + newParentView.frame = newParentView.convert(transitionCloneMasterGlobalFrame, from: nil) + } - transitionCloneContainerView.addSubview(transitionCloneViewImpl) + self.transitionCloneContainerView.addSubview(transitionCloneViewImpl) transitionSourceContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, removeOnCompletion: false) - transitionCloneContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12) + self.transitionCloneContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12) - cleanups.append({ [weak transitionCloneContainerView] in - transitionCloneContainerView?.removeFromSuperview() + cleanups.append({ [weak transitionCloneMasterView] in + transitionCloneMasterView?.removeFromSuperview() }) } } @@ -901,14 +903,17 @@ public final class StoryItemSetContainerComponent: Component { if let transitionCloneViewImpl = transitionView?.makeView() { transitionViewsImpl.append(transitionCloneViewImpl) - let transitionCloneContainerView = self.transitionCloneContainerView - transitionCloneContainerView.isUserInteractionEnabled = false - insertCloneTransitionView(transitionCloneContainerView) + transitionCloneMasterView.isUserInteractionEnabled = false + let transitionCloneMasterGlobalFrame = transitionCloneMasterView.convert(transitionCloneMasterView.bounds, to: nil) + insertCloneTransitionView(transitionCloneMasterView) + if let newParentView = transitionCloneMasterView.superview { + transitionCloneMasterView.frame = newParentView.convert(transitionCloneMasterGlobalFrame, from: nil) + } - transitionCloneContainerView.addSubview(transitionCloneViewImpl) + self.transitionCloneContainerView.addSubview(transitionCloneViewImpl) transitionSourceContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, removeOnCompletion: false) - transitionCloneContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12) + self.transitionCloneContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12) cleanups.append({ [weak transitionCloneContainerView] in transitionCloneContainerView?.removeFromSuperview() diff --git a/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/Sources/StorySetIndicatorComponent.swift b/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/Sources/StorySetIndicatorComponent.swift index 24de203d8c..df784c454e 100644 --- a/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/Sources/StorySetIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/Sources/StorySetIndicatorComponent.swift @@ -70,7 +70,15 @@ private final class ShapeImageView: UIView { let imageRect = CGRect(origin: CGPoint(x: item.position.x - item.diameter * 0.5, y: item.position.y - item.diameter * 0.5), size: CGSize(width: item.diameter, height: item.diameter)).insetBy(dx: params.lineWidth + params.innerSpacing, dy: params.lineWidth + params.innerSpacing) if let image = item.image { - context.draw(image.cgImage!, in: imageRect) + context.translateBy(x: imageRect.midX, y: imageRect.midY) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -imageRect.midX, y: -imageRect.midY) + + image.draw(in: imageRect, blendMode: .normal, alpha: 1.0) + + context.translateBy(x: imageRect.midX, y: imageRect.midY) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -imageRect.midX, y: -imageRect.midY) } else { context.setFillColor(UIColor.black.cgColor) context.fillEllipse(in: imageRect) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 23d887e9e5..43a5729f18 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -4111,10 +4111,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro sourceCornerRadius: transitionView.bounds.height * 0.5, sourceIsAvatar: true ) + self.headerNode.avatarListNode.avatarContainerNode.avatarNode.isHidden = true } - self.headerNode.avatarListNode.avatarContainerNode.avatarNode.isHidden = true - let storyContainerScreen = StoryContainerScreen( context: self.context, content: storyContent, @@ -4124,6 +4123,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return nil } if !fromAvatar { + self.headerNode.avatarListNode.avatarContainerNode.avatarNode.isHidden = false return nil } From dbb6fe27b809151b60b52d682656b8cae7848521 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 20 Jun 2023 10:48:02 +0300 Subject: [PATCH 04/13] Fix blurred first play --- .../Sources/PhotoResources.swift | 6 ++- .../Sources/StoryItemContentComponent.swift | 6 ++- .../Sources/NativeVideoContent.swift | 45 ++++++++++++------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index 2bc7c2294d..4f4d4025aa 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -477,7 +477,11 @@ private func chatMessageVideoDatas(postbox: Postbox, userLocation: MediaResource let fetchedDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(thumbnailRepresentation.resource), statsCategory: .video).start() let thumbnailDisposable = postbox.mediaBox.resourceData(thumbnailRepresentation.resource, attemptSynchronously: synchronousLoad).start(next: { next in let data: Data? = next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []) - subscriber.putNext(data ?? decodedThumbnailData) + if let data { + subscriber.putNext(data) + } else { + subscriber.putNext(decodedThumbnailData) + } }, error: subscriber.putError, completed: subscriber.putCompletion) return ActionDisposable { diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift index d575b7b4b9..cc7b9ca934 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift @@ -164,9 +164,13 @@ final class StoryItemContentComponent: Component { loopVideo: true, enableSound: true, beginWithAmbientSound: environment.sharedState.useAmbientMode, + useLargeThumbnail: true, + autoFetchFullSizeThumbnail: true, tempFilePath: nil, captureProtected: false, - storeAfterDownload: nil + hintDimensions: file.dimensions?.cgSize, + storeAfterDownload: nil, + displayImage: false ), priority: .gallery ) diff --git a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift index 68424ec9a0..8c0a716f9c 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift @@ -51,8 +51,9 @@ public final class NativeVideoContent: UniversalVideoContent { let captureProtected: Bool let hintDimensions: CGSize? let storeAfterDownload: (() -> Void)? + let displayImage: Bool - public init(id: NativeVideoContentId, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, imageReference: ImageMediaReference? = nil, streamVideo: MediaPlayerStreaming = .none, loopVideo: Bool = false, enableSound: Bool = true, beginWithAmbientSound: Bool = false, baseRate: Double = 1.0, fetchAutomatically: Bool = true, onlyFullSizeThumbnail: Bool = false, useLargeThumbnail: Bool = false, autoFetchFullSizeThumbnail: Bool = false, startTimestamp: Double? = nil, endTimestamp: Double? = nil, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor = .white, tempFilePath: String? = nil, isAudioVideoMessage: Bool = false, captureProtected: Bool = false, hintDimensions: CGSize? = nil, storeAfterDownload: (() -> Void)?) { + public init(id: NativeVideoContentId, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, imageReference: ImageMediaReference? = nil, streamVideo: MediaPlayerStreaming = .none, loopVideo: Bool = false, enableSound: Bool = true, beginWithAmbientSound: Bool = false, baseRate: Double = 1.0, fetchAutomatically: Bool = true, onlyFullSizeThumbnail: Bool = false, useLargeThumbnail: Bool = false, autoFetchFullSizeThumbnail: Bool = false, startTimestamp: Double? = nil, endTimestamp: Double? = nil, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor = .white, tempFilePath: String? = nil, isAudioVideoMessage: Bool = false, captureProtected: Bool = false, hintDimensions: CGSize? = nil, storeAfterDownload: (() -> Void)?, displayImage: Bool = true) { self.id = id self.nativeId = id self.userLocation = userLocation @@ -90,10 +91,11 @@ public final class NativeVideoContent: UniversalVideoContent { self.isAudioVideoMessage = isAudioVideoMessage self.hintDimensions = hintDimensions self.storeAfterDownload = storeAfterDownload + self.displayImage = displayImage } public func makeContentNode(postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { - return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, fileReference: self.fileReference, imageReference: self.imageReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, beginWithAmbientSound: self.beginWithAmbientSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, onlyFullSizeThumbnail: self.onlyFullSizeThumbnail, useLargeThumbnail: self.useLargeThumbnail, autoFetchFullSizeThumbnail: self.autoFetchFullSizeThumbnail, startTimestamp: self.startTimestamp, endTimestamp: self.endTimestamp, continuePlayingWithoutSoundOnLostAudioSession: self.continuePlayingWithoutSoundOnLostAudioSession, placeholderColor: self.placeholderColor, tempFilePath: self.tempFilePath, isAudioVideoMessage: self.isAudioVideoMessage, captureProtected: self.captureProtected, hintDimensions: self.hintDimensions, storeAfterDownload: self.storeAfterDownload) + return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, fileReference: self.fileReference, imageReference: self.imageReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, beginWithAmbientSound: self.beginWithAmbientSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, onlyFullSizeThumbnail: self.onlyFullSizeThumbnail, useLargeThumbnail: self.useLargeThumbnail, autoFetchFullSizeThumbnail: self.autoFetchFullSizeThumbnail, startTimestamp: self.startTimestamp, endTimestamp: self.endTimestamp, continuePlayingWithoutSoundOnLostAudioSession: self.continuePlayingWithoutSoundOnLostAudioSession, placeholderColor: self.placeholderColor, tempFilePath: self.tempFilePath, isAudioVideoMessage: self.isAudioVideoMessage, captureProtected: self.captureProtected, hintDimensions: self.hintDimensions, storeAfterDownload: self.storeAfterDownload, displayImage: self.displayImage) } public func isEqual(to other: UniversalVideoContent) -> Bool { @@ -121,6 +123,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent private let audioSessionManager: ManagedAudioSession private let isAudioVideoMessage: Bool private let captureProtected: Bool + private let displayImage: Bool private let player: MediaPlayer private var thumbnailPlayer: MediaPlayer? @@ -170,7 +173,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent private var shouldPlay: Bool = false - init(postbox: Postbox, audioSessionManager: ManagedAudioSession, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, imageReference: ImageMediaReference?, streamVideo: MediaPlayerStreaming, loopVideo: Bool, enableSound: Bool, beginWithAmbientSound: Bool, baseRate: Double, fetchAutomatically: Bool, onlyFullSizeThumbnail: Bool, useLargeThumbnail: Bool, autoFetchFullSizeThumbnail: Bool, startTimestamp: Double?, endTimestamp: Double?, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor, tempFilePath: String?, isAudioVideoMessage: Bool, captureProtected: Bool, hintDimensions: CGSize?, storeAfterDownload: (() -> Void)? = nil) { + init(postbox: Postbox, audioSessionManager: ManagedAudioSession, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, imageReference: ImageMediaReference?, streamVideo: MediaPlayerStreaming, loopVideo: Bool, enableSound: Bool, beginWithAmbientSound: Bool, baseRate: Double, fetchAutomatically: Bool, onlyFullSizeThumbnail: Bool, useLargeThumbnail: Bool, autoFetchFullSizeThumbnail: Bool, startTimestamp: Double?, endTimestamp: Double?, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor, tempFilePath: String?, isAudioVideoMessage: Bool, captureProtected: Bool, hintDimensions: CGSize?, storeAfterDownload: (() -> Void)? = nil, displayImage: Bool) { self.postbox = postbox self.userLocation = userLocation self.fileReference = fileReference @@ -182,6 +185,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent self.audioSessionManager = audioSessionManager self.isAudioVideoMessage = isAudioVideoMessage self.captureProtected = captureProtected + self.displayImage = displayImage self.imageNode = TransformImageNode() @@ -216,22 +220,25 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent self?.performActionAtEnd() } - self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, userLocation: userLocation, videoReference: fileReference, imageReference: imageReference, onlyFullSize: onlyFullSizeThumbnail, useLargeThumbnail: useLargeThumbnail, autoFetchFullSizeThumbnail: autoFetchFullSizeThumbnail || fileReference.media.isInstantVideo) |> map { [weak self] getSize, getData in - Queue.mainQueue().async { - if let strongSelf = self, strongSelf.dimensions == nil { - if let dimensions = getSize() { - strongSelf.dimensions = dimensions - strongSelf.dimensionsPromise.set(dimensions) - if let size = strongSelf.validLayout { - strongSelf.updateLayout(size: size, transition: .immediate) + if displayImage { + self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, userLocation: userLocation, videoReference: fileReference, imageReference: imageReference, onlyFullSize: onlyFullSizeThumbnail, useLargeThumbnail: useLargeThumbnail, autoFetchFullSizeThumbnail: autoFetchFullSizeThumbnail || fileReference.media.isInstantVideo) |> map { [weak self] getSize, getData in + Queue.mainQueue().async { + if let strongSelf = self, strongSelf.dimensions == nil { + if let dimensions = getSize() { + strongSelf.dimensions = dimensions + strongSelf.dimensionsPromise.set(dimensions) + if let size = strongSelf.validLayout { + strongSelf.updateLayout(size: size, transition: .immediate) + } } } } - } - return getData - }) + return getData + }) + + self.addSubnode(self.imageNode) + } - self.addSubnode(self.imageNode) self.addSubnode(self.playerNode) self._status.set(combineLatest(self.dimensionsPromise.get(), self.player.status) |> map { dimensions, status in @@ -261,8 +268,12 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent self._bufferingStatus.set(.single(nil)) } - self.imageNode.imageUpdated = { [weak self] _ in - self?._ready.set(.single(Void())) + if self.displayImage { + self.imageNode.imageUpdated = { [weak self] _ in + self?._ready.set(.single(Void())) + } + } else { + self._ready.set(.single(Void())) } if let startTimestamp = startTimestamp { From 34e8c54bb0cea5f8cb2e9bc2c1111205207906aa Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 20 Jun 2023 12:26:09 +0300 Subject: [PATCH 05/13] Stories --- .../Sources/OpenChatMessage.swift | 4 +-- .../Sources/ChatListSearchListPaneNode.swift | 8 ++--- .../Sources/ListMessageFileItemNode.swift | 2 +- .../Sources/ListMessageNode.swift | 2 +- .../Sources/ListMessageSnippetItemNode.swift | 2 +- .../Sources/TelegramBaseController.swift | 2 +- .../Sources/StorageUsageScreen.swift | 2 +- .../Stories/StoryContainerScreen/BUILD | 1 + .../Sources/StoryContainerScreen.swift | 33 +++++++++++++++++++ .../Sources/StoryContent.swift | 3 ++ .../StoryItemSetContainerComponent.swift | 19 ++++++++++- .../Stories/StoryContentComponent/BUILD | 1 - .../Sources/StoryItemContentComponent.swift | 22 ++++--------- .../TelegramUI/Sources/ChatController.swift | 8 ++--- .../ChatHistorySearchContainerNode.swift | 6 ++-- .../Sources/ChatMessageActionItemNode.swift | 2 +- .../ChatMessageBubbleContentNode.swift | 2 +- .../Sources/ChatMessageBubbleItemNode.swift | 4 +-- ...entLogPreviousDescriptionContentNode.swift | 2 +- ...ssageEventLogPreviousLinkContentNode.swift | 2 +- ...geEventLogPreviousMessageContentNode.swift | 2 +- .../ChatMessageFileBubbleContentNode.swift | 2 +- .../ChatMessageGameBubbleContentNode.swift | 2 +- ...MessageInstantVideoBubbleContentNode.swift | 2 +- .../ChatMessageInteractiveMediaNode.swift | 12 ++++--- .../ChatMessageInvoiceBubbleContentNode.swift | 2 +- .../Sources/ChatMessageItemView.swift | 2 +- .../ChatMessageMapBubbleContentNode.swift | 2 +- .../ChatMessageMediaBubbleContentNode.swift | 4 +-- ...ageProfilePhotoSuggestionContentNode.swift | 2 +- ...hatMessageWallpaperBubbleContentNode.swift | 2 +- .../ChatMessageWebpageBubbleContentNode.swift | 2 +- .../ChatRecentActionsControllerNode.swift | 4 +-- .../TelegramUI/Sources/GridMessageItem.swift | 2 +- .../TelegramUI/Sources/OpenChatMessage.swift | 16 ++++----- .../OverlayAudioPlayerControllerNode.swift | 2 +- .../PeerInfo/Panes/PeerInfoListPaneNode.swift | 2 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 2 +- 38 files changed, 119 insertions(+), 72 deletions(-) diff --git a/submodules/AccountContext/Sources/OpenChatMessage.swift b/submodules/AccountContext/Sources/OpenChatMessage.swift index df44668e5a..94f055b391 100644 --- a/submodules/AccountContext/Sources/OpenChatMessage.swift +++ b/submodules/AccountContext/Sources/OpenChatMessage.swift @@ -31,7 +31,7 @@ public final class OpenChatMessageParams { public let modal: Bool public let dismissInput: () -> Void public let present: (ViewController, Any?) -> Void - public let transitionNode: (MessageId, Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? + public let transitionNode: (MessageId, Media, Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? public let addToTransitionSurface: (UIView) -> Void public let openUrl: (String) -> Void public let openPeer: (Peer, ChatControllerInteractionNavigateToPeer) -> Void @@ -60,7 +60,7 @@ public final class OpenChatMessageParams { modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, - transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, + transitionNode: @escaping (MessageId, Media, Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: @escaping (String) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 158bcbd102..f287ad95e3 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -2069,7 +2069,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { interaction.dismissInput() }, present: { c, a in interaction.present(c, a) - }, transitionNode: { messageId, media in + }, transitionNode: { messageId, media, _ in return transitionNodeImpl?(messageId, EngineMedia(media)) }, addToTransitionSurface: { view in addToTransitionSurfaceImpl?(view) @@ -2204,12 +2204,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { interaction.dismissInput() }, present: { c, a in interaction.present(c, a) - }, transitionNode: { messageId, media in + }, transitionNode: { messageId, media, _ in var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? if let strongSelf = self { strongSelf.listNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ListMessageNode { - if let result = itemNode.transitionNode(id: messageId, media: media) { + if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: false) { transitionNode = result } } @@ -3031,7 +3031,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? self.listNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ListMessageNode { - if let result = itemNode.transitionNode(id: messageId, media: media._asMedia()) { + if let result = itemNode.transitionNode(id: messageId, media: media._asMedia(), adjustRect: false) { transitionNode = result } } diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index 7902eedd0c..23be5e376b 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -1398,7 +1398,7 @@ public final class ListMessageFileItemNode: ListMessageNode { } } - override public func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let item = self.item, let message = item.message, message.id == id, self.iconImageNode.supernode != nil { let iconImageNode = self.iconImageNode return (self.iconImageNode, self.iconImageNode.bounds, { [weak iconImageNode] in diff --git a/submodules/ListMessageItem/Sources/ListMessageNode.swift b/submodules/ListMessageItem/Sources/ListMessageNode.swift index 7c3cea93b0..af9ba2c767 100644 --- a/submodules/ListMessageItem/Sources/ListMessageNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageNode.swift @@ -28,7 +28,7 @@ public class ListMessageNode: ListViewItemNode { } } - public func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } diff --git a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift index b28c582f06..9196c1b089 100644 --- a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift @@ -775,7 +775,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { } } - override public func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let item = self.item, item.message?.id == id, self.iconImageNode.supernode != nil { let iconImageNode = self.iconImageNode return (self.iconImageNode, self.iconImageNode.bounds, { [weak iconImageNode] in diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index 2c89594ae0..4f344a515c 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -33,7 +33,7 @@ private func presentLiveLocationController(context: AccountContext, peerId: Peer controller?.view.endEditing(true) }, present: { c, a in controller?.present(c, in: .window(.root), with: a, blockInteraction: true) - }, transitionNode: { _, _ in + }, transitionNode: { _, _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift index 0a3f60fe09..11b12db4bb 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift @@ -2722,7 +2722,7 @@ final class StorageUsageScreenComponent: Component { } self.controller?()?.present(c, in: .window(.root), with: a, blockInteraction: true) }, - transitionNode: { [weak self] messageId, media in + transitionNode: { [weak self] messageId, media, _ in guard let self else { return nil } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index 1ed9e6218d..2147c87e85 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -61,6 +61,7 @@ swift_library( "//submodules/PresentationDataUtils", "//submodules/UrlEscaping", "//submodules/OverlayStatusController", + "//submodules/Utils/VolumeButtons", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index 957486ffc0..aa8df791cb 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -16,6 +16,7 @@ import EntityKeyboard import AsyncDisplayKit import AttachmentUI import simd +import VolumeButtons func hasFirstResponder(_ view: UIView) -> Bool { if view.isFirstResponder { @@ -173,6 +174,9 @@ private final class StoryContainerScreenComponent: Component { private var transitionCloneMasterView: UIView + private var volumeButtonsListener: VolumeButtonsListener? + private let volumeButtonsListenerShouldBeActvie = ValuePromise(false, ignoreRepeated: true) + private var isAnimatingOut: Bool = false private var didAnimateOut: Bool = false @@ -570,6 +574,24 @@ private final class StoryContainerScreenComponent: Component { self.didAnimateOut = true } + private func updateVolumeButtonMonitoring() { + if self.volumeButtonsListener == nil { + self.volumeButtonsListener = VolumeButtonsListener(shouldBeActive: self.volumeButtonsListenerShouldBeActvie.get(), valueChanged: { [weak self] in + guard let self, self.storyItemSharedState.useAmbientMode else { + return + } + self.storyItemSharedState.useAmbientMode = false + self.volumeButtonsListenerShouldBeActvie.set(false) + + for (_, itemSetView) in self.visibleItemSetViews { + if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View { + componentView.leaveAmbientMode() + } + } + }) + } + } + func update(component: StoryContainerScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { if self.didAnimateOut { return availableSize @@ -588,11 +610,22 @@ private final class StoryContainerScreenComponent: Component { } if update { var focusedItemId: StoryId? + var isVideo = false if let slice = component.content.stateValue?.slice { focusedItemId = StoryId(peerId: slice.peer.id, id: slice.item.storyItem.id) + if case .file = slice.item.storyItem.media { + isVideo = true + } } self.focusedItem.set(focusedItemId) + if self.storyItemSharedState.useAmbientMode { + self.volumeButtonsListenerShouldBeActvie.set(isVideo) + if isVideo { + self.updateVolumeButtonMonitoring() + } + } + if component.content.stateValue?.slice == nil { self.environment?.controller()?.dismiss() } else { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift index d90bb2da23..895feded21 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift @@ -25,6 +25,9 @@ public final class StoryContentItem { open func rewind() { } + + open func leaveAmbientMode() { + } } public final class Environment: Equatable { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 4e979c1f02..72c388e651 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -468,6 +468,18 @@ public final class StoryItemSetContainerComponent: Component { } } + func leaveAmbientMode() { + guard let component = self.component else { + return + } + guard let visibleItem = self.visibleItems[component.slice.item.id] else { + return + } + if let itemView = visibleItem.view.view as? StoryContentItem.View { + itemView.leaveAmbientMode() + } + } + @objc public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool { if otherGestureRecognizer is UIPanGestureRecognizer { return true @@ -1439,8 +1451,13 @@ public final class StoryItemSetContainerComponent: Component { }))) items.append(.action(ContextMenuActionItem(text: "Share", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) - }, action: { _, a in + }, action: { [weak self] _, a in a(.default) + + guard let self else { + return + } + self.sendMessageContext.performShareAction(view: self) }))) } diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD b/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD index e8d4b8eba6..de30c2ebad 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD @@ -23,7 +23,6 @@ swift_library( "//submodules/TelegramUniversalVideoContent", "//submodules/AvatarNode", "//submodules/Components/HierarchyTrackingLayer", - "//submodules/Utils/VolumeButtons", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift index cc7b9ca934..aff3a109c4 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryItemContentComponent.swift @@ -12,7 +12,6 @@ import UniversalMediaPlayer import TelegramUniversalVideoContent import StoryContainerScreen import HierarchyTrackingLayer -import VolumeButtons final class StoryItemContentComponent: Component { typealias EnvironmentType = StoryContentItem.Environment @@ -94,8 +93,6 @@ final class StoryItemContentComponent: Component { private let imageNode: TransformImageNode private var videoNode: UniversalVideoNode? - private var volumeButtonsListener: VolumeButtonsListener? - private var currentMessageMedia: EngineMedia? private var fetchDisposable: Disposable? @@ -197,19 +194,6 @@ final class StoryItemContentComponent: Component { if update { self.state?.updated(transition: .immediate) } - - if self.volumeButtonsListener == nil, let sharedState = self.environment?.sharedState, sharedState.useAmbientMode { - self.volumeButtonsListener = VolumeButtonsListener(shouldBeActive: .single(true), valueChanged: { [weak self] in - guard let self, let sharedState = self.environment?.sharedState, sharedState.useAmbientMode else { - return - } - sharedState.useAmbientMode = false - if let videoNode = self.videoNode { - videoNode.continueWithOverridingAmbientMode() - } - self.volumeButtonsListener = nil - }) - } } } } @@ -230,6 +214,12 @@ final class StoryItemContentComponent: Component { } } + override func leaveAmbientMode() { + if let videoNode = self.videoNode { + videoNode.continueWithOverridingAmbientMode() + } + } + private func updateIsProgressPaused() { if let videoNode = self.videoNode { var canPlay = !self.isProgressPaused && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index c29df3b8e3..01c31e3170 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -947,7 +947,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView { - if let result = itemNode.transitionNode(id: message.id, media: image) { + if let result = itemNode.transitionNode(id: message.id, media: image, adjustRect: false) { selectedNode = result } } @@ -1001,12 +1001,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.chatDisplayNode.dismissInput() }, present: { c, a in self?.present(c, in: .window(.root), with: a, blockInteraction: true) - }, transitionNode: { messageId, media in + }, transitionNode: { messageId, media, adjustRect in var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? if let strongSelf = self { strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView { - if let result = itemNode.transitionNode(id: messageId, media: media) { + if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: adjustRect) { selectedNode = result } } @@ -18243,7 +18243,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? self.chatDisplayNode.historyNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView { - if let result = itemNode.transitionNode(id: messageId, media: media) { + if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: false) { selectedNode = result } } diff --git a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift index f6797a8a97..470f874842 100644 --- a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift @@ -362,15 +362,15 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode { var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? self.listNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView { - if let result = itemNode.transitionNode(id: messageId, media: media) { + if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: false) { transitionNode = result } } else if let itemNode = itemNode as? ListMessageNode { - if let result = itemNode.transitionNode(id: messageId, media: media) { + if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: false) { transitionNode = result } } else if let itemNode = itemNode as? GridMessageItemNode { - if let result = itemNode.transitionNode(id: messageId, media: media) { + if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: false) { transitionNode = result } } diff --git a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift index dcd36a252d..6d06e24a80 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift @@ -89,7 +89,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { super.didLoad() } - override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let imageNode = self.imageNode, self.item?.message.id == messageId { return (imageNode, imageNode.bounds, { [weak self] in guard let strongSelf = self, let imageNode = strongSelf.imageNode else { diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift index 314e21b57d..7d2c2ea41c 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift @@ -199,7 +199,7 @@ class ChatMessageBubbleContentNode: ASDisplayNode { }) } - func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 933594207b..06472006c5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -4028,9 +4028,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode return super.hitTest(point, with: event) } - override func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { for contentNode in self.contentNodes { - if let result = contentNode.transitionNode(messageId: id, media: media) { + if let result = contentNode.transitionNode(messageId: id, media: media, adjustRect: adjustRect) { if self.contentNodes.count == 1 && self.contentNodes.first is ChatMessageMediaBubbleContentNode && self.nameNode == nil && self.adminBadgeNode == nil && self.forwardInfoNode == nil && self.replyInfoNode == nil { return (result.0, result.1, { [weak self] in guard let strongSelf = self, let resultView = result.2().0 else { diff --git a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift index 3fc3e8921c..4036766776 100644 --- a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift @@ -104,7 +104,7 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble return self.contentNode.updateHiddenMedia(media) } - override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift index 356472b7ec..ea50080731 100644 --- a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift @@ -99,7 +99,7 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent return self.contentNode.updateHiddenMedia(media) } - override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift index ea5a20ae3a..4acb2a4ca1 100644 --- a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift @@ -106,7 +106,7 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont return self.contentNode.updateHiddenMedia(media) } - override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift index 74bebd1f83..39a9a6d089 100644 --- a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift @@ -183,7 +183,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { } } - override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId { return self.interactiveFileNode.transitionNode(media: media) } else { diff --git a/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift index e8ae1ec54a..d4c4d55f08 100644 --- a/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift @@ -133,7 +133,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode { return self.contentNode.updateHiddenMedia(media) } - override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoBubbleContentNode.swift index 6e8a97de99..4f9bfd4deb 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoBubbleContentNode.swift @@ -364,7 +364,7 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { } } - override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index cb85943754..f3091fffbb 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -2208,14 +2208,18 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } } - func transitionNode() -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { - let bounds: CGRect + func transitionNode(adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + var bounds: CGRect if let currentImageArguments = self.currentImageArguments { - bounds = currentImageArguments.imageRect + if adjustRect { + bounds = currentImageArguments.drawingRect + } else { + bounds = currentImageArguments.imageRect + } } else { bounds = self.bounds } - return (self, bounds, { [weak self] in + return (adjustRect ? self.imageNode : self, bounds, { [weak self] in var badgeNodeHidden: Bool? if let badgeNode = self?.badgeNode { badgeNodeHidden = badgeNode.isHidden diff --git a/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift index dc495648f9..fa24d24139 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift @@ -129,7 +129,7 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode { return self.contentNode.updateHiddenMedia(media) } - override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index 6fd911f115..bfb83f0a95 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -782,7 +782,7 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol } } - func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } diff --git a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift index 97457ddc5c..167cbb4a05 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift @@ -476,7 +476,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } - override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId, let currentMedia = self.media, currentMedia.isEqual(to: media) { let imageNode = self.imageNode return (self.imageNode, self.imageNode.bounds, { [weak imageNode] in diff --git a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift index 4f99945eb8..39931215e8 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift @@ -398,13 +398,13 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } } - override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId, var currentMedia = self.media { if let invoice = currentMedia as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia { currentMedia = fullMedia } if currentMedia.isSemanticallyEqual(to: media) { - return self.interactiveImageNode.transitionNode() + return self.interactiveImageNode.transitionNode(adjustRect: adjustRect) } } return nil diff --git a/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift index fcd0fd106c..08ccb9b37e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift @@ -92,7 +92,7 @@ class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleContentNode self.fetchDisposable.dispose() } - override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId { return (self.imageNode, self.imageNode.bounds, { [weak self] in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift index b058041762..759b4108a2 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift @@ -130,7 +130,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { item.context.account.pendingPeerMediaUploadManager.cancel(peerId: item.message.id.peerId) } - override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId { return (self.imageNode, self.imageNode.bounds, { [weak self] in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index cdd8709545..7a047d172f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -523,7 +523,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } } - override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index ddfa0fa079..5f35edf58c 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -212,12 +212,12 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { //self?.chatDisplayNode.dismissInput() }, present: { c, a in self?.presentController(c, .window(.root), a) - }, transitionNode: { messageId, media in + }, transitionNode: { messageId, media, adjustRect in var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? if let strongSelf = self { strongSelf.listNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView { - if let result = itemNode.transitionNode(id: messageId, media: media) { + if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: adjustRect) { selectedNode = result } } diff --git a/submodules/TelegramUI/Sources/GridMessageItem.swift b/submodules/TelegramUI/Sources/GridMessageItem.swift index 619921d384..5d6c0903fb 100644 --- a/submodules/TelegramUI/Sources/GridMessageItem.swift +++ b/submodules/TelegramUI/Sources/GridMessageItem.swift @@ -380,7 +380,7 @@ final class GridMessageItemNode: GridItemNode { } } - func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.messageId == id { let imageNode = self.imageNode return (self.imageNode, self.imageNode.bounds, { [weak self, weak imageNode] in diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 328916515b..9c362b03b9 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -45,7 +45,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { var transitionIn: StoryContainerScreen.TransitionIn? = nil var selectedTransitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? - selectedTransitionNode = params.transitionNode(params.message.id, story) + selectedTransitionNode = params.transitionNode(params.message.id, story, true) if let selectedTransitionNode { transitionIn = StoryContainerScreen.TransitionIn( @@ -66,7 +66,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { var transitionOut: StoryContainerScreen.TransitionOut? = nil var selectedTransitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? - selectedTransitionNode = params.transitionNode(params.message.id, story) + selectedTransitionNode = params.transitionNode(params.message.id, story, true) if let selectedTransitionNode { transitionOut = StoryContainerScreen.TransitionOut( destinationView: selectedTransitionNode.0.view, @@ -92,9 +92,9 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { let scale = toScale.interpolate(to: fromScale, amount: state.progress) transition.setTransform(view: view, transform: CATransform3DMakeScale(scale, scale, 1.0)) }, - insertCloneTransitionView: { view in + insertCloneTransitionView: nil/*{ view in params.addToTransitionSurface(view) - } + }*/ ), destinationRect: selectedTransitionNode.1, destinationCornerRadius: 0.0, @@ -145,7 +145,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { params.present(gallery, InstantPageGalleryControllerPresentationArguments(transitionArguments: { entry in var selectedTransitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? if entry.index == centralIndex { - selectedTransitionNode = params.transitionNode(params.message.id, galleryMedia) + selectedTransitionNode = params.transitionNode(params.message.id, galleryMedia, false) } if let selectedTransitionNode = selectedTransitionNode { return GalleryTransitionArguments(transitionNode: selectedTransitionNode, addToTransitionSurface: params.addToTransitionSurface) @@ -277,7 +277,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { params.centralItemUpdated?(messageId) } params.present(gallery, GalleryControllerPresentationArguments(transitionArguments: { messageId, media in - let selectedTransitionNode = params.transitionNode(messageId, media) + let selectedTransitionNode = params.transitionNode(messageId, media, false) if let selectedTransitionNode = selectedTransitionNode { return GalleryTransitionArguments(transitionNode: selectedTransitionNode, addToTransitionSurface: params.addToTransitionSurface) } @@ -288,7 +288,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { case let .secretGallery(gallery): params.dismissInput() params.present(gallery, GalleryControllerPresentationArguments(transitionArguments: { messageId, media in - let selectedTransitionNode = params.transitionNode(messageId, media) + let selectedTransitionNode = params.transitionNode(messageId, media, false) if let selectedTransitionNode = selectedTransitionNode { return GalleryTransitionArguments(transitionNode: selectedTransitionNode, addToTransitionSurface: params.addToTransitionSurface) } @@ -332,7 +332,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { }, media) params.present(controller, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in - if let selectedTransitionNode = params.transitionNode(params.message.id, media) { + if let selectedTransitionNode = params.transitionNode(params.message.id, media, false) { return GalleryTransitionArguments(transitionNode: selectedTransitionNode, addToTransitionSurface: params.addToTransitionSurface) } return nil diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index c3eb48e856..6c1167e11f 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -296,7 +296,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu if let location = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, _, loadMore) = location { playlistLocation = .custom(messages: messages, at: id, loadMore: loadMore) } - return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation)) + return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation)) } return false } diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index 42bb81791f..bc6ceffaf8 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -502,7 +502,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? self.listNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ListMessageNode { - if let result = itemNode.transitionNode(id: messageId, media: media) { + if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: false) { transitionNode = result } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 43a5729f18..2df8c2e585 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -4174,7 +4174,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self?.view.endEditing(true) }, present: { [weak self] c, a in self?.controller?.present(c, in: .window(.root), with: a, blockInteraction: true) - }, transitionNode: { [weak self] messageId, media in + }, transitionNode: { [weak self] messageId, media, _ in guard let strongSelf = self else { return nil } From e9e5b4c7496db151df74301e17b93c603755fdd3 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 20 Jun 2023 13:00:27 +0300 Subject: [PATCH 06/13] Stories --- .../Sources/ChatListController.swift | 8 +++---- .../Sources/ContactsController.swift | 2 +- .../Sources/ContactsControllerNode.swift | 2 +- .../Sources/StorySubscriptionsView.swift | 2 +- .../Sources/Account/Account.swift | 8 +++---- .../Messages/StoryListContext.swift | 20 +++++++++--------- .../Messages/TelegramEngineMessages.swift | 8 +++---- .../TelegramCore/Sources/UpdatePeers.swift | 21 +++++++++++++++---- .../Sources/ChatListHeaderComponent.swift | 2 +- .../Sources/StoryChatContent.swift | 8 +++---- .../Sources/StoryPeerListComponent.swift | 16 +++++++------- .../ChatMessageInteractiveMediaNode.swift | 9 +++++++- .../ChatMessageWebpageBubbleContentNode.swift | 2 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 2 +- 14 files changed, 65 insertions(+), 45 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index bb4dbbabe1..b27ec858db 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1253,7 +1253,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return } - let storyContent = StoryContentContextImpl(context: self.context, includeHidden: false, focusedPeerId: peerId, singlePeer: false) + let storyContent = StoryContentContextImpl(context: self.context, isHidden: false, focusedPeerId: peerId, singlePeer: false) let _ = (storyContent.state |> filter { $0.slice != nil } |> take(1) @@ -1744,7 +1744,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController |> distinctUntilChanged self.preloadStorySubscriptionsDisposable = (combineLatest(queue: .mainQueue(), - self.context.engine.messages.preloadStorySubscriptions(includeHidden: false), + self.context.engine.messages.preloadStorySubscriptions(isHidden: false), self.context.sharedContext.automaticMediaDownloadSettings, automaticDownloadNetworkType ) @@ -1790,7 +1790,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.preloadStoryResourceDisposables.removeValue(forKey: id) } }) - self.storySubscriptionsDisposable = (self.context.engine.messages.storySubscriptions(includeHidden: false) + self.storySubscriptionsDisposable = (self.context.engine.messages.storySubscriptions(isHidden: false) |> deliverOnMainQueue).start(next: { [weak self] storySubscriptions in guard let self else { return @@ -2391,7 +2391,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return } - let storyContent = StoryContentContextImpl(context: self.context, includeHidden: false, focusedPeerId: peer?.id, singlePeer: false) + let storyContent = StoryContentContextImpl(context: self.context, isHidden: false, focusedPeerId: peer?.id, singlePeer: false) let _ = (storyContent.state |> take(1) |> deliverOnMainQueue).start(next: { [weak self] storyContentState in diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 1518c0b3e2..ca38aea4f9 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -515,7 +515,7 @@ public class ContactsController: ViewController { return } - let storyContent = StoryContentContextImpl(context: self.context, includeHidden: true, focusedPeerId: peer?.id, singlePeer: false) + let storyContent = StoryContentContextImpl(context: self.context, isHidden: true, focusedPeerId: peer?.id, singlePeer: false) let _ = (storyContent.state |> take(1) |> deliverOnMainQueue).start(next: { [weak self] storyContentState in diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 7274455207..5185863a3d 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -227,7 +227,7 @@ final class ContactsControllerNode: ASDisplayNode { return self.contentScrollingEnded(listView: listView) } - self.storySubscriptionsDisposable = (self.context.engine.messages.storySubscriptions(includeHidden: true) + self.storySubscriptionsDisposable = (self.context.engine.messages.storySubscriptions(isHidden: true) |> deliverOnMainQueue).start(next: { [weak self] storySubscriptions in guard let self else { return diff --git a/submodules/Postbox/Sources/StorySubscriptionsView.swift b/submodules/Postbox/Sources/StorySubscriptionsView.swift index b6c0b59a7f..deddb53237 100644 --- a/submodules/Postbox/Sources/StorySubscriptionsView.swift +++ b/submodules/Postbox/Sources/StorySubscriptionsView.swift @@ -1,7 +1,7 @@ import Foundation public enum PostboxStorySubscriptionsKey: Int32 { - case all = 0 + case hidden = 0 case filtered = 1 } diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 0c906f1779..f5bd08671c 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -975,7 +975,7 @@ public class Account { let networkStatsContext: NetworkStatsContext public let filteredStorySubscriptionsContext: StorySubscriptionsContext? - public let allStorySubscriptionsContext: StorySubscriptionsContext? + public let hiddenStorySubscriptionsContext: StorySubscriptionsContext? public init(accountManager: AccountManager, id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, networkArguments: NetworkInitializationArguments, peerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods, supplementary: Bool) { self.accountManager = accountManager @@ -995,11 +995,11 @@ public class Account { self.peerInputActivityManager = PeerInputActivityManager() if !supplementary { - self.filteredStorySubscriptionsContext = StorySubscriptionsContext(accountPeerId: peerId, postbox: postbox, network: network, includesHidden: false) - self.allStorySubscriptionsContext = StorySubscriptionsContext(accountPeerId: peerId, postbox: postbox, network: network, includesHidden: true) + self.filteredStorySubscriptionsContext = StorySubscriptionsContext(accountPeerId: peerId, postbox: postbox, network: network, isHidden: false) + self.hiddenStorySubscriptionsContext = StorySubscriptionsContext(accountPeerId: peerId, postbox: postbox, network: network, isHidden: true) } else { self.filteredStorySubscriptionsContext = nil - self.allStorySubscriptionsContext = nil + self.hiddenStorySubscriptionsContext = nil } self.callSessionManager = CallSessionManager(postbox: postbox, network: network, maxLayer: networkArguments.voipMaxLayer, versions: networkArguments.voipVersions, addUpdates: { [weak self] updates in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index f95de0c04e..e6ed812973 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -144,7 +144,7 @@ public final class StorySubscriptionsContext { private let queue: Queue private let postbox: Postbox private let network: Network - private let includesHidden: Bool + private let isHidden: Bool private var taskState = TaskState() @@ -155,12 +155,12 @@ public final class StorySubscriptionsContext { private let loadMoreDisposable = MetaDisposable() private let refreshTimerDisposable = MetaDisposable() - init(queue: Queue, accountPeerId: PeerId, postbox: Postbox, network: Network, includesHidden: Bool) { + init(queue: Queue, accountPeerId: PeerId, postbox: Postbox, network: Network, isHidden: Bool) { self.accountPeerId = accountPeerId self.queue = queue self.postbox = postbox self.network = network - self.includesHidden = includesHidden + self.isHidden = isHidden self.taskState.isRefreshScheduled = true @@ -183,7 +183,7 @@ public final class StorySubscriptionsContext { return } - let subscriptionsKey: PostboxStorySubscriptionsKey = self.includesHidden ? .all : .filtered + let subscriptionsKey: PostboxStorySubscriptionsKey = self.isHidden ? .hidden : .filtered if self.taskState.isRefreshScheduled { self.isLoading = true @@ -244,7 +244,7 @@ public final class StorySubscriptionsContext { private func loadImpl(isRefresh: Bool, stateMark: OpaqueStateMark) { var flags: Int32 = 0 - if self.includesHidden { + if self.isHidden { flags |= 1 << 2 } @@ -270,8 +270,8 @@ public final class StorySubscriptionsContext { let accountPeerId = self.accountPeerId - let includesHidden = self.includesHidden - let subscriptionsKey: PostboxStorySubscriptionsKey = self.includesHidden ? .all : .filtered + let isHidden = self.isHidden + let subscriptionsKey: PostboxStorySubscriptionsKey = self.isHidden ? .hidden : .filtered self.loadMoreDisposable.set((self.network.request(Api.functions.stories.getAllStories(flags: flags, state: state)) |> deliverOn(self.queue)).start(next: { [weak self] result in @@ -343,7 +343,7 @@ public final class StorySubscriptionsContext { } if isRefresh { - if !includesHidden { + if !isHidden { if !peerEntries.contains(where: { $0 == accountPeerId }) { transaction.setStoryItems(peerId: accountPeerId, items: []) } @@ -396,10 +396,10 @@ public final class StorySubscriptionsContext { private let queue = Queue(name: "StorySubscriptionsContext") private let impl: QueueLocalObject - init(accountPeerId: PeerId, postbox: Postbox, network: Network, includesHidden: Bool) { + init(accountPeerId: PeerId, postbox: Postbox, network: Network, isHidden: Bool) { let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { - Impl(queue: queue, accountPeerId: accountPeerId, postbox: postbox, network: network, includesHidden: includesHidden) + Impl(queue: queue, accountPeerId: accountPeerId, postbox: postbox, network: network, isHidden: isHidden) }) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 141f9c9e52..136d75faf3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -592,7 +592,7 @@ public extension TelegramEngine { }).start() } - public func storySubscriptions(includeHidden: Bool) -> Signal { + public func storySubscriptions(isHidden: Bool) -> Signal { let debugTimerSignal: Signal #if DEBUG && false debugTimerSignal = Signal.single(true) @@ -609,7 +609,7 @@ public extension TelegramEngine { debugTimerSignal = .single(true) #endif - let subscriptionsKey: PostboxStorySubscriptionsKey = includeHidden ? .all : .filtered + let subscriptionsKey: PostboxStorySubscriptionsKey = isHidden ? .hidden : .filtered let basicPeerKey = PostboxViewKey.basicPeer(self.account.peerId) let storySubscriptionsKey = PostboxViewKey.storySubscriptions(key: subscriptionsKey) @@ -759,9 +759,9 @@ public extension TelegramEngine { } } - public func preloadStorySubscriptions(includeHidden: Bool) -> Signal<[EngineMediaResource.Id: StoryPreloadInfo], NoError> { + public func preloadStorySubscriptions(isHidden: Bool) -> Signal<[EngineMediaResource.Id: StoryPreloadInfo], NoError> { let basicPeerKey = PostboxViewKey.basicPeer(self.account.peerId) - let subscriptionsKey: PostboxStorySubscriptionsKey = includeHidden ? .all : .filtered + let subscriptionsKey: PostboxStorySubscriptionsKey = isHidden ? .hidden : .filtered let storySubscriptionsKey = PostboxViewKey.storySubscriptions(key: subscriptionsKey) return self.account.postbox.combinedView(keys: [ basicPeerKey, diff --git a/submodules/TelegramCore/Sources/UpdatePeers.swift b/submodules/TelegramCore/Sources/UpdatePeers.swift index c5059b4000..c5d36188e9 100644 --- a/submodules/TelegramCore/Sources/UpdatePeers.swift +++ b/submodules/TelegramCore/Sources/UpdatePeers.swift @@ -60,12 +60,25 @@ public func updatePeers(transaction: Transaction, peers: [Peer], update: (Peer?, peerIds.removeAll(where: { $0 == updated.id }) transaction.replaceAllStorySubscriptions(key: .filtered, state: state, peerIds: peerIds) } + if transaction.storySubscriptionsContains(key: .hidden, peerId: updated.id) { + var (state, peerIds) = transaction.getAllStorySubscriptions(key: .filtered) + if !peerIds.contains(updated.id) { + peerIds.append(updated.id) + transaction.replaceAllStorySubscriptions(key: .hidden, state: state, peerIds: peerIds) + } + } } else { - if transaction.storySubscriptionsContains(key: .all, peerId: updated.id) && !transaction.storySubscriptionsContains(key: .filtered, peerId: updated.id) { - var (state, peerIds) = transaction.getAllStorySubscriptions(key: .all) + if transaction.storySubscriptionsContains(key: .hidden, peerId: updated.id) { + var (state, peerIds) = transaction.getAllStorySubscriptions(key: .filtered) peerIds.removeAll(where: { $0 == updated.id }) - peerIds.append(updated.id) - transaction.replaceAllStorySubscriptions(key: .filtered, state: state, peerIds: peerIds) + transaction.replaceAllStorySubscriptions(key: .hidden, state: state, peerIds: peerIds) + } + if transaction.storySubscriptionsContains(key: .filtered, peerId: updated.id) { + var (state, peerIds) = transaction.getAllStorySubscriptions(key: .filtered) + if !peerIds.contains(updated.id) { + peerIds.append(updated.id) + transaction.replaceAllStorySubscriptions(key: .filtered, state: state, peerIds: peerIds) + } } } } diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift index 43479b6fa9..18f5afa686 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift @@ -829,7 +829,7 @@ public final class ChatListHeaderComponent: Component { theme: component.theme, strings: component.strings, sideInset: component.sideInset, - includesHidden: component.storiesIncludeHidden, + useHiddenList: component.storiesIncludeHidden, storySubscriptions: storySubscriptions, collapseFraction: 1.0 - component.storiesFraction, unlockedFraction: 1.0 - component.storiesUnlockedFraction, diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift index acbcf89799..744ff0e840 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift @@ -358,7 +358,7 @@ public final class StoryContentContextImpl: StoryContentContext { } private let context: AccountContext - private let includeHidden: Bool + private let isHidden: Bool public private(set) var stateValue: StoryContentContextState? public var state: Signal { @@ -394,12 +394,12 @@ public final class StoryContentContextImpl: StoryContentContext { public init( context: AccountContext, - includeHidden: Bool, + isHidden: Bool, focusedPeerId: EnginePeer.Id?, singlePeer: Bool ) { self.context = context - self.includeHidden = includeHidden + self.isHidden = isHidden if let focusedPeerId { self.focusedItem = (focusedPeerId, nil) } @@ -493,7 +493,7 @@ public final class StoryContentContextImpl: StoryContentContext { self.updatePeerContexts() }) } else { - self.storySubscriptionsDisposable = (context.engine.messages.storySubscriptions(includeHidden: includeHidden) + self.storySubscriptionsDisposable = (context.engine.messages.storySubscriptions(isHidden: isHidden) |> deliverOnMainQueue).start(next: { [weak self] storySubscriptions in guard let self else { return diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 2e2c42c4fa..70cbcec0ad 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -24,7 +24,7 @@ public final class StoryPeerListComponent: Component { public let theme: PresentationTheme public let strings: PresentationStrings public let sideInset: CGFloat - public let includesHidden: Bool + public let useHiddenList: Bool public let storySubscriptions: EngineStorySubscriptions? public let collapseFraction: CGFloat public let unlockedFraction: CGFloat @@ -38,7 +38,7 @@ public final class StoryPeerListComponent: Component { theme: PresentationTheme, strings: PresentationStrings, sideInset: CGFloat, - includesHidden: Bool, + useHiddenList: Bool, storySubscriptions: EngineStorySubscriptions?, collapseFraction: CGFloat, unlockedFraction: CGFloat, @@ -51,7 +51,7 @@ public final class StoryPeerListComponent: Component { self.theme = theme self.strings = strings self.sideInset = sideInset - self.includesHidden = includesHidden + self.useHiddenList = useHiddenList self.storySubscriptions = storySubscriptions self.collapseFraction = collapseFraction self.unlockedFraction = unlockedFraction @@ -73,7 +73,7 @@ public final class StoryPeerListComponent: Component { if lhs.sideInset != rhs.sideInset { return false } - if lhs.includesHidden != rhs.includesHidden { + if lhs.useHiddenList != rhs.useHiddenList { return false } if lhs.storySubscriptions != rhs.storySubscriptions { @@ -269,7 +269,7 @@ public final class StoryPeerListComponent: Component { } let _ = hasStories - let collapseStartIndex = component.includesHidden ? 0 : 1 + let collapseStartIndex = component.useHiddenList ? 0 : 1 let collapsedItemWidth: CGFloat = 24.0 let collapsedItemDistance: CGFloat = 14.0 @@ -510,8 +510,8 @@ public final class StoryPeerListComponent: Component { if self.requestedLoadMoreToken != hasMoreToken { self.requestedLoadMoreToken = hasMoreToken - if component.includesHidden { - if let storySubscriptionsContext = component.context.account.allStorySubscriptionsContext { + if component.useHiddenList { + if let storySubscriptionsContext = component.context.account.hiddenStorySubscriptionsContext { storySubscriptionsContext.loadMore() } } else { @@ -526,7 +526,7 @@ public final class StoryPeerListComponent: Component { self.sortedItems.removeAll(keepingCapacity: true) if let storySubscriptions = component.storySubscriptions { - if !component.includesHidden, let accountItem = storySubscriptions.accountItem { + if !component.useHiddenList, let accountItem = storySubscriptions.accountItem { self.sortedItems.append(accountItem) } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index f3091fffbb..6d683c4aee 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -1686,6 +1686,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } } + var isStory: Bool = false + var game: TelegramMediaGame? var webpage: TelegramMediaWebpage? var invoice: TelegramMediaInvoice? @@ -1696,6 +1698,9 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio invoice = media } else if let media = media as? TelegramMediaGame { game = media + } else if let _ = media as? TelegramMediaStory { + isStory = true + automaticPlayback = false } } @@ -1717,6 +1722,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } else if let file = content.file, file.isVideo, !file.isAnimated && !file.isVideoSticker { progressRequired = true } + } else if isStory { + progressRequired = true } case .Remote, .Fetching, .Paused: if let webpage = webpage, let automaticDownload = self.automaticDownload, case .full = automaticDownload, case let .Loaded(content) = webpage.content { @@ -1953,7 +1960,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio state = .customIcon(secretProgressIcon) } else if let file = media as? TelegramMediaFile, !file.isVideoSticker { let isInlinePlayableVideo = file.isVideo && !isSecretMedia && (self.automaticPlayback ?? false) - if !isInlinePlayableVideo && file.isVideo { + if (!isInlinePlayableVideo || isStory) && file.isVideo { state = .play(messageTheme.mediaOverlayControlColors.foregroundColor) } else { state = .none diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index 7a047d172f..0bc21dd1da 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -326,7 +326,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { case "telegram_chatlist": actionTitle = item.presentationData.strings.Conversation_OpenChatFolder case "telegram_story": - actionTitle = "Open Story" + actionTitle = "OPEN STORY" default: break } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 2df8c2e585..976d384592 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -4094,7 +4094,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro private func openStories(fromAvatar: Bool) { if let expiringStoryList = self.expiringStoryList, let expiringStoryListState = self.expiringStoryListState, !expiringStoryListState.items.isEmpty { let _ = expiringStoryList - let storyContent = StoryContentContextImpl(context: self.context, includeHidden: false, focusedPeerId: self.peerId, singlePeer: true) + let storyContent = StoryContentContextImpl(context: self.context, isHidden: false, focusedPeerId: self.peerId, singlePeer: true) let _ = (storyContent.state |> take(1) |> deliverOnMainQueue).start(next: { [weak self] storyContentState in From 7e93b86a8b7b6010d70df5c01e8e255640f4aad2 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 20 Jun 2023 13:44:04 +0300 Subject: [PATCH 07/13] Stories --- .../Account/AccountIntermediateState.swift | 10 ++ .../State/AccountStateManagementUtils.swift | 152 ++++++++++++++++-- .../TelegramCore/Sources/State/Holes.swift | 10 +- .../Sources/State/ResetState.swift | 2 +- .../Sources/State/UpdatesApiUtils.swift | 30 +++- .../TelegramEngine/Messages/Stories.swift | 6 +- .../Sources/PeerInfoStoryPaneNode.swift | 6 + 7 files changed, 194 insertions(+), 22 deletions(-) diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index defb850e8e..f7f1caf57f 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -212,6 +212,7 @@ struct AccountMutableState { var insertedPeers: [PeerId: Peer] = [:] var preCachedResources: [(MediaResource, Data)] = [] + var preCachedStories: [StoryId: Api.StoryItem] = [:] var updatedMaxMessageId: Int32? var updatedQts: Int32? @@ -281,6 +282,11 @@ struct AccountMutableState { self.apiChats[chat.peerId] = chat } self.preCachedResources.append(contentsOf: other.preCachedResources) + + for (id, story) in other.preCachedStories { + self.preCachedStories[id] = story + } + self.externallyUpdatedPeerId.formUnion(other.externallyUpdatedPeerId) for (peerId, namespaces) in other.namespacesWithHolesFromPreviousState { if self.namespacesWithHolesFromPreviousState[peerId] == nil { @@ -305,6 +311,10 @@ struct AccountMutableState { self.preCachedResources.append((resource, data)) } + mutating func addPreCachedStory(id: StoryId, story: Api.StoryItem) { + self.preCachedStories[id] = story + } + mutating func addExternallyUpdatedPeerId(_ peerId: PeerId) { self.externallyUpdatedPeerId.insert(peerId) } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index fe503dcd3d..9c8da8be8b 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -727,6 +727,11 @@ func finalStateWithDifference(accountPeerId: PeerId, postbox: Postbox, network: updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = message.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } var peerIsForum = false if let peerId = message.peerId { peerIsForum = updatedState.isPeerForum(peerId: peerId) @@ -954,6 +959,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } var attributes = message.attributes attributes.append(ChannelMessageStateVersionAttribute(pts: pts)) updatedState.editMessage(messageId, message: message.withUpdatedAttributes(attributes)) @@ -1020,6 +1030,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } updatedState.editMessage(messageId, message: message) for media in message.media { if let media = media as? TelegramMediaAction { @@ -1050,6 +1065,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } var attributes = message.attributes attributes.append(ChannelMessageStateVersionAttribute(pts: pts)) updatedState.addMessages([message.withUpdatedAttributes(attributes)], location: .UpperHistoryBlock) @@ -1082,6 +1102,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } updatedState.addMessages([message], location: .UpperHistoryBlock) } case let .updateServiceNotification(flags, date, type, text, media, entities): @@ -1715,7 +1740,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: |> mapToSignal { finalState in return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState) |> mapToSignal { resultingState -> Signal in - return resolveAssociatedStories(postbox: postbox, network: network, state: finalState) + return resolveAssociatedStories(postbox: postbox, network: network, accountPeerId: accountPeerId, state: finalState) |> mapToSignal { resultingState -> Signal in return resolveMissingPeerChatInfos(network: network, state: resultingState) |> map { resultingState, resolveError -> AccountFinalState in @@ -2073,16 +2098,102 @@ func resolveForumThreads(postbox: Postbox, network: Network, fetchedChatList: Fe } } -func resolveAssociatedStories(postbox: Postbox, network: Network, state: AccountMutableState) -> Signal { +func resolveStories(postbox: Postbox, source: FetchMessageHistoryHoleSource, accountPeerId: PeerId, storyIds: Set, result: T) -> Signal { + var storyBuckets: [PeerId: [Int32]] = [:] + for id in storyIds { + if storyBuckets[id.peerId] == nil { + storyBuckets[id.peerId] = [] + } + storyBuckets[id.peerId]?.append(id.id) + } + + var signals: [Signal] = [] + for (peerId, allIds) in storyBuckets { + var idOffset = 0 + while idOffset < allIds.count { + let bucketLength = min(100, allIds.count - idOffset) + let ids = Array(allIds[idOffset ..< (idOffset + bucketLength)]) + signals.append(_internal_getStoriesById(accountPeerId: accountPeerId, postbox: postbox, source: source, peerId: peerId, ids: ids) + |> mapToSignal { result -> Signal in + return postbox.transaction { transaction -> Void in + for id in ids { + let current = transaction.getStory(id: StoryId(peerId: peerId, id: id)) + var updated: CodableEntry? + if let updatedItem = result.first(where: { $0.id == id }) { + if let entry = CodableEntry(updatedItem) { + updated = entry + } + } + if current != updated { + transaction.setStory(id: StoryId(peerId: peerId, id: id), value: updated ?? CodableEntry(data: Data())) + } + } + } + |> ignoreValues + }) + idOffset += bucketLength + } + } + + return combineLatest(signals) + |> ignoreValues + |> map { _ -> T in + } + |> then(.single(result)) +} + +func resolveAssociatedStories(postbox: Postbox, network: Network, accountPeerId: PeerId, state: AccountMutableState) -> Signal { return postbox.transaction { transaction -> Signal in - return .single(state) + var missingStoryIds = Set() + + for operation in state.operations { + switch operation { + case let .AddMessages(messages, _): + for message in messages { + for media in message.media { + for id in media.storyIds { + if let existing = transaction.getStory(id: id), case .item = existing.get(Stories.StoredItem.self) { + } else if state.preCachedStories[id] != nil { + } else { + missingStoryIds.insert(id) + } + } + } + } + default: + break + } + } + + if !missingStoryIds.isEmpty { + return resolveStories(postbox: postbox, source: .network(network), accountPeerId: accountPeerId, storyIds: missingStoryIds, result: state) + } else { + return .single(state) + } } |> switchToLatest } -func resolveAssociatedStories(postbox: Postbox, source: FetchMessageHistoryHoleSource, messages: [StoreMessage], result: T) -> Signal { +func resolveAssociatedStories(postbox: Postbox, source: FetchMessageHistoryHoleSource, accountPeerId: PeerId, messages: [StoreMessage], result: T) -> Signal { return postbox.transaction { transaction -> Signal in - return .single(result) + var missingStoryIds = Set() + + for message in messages { + for media in message.media { + for id in media.storyIds { + if let existing = transaction.getStory(id: id), case .item = existing.get(Stories.StoredItem.self) { + } else { + missingStoryIds.insert(id) + } + } + } + } + + if !missingStoryIds.isEmpty { + return resolveStories(postbox: postbox, source: source, accountPeerId: accountPeerId, storyIds: missingStoryIds, result: result) + } else { + return .single(result) + } } |> switchToLatest } @@ -2420,7 +2531,7 @@ func pollChannelOnce(accountPeerId: PeerId, postbox: Postbox, network: Network, |> mapToSignal { (finalState, _, timeout) -> Signal in return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState) |> mapToSignal { resultingState -> Signal in - return resolveAssociatedStories(postbox: postbox, network: network, state: finalState) + return resolveAssociatedStories(postbox: postbox, network: network, accountPeerId: accountPeerId, state: finalState) } |> mapToSignal { resultingState -> Signal in return resolveMissingPeerChatInfos(network: network, state: resultingState) @@ -2477,7 +2588,7 @@ public func standalonePollChannelOnce(accountPeerId: PeerId, postbox: Postbox, n |> mapToSignal { (finalState, _, timeout) -> Signal in return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState) |> mapToSignal { resultingState -> Signal in - return resolveAssociatedStories(postbox: postbox, network: network, state: finalState) + return resolveAssociatedStories(postbox: postbox, network: network, accountPeerId: accountPeerId, state: finalState) } |> mapToSignal { resultingState -> Signal in return resolveMissingPeerChatInfos(network: network, state: resultingState) @@ -2687,7 +2798,7 @@ func resetChannels(accountPeerId: PeerId, postbox: Postbox, network: Network, pe // TODO: delete messages later than top return resolveAssociatedMessages(postbox: postbox, network: network, state: updatedState) |> mapToSignal { resultingState -> Signal in - return resolveAssociatedStories(postbox: postbox, network: network, state: updatedState) + return resolveAssociatedStories(postbox: postbox, network: network, accountPeerId: accountPeerId, state: updatedState) } } } @@ -2758,6 +2869,11 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } updatedState.addMessages([message], location: .UpperHistoryBlock) if case let .Id(id) = message.id { updatedState.updateChannelSynchronizedUntilMessage(id.peerId, id: id.id) @@ -2787,6 +2903,11 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } var attributes = message.attributes attributes.append(ChannelMessageStateVersionAttribute(pts: pts)) updatedState.editMessage(messageId, message: message.withUpdatedAttributes(attributes)) @@ -2837,7 +2958,7 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo return resolveForumThreads(postbox: postbox, network: network, state: updatedState) |> mapToSignal { updatedState in - return resolveAssociatedStories(postbox: postbox, network: network, state: updatedState) + return resolveAssociatedStories(postbox: postbox, network: network, accountPeerId: accountPeerId, state: updatedState) |> map { updatedState -> (AccountMutableState, Bool, Int32?) in return (updatedState, true, apiTimeout) } @@ -2904,6 +3025,11 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo updatedState.addPreCachedResource(resource, data: data) } } + if let preCachedStories = apiMessage.preCachedStories { + for (id, story) in preCachedStories { + updatedState.addPreCachedStory(id: id, story: story) + } + } let location: AddMessagesLocation if case let .Id(id) = message.id, id.id == topMessage { @@ -3167,6 +3293,14 @@ func replayFinalState( var holesFromPreviousStateMessageIds: [MessageId] = [] var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:] + for (id, story) in finalState.state.preCachedStories { + if let storyItem = Stories.StoredItem(apiStoryItem: story, peerId: id.peerId, transaction: transaction) { + if let entry = CodableEntry(storyItem) { + transaction.setStory(id: id, value: entry) + } + } + } + for (peerId, namespaces) in finalState.state.namespacesWithHolesFromPreviousState { for (namespace, namespaceState) in namespaces { if let pts = namespaceState.validateChannelPts { diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index 169a101bc5..3c82fac281 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -100,7 +100,7 @@ func resolveUnknownEmojiFiles(postbox: Postbox, source: FetchMessageHistoryHo } } -func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> T) -> Signal { +func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, accountPeerId: PeerId, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> T) -> Signal { return postbox.transaction { transaction -> Signal in var storedIds = Set() var referencedReplyIds = ReferencedReplyMessageIds() @@ -129,7 +129,7 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHis if referencedReplyIds.isEmpty && referencedGeneralIds.isEmpty { return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages, reactions: [], result: Void()) |> mapToSignal { _ -> Signal in - return resolveAssociatedStories(postbox: postbox, source: source, messages: storeMessages, result: Void()) + return resolveAssociatedStories(postbox: postbox, source: source, accountPeerId: accountPeerId, messages: storeMessages, result: Void()) |> mapToSignal { _ -> Signal in return postbox.transaction { transaction -> T in return f(transaction, [], []) @@ -227,7 +227,7 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHis return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages + additionalMessages, reactions: [], result: Void()) |> mapToSignal { _ -> Signal in - return resolveAssociatedStories(postbox: postbox, source: source, messages: storeMessages + additionalMessages, result: Void()) + return resolveAssociatedStories(postbox: postbox, source: source, accountPeerId: accountPeerId, messages: storeMessages + additionalMessages, result: Void()) |> mapToSignal { _ -> Signal in return postbox.transaction { transaction -> T in return f(transaction, additionalPeers, additionalMessages) @@ -669,7 +669,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } } - return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages -> FetchMessageHistoryHoleResult? in + return withResolvedAssociatedMessages(postbox: postbox, source: source, accountPeerId: accountPeerId, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages -> FetchMessageHistoryHoleResult? in let _ = transaction.addMessages(storeMessages, location: .Random) let _ = transaction.addMessages(additionalMessages, location: .Random) var filledRange: ClosedRange @@ -805,7 +805,7 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId } |> ignoreValues } - return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), peers: Dictionary(fetchedChats.peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: fetchedChats.storeMessages, { transaction, additionalPeers, additionalMessages -> Void in + return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), accountPeerId: accountPeerId, peers: Dictionary(fetchedChats.peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: fetchedChats.storeMessages, { transaction, additionalPeers, additionalMessages -> Void in updatePeers(transaction: transaction, peers: fetchedChats.peers + additionalPeers, update: { _, updated -> Peer in return updated }) diff --git a/submodules/TelegramCore/Sources/State/ResetState.swift b/submodules/TelegramCore/Sources/State/ResetState.swift index f247619f02..521acded1a 100644 --- a/submodules/TelegramCore/Sources/State/ResetState.swift +++ b/submodules/TelegramCore/Sources/State/ResetState.swift @@ -14,7 +14,7 @@ func _internal_resetAccountState(postbox: Postbox, network: Network, accountPeer guard let fetchedChats = fetchedChats else { return .never() } - return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), peers: Dictionary(fetchedChats.peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: fetchedChats.storeMessages, { transaction, additionalPeers, additionalMessages -> Void in + return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), accountPeerId: accountPeerId, peers: Dictionary(fetchedChats.peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: fetchedChats.storeMessages, { transaction, additionalPeers, additionalMessages -> Void in for peerId in transaction.chatListGetAllPeerIds() { if peerId.namespace != Namespaces.Peer.SecretChat { transaction.updatePeerChatListInclusion(peerId, inclusion: .notIncluded) diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index bf5e2b990a..7222906a27 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -85,6 +85,19 @@ extension Api.MessageMedia { return nil } } + + var preCachedStories: [StoryId: Api.StoryItem]? { + switch self { + case let .messageMediaStory(_, userId, id, story): + if let story = story { + return [StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: id): story] + } else { + return nil + } + default: + return nil + } + } } extension Api.Message { @@ -142,10 +155,19 @@ extension Api.Message { var preCachedResources: [(MediaResource, Data)]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): - return media?.preCachedResources - default: - return nil + case let .message(_, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): + return media?.preCachedResources + default: + return nil + } + } + + var preCachedStories: [StoryId: Api.StoryItem]? { + switch self { + case let .message(_, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): + return media?.preCachedStories + default: + return nil } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index f19c83a8ac..f6954e3be3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -1263,7 +1263,7 @@ func _internal_getStoriesById(accountPeerId: PeerId, postbox: Postbox, network: } } -func _internal_getStoriesById(accountPeerId: PeerId, postbox: Postbox, network: Network, peerId: PeerId, ids: [Int32]) -> Signal<[Stories.StoredItem], NoError> { +func _internal_getStoriesById(accountPeerId: PeerId, postbox: Postbox, source: FetchMessageHistoryHoleSource, peerId: PeerId, ids: [Int32]) -> Signal<[Stories.StoredItem], NoError> { return postbox.transaction { transaction -> Api.InputUser? in return transaction.getPeer(peerId).flatMap(apiInputUser) } @@ -1272,7 +1272,7 @@ func _internal_getStoriesById(accountPeerId: PeerId, postbox: Postbox, network: return .single([]) } - return network.request(Api.functions.stories.getStoriesByID(userId: inputUser, id: ids)) + return source.request(Api.functions.stories.getStoriesByID(userId: inputUser, id: ids)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -1685,7 +1685,7 @@ func _internal_exportStoryLink(account: Account, peerId: EnginePeer.Id, id: Int3 } func _internal_refreshStories(account: Account, peerId: PeerId, ids: [Int32]) -> Signal { - return _internal_getStoriesById(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, peerId: peerId, ids: ids) + return _internal_getStoriesById(accountPeerId: account.peerId, postbox: account.postbox, source: .network(account.network), peerId: peerId, ids: ids) |> mapToSignal { result -> Signal in return account.postbox.transaction { transaction -> Void in var currentItems = transaction.getStoryItems(peerId: peerId) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index f7f6894b5d..246ffedbd7 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -891,6 +891,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr private var presentationDataDisposable: Disposable? private weak var pendingOpenListContext: PeerStoryListContentContextImpl? + + private var preloadArchiveListContext: PeerStoryListContext? public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, navigationController: @escaping () -> NavigationController?, listContext: PeerStoryListContext?) { self.context = context @@ -1448,6 +1450,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr }) self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: false) + + if peerId == context.account.peerId { + self.preloadArchiveListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: true) + } } deinit { From 53949c8b52b9a004069277ee4c984e99b98fc7ec Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 20 Jun 2023 14:09:24 +0300 Subject: [PATCH 08/13] Fix user map --- .../Sources/State/AccountStateManagementUtils.swift | 12 +++++++----- submodules/TelegramCore/Sources/State/Holes.swift | 11 +++++++++-- .../Sources/TelegramEngine/Messages/Stories.swift | 6 +++--- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 9c8da8be8b..add0ffac0c 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -2098,7 +2098,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, fetchedChatList: Fe } } -func resolveStories(postbox: Postbox, source: FetchMessageHistoryHoleSource, accountPeerId: PeerId, storyIds: Set, result: T) -> Signal { +func resolveStories(postbox: Postbox, source: FetchMessageHistoryHoleSource, accountPeerId: PeerId, storyIds: Set, additionalPeers: [PeerId: Peer], result: T) -> Signal { var storyBuckets: [PeerId: [Int32]] = [:] for id in storyIds { if storyBuckets[id.peerId] == nil { @@ -2113,7 +2113,7 @@ func resolveStories(postbox: Postbox, source: FetchMessageHistoryHoleSource, while idOffset < allIds.count { let bucketLength = min(100, allIds.count - idOffset) let ids = Array(allIds[idOffset ..< (idOffset + bucketLength)]) - signals.append(_internal_getStoriesById(accountPeerId: accountPeerId, postbox: postbox, source: source, peerId: peerId, ids: ids) + signals.append(_internal_getStoriesById(accountPeerId: accountPeerId, postbox: postbox, source: source, peerId: peerId, peerReference: additionalPeers[peerId].flatMap(PeerReference.init), ids: ids) |> mapToSignal { result -> Signal in return postbox.transaction { transaction -> Void in for id in ids { @@ -2166,7 +2166,7 @@ func resolveAssociatedStories(postbox: Postbox, network: Network, accountPeerId: } if !missingStoryIds.isEmpty { - return resolveStories(postbox: postbox, source: .network(network), accountPeerId: accountPeerId, storyIds: missingStoryIds, result: state) + return resolveStories(postbox: postbox, source: .network(network), accountPeerId: accountPeerId, storyIds: missingStoryIds, additionalPeers: state.insertedPeers, result: state) } else { return .single(state) } @@ -2174,7 +2174,7 @@ func resolveAssociatedStories(postbox: Postbox, network: Network, accountPeerId: |> switchToLatest } -func resolveAssociatedStories(postbox: Postbox, source: FetchMessageHistoryHoleSource, accountPeerId: PeerId, messages: [StoreMessage], result: T) -> Signal { +func resolveAssociatedStories(postbox: Postbox, source: FetchMessageHistoryHoleSource, accountPeerId: PeerId, messages: [StoreMessage], additionalPeers: [PeerId: Peer], result: T) -> Signal { return postbox.transaction { transaction -> Signal in var missingStoryIds = Set() @@ -2190,7 +2190,7 @@ func resolveAssociatedStories(postbox: Postbox, source: FetchMessageHistoryHo } if !missingStoryIds.isEmpty { - return resolveStories(postbox: postbox, source: source, accountPeerId: accountPeerId, storyIds: missingStoryIds, result: result) + return resolveStories(postbox: postbox, source: source, accountPeerId: accountPeerId, storyIds: missingStoryIds, additionalPeers: additionalPeers, result: result) } else { return .single(result) } @@ -3298,6 +3298,8 @@ func replayFinalState( if let entry = CodableEntry(storyItem) { transaction.setStory(id: id, value: entry) } + } else { + transaction.setStory(id: id, value: CodableEntry(data: Data())) } } diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index 3c82fac281..d3e83ac05d 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -129,7 +129,7 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHis if referencedReplyIds.isEmpty && referencedGeneralIds.isEmpty { return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages, reactions: [], result: Void()) |> mapToSignal { _ -> Signal in - return resolveAssociatedStories(postbox: postbox, source: source, accountPeerId: accountPeerId, messages: storeMessages, result: Void()) + return resolveAssociatedStories(postbox: postbox, source: source, accountPeerId: accountPeerId, messages: storeMessages, additionalPeers: peers, result: Void()) |> mapToSignal { _ -> Signal in return postbox.transaction { transaction -> T in return f(transaction, [], []) @@ -227,7 +227,14 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHis return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages + additionalMessages, reactions: [], result: Void()) |> mapToSignal { _ -> Signal in - return resolveAssociatedStories(postbox: postbox, source: source, accountPeerId: accountPeerId, messages: storeMessages + additionalMessages, result: Void()) + var additionalPeerMap: [PeerId: Peer] = [:] + for (_, peer) in peers { + additionalPeerMap[peer.id] = peer + } + for peer in additionalPeers { + additionalPeerMap[peer.id] = peer + } + return resolveAssociatedStories(postbox: postbox, source: source, accountPeerId: accountPeerId, messages: storeMessages + additionalMessages, additionalPeers: additionalPeerMap, result: Void()) |> mapToSignal { _ -> Signal in return postbox.transaction { transaction -> T in return f(transaction, additionalPeers, additionalMessages) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index f6954e3be3..fc684ab374 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -1263,12 +1263,12 @@ func _internal_getStoriesById(accountPeerId: PeerId, postbox: Postbox, network: } } -func _internal_getStoriesById(accountPeerId: PeerId, postbox: Postbox, source: FetchMessageHistoryHoleSource, peerId: PeerId, ids: [Int32]) -> Signal<[Stories.StoredItem], NoError> { +func _internal_getStoriesById(accountPeerId: PeerId, postbox: Postbox, source: FetchMessageHistoryHoleSource, peerId: PeerId, peerReference: PeerReference?, ids: [Int32]) -> Signal<[Stories.StoredItem], NoError> { return postbox.transaction { transaction -> Api.InputUser? in return transaction.getPeer(peerId).flatMap(apiInputUser) } |> mapToSignal { inputUser -> Signal<[Stories.StoredItem], NoError> in - guard let inputUser = inputUser else { + guard let inputUser = inputUser ?? peerReference?.inputUser else { return .single([]) } @@ -1685,7 +1685,7 @@ func _internal_exportStoryLink(account: Account, peerId: EnginePeer.Id, id: Int3 } func _internal_refreshStories(account: Account, peerId: PeerId, ids: [Int32]) -> Signal { - return _internal_getStoriesById(accountPeerId: account.peerId, postbox: account.postbox, source: .network(account.network), peerId: peerId, ids: ids) + return _internal_getStoriesById(accountPeerId: account.peerId, postbox: account.postbox, source: .network(account.network), peerId: peerId, peerReference: nil, ids: ids) |> mapToSignal { result -> Signal in return account.postbox.transaction { transaction -> Void in var currentItems = transaction.getStoryItems(peerId: peerId) From c81cdc271d43f730b85f0186edc5398b000f3f5f Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 20 Jun 2023 14:13:11 +0300 Subject: [PATCH 09/13] Fix caption --- .../Sources/StoryItemSetContainerComponent.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 72c388e651..52d3e60400 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -1749,9 +1749,7 @@ public final class StoryItemSetContainerComponent: Component { let captionFrame = CGRect(origin: CGPoint(x: 0.0, y: contentFrame.height - captionSize.height), size: captionSize) if let captionItemView = captionItem.view.view { if captionItemView.superview == nil { - if self.contentContainerView.subviews.count >= 1 { - self.contentContainerView.insertSubview(captionItemView, at: 1) - } + self.contentContainerView.insertSubview(captionItemView, aboveSubview: self.contentDimView) } captionItemTransition.setFrame(view: captionItemView, frame: captionFrame) captionItemTransition.setAlpha(view: captionItemView, alpha: (component.hideUI || self.displayViewList || self.inputPanelExternalState.isEditing) ? 0.0 : 1.0) From f3e989dbfa2e91f1debb36ce3896fcf84e806408 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 20 Jun 2023 14:17:47 +0300 Subject: [PATCH 10/13] Fix caption --- .../Sources/StoryContentCaptionComponent.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift index 082c3c9d6c..32194ca7ad 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift @@ -225,9 +225,6 @@ final class StoryContentCaptionComponent: Component { let expandDistance: CGFloat = 50.0 var expandFraction: CGFloat = self.scrollView.contentOffset.y / expandDistance expandFraction = max(0.0, min(1.0, expandFraction)) - if self.scrollView.contentSize.height < self.scrollView.bounds.height + expandDistance { - expandFraction = 0.0 - } let isExpanded = expandFraction > 0.0 From 13d2dd15dfbcd82a8b57c3ae4a334871ba4b7459 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 20 Jun 2023 14:26:27 +0300 Subject: [PATCH 11/13] Fix open webpage story action --- .../Sources/ChatMessageWebpageBubbleContentNode.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index 0bc21dd1da..d16746bc55 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -76,7 +76,11 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } } if let webpage = webPageContent { - item.controllerInteraction.openUrl(webpage.url, false, nil, nil) + if webpage.story != nil { + let _ = item.controllerInteraction.openMessage(item.message, .default) + } else { + item.controllerInteraction.openUrl(webpage.url, false, nil, nil) + } } } } From 78ed2459e17a1898f175d65ad48595872cefecb4 Mon Sep 17 00:00:00 2001 From: Mike Renoir <> Date: Tue, 20 Jun 2023 14:44:54 +0200 Subject: [PATCH 12/13] fix syntax --- .../Sources/TelegramEngine/Messages/PendingStoryManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/PendingStoryManager.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/PendingStoryManager.swift index dca9c9ec0b..20052eb602 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/PendingStoryManager.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/PendingStoryManager.swift @@ -194,7 +194,7 @@ final class PendingStoryManager { guard let `self` = self else { return } - if let bag, let listBag = self.storyObserverContexts[stableId], listBag === bag { + if let bag = bag, let listBag = self.storyObserverContexts[stableId], listBag === bag { bag.remove(index) if bag.isEmpty { self.storyObserverContexts.removeValue(forKey: stableId) From 13c6cf58c8c85a31ce5d42a11131157393ae1971 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 20 Jun 2023 16:58:52 +0300 Subject: [PATCH 13/13] Stories --- submodules/TelegramApi/Sources/Api31.swift | 15 +++ .../State/AccountStateManagementUtils.swift | 2 + .../Sources/State/AccountViewTracker.swift | 15 ++- .../SyncCore_TelegramMediaWebpage.swift | 10 +- .../TelegramEngine/Messages/Stories.swift | 2 + .../Resources/PresentationResourceKey.swift | 8 ++ .../Resources/PresentationResourcesChat.swift | 35 +++++++ submodules/TelegramUI/BUILD | 1 + .../Chat/ChatMessageForwardInfoNode/BUILD | 23 +++++ .../Sources/ChatMessageForwardInfoNode.swift | 60 +++++++++-- .../ExpiredStoryIcon.imageset/Contents.json | 12 +++ .../ExpiredStoryIcon.imageset/timer.pdf | 95 ++++++++++++++++++ .../Contents.json | 12 +++ .../Slice 1.png | Bin 0 -> 6637 bytes .../TelegramUI/Sources/ChatController.swift | 4 + .../Sources/ChatHistoryListNode.swift | 14 ++- .../ChatMessageAnimatedStickerItemNode.swift | 3 +- .../Sources/ChatMessageBubbleItemNode.swift | 27 ++++- .../ChatMessageInstantVideoItemNode.swift | 3 +- ...atMessageInteractiveInstantVideoNode.swift | 3 +- .../Sources/ChatMessageReplyInfoNode.swift | 51 +++++++++- .../Sources/ChatMessageStickerItemNode.swift | 3 +- .../ChatMessageWebpageBubbleContentNode.swift | 6 +- .../TelegramUI/Sources/OpenChatMessage.swift | 4 +- 24 files changed, 372 insertions(+), 36 deletions(-) create mode 100644 submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/BUILD rename submodules/TelegramUI/{ => Components/Chat/ChatMessageForwardInfoNode}/Sources/ChatMessageForwardInfoNode.swift (84%) create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/timer.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryPlaceholder.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryPlaceholder.imageset/Slice 1.png diff --git a/submodules/TelegramApi/Sources/Api31.swift b/submodules/TelegramApi/Sources/Api31.swift index fb3a5fc70c..7f56233329 100644 --- a/submodules/TelegramApi/Sources/Api31.swift +++ b/submodules/TelegramApi/Sources/Api31.swift @@ -8696,6 +8696,21 @@ public extension Api.functions.stories { }) } } +public extension Api.functions.stories { + static func toggleAllStoriesHidden(hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2082822084) + hidden.serialize(buffer, true) + return (FunctionDescription(name: "stories.toggleAllStoriesHidden", parameters: [("hidden", String(describing: hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.stories { static func togglePinned(id: [Int32], pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index add0ffac0c..a713c032f9 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -2123,6 +2123,8 @@ func resolveStories(postbox: Postbox, source: FetchMessageHistoryHoleSource, if let entry = CodableEntry(updatedItem) { updated = entry } + } else { + updated = CodableEntry(data: Data()) } if current != updated { transaction.setStory(id: StoryId(peerId: peerId, id: id), value: updated ?? CodableEntry(data: Data())) diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index f0afb02650..600d8624eb 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -1236,7 +1236,14 @@ public final class AccountViewTracker { let timestamp = Int32(CFAbsoluteTimeGetCurrent()) for messageId in messageIds { let messageTimestamp = self.refreshStoriesForMessageIdsAndTimestamps[messageId] - if messageTimestamp == nil { + var refresh = false + if let messageTimestamp = messageTimestamp { + refresh = messageTimestamp < timestamp - 60 + } else { + refresh = true + } + + if refresh { self.refreshStoriesForMessageIdsAndTimestamps[messageId] = timestamp addedMessageIds.append(messageId) } @@ -1258,6 +1265,12 @@ public final class AccountViewTracker { result.insert(story.storyId) } } + + for attribute in message.attributes { + if let attribute = attribute as? ReplyStoryAttribute { + result.insert(attribute.storyId) + } + } } } return result diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift index 8387c277fc..f56add72b1 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift @@ -308,7 +308,15 @@ public final class TelegramMediaWebpage: Media, Equatable { public var id: MediaId? { return self.webpageId } - public let peerIds: [PeerId] = [] + public var peerIds: [PeerId] { + var result: [PeerId] = [] + for storyId in self.storyIds { + if !result.contains(storyId.peerId) { + result.append(storyId.peerId) + } + } + return result + } public var storyIds: [StoryId] { if case let .Loaded(content) = self.content, let story = content.story { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index fc684ab374..bd0a6ac903 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -1707,6 +1707,8 @@ func _internal_refreshStories(account: Account, peerId: PeerId, ids: [Int32]) -> if let entry = CodableEntry(updatedItem) { updated = entry } + } else { + updated = CodableEntry(data: Data()) } if current != updated { transaction.setStory(id: StoryId(peerId: peerId, id: id), value: updated ?? CodableEntry(data: Data())) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 0b8c7195d3..36619e64be 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -297,6 +297,12 @@ public enum PresentationResourceKey: Int32 { case uploadToneIcon } +public enum ChatExpiredStoryIndicatorType: Hashable { + case incoming + case outgoing + case free +} + public enum PresentationResourceParameterKey: Hashable { case chatOutgoingFullCheck(CGFloat) case chatOutgoingPartialCheck(CGFloat) @@ -333,4 +339,6 @@ public enum PresentationResourceParameterKey: Hashable { case chatInputMediaPanelGridDismissImage(color: UInt32) case statusAutoremoveIcon(isActive: Bool) + + case chatExpiredStoryIndicatorIcon(type: ChatExpiredStoryIndicatorType) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index e0478fd6bd..d70b7e17e5 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1246,4 +1246,39 @@ public struct PresentationResourcesChat { return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: theme.chat.message.mediaOverlayControlColors.foregroundColor) }) } + + public static func chatExpiredStoryIndicatorIcon(_ theme: PresentationTheme, type: ChatExpiredStoryIndicatorType) -> UIImage? { + return theme.image(PresentationResourceParameterKey.chatExpiredStoryIndicatorIcon(type: type), { theme in + return generateImage(CGSize(width: 34.0, height: 34.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 6.0).cgPath) + context.clip() + + let color: UIColor + switch type { + case .incoming: + color = theme.chat.message.incoming.mediaPlaceholderColor + case .outgoing: + color = theme.chat.message.outgoing.mediaPlaceholderColor + case .free: + color = theme.chat.message.freeform.withWallpaper.fill[0] + } + + context.setFillColor(color.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + + if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/ExpiredStoryIcon"), color: .white), let icon = UIImage(bundleImageName: "Chat/Message/ExpiredStoryPlaceholder") { + UIGraphicsPushContext(context) + + icon.draw(in: CGRect(origin: CGPoint(), size: size)) + + //image.draw(at: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), blendMode: .destinationOut, alpha: 1.0) + image.draw(at: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), blendMode: .normal, alpha: 1.0) + + UIGraphicsPopContext() + } + }) + }) + } } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index c944fc7b3b..bcf14c354e 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -375,6 +375,7 @@ swift_library( "//submodules/TelegramUI/Components/MoreHeaderButton", "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", "//submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent", + "//submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode", "//submodules/Utils/VolumeButtons", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/BUILD new file mode 100644 index 0000000000..7f304fb3d2 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/BUILD @@ -0,0 +1,23 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageForwardInfoNode", + module_name = "ChatMessageForwardInfoNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/LocalizedPeerData", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageForwardInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift similarity index 84% rename from submodules/TelegramUI/Sources/ChatMessageForwardInfoNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift index fb8d39d8b5..3ad6b0a5fd 100644 --- a/submodules/TelegramUI/Sources/ChatMessageForwardInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift @@ -7,7 +7,7 @@ import TelegramCore import TelegramPresentationData import LocalizedPeerData -enum ChatMessageForwardInfoType: Equatable { +public enum ChatMessageForwardInfoType: Equatable { case bubble(incoming: Bool) case standalone } @@ -57,18 +57,27 @@ private final class InfoButtonNode: HighlightableButtonNode { } } -class ChatMessageForwardInfoNode: ASDisplayNode { +public class ChatMessageForwardInfoNode: ASDisplayNode { + public struct StoryData: Equatable { + public var isExpired: Bool + + public init(isExpired: Bool) { + self.isExpired = isExpired + } + } + private var textNode: TextNode? private var credibilityIconNode: ASImageNode? private var infoNode: InfoButtonNode? + private var expiredStoryIconView: UIImageView? - var openPsa: ((String, ASDisplayNode) -> Void)? + public var openPsa: ((String, ASDisplayNode) -> Void)? - override init() { + override public init() { super.init() } - func hasAction(at point: CGPoint) -> Bool { + public func hasAction(at point: CGPoint) -> Bool { if let infoNode = self.infoNode, infoNode.frame.contains(point) { return true } else { @@ -76,7 +85,7 @@ class ChatMessageForwardInfoNode: ASDisplayNode { } } - func updatePsaButtonDisplay(isVisible: Bool, animated: Bool) { + public func updatePsaButtonDisplay(isVisible: Bool, animated: Bool) { if let infoNode = self.infoNode { if isVisible != !infoNode.iconNode.alpha.isZero { let transition: ContainedViewLayoutTransition @@ -91,10 +100,10 @@ class ChatMessageForwardInfoNode: ASDisplayNode { } } - class func asyncLayout(_ maybeNode: ChatMessageForwardInfoNode?) -> (_ presentationData: ChatPresentationData, _ strings: PresentationStrings, _ type: ChatMessageForwardInfoType, _ peer: Peer?, _ authorName: String?, _ psaType: String?, _ isStory: Bool, _ constrainedSize: CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode) { + public static func asyncLayout(_ maybeNode: ChatMessageForwardInfoNode?) -> (_ presentationData: ChatPresentationData, _ strings: PresentationStrings, _ type: ChatMessageForwardInfoType, _ peer: Peer?, _ authorName: String?, _ psaType: String?, _ storyData: StoryData?, _ constrainedSize: CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode) { let textNodeLayout = TextNode.asyncLayout(maybeNode?.textNode) - return { presentationData, strings, type, peer, authorName, psaType, isStory, constrainedSize in + return { presentationData, strings, type, peer, authorName, psaType, storyData, constrainedSize in let fontSize = floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0) let prefixFont = Font.regular(fontSize) let peerFont = Font.medium(fontSize) @@ -149,7 +158,7 @@ class ChatMessageForwardInfoNode: ASDisplayNode { } else { titleColor = incoming ? presentationData.theme.theme.chat.message.incoming.accentTextColor : presentationData.theme.theme.chat.message.outgoing.accentTextColor - if isStory { + if let _ = storyData { completeSourceString = strings.Message_ForwardedStoryShort(peerString) } else { completeSourceString = strings.Message_ForwardedMessageShort(peerString) @@ -234,6 +243,11 @@ class ChatMessageForwardInfoNode: ASDisplayNode { if hasPsaInfo { infoWidth += 32.0 } + var leftOffset: CGFloat = 0.0 + if let storyData, storyData.isExpired { + leftOffset += 34.0 + 6.0 + } + infoWidth += leftOffset let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - credibilityIconWidth - infoWidth, height: constrainedSize.height), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -251,7 +265,33 @@ class ChatMessageForwardInfoNode: ASDisplayNode { node.textNode = textNode node.addSubnode(textNode) } - textNode.frame = CGRect(origin: CGPoint(), size: textLayout.size) + textNode.frame = CGRect(origin: CGPoint(x: leftOffset, y: 0.0), size: textLayout.size) + + if let storyData, storyData.isExpired { + let expiredStoryIconView: UIImageView + if let current = node.expiredStoryIconView { + expiredStoryIconView = current + } else { + expiredStoryIconView = UIImageView() + node.expiredStoryIconView = expiredStoryIconView + node.view.addSubview(expiredStoryIconView) + } + + let imageType: ChatExpiredStoryIndicatorType + switch type { + case .standalone: + imageType = .free + case let .bubble(incoming): + imageType = incoming ? .incoming : .outgoing + } + + expiredStoryIconView.image = PresentationResourcesChat.chatExpiredStoryIndicatorIcon(presentationData.theme.theme, type: imageType) + if let image = expiredStoryIconView.image { + expiredStoryIconView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: image.size) + } + } else if let expiredStoryIconView = node.expiredStoryIconView { + expiredStoryIconView.removeFromSuperview() + } if let credibilityIconImage = currentCredibilityIconImage { let credibilityIconNode: ASImageNode diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/Contents.json new file mode 100644 index 0000000000..a5c36ef619 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "timer.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/timer.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/timer.pdf new file mode 100644 index 0000000000..4767c66cdd --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/timer.pdf @@ -0,0 +1,95 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 11.386475 7.000000 cm +0.000000 0.000000 0.000000 scn +3.109776 20.000000 m +1.179763 20.000000 -0.283886 18.259834 0.046805 16.358360 c +0.156231 15.729159 0.419039 15.136623 0.811980 14.633168 c +3.413476 11.300000 l +3.439425 11.257834 l +3.914118 10.486457 3.914118 9.513542 3.439425 8.742166 c +3.413476 8.700000 l +0.811979 5.366831 l +0.419039 4.863377 0.156231 4.270840 0.046805 3.641638 c +-0.283886 1.740166 1.179765 0.000000 3.109779 0.000000 c +8.117176 0.000000 l +10.047190 0.000000 11.510839 1.740166 11.180147 3.641638 c +11.070721 4.270841 10.807913 4.863377 10.414972 5.366832 c +7.813476 8.700000 l +7.787528 8.742166 l +7.312835 9.513542 7.312835 10.486458 7.787528 11.257834 c +7.813476 11.300000 l +10.414973 14.633169 l +10.807914 15.136623 11.070721 15.729160 11.180147 16.358362 c +11.510839 18.259834 10.047188 20.000000 8.117173 20.000000 c +3.109776 20.000000 l +h +7.428961 3.624302 m +6.183511 4.350814 4.643443 4.350814 3.397992 3.624302 c +1.891497 2.745512 l +1.538821 2.539783 1.684751 2.000000 2.093044 2.000000 c +8.733909 2.000000 l +9.142202 2.000000 9.288133 2.539783 8.935456 2.745512 c +7.428961 3.624302 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1194 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 34.000000 34.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001284 00000 n +0000001307 00000 n +0000001480 00000 n +0000001554 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1613 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryPlaceholder.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryPlaceholder.imageset/Contents.json new file mode 100644 index 0000000000..037cefa85b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryPlaceholder.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Slice 1.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryPlaceholder.imageset/Slice 1.png b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryPlaceholder.imageset/Slice 1.png new file mode 100644 index 0000000000000000000000000000000000000000..7546315a2c3d064f2a9798349378c9126346cc44 GIT binary patch literal 6637 zcmVB*dsc4uh4=6GvyaCv{k-bC^YeOgd!M_0KI5L}-MIaHwrb<~JKLhR*y%*> zkH~Ft-h=Ov$DjAT&uioJwte>Jw%6Wwe_wAOb6)2=U;qBi`zLRBZXQvM^#XEbThRF2 zZ?*6Ii?gq`uRNzZ^{ndD(;feQehT~s>3If7fLGt22JC=uIe@dR+ma2NZ3OJJ$3TDt zsgTOg8z7#o3D{}>7nHSEet+}x7bxGj)|>AK$Qmqb;LqzcFJfokKG~iF(6@2t1_j>A znyeG%u&t=Z}rfCuk000k@=!ofMON5DuR9Uxx}6e~y|{SW(Awpl^+0pZwp|a zuLmsuB6tS5n6>jdhf#dziRV_|-Tik4mLQ(NJq-O6n>-nq4GaRru|y-#Ar8m_h)V!% zhNcXhu6?g*H5d!mSkhTC^=WwkFb@ESMCA1az!Xo_y|25rvGJnnY^%9@$fm+69CtaF~z?1X;((OYWp zszD&r4ojV%>o_ NgALr}bKpP3vjxV_TQk_jBE^swG#eS!e$NYUls6fBy=|0k5w2 zDKoG!KXklCGcGgD{V^c5{@F3h!?gjdUUq$d76Gzi-2SMh>&yPd#)lNs*=a)&Z zwvMBR3DRMZIF2&C`ux7N&wVF|X|Atdn%7h~Kpovn7S|vKNcJ(9ZVeeJPv8J94sKJ% zMd9X}P1MIahk_o^%ek&LuQk)~62>-j7c^Gc=09M%nlV7M_9O7P{0vafajz%SjzyOe z4Cn{kay)eo%{QXV{tWELF-q*nx#|Haw*k0524*~>ZGTMBZ5$6xpP|tLOCEUk2j8i8 ziws?&kq^TedTwzt1P%LZZ2ZS_7Eq_N`|CMRsB)Np4msc5A7~!5&;EVQ_G#AEKt;3e z07ui5>i5Ph^C)}DRKC_z_TO0WB)m%e^O znbE+$2`noe#g{qX|KK!^!~AYPZZ^+(l5r8GePjB^`zDt&MocMsXu>BBL}S~OO&kHO zn$!gQma7@MfH7Lxtn^)6|lVP1th-_YgjNLETf#fl$d;{Z|Vzc7Mj;;WmMdT<)M5Ba_q{*IbT zZ<%{^qu>{1zg_I4V*?NY~=N;S zQkBv*H4>K5yMd_OX*&nGzR+0%qG@8K7=3IfQlT7B7bqGZbnCt;JUpM`OjC zlL+eRHST1U#eb?m)LT<$;R~^(GS8c}i#Je@Wy++u&E) z%)1AWZKH40G=j_Wxx5pYeSx!YP-Enx2RCl5b@k}NVlprZBrNSLN?9x5(gRa3S{rSm zj7tMA!Q@$Cb`$Z|Niv2#t4@XhLjdYUkbXfOe-J$<(I$>KQyJIt&?7sHz8iRx0%W_* zQw|?(<<{uPqeF}q-1(XS5+GNwLSwYquCMc_#mV3O3X++aX`AbY_pW+e4#>tfVG=7K zHDGg{%Os*lvIo^$GNdCBVp%>Q{s^mj>H$*#7jTxv-=Mi;-ng#Ypyf+}%W%a0fTt$Q zYH(cxtlYy^+rvU-Z-|p2!_~wwJa&aawdERIyP|g-Zv({q3CkOIl{mD~SWq=@0d}~w z2n}aQLGQT(#zV}Fna25cHog8WfQZ?ylja@We7-ffv%TjfHgx68lI83ew(ZiaI708Z zUff{Cw~&`Sfq4ciqch@p?afXRjFy0X_w$D^b*wkE_jf}sY&S)Ri3QYn7c?NMp`Jx>9+U4J1Qag`{N6~=zg3U3@7#Cov;D+kUqF{$*An1`$soQz2sL(2EmR)O z<|~~@#*r$Rso76ls$h{s_Q#w*G=e@XVi*<`{N}Zg+ZTp0puw3`X$U;_V#u2vTf@W` zj62sq%vk{TTUJkye1NYx*<$0VzXd*Lymsz;rXll&YuXC8Tn05-S0-x>LId<4nteH? z;Bmtz6THqo%*x5diKKI8YeZCO9zS_*$9=~nPSfUX1ZyYng-Pw z-^+ETBtgv4eq`FVstZxvV*B$(Q_hm5tmG2%#*<* zLG8dbL}<75@g=kcif0D_05mDyGVe2+xP4-)1J)$u^1_kO>pGmv_BQ35H3O^stup(D zPI`Odos2AbBe#gzWk*s0qd41Yz+@@VO=<^X9_Lj+Kn3JA8i}JHM z=GEyELg*sda}NCX-TWaa=lnHJA~nx$fM0;d0m#^JOg#6|G;$2UU`vKbWCtGpa9;7c>@;uD>2}(uDLWJ4I+}$^^arbs{o3wSoB30y7+_&Bw}Yw z2^^)s+d~INI<>D&*u#u;xYV_f}=N1LtGL>;0N?a<GC$U=|3XF?m53d1t83{f5teV8$W=mZR=OMo1#OXi(HSjEWn()7f8 z-Nl7H-rc<{VdA+d$*a@5syF+xRWaSTBNF~YK>p#tTqPUvG!ycSxVns_=Ea$o9VI*K zhvUJ-x{c<6fYrCjr7zY|-`@IsVnUi`fy&IJjyYP9p^jDPvu$EFTXjyY((?}T?LSD- z!GD|a=pn*m(auJy;=l|+{5HP<())s!2M>Zbfp%wq~diHffkw1%;pKJRDv$ z17UghO;9fh~1r+?@;sOwQ)+{OV9eyV{|{MEmXiCuUlB>Ip+wbWId1TS!D z$Ha`+SGiyU2alYGr73%x**=VkXdoBNI)Q}QcTqK2F^t>@V`_yIQo?sw%V2sSC~dS$ zxuN5vmC3uD$$6moaWd+3&gTz%-kFAvtKcYAKJFSnOF-(TDWWpC+zL{-TIH7qF)Qg} zr|&V{Esg|ww>BLJwQzN1Y$bY2=_RYps7jKd+l}WV^vaX`*{%NEHM+Hx$Kt9 z^vV+8gbV|QMR$~y9e1JNGJOF`!Ml9LxnhvdKD=Z3JM3n+OF$Fisd(YgcbvOrqK8f> zdM+d|HF{FE$P!n+LZ?CTw9vcf_|2std(3@rIZx)Op)t*}E%Piwty{WuiOX(Ac2NQS za+6|$2FSxe02|SPtKN4+^@r|gx94IzE?7b~a`IzeHsQzQ$jzoVCZzFVR|B>5d*#A8 zB|uLvdGa?DfagUVy4I+n)CuE9*Jc~~8&gbyv9^qT2-+0~8Wxm8FGkOPaH96P-yRCp zkjT1{cGvvqv;+XO#h!$W`^c+fR z85cIvaiktTFk7U0zS{c+91a&T6$1xE?{v>Zf8%x{jmeLx&k;Q!pWi5*z%+e(^yK6| zUyBI?XjNSTF`=B+%*22o`Oo#pM(sl`yjVP)(5Uz4f^VPiYl84>H}hheuMPVq_FFe3 zq2SpJK6gt0wE*!UtFw_-H_&+L&QkmyC7=0{E01)YI~f4;4b|!_Sy2NSqA#LFt4&4Mcz6M2Z^yDfwLYj@}x*iCYg8b}&1cLBv3 z;n6<5h+XF1(t(2Y8H*ab7)dfw>k4d^(?;k<+^2YVW*)#Raa*hPHA0 z^YkFhNbVntDhZlVj*D=m+3#$R$;i@#wez87YBr|Y5DHjw6L+%Bduud5Egz_=3%u3jig9Q8#Wa)eI-9dTp!^(k-JB6n<4}+6?_CGeKF)nYVHfPMxqdvlw;) zfQLDGxM-T%5g?KQuwPFcGk6sXYbeKz$820WP3H%!bfyv4?dQfER4s=Gm=rM0eGOnV z3-5;R*oYe?PK?PKxO%G=D5;K(lj&n$S(B(NH3bT*W_!Na@SQ9%Gs&li8@26ooIjU> z8J&rHSC5ye#-YT|{P`y`Y}|ZUK04}m@hih^5f{B{giFLE}qD>JWVfPDKRG6;fjq#C^^pe z4|U#UR3>#JB;H`ep5?REj-OEGLQ8WPfEg3waL%F`LoH-jgMC@O&P7G2%m7TSURPM7 zWv%FG&qCdMy-YT9nRr?;PaWmBNkS{6PdV?OWLr75aUwAPjSmbfinZfadGjfSK`t=R zwa^FP`Apc?S1qtwnU~KTNnVJ7_ZkMiUM9&HaJ81%CH$$QE3iHqmbGW$2BjoN^xm+4p)e~#SU=0LTvMM-R7O&H} zz?t+)SQZ`VC}cke{5RkI^}Y&iKiwWf7L$Ig#xnRo(&$8MLhk=@_iT&^YUhwPteDZg zwWIDQ<}LT+K7HxpbUyLagTeAu$T54HJ`rGGPj|LEeGlk_Eu(p)p=(BhOMDprWioGm zg)nQhHi23`n%2gtR5$NYoATEh6E~)e(87Y+xRRlQMC%m~1Bh0cXXb9niUqA!XO%|) zsiNw}98I~D1!8oTgXsYFAp|8|4{mba`Mkz-Z5)M|5lytW!@Mg^18e#mYqK9Pw}3nv zL$lxeCs^ILxi^!;a@DGuG~>K1)lv=<|4CKl`LpzownNbT0;Sg(`t? zu51eH%wijtKh%L64D1ZXhhbzieGXYCcjjnZMOg;QwVc?eYK?V)oGP7)jMN5~{4T;4 z#{-e7c(O`fT%S$#&;{sC9sx@SGKM8I;jo@Y3g&&dn4jm2YyX(5plt(QL`W#4gXNb* zfR?=)2?WTmwH$8U<*Nu(Qe;hGPq3r(6)dhIm|m+{ZkX6-(KY9p7t9>-SZ83NGkV@y zYZnu5N0fo|hG}{K+=+Eo!r15CTuU8IB9k&d>vN8p+f`pQ4ZP3$tjC!|-60oM`rl0c zS#({axzx2KPQ&Jb%GSdwphjz4wk$PvRx22!OXJ*Z$F~9#DrGSqyYFdIP%WBQS!+zL z1G=sNW%s>So;ui(z&A??*!+bJ%iBu}t2yb;^I5^^<-IW>OI4?%O8wFqF8820zZpx` zbskh?qDx!lOt=6b@8whD+w{yE%X|$Wazcto1WgzW;>8@!f^0IOwLzzr-`UChdPs?C z$4tF>bTGkM1yrz!vd(&A0aAKom(XNQb(-c_KMzF@yn7V`y>6hAlY@sJ2IBAF&{>(! z@?##$qQtwWtgjhMoA8di(7DY}6^^b%IGD3+mVDq^od#Zx0ykJ1zno*t@hfpYrj}w~ zs|{E%t$SAq?Avj)9FnUzX&`?UkJ(;GKBW8Ih&VV0+^9sqn~t%s&lNjVrXg*>3%G zz>h|W-cM=YY7L#XxUC&T=cp6Pbw?OxaMY>| zA(|JrM}||Oj1)j+;ljkkqB{|1YvgH61@dp56rb6&cQXm}I zqd}G=3!icd5TCP8gnj^u@9cvaR zl7JQpqpQxiC5A9M(f3*0cwD`hF_g=xy};|Jk2;4HvSw#Mz1xoXIE;BOuyOB zEFjRP`U%f5TdujuvW`6Yd1Ao8u&nh-v#wub`t;*xn%@@GbInfOgo&MF6TovKW4tt~ zK!^g7ii=2N;sglZyd(g?1vG0a-9HBg^Itw1QEA}=vEH$Ad;T~q9F9qe%WL_6g(Q0( z*fDzHRlzkTl}kSUz5N4^|MK@zy&2c*4Q;X#f^-=CQUgP#3jpLNgcZ?wm0#dc*OqAC zux8ReTC~BdS3nKmTl)rRhl27m=cdU<$V>;YrGZa-Z)Pbo8IPd3*#}_Y{1WVQ`||^0 zs08_%5QfgQRzny})h5RVkc=QUO?bIC`W)~D5Z|0r81G9*;K4}0>Di%Bk=J3ib8RzO z&<-3Isw1{{v&hW)v4|?MaV~e5Rb_m1TvEK_cmknJ4d@3B=}kxV0QB;Jh;d+In#jE` zLz$gm=ljb^{Ymle^?^={W2RyKD;FK=wDe6Xgs*?nxW0}+`KyU}XLiP?va=DVtSe&? zfrl1yg6ZASgOFMl2)^^V;?t?8SXIAGFhAoY{c9)EjGq{gpuPX@AL#yQ2(t~#CXs0v zOq9df`7dmHTT5CSqrNAIVWWW(;#AC6d3Qag@3X!|o^tB@^)+^fShY~dYW2dZsOR6; z13zfN`N2kleKfFs%5NDTAgTpWj3#D*QiUrvS&3*~Au_>qGH@1W&qL4jkfr<8Wok}( zP%yCv!0+@eOiESc9m-eYWc&P$L0HW)Z@`V~Xv{hUVmR_ZC(i=S(qEYQ%b>m5{Cu7? z!E>YaU_cPl)}7yGo~Cs9lkhQsqz>dEqB>_5{++ r=GWU*r^kVvfSsVSn9KZ_Kh^U8uVX$a9UFca00000NkvXXu0mjf+f~~Q literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 01c31e3170..408c7df734 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4508,6 +4508,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let self else { return } + if let story = message.associatedStories[storyId], story.data.isEmpty { + return + } + let storyContent = SingleStoryContentContextImpl(context: self.context, storyId: storyId) let _ = (storyContent.state |> take(1) diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 2560d71d23..5c9c5808ab 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -2093,6 +2093,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if message.stableId != ChatHistoryListNode.fixedAdMessageStableId { visibleAdOpaqueIds.append(attribute.opaqueId) } + } else if let _ = attribute as? ReplyStoryAttribute { + storiesRequiredValidation = true } } @@ -2132,14 +2134,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if invoice.version != TelegramMediaInvoice.lastVersion { contentRequiredValidation = true } - } else if let story = media as? TelegramMediaStory { - if message.associatedStories[story.storyId] == nil { - storiesRequiredValidation = true - } - } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, let story = content.story { - if message.associatedStories[story.storyId] == nil { - storiesRequiredValidation = true - } + } else if let _ = media as? TelegramMediaStory { + storiesRequiredValidation = true + } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, let _ = content.story { + storiesRequiredValidation = true } } if contentRequiredValidation { diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index cc904d33eb..157d82f2c3 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -28,6 +28,7 @@ import AppBundle import ChatPresentationInterfaceState import TextNodeWithEntities import ChatControllerInteraction +import ChatMessageForwardInfoNode private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) @@ -1348,7 +1349,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } let availableWidth = max(60.0, availableContentWidth + 6.0) - forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, false, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) } if replyInfoApply != nil || viaBotApply != nil || forwardInfoSizeApply != nil { diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 06472006c5..6dab7fc27d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -29,6 +29,7 @@ import MultiAnimationRenderer import ComponentFlow import EmojiStatusComponent import ChatControllerInteraction +import ChatMessageForwardInfoNode enum InternalBubbleTapAction { case action(() -> Void) @@ -127,11 +128,14 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ messageWithCaptionToAdd = (message, itemAttributes) } result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default))) - } else if let _ = media as? TelegramMediaStory { + } else if let story = media as? TelegramMediaStory { if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), message.text.isEmpty { messageWithCaptionToAdd = (message, itemAttributes) } - result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default))) + if let storyItem = message.associatedStories[story.storyId], storyItem.data.isEmpty { + } else { + result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default))) + } } else if let file = media as? TelegramMediaFile { let isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil) if isVideo { @@ -1126,7 +1130,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode), - forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, Bool, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), + forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, ChatMessageForwardInfoNode.StoryData?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageReplyInfoNode), actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)), reactionButtonsLayout: (ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)), @@ -2012,7 +2016,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode forwardAuthorSignature = forwardInfo.authorSignature } } - let sizeAndApply = forwardInfoLayout(item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, forwardAuthorSignature, forwardPsaType, false, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) + let sizeAndApply = forwardInfoLayout(item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) forwardInfoSizeApply = (sizeAndApply.0, { width in sizeAndApply.1(width) }) forwardInfoOriginY = headerSize.height @@ -2026,12 +2030,25 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode forwardSource = firstMessage.peers[storyMedia.storyId.peerId] - let sizeAndApply = forwardInfoLayout(item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, nil, nil, true, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) + var isExpired: Bool = false + if let storyItem = firstMessage.associatedStories[storyMedia.storyId], storyItem.data.isEmpty { + isExpired = true + } + + let sizeAndApply = forwardInfoLayout(item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, nil, nil, ChatMessageForwardInfoNode.StoryData(isExpired: isExpired), CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) forwardInfoSizeApply = (sizeAndApply.0, { width in sizeAndApply.1(width) }) + if isExpired { + headerSize.height += 6.0 + } + forwardInfoOriginY = headerSize.height headerSize.width = max(headerSize.width, forwardInfoSizeApply.0.width + bubbleWidthInsets) headerSize.height += forwardInfoSizeApply.0.height + + if isExpired { + headerSize.height += 16.0 + } } var hasReply = replyMessage != nil || replyStory != nil diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 93de93541a..7917a3d28f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -13,6 +13,7 @@ import LocalizedPeerData import ContextUI import Markdown import ChatControllerInteraction +import ChatMessageForwardInfoNode private let nameFont = Font.medium(14.0) @@ -528,7 +529,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } } let availableWidth = max(60.0, availableContentWidth - normalDisplaySize.width + 6.0) - forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, false, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) } if replyInfoApply != nil || viaBotApply != nil || forwardInfoSizeApply != nil { diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index 88280f5780..c6f3efe3db 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -19,6 +19,7 @@ import UndoUI import TelegramNotices import Markdown import TextFormat +import ChatMessageForwardInfoNode struct ChatMessageInstantVideoItemLayoutResult { let contentSize: CGSize @@ -399,7 +400,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } let availableWidth = max(60.0, availableContentWidth - 210.0 + 6.0) - forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, false, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) } var notConsumed = false diff --git a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift index 7feec6b71c..ea927fb248 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift @@ -77,6 +77,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { private var dustNode: InvisibleInkDustNode? private var imageNode: TransformImageNode? private var previousMediaReference: AnyMediaReference? + private var expiredStoryIconView: UIImageView? override init() { self.contentNode = ASDisplayNode() @@ -111,6 +112,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { let textString: NSAttributedString let isMedia: Bool let isText: Bool + var isExpiredStory: Bool = false if let message = arguments.message { let author = message.effectiveAuthor @@ -135,9 +137,14 @@ class ChatMessageReplyInfoNode: ASDisplayNode { titleString = arguments.strings.User_DeletedAccount } //TODO:localize - textString = NSAttributedString(string: "Story") isMedia = true isText = false + if let storyItem = arguments.parentMessage.associatedStories[story], storyItem.data.isEmpty { + isExpiredStory = true + textString = NSAttributedString(string: "Expired story") + } else { + textString = NSAttributedString(string: "Story") + } } else { titleString = " " textString = NSAttributedString(string: " ") @@ -276,6 +283,8 @@ class ChatMessageReplyInfoNode: ASDisplayNode { imageDimensions = representation.dimensions.cgSize } } + } else if storyItem.data.isEmpty { + imageDimensions = CGSize(width: 34.0, height: 34.0) } } @@ -293,7 +302,12 @@ class ChatMessageReplyInfoNode: ASDisplayNode { let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets)) let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets)) - let imageSide = titleLayout.size.height + textLayout.size.height - 16.0 + let imageSide: CGFloat + if isExpiredStory { + imageSide = 38.0 + } else { + imageSide = titleLayout.size.height + textLayout.size.height - 16.0 + } var applyImage: (() -> TransformImageNode)? if let imageDimensions = imageDimensions { @@ -306,7 +320,9 @@ class ChatMessageReplyInfoNode: ASDisplayNode { imageSize.width += 2.0 imageSize.height += 2.0 } - applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: placeholderColor)) + if !isExpiredStory { + applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: placeholderColor)) + } } var mediaUpdated = false @@ -341,6 +357,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { } } } + let _ = isExpiredStory let size = CGSize(width: max(titleLayout.size.width - textInsets.left - textInsets.right, textLayout.size.width - textInsets.left - textInsets.right) + leftInset, height: titleLayout.size.height + textLayout.size.height - 2 * (textInsets.top + textInsets.bottom) + 2 * spacing) @@ -380,7 +397,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { if let applyImage = applyImage { let imageNode = applyImage() if node.imageNode == nil { - imageNode.isLayerBacked = false//!smartInvertColorsEnabled() + imageNode.isLayerBacked = false node.addSubnode(imageNode) node.imageNode = imageNode } @@ -397,6 +414,32 @@ class ChatMessageReplyInfoNode: ASDisplayNode { node.imageNode?.captureProtected = message.isCopyProtected() } + if isExpiredStory { + let expiredStoryIconView: UIImageView + if let current = node.expiredStoryIconView { + expiredStoryIconView = current + } else { + expiredStoryIconView = UIImageView() + node.expiredStoryIconView = expiredStoryIconView + node.view.addSubview(expiredStoryIconView) + } + + let imageType: ChatExpiredStoryIndicatorType + switch arguments.type { + case .standalone: + imageType = .free + case let .bubble(incoming): + imageType = incoming ? .incoming : .outgoing + } + + expiredStoryIconView.image = PresentationResourcesChat.chatExpiredStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType) + if let image = expiredStoryIconView.image { + expiredStoryIconView.frame = CGRect(origin: CGPoint(x: 8.0, y: 3.0), size: image.size) + } + } else if let expiredStoryIconView = node.expiredStoryIconView { + expiredStoryIconView.removeFromSuperview() + } + titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: spacing - textInsets.top + 1.0), size: titleLayout.size) let textFrame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top - 2.0), size: textLayout.size) diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index da73561f62..de5493b9f2 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -14,6 +14,7 @@ import Markdown import ShimmerEffect import WallpaperBackgroundNode import ChatControllerInteraction +import ChatMessageForwardInfoNode private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) @@ -744,7 +745,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } let availableForwardWidth = max(60.0, availableWidth + 6.0) - forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, false, CGSize(width: availableForwardWidth, height: CGFloat.greatestFiniteMagnitude)) + forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableForwardWidth, height: CGFloat.greatestFiniteMagnitude)) } var needsReplyBackground = false diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index d16746bc55..b1a02b4f41 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -221,7 +221,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { mediaAndFlags = (image, flags) } } else if let story = mainMedia as? TelegramMediaStory { - mediaAndFlags = (story, [.preferMediaBeforeText]) + mediaAndFlags = (story, []) } else if let type = webpage.type { if type == "telegram_background" { var colors: [UInt32] = [] @@ -330,6 +330,10 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { case "telegram_chatlist": actionTitle = item.presentationData.strings.Conversation_OpenChatFolder case "telegram_story": + if let story = webpage.story, let peer = item.message.peers[story.storyId.peerId] { + title = EnginePeer(peer).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) + subtitle = nil + } actionTitle = "OPEN STORY" default: break diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 9c362b03b9..29953348fe 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -92,9 +92,9 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { let scale = toScale.interpolate(to: fromScale, amount: state.progress) transition.setTransform(view: view, transform: CATransform3DMakeScale(scale, scale, 1.0)) }, - insertCloneTransitionView: nil/*{ view in + insertCloneTransitionView: { view in params.addToTransitionSurface(view) - }*/ + } ), destinationRect: selectedTransitionNode.1, destinationCornerRadius: 0.0,