Refactor url handling

This commit is contained in:
Ali 2023-10-20 21:51:45 +04:00
parent dd46ccd6ed
commit c45607ffd6
51 changed files with 835 additions and 356 deletions

View File

@ -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<EngineMessage.Id>) -> Signal<ChatAvailableMessageActions, NoError>
func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set<EngineMessage.Id>, messages: [EngineMessage.Id: EngineMessage], peers: [EnginePeer.Id: EnginePeer]) -> Signal<ChatAvailableMessageActions, NoError>
func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal<ResolvedUrl, NoError>
func resolveUrlWithProgress(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal<ResolveUrlResult, NoError>
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)

View File

@ -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)))
}
}
}

View File

@ -53,6 +53,12 @@ private func cachedInternalInstantPage(context: AccountContext, url: String) ->
return cachedInstantPage(engine: context.engine, url: cachedUrl)
|> mapToSignal { cachedInstantPage -> Signal<ResolvedUrl, NoError> in
let updated = resolveInstantViewUrl(account: context.account, url: url)
|> mapToSignal { result -> Signal<ResolvedUrl, NoError> 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 {

View File

@ -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<EnginePeer?, NoError> 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))
}

View File

@ -150,7 +150,10 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
|> mapToSignal({ peer -> Signal<EnginePeer, NoError> 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<EnginePeer, NoError> in
|> mapToSignal({ result -> Signal<EnginePeer, NoError> in
guard case let .result(updatedPeer) = result else {
return .complete()
}
if let updatedPeer = updatedPeer {
return .single(updatedPeer)
} else {

View File

@ -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<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> mapToSignal { peer -> Signal<ChatContextResultCollection?, NoError> in
guard let peer = peer else {
return .single(nil)

View File

@ -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<EnginePeer?, NoError> 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)
}

View File

@ -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<EnginePeer?, NoError> 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)

View File

@ -24,7 +24,6 @@ public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url:
}
}
|> runOn(Queue.mainQueue())
|> delay(0.05, queue: Queue.mainQueue())
} else {
progressSignal = Signal<Never, NoError> { 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<ResolvedUrl, NoError>
resolveSignal = context.sharedContext.resolveUrl(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth)
var resolveSignal: Signal<ResolveUrlResult, NoError>
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 {

View File

@ -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<ResolvedUrl, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
let resolvedUrlPromise = Promise<ResolvedUrl>()
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<ResolvedUrl, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
let resolvedUrlPromise = Promise<ResolvedUrl>()
resolvedUrlPromise.set(resolvedUrl)

View File

@ -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<ResolvedUrl, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
let resolvedUrlPromise = Promise<ResolvedUrl>()
resolvedUrlPromise.set(resolvedUrl)

View File

@ -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<EnginePeer?, NoError> 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)
}

View File

@ -469,6 +469,12 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
return .single(nil)
}
return context.engine.peers.resolvePeerByName(name: name)
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> mapToSignal { peer -> Signal<Peer?, NoError> in
if let peer = peer {
return .single(peer._asPeer())

View File

@ -136,6 +136,12 @@ public final class StickerPackPreviewController: ViewController, StandalonePrese
}
strongSelf.openMentionDisposable.set((strongSelf.context.engine.peers.resolvePeerByName(name: mention)
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> mapToSignal { peer -> Signal<Peer?, NoError> in
if let peer = peer {
return .single(peer._asPeer())

View File

@ -2029,6 +2029,12 @@ public final class StickerPackScreenImpl: ViewController {
}
strongSelf.openMentionDisposable.set((strongSelf.context.engine.peers.resolvePeerByName(name: mention)
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> mapToSignal { peer -> Signal<Peer?, NoError> in
if let peer = peer {
return .single(peer._asPeer())

View File

@ -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))
}
}
}

View File

@ -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
}
}

View File

@ -253,7 +253,12 @@ func _internal_createForumChannelTopic(account: Account, peerId: PeerId, title:
}
}
func _internal_fetchForumChannelTopic(account: Account, peerId: PeerId, threadId: Int64) -> Signal<EngineMessageHistoryThread.Info?, NoError> {
public enum FetchForumChannelTopicResult {
case progress
case result(EngineMessageHistoryThread.Info?)
}
func _internal_fetchForumChannelTopic(account: Account, peerId: PeerId, threadId: Int64) -> Signal<FetchForumChannelTopicResult, NoError> {
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<EngineMessageHistoryThread.Info?, NoError> in
|> mapToSignal { info, _ -> Signal<FetchForumChannelTopicResult, NoError> 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<EngineMessageHistoryThread.Info?, NoError> 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<FetchForumChannelTopicResult, NoError> 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)
}
}
}
})
}
}
}

