diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 08912d73da..f6faf2f539 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -304,6 +304,11 @@ public enum ResolvedUrl { case premiumGiftCode(slug: String) } +public enum ResolveUrlResult { + case progress + case result(ResolvedUrl) +} + public enum NavigateToChatKeepStack { case `default` case always @@ -887,6 +892,7 @@ public protocol SharedAccountContext: AnyObject { func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set) -> Signal func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set, messages: [EngineMessage.Id: EngineMessage], peers: [EnginePeer.Id: EnginePeer]) -> Signal func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal + func resolveUrlWithProgress(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void) func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) diff --git a/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift b/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift index a26bf20361..59bd0ac957 100644 --- a/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift +++ b/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift @@ -66,21 +66,26 @@ public class ChatMessageBackground: ASDisplayNode { private var hasWallpaper: Bool? private var graphics: PrincipalThemeEssentialGraphics? private var maskMode: Bool? - private let imageNode: ASImageNode private let outlineImageNode: ASImageNode private weak var backgroundNode: WallpaperBackgroundNode? + private var imageFrame: CGRect? + private var imageView: UIImageView? + private var imageViewImage: UIImage? + + public var customHighlightColor: UIColor? { + didSet { + self.imageView?.tintColor = self.customHighlightColor + } + } + public var backgroundFrame: CGRect = .zero public var hasImage: Bool { - self.imageNode.image != nil + self.imageViewImage != nil } public override init() { - self.imageNode = ASImageNode() - self.imageNode.displaysAsynchronously = false - self.imageNode.displayWithoutProcessing = true - self.outlineImageNode = ASImageNode() self.outlineImageNode.displaysAsynchronously = false self.outlineImageNode.displayWithoutProcessing = true @@ -89,16 +94,39 @@ public class ChatMessageBackground: ASDisplayNode { self.isUserInteractionEnabled = false self.addSubnode(self.outlineImageNode) - self.addSubnode(self.imageNode) + } + + override public func didLoad() { + super.didLoad() + + let imageView = UIImageView() + self.imageView = imageView + self.view.addSubview(imageView) + + imageView.image = self.imageViewImage + imageView.tintColor = self.customHighlightColor + + if let imageFrame = self.imageFrame { + imageView.frame = imageFrame + } } public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { - transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0)) + let imageFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0) + self.imageFrame = imageFrame + if let imageView = self.imageView { + transition.updateFrame(view: imageView, frame: imageFrame) + } transition.updateFrame(node: self.outlineImageNode, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0)) } public func updateLayout(size: CGSize, transition: ListViewItemUpdateAnimation) { - transition.animator.updateFrame(layer: self.imageNode.layer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0), completion: nil) + let imageFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0) + self.imageFrame = imageFrame + if let imageView = self.imageView { + transition.animator.updateFrame(layer: imageView.layer, frame: imageFrame, completion: nil) + } + transition.animator.updateFrame(layer: self.outlineImageNode.layer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0), completion: nil) } @@ -217,13 +245,11 @@ public class ChatMessageBackground: ASDisplayNode { } let outlineImage: UIImage? - var isIncoming = false if hasWallpaper { switch type { case .none: outlineImage = nil case let .incoming(mergeType): - isIncoming = true switch mergeType { case .None: outlineImage = graphics.chatMessageBackgroundIncomingOutlineImage @@ -267,38 +293,38 @@ public class ChatMessageBackground: ASDisplayNode { } if let previousType = previousType, previousType != .none, type == .none { - if transition.isAnimated { + if transition.isAnimated, let imageView = self.imageView { let tempLayer = CALayer() - tempLayer.contents = self.imageNode.layer.contents - tempLayer.contentsScale = self.imageNode.layer.contentsScale - tempLayer.rasterizationScale = self.imageNode.layer.rasterizationScale - tempLayer.contentsGravity = self.imageNode.layer.contentsGravity - tempLayer.contentsCenter = self.imageNode.layer.contentsCenter + tempLayer.contents = imageView.layer.contents + tempLayer.contentsScale = imageView.layer.contentsScale + tempLayer.rasterizationScale = imageView.layer.rasterizationScale + tempLayer.contentsGravity = imageView.layer.contentsGravity + tempLayer.contentsCenter = imageView.layer.contentsCenter - tempLayer.frame = self.imageNode.frame - self.layer.insertSublayer(tempLayer, above: self.imageNode.layer) + tempLayer.frame = imageView.frame + self.layer.insertSublayer(tempLayer, above: imageView.layer) transition.updateAlpha(layer: tempLayer, alpha: 0.0, completion: { [weak tempLayer] _ in tempLayer?.removeFromSuperlayer() }) } - } else if transition.isAnimated { - if let previousContents = self.imageNode.layer.contents { + } else if transition.isAnimated, let imageView = self.imageView { + if let previousContents = imageView.layer.contents { if let image = image { if (previousContents as AnyObject) !== image.cgImage { - self.imageNode.layer.animate(from: previousContents as AnyObject, to: image.cgImage! as AnyObject, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.42) + imageView.layer.animate(from: previousContents as AnyObject, to: image.cgImage! as AnyObject, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.42) } } else { let tempLayer = CALayer() - tempLayer.contents = self.imageNode.layer.contents - tempLayer.contentsScale = self.imageNode.layer.contentsScale - tempLayer.rasterizationScale = self.imageNode.layer.rasterizationScale - tempLayer.contentsGravity = self.imageNode.layer.contentsGravity - tempLayer.contentsCenter = self.imageNode.layer.contentsCenter - tempLayer.compositingFilter = self.imageNode.layer.compositingFilter + tempLayer.contents = imageView.layer.contents + tempLayer.contentsScale = imageView.layer.contentsScale + tempLayer.rasterizationScale = imageView.layer.rasterizationScale + tempLayer.contentsGravity = imageView.layer.contentsGravity + tempLayer.contentsCenter = imageView.layer.contentsCenter + tempLayer.compositingFilter = imageView.layer.compositingFilter - tempLayer.frame = self.imageNode.frame + tempLayer.frame = imageView.frame - self.imageNode.supernode?.layer.insertSublayer(tempLayer, above: self.imageNode.layer) + imageView.superview?.layer.insertSublayer(tempLayer, above: imageView.layer) transition.updateAlpha(layer: tempLayer, alpha: 0.0, completion: { [weak tempLayer] _ in tempLayer?.removeFromSuperlayer() }) @@ -306,26 +332,17 @@ public class ChatMessageBackground: ASDisplayNode { } } - self.imageNode.image = image - if highlighted && maskMode, let backdropNode = self.backdropNode, backdropNode.hasImage && isIncoming { - self.imageNode.layer.compositingFilter = "overlayBlendMode" - self.imageNode.alpha = 1.0 - - backdropNode.addSubnode(self.imageNode) - } else { - self.imageNode.layer.compositingFilter = nil - self.imageNode.alpha = 1.0 - - if self.imageNode.supernode != self { - self.addSubnode(self.imageNode) - } + self.imageViewImage = image + if let imageView = self.imageView { + imageView.image = image } + self.outlineImageNode.image = outlineImage } public func animateFrom(sourceView: UIView, transition: CombinedTransition) { if transition.isAnimated { - self.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + self.imageView?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) self.outlineImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) self.view.addSubview(sourceView) @@ -334,9 +351,11 @@ public class ChatMessageBackground: ASDisplayNode { sourceView?.removeFromSuperview() }) - transition.animateFrame(layer: self.imageNode.layer, from: sourceView.frame) + if let imageView = imageView { + transition.animateFrame(layer: imageView.layer, from: sourceView.frame) + transition.updateFrame(layer: sourceView.layer, frame: CGRect(origin: imageView.frame.origin, size: CGSize(width: imageView.frame.width - 7.0, height: imageView.frame.height))) + } transition.animateFrame(layer: self.outlineImageNode.layer, from: sourceView.frame) - transition.updateFrame(layer: sourceView.layer, frame: CGRect(origin: self.imageNode.frame.origin, size: CGSize(width: self.imageNode.frame.width - 7.0, height: self.imageNode.frame.height))) } } } diff --git a/submodules/InstantPageCache/Sources/CachedInternalInstantPages.swift b/submodules/InstantPageCache/Sources/CachedInternalInstantPages.swift index 02d6acf3b3..aa5308d68c 100644 --- a/submodules/InstantPageCache/Sources/CachedInternalInstantPages.swift +++ b/submodules/InstantPageCache/Sources/CachedInternalInstantPages.swift @@ -53,6 +53,12 @@ private func cachedInternalInstantPage(context: AccountContext, url: String) -> return cachedInstantPage(engine: context.engine, url: cachedUrl) |> mapToSignal { cachedInstantPage -> Signal in let updated = resolveInstantViewUrl(account: context.account, url: url) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> afterNext { result in if case let .instantView(webPage, _) = result, case let .Loaded(content) = webPage.content, let instantPage = content.instantPage { if instantPage.isComplete { diff --git a/submodules/InstantPageUI/Sources/InstantPageFeedbackNode.swift b/submodules/InstantPageUI/Sources/InstantPageFeedbackNode.swift index 9a756f1997..5376f7e65c 100644 --- a/submodules/InstantPageUI/Sources/InstantPageFeedbackNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageFeedbackNode.swift @@ -71,7 +71,14 @@ final class InstantPageFeedbackNode: ASDisplayNode, InstantPageNode { } @objc func buttonPressed() { - self.resolveDisposable.set((self.context.engine.peers.resolvePeerByName(name: "previews") |> deliverOnMainQueue).start(next: { [weak self] peer in + self.resolveDisposable.set((self.context.engine.peers.resolvePeerByName(name: "previews") + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } + |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self, let _ = peer, let webPageId = strongSelf.webPage.id?.id { strongSelf.openUrl(InstantPageUrlItem(url: "https://t.me/previews?start=webpage\(webPageId)", webpageId: nil)) } diff --git a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift index af034e9f3f..efb02b214e 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift @@ -150,7 +150,10 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { |> mapToSignal({ peer -> Signal in if let peer = peer as? TelegramChannel, let username = peer.addressName, peer.accessHash == nil { return .single(.channel(peer)) |> then(engine.peers.resolvePeerByName(name: username) - |> mapToSignal({ updatedPeer -> Signal in + |> mapToSignal({ result -> Signal in + guard case let .result(updatedPeer) = result else { + return .complete() + } if let updatedPeer = updatedPeer { return .single(updatedPeer) } else { diff --git a/submodules/LocationUI/Sources/LocationUtils.swift b/submodules/LocationUI/Sources/LocationUtils.swift index 933ea8ec17..3abf13348b 100644 --- a/submodules/LocationUI/Sources/LocationUtils.swift +++ b/submodules/LocationUI/Sources/LocationUtils.swift @@ -49,7 +49,12 @@ public func nearbyVenues(context: AccountContext, story: Bool = false, latitude: return botUsername |> mapToSignal { botUsername in return context.engine.peers.resolvePeerByName(name: botUsername) - |> take(1) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> mapToSignal { peer -> Signal in guard let peer = peer else { return .single(nil) diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index 3845e8f792..0c4164e3c5 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -990,7 +990,14 @@ public func channelPermissionsController(context: AccountContext, updatedPresent } pushControllerImpl?(controller) }, openChannelExample: { - resolveDisposable.set((context.engine.peers.resolvePeerByName(name: "durov") |> deliverOnMainQueue).start(next: { peer in + resolveDisposable.set((context.engine.peers.resolvePeerByName(name: "durov") + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } + |> deliverOnMainQueue).start(next: { peer in if let peer = peer { navigateToChatControllerImpl?(peer.id) } diff --git a/submodules/PeerInfoUI/Sources/GroupStickerPackSetupController.swift b/submodules/PeerInfoUI/Sources/GroupStickerPackSetupController.swift index a4761eacc6..1c3e5ae2c2 100644 --- a/submodules/PeerInfoUI/Sources/GroupStickerPackSetupController.swift +++ b/submodules/PeerInfoUI/Sources/GroupStickerPackSetupController.swift @@ -392,7 +392,14 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres }, updateSearchText: { text in searchText.set(text) }, openStickersBot: { - resolveDisposable.set((context.engine.peers.resolvePeerByName(name: "stickers") |> deliverOnMainQueue).start(next: { peer in + resolveDisposable.set((context.engine.peers.resolvePeerByName(name: "stickers") + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } + |> deliverOnMainQueue).start(next: { peer in if let peer = peer { dismissImpl?() navigateToChatControllerImpl?(peer.id) diff --git a/submodules/PresentationDataUtils/Sources/OpenUrl.swift b/submodules/PresentationDataUtils/Sources/OpenUrl.swift index 0d1cfdcf34..6a7a7a52b6 100644 --- a/submodules/PresentationDataUtils/Sources/OpenUrl.swift +++ b/submodules/PresentationDataUtils/Sources/OpenUrl.swift @@ -24,7 +24,6 @@ public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: } } |> runOn(Queue.mainQueue()) - |> delay(0.05, queue: Queue.mainQueue()) } else { progressSignal = Signal { subscriber in let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { @@ -38,18 +37,18 @@ public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: } } |> runOn(Queue.mainQueue()) - |> delay(0.1, queue: Queue.mainQueue()) } - let progressDisposable = progressSignal.start() + let progressDisposable = MetaDisposable() + var didStartProgress = false cancelImpl = { disposable.dispose() } - var resolveSignal: Signal - resolveSignal = context.sharedContext.resolveUrl(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth) + var resolveSignal: Signal + resolveSignal = context.sharedContext.resolveUrlWithProgress(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth) #if DEBUG - resolveSignal = resolveSignal |> delay(2.0, queue: .mainQueue()) + //resolveSignal = .single(.progress) |> then(resolveSignal |> delay(2.0, queue: .mainQueue())) #endif disposable.set((resolveSignal @@ -59,8 +58,16 @@ public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: } } |> deliverOnMainQueue).start(next: { result in - progressDisposable.dispose() - openResolved(result) + switch result { + case .progress: + if !didStartProgress { + didStartProgress = true + progressDisposable.set(progressSignal.start()) + } + case let .result(result): + progressDisposable.dispose() + openResolved(result) + } })) return ActionDisposable { diff --git a/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift b/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift index 529f1cdd00..09d4e1baec 100644 --- a/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift +++ b/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift @@ -295,6 +295,12 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo faqUrl = "https://telegram.org/faq#q-can-i-delete-my-messages" } let resolvedUrl = resolveInstantViewUrl(account: context.account, url: faqUrl) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } let resolvedUrlPromise = Promise() resolvedUrlPromise.set(resolvedUrl) @@ -330,6 +336,12 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo faqUrl = "https://telegram.org/faq#general" } let resolvedUrl = resolveInstantViewUrl(account: context.account, url: faqUrl) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } let resolvedUrlPromise = Promise() resolvedUrlPromise.set(resolvedUrl) diff --git a/submodules/SettingsUI/Sources/LogoutOptionsController.swift b/submodules/SettingsUI/Sources/LogoutOptionsController.swift index c43d8739c1..5ef9e4eebe 100644 --- a/submodules/SettingsUI/Sources/LogoutOptionsController.swift +++ b/submodules/SettingsUI/Sources/LogoutOptionsController.swift @@ -202,6 +202,12 @@ public func logoutOptionsController(context: AccountContext, navigationControlle faqUrl = "https://telegram.org/faq#general" } let resolvedUrl = resolveInstantViewUrl(account: context.account, url: faqUrl) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } let resolvedUrlPromise = Promise() resolvedUrlPromise.set(resolvedUrl) diff --git a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift index fa6bbb2bdd..6ec4032274 100644 --- a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift @@ -732,7 +732,14 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta ]) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }, openStickersBot: { - resolveDisposable.set((context.engine.peers.resolvePeerByName(name: "stickers") |> deliverOnMainQueue).start(next: { peer in + resolveDisposable.set((context.engine.peers.resolvePeerByName(name: "stickers") + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } + |> deliverOnMainQueue).start(next: { peer in if let peer = peer { navigateToChatControllerImpl?(peer.id) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift index 1f879b764e..7d849797f9 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift @@ -469,6 +469,12 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode { return .single(nil) } return context.engine.peers.resolvePeerByName(name: name) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> mapToSignal { peer -> Signal in if let peer = peer { return .single(peer._asPeer()) diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift index 3b56f2e42e..ba685bfdb4 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift @@ -136,6 +136,12 @@ public final class StickerPackPreviewController: ViewController, StandalonePrese } strongSelf.openMentionDisposable.set((strongSelf.context.engine.peers.resolvePeerByName(name: mention) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> mapToSignal { peer -> Signal in if let peer = peer { return .single(peer._asPeer()) diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 9d6d35e915..1b154124fe 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -2029,6 +2029,12 @@ public final class StickerPackScreenImpl: ViewController { } strongSelf.openMentionDisposable.set((strongSelf.context.engine.peers.resolvePeerByName(name: mention) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> mapToSignal { peer -> Signal in if let peer = peer { return .single(peer._asPeer()) diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index f9c0610d99..7505fd776e 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -307,6 +307,7 @@ func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: Refere struct ParsedMessageWebpageAttributes { var forceLargeMedia: Bool? var isManuallyAdded: Bool + var isSafe: Bool } func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerId: PeerId) -> (media: Media?, expirationTimer: Int32?, nonPremium: Bool?, hasSpoiler: Bool?, webpageAttributes: ParsedMessageWebpageAttributes?) { @@ -352,7 +353,8 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI return (mediaWebpage, nil, nil, nil, ParsedMessageWebpageAttributes( forceLargeMedia: webpageForceLargeMedia, - isManuallyAdded: (flags & (1 << 3)) != 0 + isManuallyAdded: (flags & (1 << 3)) != 0, + isSafe: (flags & (1 << 4)) != 0 )) } case .messageMediaUnsupported: @@ -708,7 +710,7 @@ extension StoreMessage { let leadingPreview = (flags & (1 << 27)) != 0 if let webpageAttributes { - attributes.append(WebpagePreviewMessageAttribute(leadingPreview: leadingPreview, forceLargeMedia: webpageAttributes.forceLargeMedia, isManuallyAdded: webpageAttributes.isManuallyAdded)) + attributes.append(WebpagePreviewMessageAttribute(leadingPreview: leadingPreview, forceLargeMedia: webpageAttributes.forceLargeMedia, isManuallyAdded: webpageAttributes.isManuallyAdded, isSafe: webpageAttributes.isSafe)) } } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift index d165bf05c3..4cd629839f 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift @@ -72,17 +72,20 @@ public class WebpagePreviewMessageAttribute: MessageAttribute, Equatable { public let leadingPreview: Bool public let forceLargeMedia: Bool? public let isManuallyAdded: Bool + public let isSafe: Bool - public init(leadingPreview: Bool, forceLargeMedia: Bool?, isManuallyAdded: Bool) { + public init(leadingPreview: Bool, forceLargeMedia: Bool?, isManuallyAdded: Bool, isSafe: Bool) { self.leadingPreview = leadingPreview self.forceLargeMedia = forceLargeMedia self.isManuallyAdded = isManuallyAdded + self.isSafe = isSafe } required public init(decoder: PostboxDecoder) { self.leadingPreview = decoder.decodeBoolForKey("lp", orElse: false) self.forceLargeMedia = decoder.decodeOptionalBoolForKey("lm") self.isManuallyAdded = decoder.decodeBoolForKey("ma", orElse: false) + self.isSafe = decoder.decodeBoolForKey("sf", orElse: false) } public func encode(_ encoder: PostboxEncoder) { @@ -93,6 +96,7 @@ public class WebpagePreviewMessageAttribute: MessageAttribute, Equatable { encoder.encodeNil(forKey: "lm") } encoder.encodeBool(self.isManuallyAdded, forKey: "ma") + encoder.encodeBool(self.isSafe, forKey: "sf") } public static func ==(lhs: WebpagePreviewMessageAttribute, rhs: WebpagePreviewMessageAttribute) -> Bool { @@ -105,6 +109,9 @@ public class WebpagePreviewMessageAttribute: MessageAttribute, Equatable { if lhs.isManuallyAdded != rhs.isManuallyAdded { return false } + if lhs.isSafe != rhs.isSafe { + return false + } return true } } diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index 7018b6b226..560eda65ed 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -253,7 +253,12 @@ func _internal_createForumChannelTopic(account: Account, peerId: PeerId, title: } } -func _internal_fetchForumChannelTopic(account: Account, peerId: PeerId, threadId: Int64) -> Signal { +public enum FetchForumChannelTopicResult { + case progress + case result(EngineMessageHistoryThread.Info?) +} + +func _internal_fetchForumChannelTopic(account: Account, peerId: PeerId, threadId: Int64) -> Signal { return account.postbox.transaction { transaction -> (info: EngineMessageHistoryThread.Info?, inputChannel: Api.InputChannel?) in if let data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { return (data.info, nil) @@ -261,20 +266,20 @@ func _internal_fetchForumChannelTopic(account: Account, peerId: PeerId, threadId return (nil, transaction.getPeer(peerId).flatMap(apiInputChannel)) } } - |> mapToSignal { info, _ -> Signal in + |> mapToSignal { info, _ -> Signal in if let info = info { - return .single(info) + return .single(.result(info)) } else { - return resolveForumThreads(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, ids: [MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))]) - |> mapToSignal { _ -> Signal in - return account.postbox.transaction { transaction -> EngineMessageHistoryThread.Info? in + return .single(.progress) |> then(resolveForumThreads(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, ids: [MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))]) + |> mapToSignal { _ -> Signal in + return account.postbox.transaction { transaction -> FetchForumChannelTopicResult in if let data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { - return data.info + return .result(data.info) } else { - return nil + return .result(nil) } } - } + }) } } } diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index 40bda328f7..d3dcc5fda8 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -190,38 +190,38 @@ private func convertForwardedMediaForSecretChat(_ media: Media) -> Media { private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAttribute]) -> [MessageAttribute] { return attributes.filter { attribute in switch attribute { - case _ as TextEntitiesMessageAttribute: - return true - case _ as InlineBotMessageAttribute: - return true - case _ as OutgoingMessageInfoAttribute: - return false - case _ as OutgoingContentInfoMessageAttribute: - return true - case _ as ReplyMarkupMessageAttribute: - return true - case _ as OutgoingChatContextResultMessageAttribute: - return true - case _ as AutoremoveTimeoutMessageAttribute: - return true - case _ as NotificationInfoMessageAttribute: - return true - case _ as OutgoingScheduleInfoMessageAttribute: - return true - case _ as EmbeddedMediaStickersMessageAttribute: - return true - case _ as EmojiSearchQueryMessageAttribute: - return true - case _ as ForwardOptionsMessageAttribute: - return true - case _ as SendAsMessageAttribute: - return true - case _ as MediaSpoilerMessageAttribute: - return true - case _ as WebpagePreviewMessageAttribute: - return true - default: - return false + case _ as TextEntitiesMessageAttribute: + return true + case _ as InlineBotMessageAttribute: + return true + case _ as OutgoingMessageInfoAttribute: + return false + case _ as OutgoingContentInfoMessageAttribute: + return true + case _ as ReplyMarkupMessageAttribute: + return true + case _ as OutgoingChatContextResultMessageAttribute: + return true + case _ as AutoremoveTimeoutMessageAttribute: + return true + case _ as NotificationInfoMessageAttribute: + return true + case _ as OutgoingScheduleInfoMessageAttribute: + return true + case _ as EmbeddedMediaStickersMessageAttribute: + return true + case _ as EmojiSearchQueryMessageAttribute: + return true + case _ as ForwardOptionsMessageAttribute: + return true + case _ as SendAsMessageAttribute: + return true + case _ as MediaSpoilerMessageAttribute: + return true + case _ as WebpagePreviewMessageAttribute: + return true + default: + return false } } } @@ -244,6 +244,9 @@ private func filterMessageAttributesForForwardedMessage(_ attributes: [MessageAt case _ as MediaSpoilerMessageAttribute: return true case let attribute as ReplyMessageAttribute: + if attribute.quote != nil { + return true + } if let forwardedMessageIds = forwardedMessageIds { return forwardedMessageIds.contains(attribute.messageId) } else { diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 4b266bb77c..1e4c226452 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1154,7 +1154,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: if mediaValue is TelegramMediaWebpage { if let webpageAttributes { - attributes.append(WebpagePreviewMessageAttribute(leadingPreview: false, forceLargeMedia: webpageAttributes.forceLargeMedia, isManuallyAdded: webpageAttributes.isManuallyAdded)) + attributes.append(WebpagePreviewMessageAttribute(leadingPreview: false, forceLargeMedia: webpageAttributes.forceLargeMedia, isManuallyAdded: webpageAttributes.isManuallyAdded, isSafe: webpageAttributes.isSafe)) } } } diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index f934c9b6b2..9e17bb4169 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -408,7 +408,10 @@ public final class AccountViewTracker { for messageId in addedMessageIds { if self.webpageDisposables[messageId] == nil { if let (_, url) = localWebpages[messageId] { - self.webpageDisposables[messageId] = (webpagePreview(account: account, url: url) |> mapToSignal { webpage -> Signal in + self.webpageDisposables[messageId] = (webpagePreview(account: account, url: url) |> mapToSignal { result -> Signal in + guard case let .result(webpage) = result else { + return .complete() + } return account.postbox.transaction { transaction -> Void in if let webpage = webpage { transaction.updateMessage(messageId, update: { currentMessage in diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TextEntitiesMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TextEntitiesMessageAttribute.swift index 4987c2524b..178fbdb22e 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TextEntitiesMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TextEntitiesMessageAttribute.swift @@ -337,3 +337,40 @@ public func messageTextEntitiesInRange(entities: [MessageTextEntity], range: NSR } return result } + +public func quoteMaxLength(appConfig: AppConfiguration) -> Int { + if let data = appConfig.data, let quoteLengthMax = data["quote_length_max"] as? Double { + return Int(quoteLengthMax) + } + return 1024 +} + +public func trimStringWithEntities(string: String, entities: [MessageTextEntity], maxLength: Int) -> (string: String, entities: [MessageTextEntity]) { + let nsString = string as NSString + var range = 0 ..< nsString.length + + while range.lowerBound < nsString.length { + let c = nsString.character(at: range.lowerBound) + if c == 0x0a || c == 0x20 { + range = (range.lowerBound + 1) ..< range.upperBound + } else { + break + } + } + + while range.upperBound > range.lowerBound { + let c = nsString.character(at: range.lowerBound) + if c == 0x0a || c == 0x20 { + range = range.lowerBound ..< (range.upperBound - 1) + } else { + break + } + } + + while range.upperBound - range.lowerBound > maxLength { + range = range.lowerBound ..< (range.upperBound - 1) + } + + let nsRange = NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound) + return (nsString.substring(with: nsRange), messageTextEntitiesInRange(entities: entities, range: nsRange, onlyQuoteable: false)) +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/LoadMessagesIfNecessary.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/LoadMessagesIfNecessary.swift index e9cd4967be..0d8ecdba8e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/LoadMessagesIfNecessary.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/LoadMessagesIfNecessary.swift @@ -4,13 +4,17 @@ import SwiftSignalKit import TelegramApi import MtProtoKit +public enum GetMessagesResult { + case progress + case result([Message]) +} public enum GetMessagesStrategy { case local case cloud(skipLocal: Bool) } -func _internal_getMessagesLoadIfNecessary(_ messageIds: [MessageId], postbox: Postbox, network: Network, accountPeerId: PeerId, strategy: GetMessagesStrategy = .cloud(skipLocal: false)) -> Signal <[Message], NoError> { +func _internal_getMessagesLoadIfNecessary(_ messageIds: [MessageId], postbox: Postbox, network: Network, accountPeerId: PeerId, strategy: GetMessagesStrategy = .cloud(skipLocal: false)) -> Signal { let postboxSignal = postbox.transaction { transaction -> ([Message], Set, SimpleDictionary) in var ids = messageIds @@ -78,8 +82,8 @@ func _internal_getMessagesLoadIfNecessary(_ messageIds: [MessageId], postbox: Po } } - return combineLatest(signals) |> mapToSignal { results -> Signal<[Message], NoError> in - return postbox.transaction { transaction -> [Message] in + return .single(.progress) |> then(combineLatest(signals) |> mapToSignal { results -> Signal in + return postbox.transaction { transaction -> GetMessagesResult in for (peer, messages, chats, users) in results { if !messages.isEmpty { var storeMessages: [StoreMessage] = [] @@ -102,13 +106,14 @@ func _internal_getMessagesLoadIfNecessary(_ messageIds: [MessageId], postbox: Po } } - return existMessages + loadedMessages + return .result(existMessages + loadedMessages) } - } - + }) } } else { - return postboxSignal |> map {$0.0} + return postboxSignal + |> map { + return .result($0.0) + } } - } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 901fcd79e1..174a7c82ce 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -159,7 +159,7 @@ public extension TelegramEngine { return _internal_markAllChatsAsRead(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager) } - public func getMessagesLoadIfNecessary(_ messageIds: [MessageId], strategy: GetMessagesStrategy = .cloud(skipLocal: false)) -> Signal <[Message], NoError> { + public func getMessagesLoadIfNecessary(_ messageIds: [MessageId], strategy: GetMessagesStrategy = .cloud(skipLocal: false)) -> Signal { return _internal_getMessagesLoadIfNecessary(messageIds, postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId, strategy: strategy) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ResolvePeerByName.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ResolvePeerByName.swift index beacb44396..42aaf1c454 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ResolvePeerByName.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ResolvePeerByName.swift @@ -14,7 +14,17 @@ public enum ResolvePeerByNameOptionRemote { case update } -func _internal_resolvePeerByName(account: Account, name: String, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal { +public enum ResolvePeerIdByNameResult { + case progress + case result(PeerId?) +} + +public enum ResolvePeerResult { + case progress + case result(EnginePeer?) +} + +func _internal_resolvePeerByName(account: Account, name: String, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal { var normalizedName = name if normalizedName.hasPrefix("@") { normalizedName = String(normalizedName[name.index(after: name.startIndex)...]) @@ -24,17 +34,19 @@ func _internal_resolvePeerByName(account: Account, name: String, ageLimit: Int32 return account.postbox.transaction { transaction -> CachedResolvedByNamePeer? in return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.resolvedByNamePeers, key: CachedResolvedByNamePeer.key(name: normalizedName)))?.get(CachedResolvedByNamePeer.self) - } |> mapToSignal { cachedEntry -> Signal in + } + |> mapToSignal { cachedEntry -> Signal in let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) if let cachedEntry = cachedEntry, cachedEntry.timestamp <= timestamp && cachedEntry.timestamp >= timestamp - ageLimit { - return .single(cachedEntry.peerId) + return .single(.result(cachedEntry.peerId)) } else { - return account.network.request(Api.functions.contacts.resolveUsername(username: normalizedName)) + return .single(.progress) + |> then(account.network.request(Api.functions.contacts.resolveUsername(username: normalizedName)) |> mapError { _ -> Void in return Void() } - |> mapToSignal { result -> Signal in - return account.postbox.transaction { transaction -> PeerId? in + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction -> ResolvePeerIdByNameResult in var peerId: PeerId? = nil switch result { @@ -52,13 +64,13 @@ func _internal_resolvePeerByName(account: Account, name: String, ageLimit: Int32 if let entry = CodableEntry(CachedResolvedByNamePeer(peerId: peerId, timestamp: timestamp)) { transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.resolvedByNamePeers, key: CachedResolvedByNamePeer.key(name: normalizedName)), entry: entry) } - return peerId + return .result(peerId) } |> castError(Void.self) } - |> `catch` { _ -> Signal in - return .single(nil) - } + |> `catch` { _ -> Signal in + return .single(.result(nil)) + }) } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 90dd49613e..8079defdc8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -114,14 +114,19 @@ public extension TelegramEngine { return _internal_inactiveChannelList(network: self.account.network) } - public func resolvePeerByName(name: String, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal { + public func resolvePeerByName(name: String, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal { return _internal_resolvePeerByName(account: self.account, name: name, ageLimit: ageLimit) - |> mapToSignal { peerId -> Signal in - guard let peerId = peerId else { - return .single(nil) - } - return self.account.postbox.transaction { transaction -> EnginePeer? in - return transaction.getPeer(peerId).flatMap(EnginePeer.init) + |> mapToSignal { result -> Signal in + switch result { + case .progress: + return .single(.progress) + case let .result(peerId): + guard let peerId = peerId else { + return .single(.result(nil)) + } + return self.account.postbox.transaction { transaction -> ResolvePeerResult in + return .result(transaction.getPeer(peerId).flatMap(EnginePeer.init)) + } } } } @@ -710,7 +715,7 @@ public extension TelegramEngine { return _internal_updateBotAbout(account: self.account, peerId: peerId, about: about) } - public func updatePeerNameColorAndEmoji(peerId: EnginePeer.Id, nameColor: PeerNameColor, backgroundEmojiId: Int64) -> Signal { + public func updatePeerNameColorAndEmoji(peerId: EnginePeer.Id, nameColor: PeerNameColor, backgroundEmojiId: Int64?) -> Signal { return _internal_updatePeerNameColorAndEmoji(account: self.account, peerId: peerId, nameColor: nameColor, backgroundEmojiId: backgroundEmojiId) } @@ -1005,7 +1010,7 @@ public extension TelegramEngine { return _internal_createForumChannelTopic(account: self.account, peerId: id, title: title, iconColor: iconColor, iconFileId: iconFileId) } - public func fetchForumChannelTopic(id: EnginePeer.Id, threadId: Int64) -> Signal { + public func fetchForumChannelTopic(id: EnginePeer.Id, threadId: Int64) -> Signal { return _internal_fetchForumChannelTopic(account: self.account, peerId: id, threadId: threadId) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift index 8bacb1df10..b745f9583b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift @@ -596,7 +596,12 @@ func _internal_searchGifs(account: Account, query: String, nextOffset: String = let configuration = currentSearchBotsConfiguration(transaction: transaction) return configuration.gifBotUsername ?? "gif" } |> mapToSignal { - return _internal_resolvePeerByName(account: account, name: $0) + return _internal_resolvePeerByName(account: account, name: $0) |> mapToSignal { result in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } } |> filter { $0 != nil } |> map { $0! } |> mapToSignal { peerId -> Signal in diff --git a/submodules/TelegramCore/Sources/WebpagePreview.swift b/submodules/TelegramCore/Sources/WebpagePreview.swift index e6edb03da4..54e989cf0e 100644 --- a/submodules/TelegramCore/Sources/WebpagePreview.swift +++ b/submodules/TelegramCore/Sources/WebpagePreview.swift @@ -5,13 +5,18 @@ import TelegramApi import MtProtoKit -public func webpagePreview(account: Account, url: String, webpageId: MediaId? = nil) -> Signal { +public enum WebpagePreviewResult { + case progress + case result(TelegramMediaWebpage?) +} + +public func webpagePreview(account: Account, url: String, webpageId: MediaId? = nil) -> Signal { return webpagePreviewWithProgress(account: account, url: url) - |> mapToSignal { next -> Signal in + |> mapToSignal { next -> Signal in if case let .result(result) = next { - return .single(result) + return .single(.result(result)) } else { - return .complete() + return .single(.progress) } } } diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift index 048308dd71..997b19916e 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift @@ -382,27 +382,27 @@ public final class PrincipalThemeEssentialGraphics { self.chatMessageBackgroundIncomingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundIncomingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundIncomingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) - self.chatMessageBackgroundIncomingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true) + self.chatMessageBackgroundIncomingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate) self.chatMessageBackgroundIncomingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundIncomingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundIncomingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundIncomingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) - self.chatMessageBackgroundIncomingMergedTopHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true) + self.chatMessageBackgroundIncomingMergedTopHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate) self.chatMessageBackgroundIncomingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundIncomingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundIncomingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundIncomingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) - self.chatMessageBackgroundIncomingMergedTopSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true) + self.chatMessageBackgroundIncomingMergedTopSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate) self.chatMessageBackgroundIncomingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundIncomingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundIncomingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundIncomingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) - self.chatMessageBackgroundIncomingMergedBottomHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true) + self.chatMessageBackgroundIncomingMergedBottomHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate) self.chatMessageBackgroundIncomingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundIncomingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundIncomingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundIncomingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) - self.chatMessageBackgroundIncomingMergedBothHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true) + self.chatMessageBackgroundIncomingMergedBothHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate) self.chatMessageBackgroundOutgoingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundOutgoingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) @@ -411,27 +411,27 @@ public final class PrincipalThemeEssentialGraphics { self.chatMessageBackgroundOutgoingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundOutgoingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundOutgoingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) - self.chatMessageBackgroundOutgoingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true) + self.chatMessageBackgroundOutgoingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .white, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate) self.chatMessageBackgroundOutgoingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundOutgoingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) - self.chatMessageBackgroundOutgoingMergedTopHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true) + self.chatMessageBackgroundOutgoingMergedTopHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .white, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate) self.chatMessageBackgroundOutgoingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundOutgoingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) - self.chatMessageBackgroundOutgoingMergedTopSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true) + self.chatMessageBackgroundOutgoingMergedTopSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .white, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate) self.chatMessageBackgroundOutgoingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundOutgoingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) - self.chatMessageBackgroundOutgoingMergedBottomHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true) + self.chatMessageBackgroundOutgoingMergedBottomHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .white, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate) self.chatMessageBackgroundOutgoingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundOutgoingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) - self.chatMessageBackgroundOutgoingMergedBothHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true) + self.chatMessageBackgroundOutgoingMergedBothHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .white, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate) self.chatMessageBackgroundIncomingMergedSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundIncomingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) @@ -441,8 +441,8 @@ public final class PrincipalThemeEssentialGraphics { self.chatMessageBackgroundOutgoingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundOutgoingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) - self.chatMessageBackgroundIncomingMergedSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true) - self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true) + self.chatMessageBackgroundIncomingMergedSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate) + self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .white, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: highlightKnockout, extendedEdges: true, alwaysFillColor: true).withRenderingMode(.alwaysTemplate) self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.message.outgoingCheckColor, width: 11.0)! self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor, width: 11.0)! diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 4e64472871..f15621f284 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -572,6 +572,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI private var appliedForwardInfo: (Peer?, String?)? private var disablesComments = true + private var authorNameColor: UIColor? + private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer? private var replyRecognizer: ChatSwipeToReplyRecognizer? @@ -1854,6 +1856,18 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } } + + if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info, item.content.firstMessage.adAttribute == nil { + authorNameColor = (peer as Peer).nameColor?.color + } else if let effectiveAuthor = effectiveAuthor { + let nameColor: UIColor + if incoming { + nameColor = (effectiveAuthor.nameColor ?? .blue).color + } else { + nameColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor + } + authorNameColor = nameColor + } if initialDisplayHeader && displayAuthorInfo { if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info, item.content.firstMessage.adAttribute == nil { @@ -2696,6 +2710,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI nameNodeSizeApply: nameNodeSizeApply, contentOrigin: contentOrigin, nameNodeOriginY: nameNodeOriginY, + authorNameColor: authorNameColor, layoutConstants: layoutConstants, currentCredibilityIcon: currentCredibilityIcon, adminNodeSizeApply: adminNodeSizeApply, @@ -2747,6 +2762,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI nameNodeSizeApply: (CGSize, () -> TextNode?), contentOrigin: CGPoint, nameNodeOriginY: CGFloat, + authorNameColor: UIColor?, layoutConstants: ChatMessageItemLayoutConstants, currentCredibilityIcon: EmojiStatusComponent.Content?, adminNodeSizeApply: (CGSize, () -> TextNode?), @@ -2786,6 +2802,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.updateAccessibilityData(accessibilityData) strongSelf.disablesComments = disablesComments + strongSelf.authorNameColor = authorNameColor + strongSelf.replyRecognizer?.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply strongSelf.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply @@ -4499,6 +4517,26 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI self.mainContextSourceNode.contentNode.insertSubnode(backgroundHighlightNode, aboveSubnode: self.backgroundNode) self.backgroundHighlightNode = backgroundHighlightNode + let hasWallpaper = item.presentationData.theme.wallpaper.hasWallpaper + let incoming: PresentationThemeBubbleColorComponents = !hasWallpaper ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper : item.presentationData.theme.theme.chat.message.incoming.bubble.withWallpaper + let outgoing: PresentationThemeBubbleColorComponents = !hasWallpaper ? item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper : item.presentationData.theme.theme.chat.message.outgoing.bubble.withWallpaper + + let highlightColor: UIColor + if item.message.effectivelyIncoming(item.context.account.peerId) { + if let authorNameColor = self.authorNameColor { + highlightColor = authorNameColor.withMultipliedAlpha(0.2) + } else { + highlightColor = incoming.highlightedFill + } + } else { + if let authorNameColor = self.authorNameColor { + highlightColor = authorNameColor.withMultipliedAlpha(0.2) + } else { + highlightColor = outgoing.highlightedFill + } + } + + backgroundHighlightNode.customHighlightColor = highlightColor backgroundHighlightNode.setType(type: backgroundType, highlighted: true, graphics: graphics, maskMode: true, hasWallpaper: false, transition: .immediate, backgroundNode: nil) backgroundHighlightNode.frame = self.backgroundNode.frame @@ -4511,17 +4549,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } if let highlightedState = self.highlightedState, let quote = highlightedState.quote { - let hasWallpaper = item.presentationData.theme.wallpaper.hasWallpaper - let incoming: PresentationThemeBubbleColorComponents = !hasWallpaper ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper : item.presentationData.theme.theme.chat.message.incoming.bubble.withWallpaper - let outgoing: PresentationThemeBubbleColorComponents = !hasWallpaper ? item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper : item.presentationData.theme.theme.chat.message.outgoing.bubble.withWallpaper - - let highlightColor: UIColor - if item.message.effectivelyIncoming(item.context.account.peerId) { - highlightColor = incoming.highlightedFill - } else { - highlightColor = outgoing.highlightedFill - } - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) var quoteFrame: CGRect? @@ -4552,7 +4579,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI backgroundHighlightNode.updateLayout(size: quoteFrame.size, transition: transition) transition.updateFrame(node: backgroundHighlightNode, frame: quoteFrame) - backgroundHighlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, delay: 0.1, removeOnCompletion: false, completion: { [weak backgroundHighlightNode] _ in + backgroundHighlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, delay: 0.05, removeOnCompletion: false, completion: { [weak backgroundHighlightNode] _ in backgroundHighlightNode?.removeFromSupernode() }) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift index 65a301126f..7abcd20972 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift @@ -32,7 +32,7 @@ private let channelIcon: UIImage = { return generateImage(CGSize(width: sourceImage.size.width + 4.0, height: sourceImage.size.height + 4.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) UIGraphicsPushContext(context) - sourceImage.draw(at: CGPoint(x: 2.0, y: 2.0)) + sourceImage.draw(at: CGPoint(x: 2.0, y: 1.0 + UIScreenPixel)) UIGraphicsPopContext() })!.precomposed().withRenderingMode(.alwaysTemplate) }() @@ -42,7 +42,7 @@ private let groupIcon: UIImage = { return generateImage(CGSize(width: sourceImage.size.width + 3.0, height: sourceImage.size.height + 4.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) UIGraphicsPushContext(context) - sourceImage.draw(at: CGPoint(x: 3.0, y: 1.0)) + sourceImage.draw(at: CGPoint(x: 3.0, y: 1.0 - UIScreenPixel)) UIGraphicsPopContext() })!.precomposed().withRenderingMode(.alwaysTemplate) }() @@ -388,7 +388,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { var text: String var messageEntities: [MessageTextEntity] - if let quote = arguments.quote { + if let quote = arguments.quote, !quote.text.isEmpty { text = quote.text messageEntities = quote.entities } else { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index 65b081d017..a357a92bbc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -956,7 +956,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { currentRect = currentRect.insetBy(dx: -quoteHighlightingNode.inset, dy: -quoteHighlightingNode.inset) let innerRect = currentRect.offsetBy(dx: quoteHighlightingNode.frame.minX, dy: quoteHighlightingNode.frame.minY) - quoteHighlightingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.08, delay: 0.1) + quoteHighlightingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, delay: 0.04) let fromScale = CGPoint(x: sourceFrame.width / innerRect.width, y: sourceFrame.height / innerRect.height) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 43f59e8141..39d19f49d1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -54,12 +54,17 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent item.controllerInteraction.openTheme(item.message) return } else { - if content.title != nil || content.text != nil { + if content.embedUrl == nil && (content.title != nil || content.text != nil) { var isConcealed = true if item.message.text.contains(content.url) { isConcealed = false } - item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: content.url, concealed: isConcealed)) + if let attribute = item.message.webpagePreviewAttribute { + if attribute.isSafe { + isConcealed = false + } + } + item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: content.url, concealed: isConcealed, progress: strongSelf.contentNode.makeProgress())) return } } @@ -100,6 +105,11 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent if item.message.text.contains(webpage.url) { isConcealed = false } + if let attribute = item.message.webpagePreviewAttribute { + if attribute.isSafe { + isConcealed = false + } + } item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: webpage.url, concealed: isConcealed, progress: strongSelf.contentNode.makeProgress())) } } @@ -119,6 +129,11 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent if item.message.text.contains(content.url) { isConcealed = false } + if let attribute = item.message.webpagePreviewAttribute { + if attribute.isSafe { + isConcealed = false + } + } return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: content.url, concealed: isConcealed, allowInlineWebpageResolution: true)), activate: { [weak self] in guard let self else { return nil diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index fd8177dcd1..4db80d297e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -296,6 +296,12 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { let resolveSignal: Signal if let peerName = peerName { resolveSignal = strongSelf.context.engine.peers.resolvePeerByName(name: peerName) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> mapToSignal { peer -> Signal in return .single(peer?._asPeer()) } @@ -824,7 +830,12 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { private func openPeerMention(_ name: String) { self.navigationActionDisposable.set((self.context.engine.peers.resolvePeerByName(name: name, ageLimit: 10) - |> take(1) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> deliverOnMainQueue).startStrict(next: { [weak self] peer in if let strongSelf = self { if let peer = peer { diff --git a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift index f4a91de421..fb811c36ea 100644 --- a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift +++ b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift @@ -232,7 +232,7 @@ private final class LineView: UIView { dashBackgroundView.layer.add(animation, forKey: "progress") } } else { - let phaseDuration: Double = 0.8 + let phaseDuration: Double = 1.0 if self.backgroundView.layer.animation(forKey: "progress") == nil { let animation = self.backgroundView.layer.makeAnimation(from: 0.0 as NSNumber, to: -params.size.height as NSNumber, keyPath: "position.y", timingFunction: kCAMediaTimingFunctionSpring, duration: phaseDuration * 0.5, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: false, additive: true) animation.repeatCount = 1.0 diff --git a/submodules/TelegramUI/Components/EntityKeyboardGifContent/Sources/GifContext.swift b/submodules/TelegramUI/Components/EntityKeyboardGifContent/Sources/GifContext.swift index 7085d8c9c0..15c955e0b3 100644 --- a/submodules/TelegramUI/Components/EntityKeyboardGifContent/Sources/GifContext.swift +++ b/submodules/TelegramUI/Components/EntityKeyboardGifContent/Sources/GifContext.swift @@ -55,6 +55,12 @@ public func paneGifSearchForQuery(context: AccountContext, query: String, offset |> mapToSignal { searchBots -> Signal in let botName = searchBots.gifBotUsername ?? "gif" return context.engine.peers.resolvePeerByName(name: botName) + |> mapToSignal { result in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } } |> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?, Bool, Bool), NoError> in if case let .user(user) = peer, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index fcd7692bc3..6c5fd1edc8 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -2772,7 +2772,12 @@ final class StoryItemSetContainerSendMessage { self.resolvePeerByNameDisposable.set(nil) } disposable.set((resolveSignal - |> take(1) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> mapToSignal { peer -> Signal in return .single(peer?._asPeer()) } @@ -2805,6 +2810,12 @@ final class StoryItemSetContainerSendMessage { var resolveSignal: Signal if let peerName = peerName { resolveSignal = component.context.engine.peers.resolvePeerByName(name: peerName) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> mapToSignal { peer -> Signal in if let peer = peer { return .single(peer._asPeer()) diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index be5f0c6a09..a1aa16eac1 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -487,7 +487,14 @@ final class AuthorizedApplicationContext { |> deliverOnMainQueue).start(completed: { controller?.dismiss() if let strongSelf = self, let botName = botName { - strongSelf.termsOfServiceProceedToBotDisposable.set((strongSelf.context.engine.peers.resolvePeerByName(name: botName, ageLimit: 10) |> take(1) |> deliverOnMainQueue).start(next: { peer in + strongSelf.termsOfServiceProceedToBotDisposable.set((strongSelf.context.engine.peers.resolvePeerByName(name: botName, ageLimit: 10) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } + |> deliverOnMainQueue).start(next: { peer in if let strongSelf = self, let peer = peer { self?.rootController.pushViewController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(id: peer.id))) } diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Sources/AttachmentFileController.swift index e5621608c8..c009a04742 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -246,6 +246,12 @@ func makeAttachmentFileControllerImpl(context: AccountContext, updatedPresentati }, send: { message in let _ = (context.engine.messages.getMessagesLoadIfNecessary([message.id], strategy: .cloud(skipLocal: true)) + |> mapToSignal { result -> Signal<[Message], NoError> in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> deliverOnMainQueue).startStandalone(next: { messages in if let message = messages.first, let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile { send(.message(message: MessageReference(message), media: file)) diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 2ce0a8cd7a..b3602523f6 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -267,6 +267,16 @@ private func chatForwardOptions(selfController: ChatControllerImpl, sourceNode: f(.default) }))) + //TODO:localize + items.append(.action(ContextMenuActionItem(text: "Remove Forward", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak selfController] c, f in + f(.default) + + guard let selfController else { + return + } + selfController.updateChatPresentationInterfaceState(interactive: false, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(nil).withoutSelectionState() }) }) + }))) + return items } @@ -338,8 +348,13 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch guard let textSelection = contentNode.getCurrentTextSelection() else { return } + var quote: EngineMessageReplyQuote? + let trimmedText = trimStringWithEntities(string: textSelection.text, entities: textSelection.entities, maxLength: quoteMaxLength(appConfig: selfController.context.currentAppConfiguration.with({ $0 }))) + if !trimmedText.string.isEmpty { + quote = EngineMessageReplyQuote(text: trimmedText.string, entities: trimmedText.entities, media: nil) + } - selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject(messageId: replySubject.messageId, quote: EngineMessageReplyQuote(text: textSelection.text, entities: textSelection.entities, media: nil))).withoutSelectionState() }) }) + selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject(messageId: replySubject.messageId, quote: quote)).withoutSelectionState() }) }) f(.default) }))) @@ -381,7 +396,13 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch return } - selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject(messageId: replySubject.messageId, quote: EngineMessageReplyQuote(text: textSelection.text, entities: textSelection.entities, media: nil))).withoutSelectionState() }) }) + var quote: EngineMessageReplyQuote? + let trimmedText = trimStringWithEntities(string: textSelection.text, entities: textSelection.entities, maxLength: quoteMaxLength(appConfig: selfController.context.currentAppConfiguration.with({ $0 }))) + if !trimmedText.string.isEmpty { + quote = EngineMessageReplyQuote(text: trimmedText.string, entities: trimmedText.entities, media: nil) + } + + selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject(messageId: replySubject.messageId, quote: quote)).withoutSelectionState() }) }) f(.default) }))) @@ -424,6 +445,17 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch replySubject.quote = nil selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject).withoutSelectionState() }).updatedSearch(nil) }) }))) + } else { + items.append(.action(ContextMenuActionItem(text: "Remove Reply", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak selfController] c, f in + f(.default) + + guard let selfController else { + return + } + var replySubject = replySubject + replySubject.quote = nil + selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil).withoutSelectionState() }).updatedSearch(nil) }) + }))) } return items diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 707a7f53dd..f4898f2dfb 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3974,7 +3974,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let nsRange = NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound) let quoteText = (message.text as NSString).substring(with: nsRange) - quoteData = EngineMessageReplyQuote(text: quoteText, entities: messageTextEntitiesInRange(entities: message.textEntitiesAttribute?.entities ?? [], range: nsRange, onlyQuoteable: true), media: nil) + + let trimmedText = trimStringWithEntities(string: quoteText, entities: messageTextEntitiesInRange(entities: message.textEntitiesAttribute?.entities ?? [], range: nsRange, onlyQuoteable: true), maxLength: quoteMaxLength(appConfig: strongSelf.context.currentAppConfiguration.with({ $0 }))) + if !trimmedText.string.isEmpty { + quoteData = EngineMessageReplyQuote(text: trimmedText.string, entities: trimmedText.entities, media: nil) + } let replySubject = ChatInterfaceState.ReplyMessageSubject( messageId: message.id, @@ -9038,7 +9042,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G disableUrlPreview = true } else { webpage = urlPreview.webPage - webpagePreviewAttribute = WebpagePreviewMessageAttribute(leadingPreview: !urlPreview.positionBelowText, forceLargeMedia: urlPreview.largeMedia, isManuallyAdded: true) + webpagePreviewAttribute = WebpagePreviewMessageAttribute(leadingPreview: !urlPreview.positionBelowText, forceLargeMedia: urlPreview.largeMedia, isManuallyAdded: true, isSafe: false) } } @@ -9103,7 +9107,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { if let currentMessage = currentMessage { let currentEntities = currentMessage.textEntitiesAttribute?.entities ?? [] - let currentWebpagePreviewAttribute = currentMessage.webpagePreviewAttribute ?? WebpagePreviewMessageAttribute(leadingPreview: false, forceLargeMedia: nil, isManuallyAdded: true) + let currentWebpagePreviewAttribute = currentMessage.webpagePreviewAttribute ?? WebpagePreviewMessageAttribute(leadingPreview: false, forceLargeMedia: nil, isManuallyAdded: true, isSafe: false) if currentMessage.text != text.string || currentEntities != entities || updatingMedia || webpagePreviewAttribute != currentWebpagePreviewAttribute || disableUrlPreview { strongSelf.context.account.pendingUpdateMessageManager.add(messageId: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview) @@ -16803,6 +16807,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = (combineLatest( self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)), self.context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .local) + |> mapToSignal { result -> Signal<[Message], NoError> in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } ) |> deliverOnMainQueue).startStandalone(next: { [weak self] peer, messages in guard let self, let peer = peer else { @@ -17568,6 +17578,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.resolvePeerByNameDisposable = disposable } var resolveSignal = self.context.engine.peers.resolvePeerByName(name: name, ageLimit: 10) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } var cancelImpl: (() -> Void)? let presentationData = self.presentationData @@ -17629,6 +17645,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var resolveSignal: Signal if let peerName = peerName { resolveSignal = self.context.engine.peers.resolvePeerByName(name: peerName) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> mapToSignal { peer -> Signal in if let peer = peer { return .single(peer._asPeer()) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 9dba950e83..82436a5d8a 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -426,8 +426,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { return false } if let attribute = attribute as? ReplyMessageAttribute { - if !forwardedMessageIds.contains(attribute.messageId) || hideNames { - return false + if attribute.quote != nil { + } else { + if !forwardedMessageIds.contains(attribute.messageId) || hideNames { + return false + } } } if attribute is ReplyMarkupMessageAttribute { @@ -535,7 +538,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { attributes.append(TextEntitiesMessageAttribute(entities: options.messageEntities)) - attributes.append(WebpagePreviewMessageAttribute(leadingPreview: !options.linkBelowText, forceLargeMedia: options.largeMedia, isManuallyAdded: true)) + attributes.append(WebpagePreviewMessageAttribute(leadingPreview: !options.linkBelowText, forceLargeMedia: options.largeMedia, isManuallyAdded: true, isSafe: false)) if let replyMessage { associatedMessages[replyMessage.id] = replyMessage @@ -3436,7 +3439,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { attributes.append(OutgoingContentInfoMessageAttribute(flags: [.disableLinkPreviews])) } else { webpage = urlPreview.webPage - attributes.append(WebpagePreviewMessageAttribute(leadingPreview: !urlPreview.positionBelowText, forceLargeMedia: urlPreview.largeMedia, isManuallyAdded: true)) + attributes.append(WebpagePreviewMessageAttribute(leadingPreview: !urlPreview.positionBelowText, forceLargeMedia: urlPreview.largeMedia, isManuallyAdded: true, isSafe: false)) } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift index 1c0df34991..408ec77ba8 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift @@ -240,6 +240,12 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee let chatPeer = peer let contextBot = context.engine.peers.resolvePeerByName(name: addressName) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> castError(ChatContextQueryError.self) |> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> in if case let .user(user) = peer, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder { @@ -512,7 +518,14 @@ func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: Acco let detectedUrl = detectUrls(inputText).first if detectedUrl != currentQuery { if let detectedUrl = detectedUrl { - return (detectedUrl, webpagePreview(account: context.account, url: detectedUrl) |> map { value in + return (detectedUrl, webpagePreview(account: context.account, url: detectedUrl) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } + |> map { value in return { _ in return value } }) } else { diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 3308b249e9..3372203cab 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -318,6 +318,12 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur if let to = to { if to.hasPrefix("@") { let _ = (context.engine.peers.resolvePeerByName(name: String(to[to.index(to.startIndex, offsetBy: 1)...])) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> deliverOnMainQueue).startStandalone(next: { peer in if let peer = peer { context.sharedContext.applicationBindings.dismissNativeController() diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 4a3af7f48b..f5bea1e8c1 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -4599,6 +4599,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.resolvePeerByNameDisposable = disposable } var resolveSignal = self.context.engine.peers.resolvePeerByName(name: name, ageLimit: 10) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } var cancelImpl: (() -> Void)? let presentationData = self.presentationData @@ -4655,6 +4661,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro var resolveSignal: Signal if let peerName = peerName { resolveSignal = self.context.engine.peers.resolvePeerByName(name: peerName) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> mapToSignal { peer -> Signal in return .single(peer?._asPeer()) } @@ -8674,7 +8686,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let context = self.context let navigationController = self.controller?.navigationController as? NavigationController - self.tipsPeerDisposable.set((self.context.engine.peers.resolvePeerByName(name: self.presentationData.strings.Settings_TipsUsername) |> deliverOnMainQueue).startStrict(next: { [weak controller] peer in + self.tipsPeerDisposable.set((self.context.engine.peers.resolvePeerByName(name: self.presentationData.strings.Settings_TipsUsername) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } + |> deliverOnMainQueue).startStrict(next: { [weak controller] peer in controller?.dismiss() if let peer = peer, let navigationController = navigationController { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer))) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 7d20cdce13..90a2dab43c 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1402,6 +1402,18 @@ public final class SharedAccountContextImpl: SharedAccountContext { public func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal { return resolveUrlImpl(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth) + |> mapToSignal { result -> Signal in + switch result { + case .progress: + return .complete() + case let .result(value): + return .single(value) + } + } + } + + public func resolveUrlWithProgress(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal { + return resolveUrlImpl(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth) } public func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) { diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index f10530b8dc..4cf6aa14dd 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -104,7 +104,14 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n } let openPeerMentionImpl: (String) -> Void = { mention in - navigateDisposable.set((context.engine.peers.resolvePeerByName(name: mention, ageLimit: 10) |> take(1) |> deliverOnMainQueue).start(next: { peer in + navigateDisposable.set((context.engine.peers.resolvePeerByName(name: mention, ageLimit: 10) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } + |> deliverOnMainQueue).start(next: { peer in openResolvedPeerImpl(peer, .default) })) } diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index a069796020..ea595eb8dc 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -591,227 +591,277 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { return nil } -private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) -> Signal { +private enum ResolveInternalUrlResult { + case progress + case result(ResolvedUrl?) +} + +private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) -> Signal { switch url { case let .phone(phone, attach, startAttach): return context.engine.peers.resolvePeerByPhone(phone: phone) - |> take(1) - |> mapToSignal { peer -> Signal in + |> mapToSignal { peer -> Signal in if let peer = peer?._asPeer() { if let attach = attach { return context.engine.peers.resolvePeerByName(name: attach) - |> take(1) - |> map { botPeer -> ResolvedUrl? in - if let botPeer = botPeer?._asPeer() { - return .peer(peer, .withAttachBot(ChatControllerInitialAttachBotStart(botId: botPeer.id, payload: startAttach, justInstalled: false))) - } else { - return .peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)) + |> map { result -> ResolveInternalUrlResult in + switch result { + case .progress: + return .progress + case let .result(botPeer): + if let botPeer = botPeer?._asPeer() { + return .result(.peer(peer, .withAttachBot(ChatControllerInitialAttachBotStart(botId: botPeer.id, payload: startAttach, justInstalled: false)))) + } else { + return .result(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))) + } } } } else { - return .single(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))) + return .single(.result(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)))) } } else { - return .single(.peer(nil, .info)) + return .single(.result(.peer(nil, .info))) } } case let .peer(reference, parameter): - let resolvedPeer: Signal + let resolvedPeer: Signal switch reference { case let .name(name): resolvedPeer = context.engine.peers.resolvePeerByName(name: name) - |> take(1) - |> mapToSignal { peer -> Signal in - return .single(peer?._asPeer()) + |> mapToSignal { result -> Signal in + switch result { + case .progress: + return .single(.progress) + case let .result(peer): + return .single(.result(peer)) + } } case let .id(id): if id.namespace == Namespaces.Peer.CloudChannel { resolvedPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: id)) - |> mapToSignal { peer -> Signal in - let foundPeer: Signal + |> mapToSignal { peer -> Signal in + let foundPeer: Signal if let peer = peer { - foundPeer = .single(peer._asPeer()) + foundPeer = .single(.result(peer)) } else { - foundPeer = TelegramEngine(account: context.account).peers.findChannelById(channelId: id.id._internalGetInt64Value()) - |> map { peer -> Peer? in - return peer?._asPeer() - } + foundPeer = .single(.progress) |> then(context.engine.peers.findChannelById(channelId: id.id._internalGetInt64Value()) + |> map { peer -> ResolvePeerResult in + return .result(peer) + }) } return foundPeer } } else { - resolvedPeer = .single(nil) + resolvedPeer = .single(.result(nil)) } } return resolvedPeer - |> mapToSignal { peer -> Signal in + |> mapToSignal { result -> Signal in + guard case let .result(peer) = result else { + return .single(.progress) + } + if let peer = peer { if let parameter = parameter { switch parameter { case let .botStart(payload): - return .single(.botStart(peer: peer, payload: payload)) + return .single(.result(.botStart(peer: peer._asPeer(), payload: payload))) case let .groupBotStart(payload, adminRights): - return .single(.groupBotStart(peerId: peer.id, payload: payload, adminRights: adminRights)) + return .single(.result(.groupBotStart(peerId: peer.id, payload: payload, adminRights: adminRights))) case let .gameStart(game): - return .single(.gameStart(peerId: peer.id, game: game)) + return .single(.result(.gameStart(peerId: peer.id, game: game))) case let .attachBotStart(name, payload): return context.engine.peers.resolvePeerByName(name: name) - |> take(1) - |> mapToSignal { botPeer -> Signal in - return .single(botPeer?._asPeer()) - } - |> mapToSignal { botPeer -> Signal in - if let botPeer = botPeer { - return .single(.peer(peer, .withAttachBot(ChatControllerInitialAttachBotStart(botId: botPeer.id, payload: payload, justInstalled: false)))) - } else { - return .single(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))) + |> mapToSignal { botPeerResult -> Signal in + switch botPeerResult { + case .progress: + return .single(.progress) + case let .result(botPeer): + if let botPeer = botPeer { + return .single(.result(.peer(peer._asPeer(), .withAttachBot(ChatControllerInitialAttachBotStart(botId: botPeer.id, payload: payload, justInstalled: false))))) + } else { + return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)))) + } } } case let .appStart(name, payload): - return context.engine.messages.getBotApp(botId: peer.id, shortName: name, cached: false) + return .single(.progress) |> then(context.engine.messages.getBotApp(botId: peer.id, shortName: name, cached: false) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } - |> take(1) - |> mapToSignal { botApp -> Signal in + |> mapToSignal { botApp -> Signal in if let botApp { - return .single(.peer(peer, .withBotApp(ChatControllerInitialBotAppStart(botApp: botApp, payload: payload, justInstalled: false)))) + return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: botApp, payload: payload, justInstalled: false))))) } else { - return .single(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))) + return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)))) } - } + }) case let .channelMessage(id, timecode): - if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) { + if case let .channel(channel) = peer, channel.flags.contains(.isForum) { let messageId = MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: id) return context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .cloud(skipLocal: false)) |> take(1) - |> mapToSignal { messages -> Signal in - if let threadId = messages.first?.threadId { - return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: threadId) - |> map { info -> ResolvedUrl? in - if let _ = info { - return .replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: messageId) - } else { - return .peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)) + |> mapToSignal { result -> Signal in + switch result { + case .progress: + return .single(.progress) + case let .result(messages): + if let threadId = messages.first?.threadId { + return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: threadId) + |> map { result -> ResolveInternalUrlResult in + switch result { + case .progress: + return .progress + case let .result(info): + if let _ = info { + return .result(.replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: messageId)) + } else { + return .result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))) + } + } } + } else { + return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)))) } - } else { - return .single(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))) } } } else { - return .single(.channelMessage(peer: peer, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode)) + return .single(.result(.channelMessage(peer: peer._asPeer(), messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode))) } case let .replyThread(id, replyId): let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id) - if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) { + if case let .channel(channel) = peer, channel.flags.contains(.isForum) { return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(replyThreadMessageId.id)) - |> map { info -> ResolvedUrl? in - if let _ = info { - return .replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: replyThreadMessageId.id)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: replyId)) - } else { - return .peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)) + |> map { result -> ResolveInternalUrlResult in + switch result { + case .progress: + return .progress + case let .result(info): + if let _ = info { + return .result(.replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: replyThreadMessageId.id)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: replyId))) + } else { + return .result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))) + } } } } else { - return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil) + return .single(.progress) |> then(context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } - |> map { result -> ResolvedUrl? in + |> map { result -> ResolveInternalUrlResult in guard let result = result else { - return .channelMessage(peer: peer, messageId: replyThreadMessageId, timecode: nil) + return .result(.channelMessage(peer: peer._asPeer(), messageId: replyThreadMessageId, timecode: nil)) } - return .replyThreadMessage(replyThreadMessage: result, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId)) - } + return .result(.replyThreadMessage(replyThreadMessage: result, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId))) + }) } case let .voiceChat(invite): - return .single(.joinVoiceChat(peer.id, invite)) + return .single(.result(.joinVoiceChat(peer.id, invite))) case let .story(id): - return context.engine.messages.refreshStories(peerId: peer.id, ids: [id]) - |> map { _ -> ResolvedUrl? in + return .single(.progress) |> then(context.engine.messages.refreshStories(peerId: peer.id, ids: [id]) + |> map { _ -> ResolveInternalUrlResult in } - |> then(.single(.story(peerId: peer.id, id: id))) + |> then(.single(.result(.story(peerId: peer.id, id: id))))) case .boost: - return combineLatest( + return .single(.progress) |> then(combineLatest( context.engine.peers.getChannelBoostStatus(peerId: peer.id), context.engine.peers.getMyBoostStatus() ) - |> map { boostStatus, myBoostStatus -> ResolvedUrl? in - return .boost(peerId: peer.id, status: boostStatus, myBoostStatus: myBoostStatus) - } + |> map { boostStatus, myBoostStatus -> ResolveInternalUrlResult in + return .result(.boost(peerId: peer.id, status: boostStatus, myBoostStatus: myBoostStatus)) + }) } } else { - return .single(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))) + return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)))) } } else { - return .single(.peer(nil, .info)) + return .single(.result(.peer(nil, .info))) } } case let .peerId(peerId): return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> mapToSignal { peer -> Signal in + |> mapToSignal { peer -> Signal in if let peer = peer { - return .single(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))) + return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)))) } else { - return .single(.inaccessiblePeer) + return .single(.result(.inaccessiblePeer)) } } case let .contactToken(token): - return context.engine.peers.importContactToken(token: token) - |> mapToSignal { peer -> Signal in + return .single(.progress) |> then(context.engine.peers.importContactToken(token: token) + |> mapToSignal { peer -> Signal in if let peer = peer { - return .single(.peer(peer._asPeer(), .info)) + return .single(.result(.peer(peer._asPeer(), .info))) } else { - return .single(.peer(nil, .info)) + return .single(.result(.peer(nil, .info))) } - } + }) case let .privateMessage(messageId, threadId, timecode): return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)) - |> mapToSignal { peer -> Signal in + |> mapToSignal { peer -> Signal in let foundPeer: Signal if let peer = peer { foundPeer = .single(peer) } else { - foundPeer = TelegramEngine(account: context.account).peers.findChannelById(channelId: messageId.peerId.id._internalGetInt64Value()) + foundPeer = context.engine.peers.findChannelById(channelId: messageId.peerId.id._internalGetInt64Value()) } - return foundPeer - |> mapToSignal { foundPeer -> Signal in + return .single(.progress) |> then(foundPeer + |> mapToSignal { foundPeer -> Signal in if let foundPeer = foundPeer { if case let .channel(channel) = foundPeer, channel.flags.contains(.isForum) { if let threadId = threadId { return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(threadId)) - |> map { info -> ResolvedUrl? in - if let _ = info { - return .replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: messageId) - } else { - return .peer(peer?._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)) + |> map { result -> ResolveInternalUrlResult in + switch result { + case .progress: + return .progress + case let .result(info): + if let _ = info { + return .result(.replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: messageId)) + } else { + return .result(.peer(peer?._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))) + } } } } else { return context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .cloud(skipLocal: false)) - |> take(1) - |> mapToSignal { messages -> Signal in - if let threadId = messages.first?.threadId { - return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: threadId) - |> map { info -> ResolvedUrl? in - if let _ = info { - return .replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: messageId) - } else { - return .peer(peer?._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)) + |> mapToSignal { result -> Signal in + switch result { + case .progress: + return .single(.progress) + case let .result(messages): + if let threadId = messages.first?.threadId { + return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: threadId) + |> map { result -> ResolveInternalUrlResult in + switch result { + case .progress: + return .progress + case let .result(info): + if let _ = info { + return .result(.replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: messageId)) + } else { + return .result(.peer(peer?._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))) + } + } } - } - } else { - return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(messageId.id)) - |> map { info -> ResolvedUrl? in - if let _ = info { - return .replyThread(messageId: messageId) - } else { - return .peer(foundPeer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)) + } else { + return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(messageId.id)) + |> map { result -> ResolveInternalUrlResult in + switch result { + case .progress: + return .progress + case let .result(info): + if let _ = info { + return .result(.replyThread(messageId: messageId)) + } else { + return .result(.peer(foundPeer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))) + } + } } } } @@ -819,60 +869,67 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } } else if let threadId = threadId { let replyThreadMessageId = MessageId(peerId: foundPeer.id, namespace: Namespaces.Message.Cloud, id: threadId) - return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil) + return .single(.progress) |> then(context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } - |> map { result -> ResolvedUrl? in + |> map { result -> ResolveInternalUrlResult in guard let result = result else { - return .channelMessage(peer: foundPeer._asPeer(), messageId: replyThreadMessageId, timecode: timecode) + return .result(.channelMessage(peer: foundPeer._asPeer(), messageId: replyThreadMessageId, timecode: timecode)) } - return .replyThreadMessage(replyThreadMessage: result, messageId: messageId) - } + return .result(.replyThreadMessage(replyThreadMessage: result, messageId: messageId)) + }) } else { - return .single(.peer(foundPeer._asPeer(), .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: timecode), peekData: nil))) + return .single(.result(.peer(foundPeer._asPeer(), .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: timecode), peekData: nil)))) } } else { - return .single(.inaccessiblePeer) + return .single(.result(.inaccessiblePeer)) } - } + }) } case let .stickerPack(name, type): - return .single(.stickerPack(name: name, type: type)) + return .single(.result(.stickerPack(name: name, type: type))) case let .chatFolder(slug): - return .single(.chatFolder(slug: slug)) + return .single(.result(.chatFolder(slug: slug))) case let .invoice(slug): - return context.engine.payments.fetchBotPaymentInvoice(source: .slug(slug)) + return .single(.progress) |> then(context.engine.payments.fetchBotPaymentInvoice(source: .slug(slug)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } - |> map { invoice -> ResolvedUrl? in + |> map { invoice -> ResolveInternalUrlResult in guard let invoice = invoice else { - return .invoice(slug: slug, invoice: nil) + return .result(.invoice(slug: slug, invoice: nil)) } - return .invoice(slug: slug, invoice: invoice) - } + return .result(.invoice(slug: slug, invoice: invoice)) + }) case let .join(link): - return .single(.join(link)) + return .single(.result(.join(link))) case let .localization(identifier): - return .single(.localization(identifier)) + return .single(.result(.localization(identifier))) case let .proxy(host, port, username, password, secret): - return .single(.proxy(host: host, port: port, username: username, password: password, secret: secret)) + return .single(.result(.proxy(host: host, port: port, username: username, password: password, secret: secret))) case let .internalInstantView(url): return resolveInstantViewUrl(account: context.account, url: url) - |> map(Optional.init) + |> map { result in + switch result { + case .progress: + return .progress + case let .result(result): + return .result(result) + } + } case let .confirmationCode(code): - return .single(.confirmationCode(code)) + return .single(.result(.confirmationCode(code))) case let .cancelAccountReset(phone, hash): - return .single(.cancelAccountReset(phone: phone, hash: hash)) + return .single(.result(.cancelAccountReset(phone: phone, hash: hash))) case let .share(url, text, to): - return .single(.share(url: url, text: text, to: to)) + return .single(.result(.share(url: url, text: text, to: to))) case let .wallpaper(parameter): - return .single(.wallpaper(parameter)) + return .single(.result(.wallpaper(parameter))) case let .theme(slug): - return .single(.theme(slug)) + return .single(.result(.theme(slug))) case let .startAttach(name, payload, chooseValue): var choose: ResolvedBotChoosePeerTypes = [] if let chooseValue = chooseValue?.lowercased() { @@ -891,19 +948,20 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } } return context.engine.peers.resolvePeerByName(name: name) - |> take(1) - |> mapToSignal { peer -> Signal in - return .single(peer?._asPeer()) - } - |> mapToSignal { peer -> Signal in - if let peer = peer { - return .single(.startAttach(peerId: peer.id, payload: payload, choose: !choose.isEmpty ? choose : nil)) - } else { - return .single(.inaccessiblePeer) + |> mapToSignal { result -> Signal in + switch result { + case .progress: + return .single(.progress) + case let .result(peer): + if let peer = peer { + return .single(.result(.startAttach(peerId: peer.id, payload: payload, choose: !choose.isEmpty ? choose : nil))) + } else { + return .single(.result(.inaccessiblePeer)) + } } } case let .premiumGiftCode(slug): - return .single(.premiumGiftCode(slug: slug)) + return .single(.result(.premiumGiftCode(slug: slug))) } } @@ -1020,13 +1078,13 @@ private struct UrlHandlingConfiguration { } } -public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal { +public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal { let schemes = ["http://", "https://", ""] return ApplicationSpecificNotice.getSecretChatLinkPreviews(accountManager: context.sharedContext.accountManager) - |> mapToSignal { linkPreviews -> Signal in + |> mapToSignal { linkPreviews -> Signal in return context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.App(), TelegramEngine.EngineData.Item.Configuration.Links()) - |> mapToSignal { appConfiguration, linksConfiguration -> Signal in + |> mapToSignal { appConfiguration, linksConfiguration -> Signal in let urlHandlingConfiguration = UrlHandlingConfiguration.with(appConfiguration: appConfiguration) var skipUrlAuth = skipUrlAuth @@ -1052,7 +1110,7 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String components.queryItems = queryItems url = components.url?.absoluteString ?? url } else if !skipUrlAuth && urlHandlingConfiguration.urlAuthDomains.contains(host) { - return .single(.urlAuth(url)) + return .single(.result(.urlAuth(url))) } } @@ -1067,15 +1125,20 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String if url.lowercased().hasPrefix(basePrefix) { if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])) { return resolveInternalUrl(context: context, url: internalUrl) - |> map { resolved -> ResolvedUrl in - if let resolved = resolved { - return resolved - } else { - return .externalUrl(url) + |> map { result -> ResolveUrlResult in + switch result { + case .progress: + return .progress + case let .result(resolved): + if let resolved = resolved { + return .result(resolved) + } else { + return .result(.externalUrl(url)) + } } } } else { - return .single(.externalUrl(url)) + return .single(.result(.externalUrl(url))) } } } @@ -1088,33 +1151,38 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String } } } - return .single(.externalUrl(url)) + return .single(.result(.externalUrl(url))) } } } -public func resolveInstantViewUrl(account: Account, url: String) -> Signal { +public func resolveInstantViewUrl(account: Account, url: String) -> Signal { return webpagePreview(account: account, url: url) - |> mapToSignal { webpage -> Signal in - if let webpage = webpage { - if case let .Loaded(content) = webpage.content { - if content.instantPage != nil { - var anchorValue: String? - if let anchorRange = url.range(of: "#") { - let anchor = url[anchorRange.upperBound...] - if !anchor.isEmpty { - anchorValue = String(anchor) + |> mapToSignal { result -> Signal in + switch result { + case .progress: + return .single(.progress) + case let .result(webpage): + if let webpage = webpage { + if case let .Loaded(content) = webpage.content { + if content.instantPage != nil { + var anchorValue: String? + if let anchorRange = url.range(of: "#") { + let anchor = url[anchorRange.upperBound...] + if !anchor.isEmpty { + anchorValue = String(anchor) + } } + return .single(.result(.instantView(webpage, anchorValue))) + } else { + return .single(.result(.externalUrl(url))) } - return .single(.instantView(webpage, anchorValue)) } else { - return .single(.externalUrl(url)) + return .complete() } } else { - return .complete() + return .single(.result(.externalUrl(url))) } - } else { - return .single(.externalUrl(url)) } } } diff --git a/submodules/WatchBridge/Sources/WatchRequestHandlers.swift b/submodules/WatchBridge/Sources/WatchRequestHandlers.swift index 16d4f3e4aa..ec0300c0e3 100644 --- a/submodules/WatchBridge/Sources/WatchRequestHandlers.swift +++ b/submodules/WatchBridge/Sources/WatchRequestHandlers.swift @@ -742,9 +742,15 @@ final class WatchLocationHandler: WatchRequestHandler { |> mapToSignal({ context -> Signal<[ChatContextResultMessage], NoError> in if let context = context { return context.engine.peers.resolvePeerByName(name: "foursquare") + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> take(1) |> mapToSignal { peer -> Signal in - guard let peer = peer else { + guard let peer = peer?._asPeer() else { return .single(nil) } return context.engine.messages.requestChatContextResults(botId: peer.id, peerId: context.account.peerId, query: "", location: .single((args.coordinate.latitude, args.coordinate.longitude)), offset: "") diff --git a/submodules/WebSearchUI/Sources/WebSearchController.swift b/submodules/WebSearchUI/Sources/WebSearchController.swift index 009dcc2d8b..cbd8e9554b 100644 --- a/submodules/WebSearchUI/Sources/WebSearchController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchController.swift @@ -490,8 +490,11 @@ public final class WebSearchController: ViewController { let context = self.context let contextBot = self.context.engine.peers.resolvePeerByName(name: name) - |> mapToSignal { peer -> Signal in - return .single(peer) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) } |> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> in if case let .user(user) = peer, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder { diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 6148eca3f4..3918ba3be1 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -812,6 +812,12 @@ public final class WebAppController: ViewController, AttachmentContainable { self.webView?.lastTouchTimestamp = nil if tryInstantView { let _ = (resolveInstantViewUrl(account: self.context.account, url: url) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } |> deliverOnMainQueue).start(next: { [weak self] result in guard let strongSelf = self else { return