View File

@ -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 {

View File

@ -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))
}
}
}

View File

@ -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<Void, NoError> in
self.webpageDisposables[messageId] = (webpagePreview(account: account, url: url) |> mapToSignal { result -> Signal<Void, NoError> 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

View File

@ -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))
}

View File

@ -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<GetMessagesResult, NoError> {
let postboxSignal = postbox.transaction { transaction -> ([Message], Set<MessageId>, SimpleDictionary<PeerId, Peer>) 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<GetMessagesResult, NoError> 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)
}
}
}

View File

@ -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<GetMessagesResult, NoError> {
return _internal_getMessagesLoadIfNecessary(messageIds, postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId, strategy: strategy)
}

View File

@ -14,7 +14,17 @@ public enum ResolvePeerByNameOptionRemote {
case update
}
func _internal_resolvePeerByName(account: Account, name: String, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal<PeerId?, NoError> {
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<ResolvePeerIdByNameResult, NoError> {
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<PeerId?, NoError> in
}
|> mapToSignal { cachedEntry -> Signal<ResolvePeerIdByNameResult, NoError> 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<PeerId?, Void> in
return account.postbox.transaction { transaction -> PeerId? in
|> mapToSignal { result -> Signal<ResolvePeerIdByNameResult, Void> 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<PeerId?, NoError> in
return .single(nil)
}
|> `catch` { _ -> Signal<ResolvePeerIdByNameResult, NoError> in
return .single(.result(nil))
})
}
}
}

View File

@ -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<EnginePeer?, NoError> {
public func resolvePeerByName(name: String, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal<ResolvePeerResult, NoError> {
return _internal_resolvePeerByName(account: self.account, name: name, ageLimit: ageLimit)
|> mapToSignal { peerId -> Signal<EnginePeer?, NoError> 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<ResolvePeerResult, NoError> 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))
}
}
}
}
@ -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<EngineMessageHistoryThread.Info?, NoError> {
public func fetchForumChannelTopic(id: EnginePeer.Id, threadId: Int64) -> Signal<FetchForumChannelTopicResult, NoError> {
return _internal_fetchForumChannelTopic(account: self.account, peerId: id, threadId: threadId)
}

View File

@ -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<Peer, NoError> in

View File

@ -5,13 +5,18 @@ import TelegramApi
import MtProtoKit
public func webpagePreview(account: Account, url: String, webpageId: MediaId? = nil) -> Signal<TelegramMediaWebpage?, NoError> {
public enum WebpagePreviewResult {
case progress
case result(TelegramMediaWebpage?)
}
public func webpagePreview(account: Account, url: String, webpageId: MediaId? = nil) -> Signal<WebpagePreviewResult, NoError> {
return webpagePreviewWithProgress(account: account, url: url)
|> mapToSignal { next -> Signal<TelegramMediaWebpage?, NoError> in
|> mapToSignal { next -> Signal<WebpagePreviewResult, NoError> in
if case let .result(result) = next {
return .single(result)
return .single(.result(result))
} else {
return .complete()
return .single(.progress)
}
}
}

View File

@ -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)!

View File

@ -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()
})
}

View File

@ -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 {

View File

@ -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)

View File

@ -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

View File

@ -296,6 +296,12 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
let resolveSignal: Signal<Peer?, NoError>
if let peerName = peerName {
resolveSignal = strongSelf.context.engine.peers.resolvePeerByName(name: peerName)
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> mapToSignal { peer -> Signal<Peer?, NoError> 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<EnginePeer?, NoError> 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 {

View File

@ -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

View File

@ -55,6 +55,12 @@ public func paneGifSearchForQuery(context: AccountContext, query: String, offset
|> mapToSignal { searchBots -> Signal<EnginePeer?, NoError> 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 {

View File

@ -2772,7 +2772,12 @@ final class StoryItemSetContainerSendMessage {
self.resolvePeerByNameDisposable.set(nil)
}
disposable.set((resolveSignal
|> take(1)
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> mapToSignal { peer -> Signal<Peer?, NoError> in
return .single(peer?._asPeer())
}
@ -2805,6 +2810,12 @@ final class StoryItemSetContainerSendMessage {
var resolveSignal: Signal<Peer?, NoError>
if let peerName = peerName {
resolveSignal = component.context.engine.peers.resolvePeerByName(name: peerName)
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> mapToSignal { peer -> Signal<Peer?, NoError> in
if let peer = peer {
return .single(peer._asPeer())

View File

@ -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<EnginePeer?, NoError> 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)))
}

View File

@ -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))

View File

@ -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

View File

@ -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 {
@ -17566,6 +17576,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<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
var cancelImpl: (() -> Void)?
let presentationData = self.presentationData
@ -17627,6 +17643,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var resolveSignal: Signal<Peer?, NoError>
if let peerName = peerName {
resolveSignal = self.context.engine.peers.resolvePeerByName(name: peerName)
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> mapToSignal { peer -> Signal<Peer?, NoError> in
if let peer = peer {
return .single(peer._asPeer())

View File

@ -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))
}
}

View File

@ -240,6 +240,12 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
let chatPeer = peer
let contextBot = context.engine.peers.resolvePeerByName(name: addressName)
|> mapToSignal { result -> Signal<EnginePeer?, NoError> 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<TelegramMediaWebpage?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> map { value in
return { _ in return value }
})
} else {

View File

@ -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<EnginePeer?, NoError> 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()

View File

@ -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<EnginePeer?, NoError> 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<Peer?, NoError>
if let peerName = peerName {
resolveSignal = self.context.engine.peers.resolvePeerByName(name: peerName)
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> mapToSignal { peer -> Signal<Peer?, NoError> 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<EnginePeer?, NoError> 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)))

View File

@ -1402,6 +1402,18 @@ public final class SharedAccountContextImpl: SharedAccountContext {
public func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal<ResolvedUrl, NoError> {
return resolveUrlImpl(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth)
|> mapToSignal { result -> Signal<ResolvedUrl, NoError> 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<ResolveUrlResult, NoError> {
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?) {

View File

@ -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<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> deliverOnMainQueue).start(next: { peer in
openResolvedPeerImpl(peer, .default)
}))
}

View File

@ -591,227 +591,277 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
return nil
}
private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) -> Signal<ResolvedUrl?, NoError> {
private enum ResolveInternalUrlResult {
case progress
case result(ResolvedUrl?)
}
private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) -> Signal<ResolveInternalUrlResult, NoError> {
switch url {
case let .phone(phone, attach, startAttach):
return context.engine.peers.resolvePeerByPhone(phone: phone)
|> take(1)
|> mapToSignal { peer -> Signal<ResolvedUrl?, NoError> in
|> mapToSignal { peer -> Signal<ResolveInternalUrlResult, NoError> 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<Peer?, NoError>
let resolvedPeer: Signal<ResolvePeerResult, NoError>
switch reference {
case let .name(name):
resolvedPeer = context.engine.peers.resolvePeerByName(name: name)
|> take(1)
|> mapToSignal { peer -> Signal<Peer?, NoError> in
return .single(peer?._asPeer())
|> mapToSignal { result -> Signal<ResolvePeerResult, NoError> 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<Peer?, NoError> in
let foundPeer: Signal<Peer?, NoError>
|> mapToSignal { peer -> Signal<ResolvePeerResult, NoError> in
let foundPeer: Signal<ResolvePeerResult, NoError>
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<ResolvedUrl?, NoError> in
|> mapToSignal { result -> Signal<ResolveInternalUrlResult, NoError> 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<Peer?, NoError> in
return .single(botPeer?._asPeer())
}
|> mapToSignal { botPeer -> Signal<ResolvedUrl?, NoError> 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<ResolveInternalUrlResult, NoError> 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<BotApp?, NoError> in
return .single(nil)
}
|> take(1)
|> mapToSignal { botApp -> Signal<ResolvedUrl?, NoError> in
|> mapToSignal { botApp -> Signal<ResolveInternalUrlResult, NoError> 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<ResolvedUrl?, NoError> 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<ResolveInternalUrlResult, NoError> 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<ChatReplyThreadMessage?, NoError> 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<ResolvedUrl?, NoError> in
|> mapToSignal { peer -> Signal<ResolveInternalUrlResult, NoError> 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<ResolvedUrl?, NoError> in
return .single(.progress) |> then(context.engine.peers.importContactToken(token: token)
|> mapToSignal { peer -> Signal<ResolveInternalUrlResult, NoError> 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<ResolvedUrl?, NoError> in
|> mapToSignal { peer -> Signal<ResolveInternalUrlResult, NoError> in
let foundPeer: Signal<EnginePeer?, NoError>
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<ResolvedUrl?, NoError> in
return .single(.progress) |> then(foundPeer
|> mapToSignal { foundPeer -> Signal<ResolveInternalUrlResult, NoError> 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<ResolvedUrl?, NoError> 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<ResolveInternalUrlResult, NoError> 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<ChatReplyThreadMessage?, NoError> 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<TelegramMediaInvoice?, NoError> 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<Peer?, NoError> in
return .single(peer?._asPeer())
}
|> mapToSignal { peer -> Signal<ResolvedUrl?, NoError> 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<ResolveInternalUrlResult, NoError> 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<ResolvedUrl, NoError> {
public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal<ResolveUrlResult, NoError> {
let schemes = ["http://", "https://", ""]
return ApplicationSpecificNotice.getSecretChatLinkPreviews(accountManager: context.sharedContext.accountManager)
|> mapToSignal { linkPreviews -> Signal<ResolvedUrl, NoError> in
|> mapToSignal { linkPreviews -> Signal<ResolveUrlResult, NoError> in
return context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.App(), TelegramEngine.EngineData.Item.Configuration.Links())
|> mapToSignal { appConfiguration, linksConfiguration -> Signal<ResolvedUrl, NoError> in
|> mapToSignal { appConfiguration, linksConfiguration -> Signal<ResolveUrlResult, NoError> 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<ResolvedUrl, NoError> {
public func resolveInstantViewUrl(account: Account, url: String) -> Signal<ResolveUrlResult, NoError> {
return webpagePreview(account: account, url: url)
|> mapToSignal { webpage -> Signal<ResolvedUrl, NoError> 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<ResolveUrlResult, NoError> 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))
}
}
}

View File

@ -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<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> take(1)
|> mapToSignal { peer -> Signal<ChatContextResultCollection?, NoError> 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: "")

View File

@ -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<EnginePeer?, NoError> in
return .single(peer)
|> mapToSignal { result -> Signal<EnginePeer?, NoError> 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 {

View File

@ -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<ResolvedUrl, NoError> 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