diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 8ce095bd44..15a4b50bfe 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10143,6 +10143,12 @@ Sorry for the inconvenience."; "NameColor.TooltipPremium.Account" = "Subscribe to [Telegram Premium]() to choose a custom color for your name."; "NameColor.BackgroundEmoji.Title" = "ADD ICONS TO REPLIES"; +"NameColor.BackgroundEmoji.Remove" = "REMOVE ICON"; + +"NameColor.UnsavedChanges.Title" = "Unsaved Changes"; +"NameColor.UnsavedChanges.Text" = "You have changed your color or icon settings. Apply changes?"; +"NameColor.UnsavedChanges.Discard" = "Discard"; +"NameColor.UnsavedChanges.Apply" = "Apply"; "Chat.ErrorQuoteOutdatedTitle" = "Quote Outdated"; "Chat.ErrorQuoteOutdatedText" = "**%@** updated the message you are quoting. Edit your quote to make it up-to-date."; @@ -10404,3 +10410,6 @@ Sorry for the inconvenience."; "ChannelBoost.EnableColors" = "Enable Colors"; "ChannelBoost.EnableColorsText" = "Your channel needs %1$@ to change channel color.\n\nAsk your **Premium** subscribers to boost your channel with this link:"; +"ChannelBoost.BoostAgain" = "Boost Again"; + +"Settings.New" = "NEW"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 0a228fcdb6..0cf6554a85 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1000,7 +1000,7 @@ public enum PremiumLimitSubject { case expiringStories case storiesWeekly case storiesMonthly - case storiesChannelBoost(peer: EnginePeer, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, myBoostCount: Int32) + case storiesChannelBoost(peer: EnginePeer, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, myBoostCount: Int32, canBoostAgain: Bool) } public protocol ComposeController: ViewController { @@ -1262,8 +1262,10 @@ public class PeerNameColors: Equatable { public let darkColors: [Int32: Colors] public let displayOrder: [Int32] - public func get(_ color: PeerNameColor) -> Colors { - if let colors = self.colors[color.rawValue] { + public func get(_ color: PeerNameColor, dark: Bool = false) -> Colors { + if dark, let colors = self.darkColors[color.rawValue] { + return colors + } else if let colors = self.colors[color.rawValue] { return colors } else { return PeerNameColors.defaultSingleColors[5]! diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift index efd884e0fd..0efd6a7a4f 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift @@ -271,16 +271,28 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode, if let attributedText = textInputView.attributedText, !attributedText.string.isEmpty { self.animateInputField = true if let textInputView = self.textInputView as? ChatInputTextView { - self.fromMessageTextNode.textView.theme = textInputView.theme - - let mainColor = presentationData.theme.chat.message.outgoing.accentControlColor - self.toMessageTextNode.textView.theme = ChatInputTextView.Theme( - quote: ChatInputTextView.Theme.Quote( - background: mainColor.withMultipliedAlpha(0.1), - foreground: mainColor, - lineStyle: textInputView.theme?.quote.lineStyle ?? .solid + if let textTheme = textInputView.theme { + self.fromMessageTextNode.textView.theme = textTheme + + let mainColor = presentationData.theme.chat.message.outgoing.accentControlColor + let mappedLineStyle: ChatInputTextView.Theme.Quote.LineStyle + switch textTheme.quote.lineStyle { + case .solid: + mappedLineStyle = .solid(color: mainColor) + case .doubleDashed: + mappedLineStyle = .doubleDashed(mainColor: mainColor, secondaryColor: .clear) + case .tripleDashed: + mappedLineStyle = .tripleDashed(mainColor: mainColor, secondaryColor: .clear, tertiaryColor: .clear) + } + + self.toMessageTextNode.textView.theme = ChatInputTextView.Theme( + quote: ChatInputTextView.Theme.Quote( + background: mainColor.withMultipliedAlpha(0.1), + foreground: mainColor, + lineStyle: mappedLineStyle + ) ) - ) + } } self.fromMessageTextNode.attributedText = attributedText diff --git a/submodules/Display/Source/InteractiveTransitionGestureRecognizer.swift b/submodules/Display/Source/InteractiveTransitionGestureRecognizer.swift index 8b72c2f7a0..56120b515e 100644 --- a/submodules/Display/Source/InteractiveTransitionGestureRecognizer.swift +++ b/submodules/Display/Source/InteractiveTransitionGestureRecognizer.swift @@ -61,21 +61,16 @@ public enum InteractiveTransitionGestureRecognizerEdgeWidth { } public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { - private let staticEdgeWidth: InteractiveTransitionGestureRecognizerEdgeWidth + private let edgeWidth: InteractiveTransitionGestureRecognizerEdgeWidth private let allowedDirections: (CGPoint) -> InteractiveTransitionGestureRecognizerDirections - public var dynamicEdgeWidth: ((CGPoint) -> InteractiveTransitionGestureRecognizerEdgeWidth)? - private var currentEdgeWidth: InteractiveTransitionGestureRecognizerEdgeWidth - - private var ignoreOffset: CGPoint = CGPoint() private var validatedGesture = false private var firstLocation: CGPoint = CGPoint() private var currentAllowedDirections: InteractiveTransitionGestureRecognizerDirections = [] public init(target: Any?, action: Selector?, allowedDirections: @escaping (CGPoint) -> InteractiveTransitionGestureRecognizerDirections, edgeWidth: InteractiveTransitionGestureRecognizerEdgeWidth = .constant(16.0)) { self.allowedDirections = allowedDirections - self.staticEdgeWidth = edgeWidth - self.currentEdgeWidth = edgeWidth + self.edgeWidth = edgeWidth super.init(target: target, action: action) @@ -86,7 +81,6 @@ public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { override public func reset() { super.reset() - self.ignoreOffset = CGPoint() self.validatedGesture = false self.currentAllowedDirections = [] } @@ -105,10 +99,6 @@ public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { return } - if let dynamicEdgeWidth = self.dynamicEdgeWidth { - self.currentEdgeWidth = dynamicEdgeWidth(point) - } - super.touchesBegan(touches, with: event) self.firstLocation = point @@ -138,11 +128,6 @@ public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { } } - override public func translation(in view: UIView?) -> CGPoint { - let result = super.translation(in: view) - return result//.offsetBy(dx: self.ignoreOffset.x, dy: self.ignoreOffset.y) - } - override public func touchesMoved(_ touches: Set, with event: UIEvent) { let location = touches.first!.location(in: self.view) let translation = CGPoint(x: location.x - self.firstLocation.x, y: location.y - self.firstLocation.y) @@ -161,63 +146,41 @@ public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { if absTranslationX > 2.0 && absTranslationX > absTranslationY * 2.0 { self.state = .failed } else if absTranslationY > 2.0 && absTranslationX * 2.0 < absTranslationY { - self.ignoreOffset = CGPoint(x: -translation.x, y: -translation.y) self.validatedGesture = true } } } else { - let defaultEdgeWidth: CGFloat - switch self.staticEdgeWidth { + let edgeWidth: CGFloat + switch self.edgeWidth { case let .constant(value): - defaultEdgeWidth = value + edgeWidth = value case let .widthMultiplier(factor, minValue, maxValue): - defaultEdgeWidth = max(minValue, min(size.width * factor, maxValue)) - } - - let extendedEdgeWidth: CGFloat - switch self.currentEdgeWidth { - case let .constant(value): - extendedEdgeWidth = value - case let .widthMultiplier(factor, minValue, maxValue): - extendedEdgeWidth = max(minValue, min(size.width * factor, maxValue)) + edgeWidth = max(minValue, min(size.width * factor, maxValue)) } if !self.validatedGesture { - if self.firstLocation.x < extendedEdgeWidth && !self.currentAllowedDirections.contains(.rightEdge) { + if self.firstLocation.x < edgeWidth && !self.currentAllowedDirections.contains(.rightEdge) { self.state = .failed return } - if self.firstLocation.x > size.width - extendedEdgeWidth && !self.currentAllowedDirections.contains(.leftEdge) { + if self.firstLocation.x > size.width - edgeWidth && !self.currentAllowedDirections.contains(.leftEdge) { self.state = .failed return } - if self.currentAllowedDirections.contains(.rightEdge) && self.firstLocation.x < extendedEdgeWidth { - if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 { - self.state = .failed - } else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX { - self.ignoreOffset = CGPoint(x: -translation.x, y: -translation.y) - self.validatedGesture = true - fireBegan = true - } - } else { - if self.currentAllowedDirections.contains(.rightEdge) && self.firstLocation.x < defaultEdgeWidth { - self.ignoreOffset = CGPoint(x: -translation.x, y: -translation.y) - self.validatedGesture = true - } else if self.currentAllowedDirections.contains(.leftEdge) && self.firstLocation.x > size.width - defaultEdgeWidth { - self.ignoreOffset = CGPoint(x: -translation.x, y: -translation.y) - self.validatedGesture = true - } else if !self.currentAllowedDirections.contains(.leftCenter) && translation.x < 0.0 { - self.state = .failed - } else if !self.currentAllowedDirections.contains(.rightCenter) && translation.x > 0.0 { - self.state = .failed - } else if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 { - self.state = .failed - } else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX { - self.ignoreOffset = CGPoint(x: -translation.x, y: -translation.y) - self.validatedGesture = true - fireBegan = true - } + if self.currentAllowedDirections.contains(.rightEdge) && self.firstLocation.x < edgeWidth { + self.validatedGesture = true + } else if self.currentAllowedDirections.contains(.leftEdge) && self.firstLocation.x > size.width - edgeWidth { + self.validatedGesture = true + } else if !self.currentAllowedDirections.contains(.leftCenter) && translation.x < 0.0 { + self.state = .failed + } else if !self.currentAllowedDirections.contains(.rightCenter) && translation.x > 0.0 { + self.state = .failed + } else if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 { + self.state = .failed + } else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX { + self.validatedGesture = true + fireBegan = true } } } diff --git a/submodules/Display/Source/Navigation/NavigationContainer.swift b/submodules/Display/Source/Navigation/NavigationContainer.swift index 2a9614ff24..516639626d 100644 --- a/submodules/Display/Source/Navigation/NavigationContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationContainer.swift @@ -142,12 +142,12 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega } return .right }) - panRecognizer.dynamicEdgeWidth = { [weak self] _ in + /*panRecognizer.dynamicEdgeWidth = { [weak self] _ in guard let self, let controller = self.controllers.last, let value = controller.interactiveNavivationGestureEdgeWidth else { return .constant(16.0) } return value - } + }*/ if #available(iOS 13.4, *) { panRecognizer.allowedScrollTypesMask = .continuous } diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index 6a8c165c6f..87a14b5113 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -2421,6 +2421,25 @@ public func chatMessageImageFile(account: Account, userLocation: MediaResourceUs } } +public func preloadedBotIcon(account: Account, fileReference: FileMediaReference) -> Signal { + let signal = Signal { subscriber in + let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(fileReference.media.resource)).start() + let dataDisposable = account.postbox.mediaBox.resourceData(fileReference.media.resource, option: .incremental(waitUntilFetchStatus: false)).start(next: { data in + if data.complete { + subscriber.putNext(true) + subscriber.putCompletion() + } else { + subscriber.putNext(false) + } + }) + return ActionDisposable { + fetched.dispose() + dataDisposable.dispose() + } + } + return signal +} + public func instantPageImageFile(account: Account, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, fetched: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return chatMessageFileDatas(account: account, userLocation: userLocation, fileReference: fileReference, progressive: false, fetched: fetched) |> map { value in diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index 9a3c87e073..4611261d2e 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -683,6 +683,11 @@ final class MutableMessageHistoryView { hasChanges = true } } + if !transaction.currentUpdatedPeers.isEmpty { + if loadedState.updatePeers(postbox: postbox, updatedPeers: transaction.currentUpdatedPeers) { + hasChanges = true + } + } } if hasChanges { diff --git a/submodules/Postbox/Sources/MessageHistoryViewState.swift b/submodules/Postbox/Sources/MessageHistoryViewState.swift index cec31265a6..b2e937db9e 100644 --- a/submodules/Postbox/Sources/MessageHistoryViewState.swift +++ b/submodules/Postbox/Sources/MessageHistoryViewState.swift @@ -1514,6 +1514,43 @@ final class HistoryViewLoadedState { return updated } + func updatePeers(postbox: PostboxImpl, updatedPeers: [PeerId: Peer]) -> Bool { + var updated = false + for space in self.orderedEntriesBySpace.keys { + let spaceUpdated = self.orderedEntriesBySpace[space]!.mutableScan({ entry in + switch entry { + case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers): + let message = value.message + var rebuild = false + var peers = message.peers + var author = message.author + for (peerId, _) in message.peers { + if let updatedPeer = updatedPeers[peerId] { + peers[peerId] = updatedPeer + rebuild = true + } + } + if let authorValue = author, let updatedAuthor = updatedPeers[authorValue.id] { + author = updatedAuthor + rebuild = true + } + + if rebuild { + let updatedMessage = message.withUpdatedPeers(peers).withUpdatedAuthor(author) + return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers) + } + case .IntermediateMessageEntry: + break + } + return nil + }) + if spaceUpdated { + updated = true + } + } + return updated + } + func add(entry: MutableMessageHistoryEntry) -> Bool { if let ignoreMessagesInTimestampRange = self.ignoreMessagesInTimestampRange { if ignoreMessagesInTimestampRange.contains(entry.index.timestamp) { diff --git a/submodules/PremiumUI/Sources/PremiumBoostScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostScreen.swift index 2c7880d424..3547991b3a 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostScreen.swift @@ -14,7 +14,7 @@ private struct BoostState { let nextLevelBoosts: Int32? let boosts: Int32 - func displayData(peer: EnginePeer, isCurrent: Bool, myBoostCount: Int32, currentMyBoostCount: Int32, replacedBoosts: Int32? = nil) -> (subject: PremiumLimitScreen.Subject, count: Int32) { + func displayData(peer: EnginePeer, isCurrent: Bool, canBoostAgain: Bool, myBoostCount: Int32, currentMyBoostCount: Int32, replacedBoosts: Int32? = nil) -> (subject: PremiumLimitScreen.Subject, count: Int32) { var currentLevel = self.level var nextLevelBoosts = self.nextLevelBoosts var currentLevelBoosts = self.currentLevelBoosts @@ -30,7 +30,7 @@ private struct BoostState { } return ( - .storiesChannelBoost(peer: peer, boostSubject: .stories, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount), + .storiesChannelBoost(peer: peer, boostSubject: .stories, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount, canBoostAgain: canBoostAgain), boosts ) } @@ -86,7 +86,10 @@ public func PremiumBoostScreen( var updateImpl: (() -> Void)? var dismissImpl: (() -> Void)? - let (initialSubject, initialCount) = initialState.displayData(peer: peer, isCurrent: isCurrent, myBoostCount: myBoostCount, currentMyBoostCount: 0, replacedBoosts: replacedBoosts?.0) + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with({ $0 })) + let canBoostAgain = premiumConfiguration.boostsPerGiftCount > 0 + + let (initialSubject, initialCount) = initialState.displayData(peer: peer, isCurrent: isCurrent, canBoostAgain: canBoostAgain, myBoostCount: myBoostCount, currentMyBoostCount: 0, replacedBoosts: replacedBoosts?.0) let controller = PremiumLimitScreen(context: context, subject: initialSubject, count: initialCount, forceDark: forceDark, action: { let dismiss = false updateImpl?() @@ -99,7 +102,7 @@ public func PremiumBoostScreen( if let (replacedBoosts, inChannels) = replacedBoosts { currentMyBoostCount += 1 - let (subject, count) = initialState.displayData(peer: peer, isCurrent: isCurrent, myBoostCount: myBoostCount, currentMyBoostCount: 1, replacedBoosts: nil) + let (subject, count) = initialState.displayData(peer: peer, isCurrent: isCurrent, canBoostAgain: canBoostAgain, myBoostCount: myBoostCount, currentMyBoostCount: 1, replacedBoosts: nil) controller.updateSubject(subject, count: count) Queue.mainQueue().after(0.3) { @@ -138,52 +141,75 @@ public func PremiumBoostScreen( guard let state else { return } - let (subject, count) = state.displayData(peer: peer, isCurrent: isCurrent, myBoostCount: myBoostCount, currentMyBoostCount: currentMyBoostCount) + let (subject, count) = state.displayData(peer: peer, isCurrent: isCurrent, canBoostAgain: canBoostAgain, myBoostCount: myBoostCount, currentMyBoostCount: currentMyBoostCount) controller?.updateSubject(subject, count: count) }) availableBoosts.removeFirst() } else if !occupiedBoosts.isEmpty, let myBoostStatus { - var dismissReplaceImpl: (() -> Void)? - let replaceController = ReplaceBoostScreen(context: context, peerId: peerId, myBoostStatus: myBoostStatus, replaceBoosts: { slots in - var channelIds = Set() - for boost in myBoostStatus.boosts { - if slots.contains(boost.slot) { - if let peer = boost.peer { - channelIds.insert(peer.id) + if canBoostAgain { + var dismissReplaceImpl: (() -> Void)? + let replaceController = ReplaceBoostScreen(context: context, peerId: peerId, myBoostStatus: myBoostStatus, replaceBoosts: { slots in + var channelIds = Set() + for boost in myBoostStatus.boosts { + if slots.contains(boost.slot) { + if let peer = boost.peer { + channelIds.insert(peer.id) + } } } - } - - let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone(completed: { - let _ = combineLatest(queue: Queue.mainQueue(), - context.engine.peers.getChannelBoostStatus(peerId: peerId), - context.engine.peers.getMyBoostStatus() - ).startStandalone(next: { boostStatus, myBoostStatus in - dismissReplaceImpl?() - PremiumBoostScreen(context: context, contentContext: contentContext, peerId: peerId, isCurrent: isCurrent, status: boostStatus, myBoostStatus: myBoostStatus, replacedBoosts: (Int32(slots.count), Int32(channelIds.count)), forceDark: forceDark, openPeer: openPeer, presentController: presentController, pushController: pushController, dismissed: dismissed) + + let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone(completed: { + let _ = combineLatest(queue: Queue.mainQueue(), + context.engine.peers.getChannelBoostStatus(peerId: peerId), + context.engine.peers.getMyBoostStatus() + ).startStandalone(next: { boostStatus, myBoostStatus in + dismissReplaceImpl?() + PremiumBoostScreen(context: context, contentContext: contentContext, peerId: peerId, isCurrent: isCurrent, status: boostStatus, myBoostStatus: myBoostStatus, replacedBoosts: (Int32(slots.count), Int32(channelIds.count)), forceDark: forceDark, openPeer: openPeer, presentController: presentController, pushController: pushController, dismissed: dismissed) + }) }) }) - }) - dismissImpl?() - pushController(replaceController) - dismissReplaceImpl = { [weak replaceController] in - replaceController?.dismiss(animated: true) + dismissImpl?() + pushController(replaceController) + dismissReplaceImpl = { [weak replaceController] in + replaceController?.dismiss(animated: true) + } + } else if let boost = occupiedBoosts.first, let occupiedPeer = boost.peer { + let replaceController = replaceBoostConfirmationController(context: context, fromPeers: [occupiedPeer], toPeer: peer, commit: { + currentMyBoostCount += 1 + myBoostCount += 1 + let _ = (context.engine.peers.applyChannelBoost(peerId: peerId, slots: [boost.slot]) + |> deliverOnMainQueue).startStandalone(completed: { [weak controller] in + let _ = (updatedState.get() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak controller] state in + guard let state else { + return + } + let (subject, count) = state.displayData(peer: peer, isCurrent: isCurrent, canBoostAgain: canBoostAgain, myBoostCount: myBoostCount, currentMyBoostCount: currentMyBoostCount) + controller?.updateSubject(subject, count: count) + }) + }) + }) + presentController(replaceController) } } else { if isPremium { - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with({ $0 })) - let controller = textAlertController( - sharedContext: context.sharedContext, - updatedPresentationData: nil, - title: presentationData.strings.ChannelBoost_MoreBoosts_Title, - text: presentationData.strings.ChannelBoost_MoreBoosts_Text(peer.compactDisplayTitle, "\(premiumConfiguration.boostsPerGiftCount)").string, - actions: [ - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) - ], - parseMarkdown: true - ) - presentController(controller) + if !canBoostAgain { + dismissImpl?() + } else { + let controller = textAlertController( + sharedContext: context.sharedContext, + updatedPresentationData: nil, + title: presentationData.strings.ChannelBoost_MoreBoosts_Title, + text: presentationData.strings.ChannelBoost_MoreBoosts_Text(peer.compactDisplayTitle, "\(premiumConfiguration.boostsPerGiftCount)").string, + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) + ], + parseMarkdown: true + ) + presentController(controller) + } } else { let controller = textAlertController( sharedContext: context.sharedContext, diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index e5e17d76b2..653321026c 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -1105,7 +1105,7 @@ private final class LimitSheetContent: CombinedComponent { string = strings.Premium_MaxStoriesMonthlyNoPremiumText("\(limit)").string } buttonAnimationName = nil - case let .storiesChannelBoost(peer, boostSubject, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, myBoostCount): + case let .storiesChannelBoost(peer, boostSubject, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, myBoostCount, canBoostAgain): if link == nil, !isCurrent, state.initialized { peerShortcutChild = peerShortcut.update( component: Button( @@ -1231,8 +1231,8 @@ private final class LimitSheetContent: CombinedComponent { alternateBadge = "X\(myBoostCount)" } let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1) - if let _ = remaining { - actionButtonText = "Boost Again" + if let _ = remaining, canBoostAgain { + actionButtonText = strings.ChannelBoost_BoostAgain } else { buttonIconName = nil actionButtonText = environment.strings.Common_OK @@ -1428,7 +1428,7 @@ private final class LimitSheetContent: CombinedComponent { var buttonOffset: CGFloat = 0.0 var textOffset: CGFloat = 184.0 + topOffset - if case let .storiesChannelBoost(_, _, _, _, _, _, link, _) = component.subject { + if case let .storiesChannelBoost(_, _, _, _, _, _, link, _, _) = component.subject { if let link { let linkButton = linkButton.update( component: SolidRoundedButtonComponent( @@ -1539,7 +1539,7 @@ private final class LimitSheetContent: CombinedComponent { ) var additionalContentHeight: CGFloat = 0.0 - if case let .storiesChannelBoost(_, _, _, _, _, _, link, _) = component.subject, link != nil, let openGift = component.openGift { + if case let .storiesChannelBoost(_, _, _, _, _, _, link, _, _) = component.subject, link != nil, let openGift = component.openGift { let orText = orText.update( component: MultilineTextComponent(text: .plain(NSAttributedString(string: "or", font: Font.regular(15.0), textColor: textColor.withAlphaComponent(0.8), paragraphAlignment: .center))), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), @@ -1608,7 +1608,7 @@ private final class LimitSheetContent: CombinedComponent { height -= 78.0 } - if case let .storiesChannelBoost(_, _, isCurrent, _, _, _, link, _) = component.subject { + if case let .storiesChannelBoost(_, _, isCurrent, _, _, _, link, _, _) = component.subject { if link != nil { height += 66.0 @@ -1774,7 +1774,7 @@ public class PremiumLimitScreen: ViewControllerComponentContainer { case stories case nameColors } - case storiesChannelBoost(peer: EnginePeer, boostSubject: BoostSubject, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, myBoostCount: Int32) + case storiesChannelBoost(peer: EnginePeer, boostSubject: BoostSubject, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, myBoostCount: Int32, canBoostAgain: Bool) } private let context: AccountContext diff --git a/submodules/TelegramUI/Sources/ReplaceBoostConfirmationController.swift b/submodules/PremiumUI/Sources/ReplaceBoostConfirmationController.swift similarity index 96% rename from submodules/TelegramUI/Sources/ReplaceBoostConfirmationController.swift rename to submodules/PremiumUI/Sources/ReplaceBoostConfirmationController.swift index 660b0b90be..08d638c228 100644 --- a/submodules/TelegramUI/Sources/ReplaceBoostConfirmationController.swift +++ b/submodules/PremiumUI/Sources/ReplaceBoostConfirmationController.swift @@ -1,3 +1,4 @@ + import Foundation import UIKit import SwiftSignalKit @@ -342,30 +343,21 @@ private final class ReplaceBoostConfirmationAlertContentNode: AlertContentNode { } func replaceBoostConfirmationController(context: AccountContext, fromPeers: [EnginePeer], toPeer: EnginePeer, commit: @escaping () -> Void) -> AlertController { - let fromPeers = [fromPeers.first!, fromPeers.first!] - - let theme = defaultDarkColorPresentationTheme let presentationData = context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings - let text: String - if fromPeers.count > 1 { - text = "To boost **\(toPeer.compactDisplayTitle)**, reassign a previous boost from:" - //strings.ChannelBoost_ReplaceBoost(previousPeer.compactDisplayTitle, toPeer.compactDisplayTitle).string - } else { - text = strings.ChannelBoost_ReplaceBoost(fromPeers.first!.compactDisplayTitle, toPeer.compactDisplayTitle).string - } + let text = strings.ChannelBoost_ReplaceBoost(fromPeers.first!.compactDisplayTitle, toPeer.compactDisplayTitle).string var dismissImpl: ((Bool) -> Void)? var contentNode: ReplaceBoostConfirmationAlertContentNode? let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { dismissImpl?(true) - }), TextAlertAction(type: .defaultAction, title: "Reassign", action: { + }), TextAlertAction(type: .defaultAction, title: strings.ChannelBoost_Replace, action: { dismissImpl?(true) commit() })] - contentNode = ReplaceBoostConfirmationAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, fromPeers: fromPeers, toPeer: toPeer, text: text, actions: actions) + contentNode = ReplaceBoostConfirmationAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, fromPeers: fromPeers, toPeer: toPeer, text: text, actions: actions) let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) dismissImpl = { [weak controller] animated in diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index e74d55a263..d7d088dbdd 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -1677,7 +1677,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { chatPeerId: nil, peekBehavior: nil, customLayout: emojiContentLayout, - externalBackground: EmojiPagerContentComponent.ExternalBackground( + externalBackground: self.backgroundNode.vibrancyEffectView == nil ? nil : EmojiPagerContentComponent.ExternalBackground( effectContainerView: self.backgroundNode.vibrancyEffectView?.contentView ), externalExpansionView: self.view, diff --git a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift index c3da0743ba..b549d3d41e 100644 --- a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift +++ b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift @@ -172,7 +172,7 @@ private final class BubbleSettingsControllerNode: ASDisplayNode, UIScrollViewDel let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.chatBackgroundNode, availableReactions: nil, isCentered: false)) - let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.chatBackgroundNode, availableReactions: nil, isCentered: false)) let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA=" diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 9cb991d7b9..ca37f1a9d8 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -436,7 +436,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.chatBackgroundNode, availableReactions: nil, isCentered: false)) - let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.chatBackgroundNode, availableReactions: nil, isCentered: false)) let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA=" diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 4490fc76cb..cdf27920f1 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -1048,7 +1048,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate messages[message4.id] = message4 sampleMessages.append(message4) - let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [ReplyMessageAttribute(messageId: message4.id, threadMessageId: nil, quote: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [ReplyMessageAttribute(messageId: message4.id, threadMessageId: nil, quote: nil, isQuote: false)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) messages[message5.id] = message5 sampleMessages.append(message5) @@ -1059,7 +1059,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message6) - let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil, quote: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil, quote: nil, isQuote: false)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message7) let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index d89f7d51a7..e7540c31c9 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -599,7 +599,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { messages[message4.id] = message4 sampleMessages.append(message4) - let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [ReplyMessageAttribute(messageId: message4.id, threadMessageId: nil, quote: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [ReplyMessageAttribute(messageId: message4.id, threadMessageId: nil, quote: nil, isQuote: false)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) messages[message5.id] = message5 sampleMessages.append(message5) @@ -610,7 +610,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message6) - let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil, quote: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil, quote: nil, isQuote: false)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message7) let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift index a20fecdc7e..a329a970dc 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift @@ -159,7 +159,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) } - let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, isCentered: false)) } diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index e67b052eab..6a93e6f1b1 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -968,6 +968,12 @@ public func channelStatsController(context: AccountContext, updatedPresentationD }, openBoost: { boost in dismissAllTooltipsImpl?() + + if let peer = boost.peer, !boost.flags.contains(.isGiveaway) && !boost.flags.contains(.isGift) { + navigateToChatImpl?(peer) + return + } + if boost.peer == nil, boost.flags.contains(.isGiveaway) && !boost.flags.contains(.isUnclaimed) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } presentImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Stats_Boosts_TooltipToBeDistributed, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false })) diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index b35e858e96..f130e86a24 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -590,6 +590,8 @@ extension StoreMessage { let isForumTopic = (innerFlags & (1 << 3)) != 0 var quote: EngineMessageReplyQuote? + let isQuote = (innerFlags & (1 << 9)) != 0 + if quoteText != nil || replyMedia != nil { quote = EngineMessageReplyQuote(text: quoteText ?? "", entities: messageTextEntitiesFromApiEntities(quoteEntities ?? []), media: textMediaAndExpirationTimerFromApiMedia(replyMedia, peerId).media) } @@ -601,9 +603,7 @@ extension StoreMessage { if isForumTopic { let threadIdValue = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToTopId) threadMessageId = threadIdValue - if replyPeerId == peerId { - threadId = makeMessageThreadId(threadIdValue) - } + threadId = makeMessageThreadId(threadIdValue) } } else { let threadIdValue = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToTopId) @@ -623,10 +623,10 @@ extension StoreMessage { threadId = makeMessageThreadId(threadIdValue) } } - attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId, quote: quote)) + attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId, quote: quote, isQuote: isQuote)) } if let replyHeader = replyHeader { - attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote)) + attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote, isQuote: isQuote)) } case let .messageReplyStoryHeader(userId, storyId): attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: storyId))) @@ -867,8 +867,9 @@ extension StoreMessage { if let replyTo = replyTo { var threadMessageId: MessageId? switch replyTo { - case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities): + case let .messageReplyHeader(innerFlags, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities): var quote: EngineMessageReplyQuote? + let isQuote = (innerFlags & (1 << 9)) != 0 if quoteText != nil || replyMedia != nil { quote = EngineMessageReplyQuote(text: quoteText ?? "", entities: messageTextEntitiesFromApiEntities(quoteEntities ?? []), media: textMediaAndExpirationTimerFromApiMedia(replyMedia, peerId).media) } @@ -892,9 +893,9 @@ extension StoreMessage { default: break } - attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId, quote: quote)) + attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId, quote: quote, isQuote: isQuote)) } else if let replyHeader = replyHeader { - attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote)) + attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote, isQuote: isQuote)) } case let .messageReplyStoryHeader(userId, storyId): attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: storyId))) diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index 897da394fe..a2c3ed32dd 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -563,6 +563,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, if let replyToMessageId = replyToMessageId { var threadMessageId: MessageId? var quote = replyToMessageId.quote + let isQuote = quote != nil if let replyMessage = transaction.getMessage(replyToMessageId.messageId) { threadMessageId = replyMessage.effectiveReplyThreadMessageId if quote == nil, replyToMessageId.messageId.peerId != peerId { @@ -579,7 +580,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, quote = EngineMessageReplyQuote(text: replyMessage.text, entities: messageTextEntitiesInRange(entities: replyMessage.textEntitiesAttribute?.entities ?? [], range: NSRange(location: 0, length: nsText.length), onlyQuoteable: true), media: replyMedia) } } - attributes.append(ReplyMessageAttribute(messageId: replyToMessageId.messageId, threadMessageId: threadMessageId, quote: quote)) + attributes.append(ReplyMessageAttribute(messageId: replyToMessageId.messageId, threadMessageId: threadMessageId, quote: quote, isQuote: isQuote)) } if let replyToStoryId = replyToStoryId { attributes.append(ReplyStoryAttribute(storyId: replyToStoryId)) diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index bfc7e28cbe..7e8f5e7440 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -164,7 +164,7 @@ public func standaloneSendEnqueueMessages( } if let replyToMessageId = message.replyToMessageId { - attributes.append(ReplyMessageAttribute(messageId: replyToMessageId, threadMessageId: nil, quote: nil)) + attributes.append(ReplyMessageAttribute(messageId: replyToMessageId, threadMessageId: nil, quote: nil, isQuote: false)) } if let forwardOptions = message.forwardOptions { attributes.append(ForwardOptionsMessageAttribute(hideNames: forwardOptions.hideNames, hideCaptions: forwardOptions.hideCaptions)) diff --git a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift index 7f165b1b8f..726ccbef8e 100644 --- a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift @@ -104,6 +104,7 @@ final class AccountTaskManager { tasks.add(managedFeaturedStatusEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedProfilePhotoEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedGroupPhotoEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) + tasks.add(managedBackgroundIconEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedRecentReactions(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(_internal_loadedStickerPack(postbox: self.stateManager.postbox, network: self.stateManager.network, reference: .iconStatusEmoji, forceActualized: true).start()) tasks.add(_internal_loadedStickerPack(postbox: self.stateManager.postbox, network: self.stateManager.network, reference: .iconTopicEmoji, forceActualized: true).start()) diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index f49fff8893..3c3bd5ae1a 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -793,7 +793,9 @@ public final class PendingMessageManager { if peerId != replyAttribute.messageId.peerId { replyPeerId = replyAttribute.messageId.peerId } - replyQuote = replyAttribute.quote + if replyAttribute.isQuote { + replyQuote = replyAttribute.quote + } } else if let attribute = attribute as? ReplyStoryAttribute { replyToStoryId = attribute.storyId } else if let _ = attribute as? ForwardSourceInfoAttribute { @@ -1160,7 +1162,9 @@ public final class PendingMessageManager { if peer.id != replyAttribute.messageId.peerId { replyPeerId = replyAttribute.messageId.peerId } - replyQuote = replyAttribute.quote + if replyAttribute.isQuote { + replyQuote = replyAttribute.quote + } } else if let attribute = attribute as? ReplyStoryAttribute { replyToStoryId = attribute.storyId } else if let outgoingInfo = attribute as? OutgoingMessageInfoAttribute { diff --git a/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift b/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift index fe35e55b5d..957ab38068 100644 --- a/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift +++ b/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift @@ -881,7 +881,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil)) + attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false)) } var entitiesAttribute: TextEntitiesMessageAttribute? @@ -1113,7 +1113,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil)) + attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false)) } var entitiesAttribute: TextEntitiesMessageAttribute? @@ -1392,7 +1392,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil)) + attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false)) } var entitiesAttribute: TextEntitiesMessageAttribute? @@ -1593,7 +1593,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil)) + attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false)) } var entitiesAttribute: TextEntitiesMessageAttribute? diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMessageAttribute.swift index 5f7bcb3c03..b5918615dd 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMessageAttribute.swift @@ -6,15 +6,17 @@ public class ReplyMessageAttribute: MessageAttribute { public let messageId: MessageId public let threadMessageId: MessageId? public let quote: EngineMessageReplyQuote? + public let isQuote: Bool public var associatedMessageIds: [MessageId] { return [self.messageId] } - public init(messageId: MessageId, threadMessageId: MessageId?, quote: EngineMessageReplyQuote?) { + public init(messageId: MessageId, threadMessageId: MessageId?, quote: EngineMessageReplyQuote?, isQuote: Bool) { self.messageId = messageId self.threadMessageId = threadMessageId self.quote = quote + self.isQuote = isQuote } required public init(decoder: PostboxDecoder) { @@ -28,6 +30,7 @@ public class ReplyMessageAttribute: MessageAttribute { } self.quote = decoder.decodeCodable(EngineMessageReplyQuote.self, forKey: "qu") + self.isQuote = decoder.decodeBoolForKey("iq", orElse: self.quote != nil) } public func encode(_ encoder: PostboxEncoder) { @@ -44,6 +47,7 @@ public class ReplyMessageAttribute: MessageAttribute { } else { encoder.encodeNil(forKey: "qu") } + encoder.encodeBool(self.isQuote, forKey: "iq") } } @@ -51,6 +55,7 @@ public class QuotedReplyMessageAttribute: MessageAttribute { public let peerId: PeerId? public let authorName: String? public let quote: EngineMessageReplyQuote? + public let isQuote: Bool public var associatedMessageIds: [MessageId] { return [] @@ -64,16 +69,18 @@ public class QuotedReplyMessageAttribute: MessageAttribute { } } - public init(peerId: PeerId?, authorName: String?, quote: EngineMessageReplyQuote?) { + public init(peerId: PeerId?, authorName: String?, quote: EngineMessageReplyQuote?, isQuote: Bool) { self.peerId = peerId self.authorName = authorName self.quote = quote + self.isQuote = isQuote } required public init(decoder: PostboxDecoder) { self.peerId = decoder.decodeOptionalInt64ForKey("p").flatMap(PeerId.init) self.authorName = decoder.decodeOptionalStringForKey("a") self.quote = decoder.decodeCodable(EngineMessageReplyQuote.self, forKey: "qu") + self.isQuote = decoder.decodeBoolForKey("iq", orElse: true) } public func encode(_ encoder: PostboxEncoder) { @@ -94,14 +101,16 @@ public class QuotedReplyMessageAttribute: MessageAttribute { } else { encoder.encodeNil(forKey: "qu") } + + encoder.encodeBool(self.isQuote, forKey: "iq") } } extension QuotedReplyMessageAttribute { - convenience init(apiHeader: Api.MessageFwdHeader, quote: EngineMessageReplyQuote?) { + convenience init(apiHeader: Api.MessageFwdHeader, quote: EngineMessageReplyQuote?, isQuote: Bool) { switch apiHeader { case let .messageFwdHeader(_, fromId, fromName, _, _, _, _, _, _): - self.init(peerId: fromId?.peerId, authorName: fromName, quote: quote) + self.init(peerId: fromId?.peerId, authorName: fromName, quote: quote, isQuote: isQuote) } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift index 33861f0caa..da7da6772d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift @@ -471,7 +471,7 @@ func _internal_acceptAttachMenuBotDisclaimer(postbox: Postbox, botId: PeerId) -> } |> ignoreValues } -public struct AttachMenuBot { +public struct AttachMenuBot: Equatable { public let peer: EnginePeer public let shortName: String public let icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile] diff --git a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift index 9b64930637..c755c7aa44 100644 --- a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift @@ -466,7 +466,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode { }) } case let .textMention(name): - self.item?.controllerInteraction.openPeerMention(name) + self.item?.controllerInteraction.openPeerMention(name, tapAction.activate?()) case let .botCommand(command): self.item?.controllerInteraction.sendBotCommand(nil, command) case let .hashtag(peerName, hashtag): diff --git a/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/Sources/ChatHistoryEntry.swift b/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/Sources/ChatHistoryEntry.swift index d402913f9e..ee01ff53fd 100644 --- a/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/Sources/ChatHistoryEntry.swift +++ b/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/Sources/ChatHistoryEntry.swift @@ -120,6 +120,18 @@ public enum ChatHistoryEntry: Identifiable, Comparable { if lhsMessage.stableVersion != rhsMessage.stableVersion { return false } + + if lhsMessage.peers.count != rhsMessage.peers.count { + return false + } + for (id, peer) in lhsMessage.peers { + if let otherPeer = rhsMessage.peers[id] { + if !peer.isEqual(otherPeer) { + return false + } + } + } + if lhsMessage.media.count != rhsMessage.media.count { return false } diff --git a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/PublicHeaders/ChatInputTextViewImpl/ChatInputTextViewImpl.h b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/PublicHeaders/ChatInputTextViewImpl/ChatInputTextViewImpl.h index fc04921265..528c45c33f 100755 --- a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/PublicHeaders/ChatInputTextViewImpl/ChatInputTextViewImpl.h +++ b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/PublicHeaders/ChatInputTextViewImpl/ChatInputTextViewImpl.h @@ -19,6 +19,7 @@ @property (nonatomic, copy) bool (^ _Nullable shouldRespondToAction)(SEL _Nullable); @property (nonatomic, copy) bool (^ _Nullable shouldReturn)(); @property (nonatomic, copy) void (^ _Nullable backspaceWhileEmpty)(); +@property (nonatomic, copy) void (^ _Nullable dropAutocorrectioniOS16)(); - (instancetype _Nonnull)initWithFrame:(CGRect)frame textContainer:(NSTextContainer * _Nullable)textContainer disableTiling:(bool)disableTiling; diff --git a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m index a92c35160a..8e96504a12 100755 --- a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m +++ b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m @@ -12,6 +12,12 @@ @end +@interface ChatInputTextViewImpl () { + UIGestureRecognizer *_tapRecognizer; +} + +@end + @implementation ChatInputTextViewImpl - (instancetype _Nonnull)initWithFrame:(CGRect)frame textContainer:(NSTextContainer * _Nullable)textContainer disableTiling:(bool)disableTiling { @@ -26,10 +32,40 @@ #pragma clang diagnostic pop } } + + if (@available(iOS 17.0, *)) { + } else { + _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(workaroundTapGesture:)]; + _tapRecognizer.cancelsTouchesInView = false; + _tapRecognizer.delaysTouchesBegan = false; + _tapRecognizer.delaysTouchesEnded = false; + _tapRecognizer.delegate = self; + [self addGestureRecognizer:_tapRecognizer]; + } } return self; } +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { + return true; +} + +- (void)workaroundTapGesture:(UITapGestureRecognizer *)recognizer { + if (recognizer.state == UIGestureRecognizerStateEnded) { + static Class promptClass = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + promptClass = NSClassFromString([[NSString alloc] initWithFormat:@"%@AutocorrectInlinePrompt", @"UI"]); + }); + UIView *result = [self hitTest:[recognizer locationInView:self] withEvent:nil]; + if (result != nil && [result class] == promptClass) { + if (_dropAutocorrectioniOS16) { + _dropAutocorrectioniOS16(); + } + } + } +} + - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { if (_shouldRespondToAction) { diff --git a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift index 8042cee64e..c293c74e2f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift @@ -32,7 +32,6 @@ open class ChatInputTextNode: ASDisplayNode, UITextViewDelegate { } private var selectionChangedForEditedText: Bool = false - private var isPreservingSelection: Bool = false public var textView: ChatInputTextView { return self.view as! ChatInputTextView @@ -81,20 +80,7 @@ open class ChatInputTextNode: ASDisplayNode, UITextViewDelegate { get { return self.textView.attributedText } set(value) { - if self.textView.attributedText != value { - let selectedRange = self.textView.selectedRange; - let preserveSelectedRange = selectedRange.location != self.textView.textStorage.length - - self.textView.attributedText = value ?? NSAttributedString() - - if preserveSelectedRange { - self.isPreservingSelection = true - self.textView.selectedRange = selectedRange - self.isPreservingSelection = false - } - - self.textView.updateTextContainerInset() - } + self.textView.attributedText = value } } @@ -164,7 +150,7 @@ open class ChatInputTextNode: ASDisplayNode, UITextViewDelegate { } @objc public func textViewDidChangeSelection(_ textView: UITextView) { - if self.isPreservingSelection { + if self.textView.isPreservingSelection { return } @@ -187,6 +173,9 @@ open class ChatInputTextNode: ASDisplayNode, UITextViewDelegate { guard let delegate = self.delegate else { return true } + if self.textView.isPreservingText { + return false + } return delegate.chatInputTextNode(shouldChangeTextIn: range, replacementText: text) } @@ -259,10 +248,10 @@ private final class ChatInputTextContainer: NSTextContainer { public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDelegate, NSTextStorageDelegate { public final class Theme: Equatable { public final class Quote: Equatable { - public enum LineStyle { - case solid - case doubleDashed - case tripleDashed + public enum LineStyle: Equatable { + case solid(color: UIColor) + case doubleDashed(mainColor: UIColor, secondaryColor: UIColor) + case tripleDashed(mainColor: UIColor, secondaryColor: UIColor, tertiaryColor: UIColor) } public let background: UIColor public let foreground: UIColor @@ -306,6 +295,30 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele } } + override public var attributedText: NSAttributedString? { + get { + return super.attributedText + } set(value) { + if self.attributedText != value { + let selectedRange = self.selectedRange + let preserveSelectedRange = selectedRange.location != self.textStorage.length + + super.attributedText = value ?? NSAttributedString() + + if preserveSelectedRange { + self.isPreservingSelection = true + self.selectedRange = selectedRange + self.isPreservingSelection = false + } + + self.updateTextContainerInset() + } + } + } + + fileprivate var isPreservingSelection: Bool = false + fileprivate var isPreservingText: Bool = false + public weak var customDelegate: ChatInputTextNodeDelegate? public var theme: Theme? { @@ -391,6 +404,27 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele self.customTextStorage.delegate = self self.measurementTextStorage.delegate = self + self.dropAutocorrectioniOS16 = { [weak self] in + guard let self else { + return + } + + self.isPreservingSelection = true + self.isPreservingText = true + + let rangeCopy = self.selectedRange + var fakeRange = rangeCopy + if fakeRange.location != 0 { + fakeRange.location -= 1 + } + self.unmarkText() + self.selectedRange = fakeRange + self.selectedRange = rangeCopy + + self.isPreservingSelection = false + self.isPreservingText = false + } + self.shouldCopy = { [weak self] in guard let self else { return true @@ -542,7 +576,7 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele if self.measurementTextStorage != self.attributedText || self.measurementTextContainer.size != measureSize || self.measurementTextContainer.rightInset != rightInset { self.measurementTextContainer.rightInset = rightInset - self.measurementTextStorage.setAttributedString(self.attributedText) + self.measurementTextStorage.setAttributedString(self.attributedText ?? NSAttributedString()) self.measurementTextContainer.size = measureSize self.measurementLayoutManager.invalidateLayout(forCharacterRange: NSRange(location: 0, length: self.measurementTextStorage.length), actualCharacterRange: nil) self.measurementLayoutManager.ensureLayout(for: self.measurementTextContainer) @@ -698,6 +732,11 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele return result } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + return result + } } private final class CustomTextSelectionRect: UITextSelectionRect { @@ -765,11 +804,27 @@ private final class QuoteBackgroundView: UIView { self.iconView.frame = CGRect(origin: CGPoint(x: size.width - 4.0 - quoteIcon.size.width, y: 4.0), size: quoteIcon.size) + var primaryColor: UIColor + var secondaryColor: UIColor? + var tertiaryColor: UIColor? + switch theme.lineStyle { + case let .solid(color): + primaryColor = color + case let .doubleDashed(mainColor, secondaryColorValue): + primaryColor = mainColor + secondaryColor = secondaryColorValue + case let .tripleDashed(mainColor, secondaryColorValue, tertiaryColorValue): + primaryColor = mainColor + secondaryColor = secondaryColorValue + tertiaryColor = tertiaryColorValue + } + self.backgroundView.update( size: size, - primaryColor: theme.foreground, - secondaryColor: theme.lineStyle != .solid ? .clear : nil, - thirdColor: theme.lineStyle == .tripleDashed ? .clear : nil, + isTransparent: false, + primaryColor: primaryColor, + secondaryColor: secondaryColor, + thirdColor: tertiaryColor, pattern: nil, animation: .None ) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 2633d227df..f75ac88511 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -114,8 +114,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private var threadInfoNode: ChatMessageThreadInfoNode? private var replyInfoNode: ChatMessageReplyInfoNode? private var replyBackgroundContent: WallpaperBubbleBackgroundNode? - private var replyBackgroundNode: NavigationBackgroundNode? private var forwardInfoNode: ChatMessageForwardInfoNode? + private var forwardBackgroundContent: WallpaperBubbleBackgroundNode? private var actionButtonsNode: ChatMessageActionButtonsNode? private var reactionButtonsNode: ChatMessageReactionButtonsNode? @@ -305,7 +305,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if let item = self.item { let _ = item replyRecognizer.allowBothDirections = false//!item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply - self.view.disablesInteractiveTransitionGestureRecognizer = true + self.view.disablesInteractiveTransitionGestureRecognizer = false } replyRecognizer.shouldBegin = { [weak self] in if let strongSelf = self, let item = strongSelf.item { @@ -1058,7 +1058,6 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var viaBotApply: (TextNodeLayout, () -> TextNode)? var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)? var replyInfoApply: (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode)? - var needsReplyBackground = false var replyMarkup: ReplyMarkupMessageAttribute? var availableContentWidth = min(200.0, max(60.0, params.width - params.leftInset - params.rightInset - max(imageSize.width, 160.0) - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left)) @@ -1080,7 +1079,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var replyMessage: Message? var replyForward: QuotedReplyMessageAttribute? - var replyQuote: EngineMessageReplyQuote? + var replyQuote: (quote: EngineMessageReplyQuote, isQuote: Bool)? var replyStory: StoryId? for attribute in item.message.attributes { if let attribute = attribute as? InlineBotMessageAttribute { @@ -1107,7 +1106,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } else { replyMessage = item.message.associatedMessages[replyAttribute.messageId] } - replyQuote = replyAttribute.quote + replyQuote = replyAttribute.quote.flatMap { ($0, replyAttribute.isQuote) } } else if let quoteReplyAttribute = attribute as? QuotedReplyMessageAttribute { replyForward = quoteReplyAttribute } else if let attribute = attribute as? ReplyStoryAttribute { @@ -1216,10 +1215,16 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) } - if replyInfoApply != nil || viaBotApply != nil || forwardInfoSizeApply != nil { + var needsReplyBackground = false + if replyInfoApply != nil { needsReplyBackground = true } + var needsForwardBackground = false + if viaBotApply != nil || forwardInfoSizeApply != nil { + needsForwardBackground = true + } + var maxContentWidth = imageSize.width var actionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode))? if let replyMarkup = replyMarkup { @@ -1272,6 +1277,53 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { layoutSize.height += 4.0 + reactionButtonsSizeAndApply.0.height } + var headersOffset: CGFloat = 0.0 + if let (threadInfoSize, _) = threadInfoApply { + headersOffset += threadInfoSize.height + 10.0 + } + + var viaBotFrame: CGRect? + if let (viaBotLayout, _) = viaBotApply { + viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 15.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 14.0)), y: headersOffset + 8.0), size: viaBotLayout.size) + } + + var replyInfoFrame: CGRect? + if let (replyInfoSize, _) = replyInfoApply { + var viaBotSize = CGSize() + if let viaBotFrame = viaBotFrame { + viaBotSize = viaBotFrame.size + } + let replyInfoFrameValue = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - max(replyInfoSize.width, viaBotSize.width) - layoutConstants.bubble.edgeInset - 10.0)), y: headersOffset + 8.0 + viaBotSize.height), size: replyInfoSize) + replyInfoFrame = replyInfoFrameValue + if let viaBotFrameValue = viaBotFrame { + if replyInfoFrameValue.minX < replyInfoFrameValue.minX { + viaBotFrame = viaBotFrameValue.offsetBy(dx: replyInfoFrameValue.minX - viaBotFrameValue.minX, dy: 0.0) + } + } + } + + var replyBackgroundFrame: CGRect? + if let replyInfoFrame = replyInfoFrame { + var viaBotSize = CGSize() + if let viaBotFrame = viaBotFrame { + viaBotSize = viaBotFrame.size + } + + replyBackgroundFrame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: headersOffset + replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0)) + } + let _ = replyBackgroundFrame + + /*if let replyBackgroundFrameValue = replyBackgroundFrame { + if replyBackgroundFrameValue.insetBy(dx: -2.0, dy: -2.0).intersects(baseShareButtonFrame) { + let offset: CGFloat = 25.0 + + layoutSize.height += offset + updatedImageFrame.origin.y += offset + dateAndStatusFrame.origin.y += offset + baseShareButtonFrame.origin.y += offset + } + }*/ + func finishLayout(_ animation: ListViewItemUpdateAnimation, _ apply: ListViewItemApply, _ synchronousLoads: Bool) { if let strongSelf = weakSelf.value { strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature) @@ -1391,34 +1443,31 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { dateAndStatusApply(animation) if needsReplyBackground { - if let replyBackgroundNode = strongSelf.replyBackgroundNode { - replyBackgroundNode.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate) - } else { - let replyBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)) - strongSelf.replyBackgroundNode = replyBackgroundNode - strongSelf.contextSourceNode.contentNode.addSubnode(replyBackgroundNode) + if strongSelf.replyBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + backgroundContent.clipsToBounds = true + strongSelf.replyBackgroundContent = backgroundContent + strongSelf.contextSourceNode.contentNode.insertSubnode(backgroundContent, at: 0) } - - if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true { - if strongSelf.replyBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { - backgroundContent.clipsToBounds = true - strongSelf.replyBackgroundContent = backgroundContent - strongSelf.insertSubnode(backgroundContent, at: 0) - } - } else { - strongSelf.replyBackgroundContent?.removeFromSupernode() - strongSelf.replyBackgroundContent = nil - } - } else if let replyBackgroundNode = strongSelf.replyBackgroundNode { - strongSelf.replyBackgroundNode = nil - replyBackgroundNode.removeFromSupernode() - + } else { if let replyBackgroundContent = strongSelf.replyBackgroundContent { replyBackgroundContent.removeFromSupernode() strongSelf.replyBackgroundContent = nil } } + if needsForwardBackground { + if strongSelf.forwardBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + backgroundContent.clipsToBounds = true + strongSelf.forwardBackgroundContent = backgroundContent + strongSelf.contextSourceNode.contentNode.insertSubnode(backgroundContent, at: 0) + } + } else { + if let forwardBackgroundContent = strongSelf.forwardBackgroundContent { + forwardBackgroundContent.removeFromSupernode() + strongSelf.forwardBackgroundContent = nil + } + } + var headersOffset: CGFloat = 0.0 if let (threadInfoSize, threadInfoApply) = threadInfoApply { let threadInfoNode = threadInfoApply(synchronousLoads) @@ -1446,16 +1495,24 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: 0.0) } + var forwardAreaFrame: CGRect? if let (viaBotLayout, viaBotApply) = viaBotApply, forwardInfoSizeApply == nil { let viaBotNode = viaBotApply() if strongSelf.viaBotNode == nil { strongSelf.viaBotNode = viaBotNode strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode) } - let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0), size: viaBotLayout.size) + let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0 + 5.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0 - 5.0)), y: headersOffset + 8.0), size: viaBotLayout.size) + viaBotNode.frame = viaBotFrame messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height) + + if let forwardAreaFrameValue = forwardAreaFrame { + forwardAreaFrame = forwardAreaFrameValue.union(viaBotFrame) + } else { + forwardAreaFrame = viaBotFrame + } } else if let viaBotNode = strongSelf.viaBotNode { viaBotNode.removeFromSupernode() strongSelf.viaBotNode = nil @@ -1471,10 +1528,16 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } - let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: forwardInfoSize) + let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0 + 5.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0 - 5.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: forwardInfoSize) forwardInfoNode.frame = forwardInfoFrame - messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0) + messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height + 8.0) + + if let forwardAreaFrameValue = forwardAreaFrame { + forwardAreaFrame = forwardAreaFrameValue.union(forwardInfoFrame) + } else { + forwardAreaFrame = forwardInfoFrame + } } else if let forwardInfoNode = strongSelf.forwardInfoNode { if animation.isAnimated { if let forwardInfoNode = strongSelf.forwardInfoNode { @@ -1489,9 +1552,18 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } + var forwardBackgroundFrame: CGRect? + if let forwardAreaFrame { + forwardBackgroundFrame = forwardAreaFrame.insetBy(dx: -6.0, dy: -3.0) + } + var replyBackgroundFrame: CGRect? if let (replyInfoSize, replyInfoApply) = replyInfoApply { - let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize) + if headersOffset != 0.0 { + headersOffset += 6.0 + } + + let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - replyInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize) replyBackgroundFrame = replyInfoFrame let replyInfoNode = replyInfoApply(replyInfoFrame.size, synchronousLoads, animation) @@ -1507,24 +1579,25 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { strongSelf.replyInfoNode = nil } - if let replyBackgroundNode = strongSelf.replyBackgroundNode, let replyBackgroundFrame { - replyBackgroundNode.frame = replyBackgroundFrame - - let cornerRadius = 4.0 - replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate) - - if let backgroundContent = strongSelf.replyBackgroundContent { - replyBackgroundNode.isHidden = true - backgroundContent.cornerRadius = cornerRadius - backgroundContent.frame = replyBackgroundNode.frame - if let (rect, containerSize) = strongSelf.absoluteRect { - var backgroundFrame = backgroundContent.frame - backgroundFrame.origin.x += rect.minX - backgroundFrame.origin.y += rect.minY - backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) - } - } else { - replyBackgroundNode.isHidden = false + if let backgroundContent = strongSelf.replyBackgroundContent, let replyBackgroundFrame { + backgroundContent.cornerRadius = 4.0 + backgroundContent.frame = replyBackgroundFrame + if let (rect, containerSize) = strongSelf.absoluteRect { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } + + if let backgroundContent = strongSelf.forwardBackgroundContent, let forwardBackgroundFrame { + backgroundContent.cornerRadius = 4.0 + backgroundContent.frame = forwardBackgroundFrame + if let (rect, containerSize) = strongSelf.absoluteRect { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) } } @@ -1533,7 +1606,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { strongSelf.replyInfoNode?.alpha = panelsAlpha strongSelf.viaBotNode?.alpha = panelsAlpha strongSelf.forwardInfoNode?.alpha = panelsAlpha - strongSelf.replyBackgroundNode?.alpha = panelsAlpha + strongSelf.replyBackgroundContent?.alpha = panelsAlpha + strongSelf.forwardBackgroundContent?.alpha = panelsAlpha if isFailed { let deliveryFailedNode: ChatMessageDeliveryFailedNode @@ -1969,7 +2043,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { for attribute in item.message.attributes { if let attribute = attribute as? ReplyMessageAttribute { return .optionalAction({ - item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text)) + item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil)) }) } else if let attribute = attribute as? ReplyStoryAttribute { return .optionalAction({ @@ -2445,8 +2519,11 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if let forwardInfoNode = self.forwardInfoNode { transition.updateAlpha(node: forwardInfoNode, alpha: panelsAlpha) } - if let replyBackgroundNode = self.replyBackgroundNode { - transition.updateAlpha(node: replyBackgroundNode, alpha: panelsAlpha) + if let replyBackgroundContent = self.replyBackgroundContent { + transition.updateAlpha(node: replyBackgroundContent, alpha: panelsAlpha) + } + if let forwardBackgroundContent = self.forwardBackgroundContent { + transition.updateAlpha(node: forwardBackgroundContent, alpha: panelsAlpha) } if let selectionState = item.controllerInteraction.selectionState { @@ -2814,9 +2891,13 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { relativeTargetRect: sourceReplyPanel.relativeTargetRect ) let offset = replyInfoNode.animateFromInputPanel(sourceReplyPanel: mappedPanel, localRect: localRect, transition: transition) - if let replyBackgroundNode = self.replyBackgroundNode { - transition.animatePositionAdditive(layer: replyBackgroundNode.layer, offset: offset) - replyBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + if let replyBackgroundContent = self.replyBackgroundContent { + transition.animatePositionAdditive(layer: replyBackgroundContent.layer, offset: offset) + replyBackgroundContent.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + } + if let forwardBackgroundContent = self.forwardBackgroundContent { + transition.animatePositionAdditive(layer: forwardBackgroundContent.layer, offset: offset) + forwardBackgroundContent.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index a57e1fc1a1..67455efa73 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -177,7 +177,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing let author = message.author - let nameColors = author?.nameColor.flatMap { context.peerNameColors.get($0) } + let nameColors = author?.nameColor.flatMap { context.peerNameColors.get($0, dark: presentationData.theme.theme.overallDarkAppearance) } let mainColor: UIColor var secondaryColor: UIColor? @@ -250,10 +250,12 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { var contentMediaAutomaticDownload: InteractiveMediaNodeAutodownloadMode = .none var mediaAndFlags = mediaAndFlags - if let mediaAndFlagsValue = mediaAndFlags, let _ = mediaAndFlagsValue.0 as? TelegramMediaStory { - var flags = mediaAndFlagsValue.1 - flags.remove(.preferMediaInline) - mediaAndFlags = (mediaAndFlagsValue.0, flags) + if let mediaAndFlagsValue = mediaAndFlags { + if mediaAndFlagsValue.0 is TelegramMediaStory || mediaAndFlagsValue.0 is WallpaperPreviewMedia { + var flags = mediaAndFlagsValue.1 + flags.remove(.preferMediaInline) + mediaAndFlags = (mediaAndFlagsValue.0, flags) + } } var contentMediaAspectFilled = false @@ -867,13 +869,13 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { if let current = self.backgroundView { backgroundView = current animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil) - backgroundView.update(size: backgroundFrame.size, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, pattern: nil, animation: animation) + backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, pattern: nil, animation: animation) } else { backgroundView = MessageInlineBlockBackgroundView() self.backgroundView = backgroundView backgroundView.frame = backgroundFrame self.transformContainer.view.insertSubview(backgroundView, at: 0) - backgroundView.update(size: backgroundFrame.size, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, pattern: nil, animation: .None) + backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, pattern: nil, animation: .None) } } else { if let backgroundView = self.backgroundView { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index f39d756456..52ba4c402c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -1615,7 +1615,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI var inlineBotNameString: String? var replyMessage: Message? var replyForward: QuotedReplyMessageAttribute? - var replyQuote: EngineMessageReplyQuote? + var replyQuote: (quote: EngineMessageReplyQuote, isQuote: Bool)? var replyStory: StoryId? var replyMarkup: ReplyMarkupMessageAttribute? var authorNameColor: UIColor? @@ -1632,7 +1632,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } else { replyMessage = firstMessage.associatedMessages[attribute.messageId] } - replyQuote = attribute.quote + replyQuote = attribute.quote.flatMap { ($0, attribute.isQuote) } } else if let attribute = attribute as? QuotedReplyMessageAttribute { replyForward = attribute } else if let attribute = attribute as? ReplyStoryAttribute { @@ -1882,11 +1882,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info, item.content.firstMessage.adAttribute == nil { let peer = (peer as Peer) - let nameColors = peer.nameColor.flatMap { item.context.peerNameColors.get($0) } + let nameColors = peer.nameColor.flatMap { item.context.peerNameColors.get($0, dark: item.presentationData.theme.theme.overallDarkAppearance) } authorNameColor = nameColors?.main } else if let effectiveAuthor = effectiveAuthor { let nameColor = effectiveAuthor.nameColor ?? .blue - let nameColors = item.context.peerNameColors.get(nameColor) + let nameColors = item.context.peerNameColors.get(nameColor, dark: item.presentationData.theme.theme.overallDarkAppearance) let color: UIColor if incoming { color = nameColors.main @@ -1901,13 +1901,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI authorNameString = EnginePeer(peer).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) let peer = (peer as Peer) - let nameColors = peer.nameColor.flatMap { item.context.peerNameColors.get($0) } + let nameColors = peer.nameColor.flatMap { item.context.peerNameColors.get($0, dark: item.presentationData.theme.theme.overallDarkAppearance) } authorNameColor = nameColors?.main } else if let effectiveAuthor = effectiveAuthor { authorNameString = EnginePeer(effectiveAuthor).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) let nameColor = effectiveAuthor.nameColor ?? .blue - let nameColors = item.context.peerNameColors.get(nameColor) + let nameColors = item.context.peerNameColors.get(nameColor, dark: item.presentationData.theme.theme.overallDarkAppearance) let color: UIColor if incoming { color = nameColors.main @@ -4034,12 +4034,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI for attribute in item.message.attributes { if let attribute = attribute as? ReplyMessageAttribute { if let threadId = item.message.threadId, makeThreadIdMessageId(peerId: item.message.id.peerId, threadId: threadId) == attribute.messageId, let quotedReply = item.message.attributes.first(where: { $0 is QuotedReplyMessageAttribute }) as? QuotedReplyMessageAttribute { - return .action(InternalBubbleTapAction.Action { + return .action(InternalBubbleTapAction.Action({ item.controllerInteraction.attemptedNavigationToPrivateQuote(quotedReply.peerId.flatMap { item.message.peers[$0] }) - }) + }, contextMenuOnLongPress: true)) } - return .action(InternalBubbleTapAction.Action { [weak self] in + return .action(InternalBubbleTapAction.Action({ [weak self] in guard let self else { return } @@ -4047,16 +4047,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let replyInfoNode = self.replyInfoNode { progress = replyInfoNode.makeProgress() } - item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text, progress: progress)) - }) + item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil, progress: progress)) + }, contextMenuOnLongPress: true)) } else if let attribute = attribute as? ReplyStoryAttribute { - return .action(InternalBubbleTapAction.Action { + return .action(InternalBubbleTapAction.Action({ item.controllerInteraction.navigateToStory(item.message, attribute.storyId) - }) + }, contextMenuOnLongPress: true)) } else if let attribute = attribute as? QuotedReplyMessageAttribute { - return .action(InternalBubbleTapAction.Action { + return .action(InternalBubbleTapAction.Action({ item.controllerInteraction.attemptedNavigationToPrivateQuote(attribute.peerId.flatMap { item.message.peers[$0] }) - }) + }, contextMenuOnLongPress: true)) } } } @@ -4166,7 +4166,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI }) case let .textMention(name): return .action(InternalBubbleTapAction.Action { - self.item?.controllerInteraction.openPeerMention(name) + self.item?.controllerInteraction.openPeerMention(name, tapAction.activate?()) }) case let .botCommand(command): if let item = self.item { @@ -4265,23 +4265,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return .action(InternalBubbleTapAction.Action {}) } if let replyInfoNode = self.replyInfoNode, self.item?.controllerInteraction.tapMessage == nil, replyInfoNode.frame.contains(location) { - if let item = self.item { - for attribute in item.message.attributes { - if let attribute = attribute as? ReplyMessageAttribute { - return .action(InternalBubbleTapAction.Action { - item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text)) - }) - } else if let attribute = attribute as? ReplyStoryAttribute { - return .action(InternalBubbleTapAction.Action { - item.controllerInteraction.navigateToStory(item.message, attribute.storyId) - }) - } else if let attribute = attribute as? QuotedReplyMessageAttribute { - return .action(InternalBubbleTapAction.Action { - item.controllerInteraction.attemptedNavigationToPrivateQuote(attribute.peerId.flatMap { item.message.peers[$0] }) - }) - } - } - } + return .openContextMenu(InternalBubbleTapAction.OpenContextMenu(tapMessage: item.content.firstMessage, selectAll: false, subFrame: self.backgroundNode.frame, disableDefaultPressAnimation: true)) } var tapMessage: Message? = item.content.firstMessage diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift index 0359b0d89b..9ff63e0b98 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift @@ -165,7 +165,7 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { } else { if incoming { if let nameColor = peer?.nameColor { - titleColor = context.peerNameColors.get(nameColor).main + titleColor = context.peerNameColors.get(nameColor, dark: presentationData.theme.theme.overallDarkAppearance).main } else { titleColor = presentationData.theme.theme.chat.message.incoming.accentTextColor } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift index ec0267c3b3..92dc14d03c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift @@ -456,7 +456,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco var replyMessage: Message? var replyForward: QuotedReplyMessageAttribute? - var replyQuote: EngineMessageReplyQuote? + var replyQuote: (quote: EngineMessageReplyQuote, isQuote: Bool)? var replyStory: StoryId? for attribute in item.message.attributes { if let attribute = attribute as? InlineBotMessageAttribute { @@ -499,7 +499,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco } else { replyMessage = item.message.associatedMessages[replyAttribute.messageId] } - replyQuote = replyAttribute.quote + replyQuote = replyAttribute.quote.flatMap { ($0, replyAttribute.isQuote) } } else if let attribute = attribute as? QuotedReplyMessageAttribute { replyForward = attribute } else if let attribute = attribute as? ReplyStoryAttribute { @@ -963,7 +963,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco for attribute in item.message.attributes { if let attribute = attribute as? ReplyMessageAttribute { return .optionalAction({ - item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text)) + item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil)) }) } else if let attribute = attribute as? QuotedReplyMessageAttribute { return .action(InternalBubbleTapAction.Action { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift index 7215cdba22..d8dfd8c06a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -331,7 +331,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if !ignoreHeaders { var replyMessage: Message? var replyForward: QuotedReplyMessageAttribute? - var replyQuote: EngineMessageReplyQuote? + var replyQuote: (quote: EngineMessageReplyQuote, isQuote: Bool)? var replyStory: StoryId? for attribute in item.message.attributes { @@ -361,7 +361,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } else { replyMessage = item.message.associatedMessages[replyAttribute.messageId] } - replyQuote = replyAttribute.quote + replyQuote = replyAttribute.quote.flatMap { ($0, replyAttribute.isQuote) } } else if let attribute = attribute as? QuotedReplyMessageAttribute { replyForward = attribute } else if let attribute = attribute as? ReplyStoryAttribute { @@ -1291,7 +1291,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if let item = self.item { for attribute in item.message.attributes { if let attribute = attribute as? ReplyMessageAttribute { - item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text)) + item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil)) return } else if let attribute = attribute as? ReplyStoryAttribute { item.controllerInteraction.navigateToStory(item.message, attribute.storyId) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift index 01b21089e9..64d8b54e47 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift @@ -80,7 +80,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { public let type: ChatMessageReplyInfoType public let message: Message? public let replyForward: QuotedReplyMessageAttribute? - public let quote: EngineMessageReplyQuote? + public let quote: (quote: EngineMessageReplyQuote, isQuote: Bool)? public let story: StoryId? public let parentMessage: Message public let constrainedSize: CGSize @@ -95,7 +95,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { type: ChatMessageReplyInfoType, message: Message?, replyForward: QuotedReplyMessageAttribute?, - quote: EngineMessageReplyQuote?, + quote: (quote: EngineMessageReplyQuote, isQuote: Bool)?, story: StoryId?, parentMessage: Message, constrainedSize: CGSize, @@ -201,7 +201,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { let author = arguments.message?.effectiveAuthor - let colors = author?.nameColor.flatMap { arguments.context.peerNameColors.get($0) } + let colors = author?.nameColor.flatMap { arguments.context.peerNameColors.get($0, dark: arguments.presentationData.theme.theme.overallDarkAppearance) } authorNameColor = colors?.main dashSecondaryColor = colors?.secondary dashTertiaryColor = colors?.tertiary @@ -410,7 +410,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { var text: String var messageEntities: [MessageTextEntity] - if let quote = arguments.quote, !quote.text.isEmpty { + if let quote = arguments.quote?.quote, !quote.text.isEmpty { text = quote.text messageEntities = quote.entities } else { @@ -547,7 +547,15 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { var adjustedConstrainedTextSize = contrainedTextSize var textCutout: TextNodeCutout? var textCutoutWidth: CGFloat = 0.0 - if arguments.quote != nil || arguments.replyForward?.quote != nil { + + var isQuote = false + if let quote = arguments.quote, quote.isQuote { + isQuote = true + } else if let replyForward = arguments.replyForward, replyForward.quote != nil, replyForward.isQuote { + isQuote = true + } + + if isQuote { additionalTitleWidth += 10.0 maxTitleNumberOfLines = 2 maxTextNumberOfLines = 5 @@ -761,8 +769,13 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { )] as? TelegramMediaFile ) } + var isTransparent: Bool = false + if case .standalone = arguments.type { + isTransparent = true + } node.backgroundView.update( size: backgroundFrame.size, + isTransparent: isTransparent, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, @@ -770,7 +783,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { animation: animation ) - if arguments.quote != nil || arguments.replyForward?.quote != nil { + if isQuote { let quoteIconView: UIImageView if let current = node.quoteIconView { quoteIconView = current diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index 168cd19a7f..9e077fedce 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -56,8 +56,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { private var threadInfoNode: ChatMessageThreadInfoNode? private var replyInfoNode: ChatMessageReplyInfoNode? private var replyBackgroundContent: WallpaperBubbleBackgroundNode? - private var replyBackgroundNode: NavigationBackgroundNode? private var forwardInfoNode: ChatMessageForwardInfoNode? + private var forwardBackgroundContent: WallpaperBubbleBackgroundNode? private var actionButtonsNode: ChatMessageActionButtonsNode? private var reactionButtonsNode: ChatMessageReactionButtonsNode? @@ -350,6 +350,14 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { replyBackgroundContent.update(rect: rect, within: containerSize, transition: .immediate) } + + if let forwardBackgroundContent = self.forwardBackgroundContent { + var forwardBackgroundContentFrame = forwardBackgroundContent.frame + forwardBackgroundContentFrame.origin.x += rect.minX + forwardBackgroundContentFrame.origin.y += rect.minY + + forwardBackgroundContent.update(rect: rect, within: containerSize, transition: .immediate) + } } } @@ -651,7 +659,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { var replyMessage: Message? var replyForward: QuotedReplyMessageAttribute? - var replyQuote: EngineMessageReplyQuote? + var replyQuote: (quote: EngineMessageReplyQuote, isQuote: Bool)? var replyStory: StoryId? for attribute in item.message.attributes { if let attribute = attribute as? InlineBotMessageAttribute { @@ -679,7 +687,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } else { replyMessage = item.message.associatedMessages[replyAttribute.messageId] } - replyQuote = replyAttribute.quote + replyQuote = replyAttribute.quote.flatMap { ($0, replyAttribute.isQuote) } } else if let attribute = attribute as? QuotedReplyMessageAttribute { replyForward = attribute } else if let attribute = attribute as? ReplyStoryAttribute { @@ -784,10 +792,15 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } var needsReplyBackground = false - if replyInfoApply != nil || viaBotApply != nil || forwardInfoSizeApply != nil { + if replyInfoApply != nil { needsReplyBackground = true } + var needsForwardBackground = false + if viaBotApply != nil || forwardInfoSizeApply != nil { + needsForwardBackground = true + } + var maxContentWidth = imageSize.width var actionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode))? if let replyMarkup = replyMarkup { @@ -971,34 +984,31 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } if needsReplyBackground { - if let replyBackgroundNode = strongSelf.replyBackgroundNode { - replyBackgroundNode.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate) - } else { - let replyBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)) - strongSelf.replyBackgroundNode = replyBackgroundNode - strongSelf.contextSourceNode.contentNode.addSubnode(replyBackgroundNode) + if strongSelf.replyBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + backgroundContent.clipsToBounds = true + strongSelf.replyBackgroundContent = backgroundContent + strongSelf.contextSourceNode.contentNode.insertSubnode(backgroundContent, at: 0) } - - if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true { - if strongSelf.replyBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { - backgroundContent.clipsToBounds = true - strongSelf.replyBackgroundContent = backgroundContent - strongSelf.insertSubnode(backgroundContent, at: 0) - } - } else { - strongSelf.replyBackgroundContent?.removeFromSupernode() - strongSelf.replyBackgroundContent = nil - } - } else if let replyBackgroundNode = strongSelf.replyBackgroundNode { - replyBackgroundNode.removeFromSupernode() - strongSelf.replyBackgroundNode = nil - + } else { if let replyBackgroundContent = strongSelf.replyBackgroundContent { replyBackgroundContent.removeFromSupernode() strongSelf.replyBackgroundContent = nil } } + if needsForwardBackground { + if strongSelf.forwardBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + backgroundContent.clipsToBounds = true + strongSelf.forwardBackgroundContent = backgroundContent + strongSelf.contextSourceNode.contentNode.insertSubnode(backgroundContent, at: 0) + } + } else { + if let forwardBackgroundContent = strongSelf.forwardBackgroundContent { + forwardBackgroundContent.removeFromSupernode() + strongSelf.forwardBackgroundContent = nil + } + } + var headersOffset: CGFloat = 0.0 if let (threadInfoSize, threadInfoApply) = threadInfoApply { let threadInfoNode = threadInfoApply(synchronousLoads) @@ -1026,16 +1036,24 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: 0.0) } + var forwardAreaFrame: CGRect? if let (viaBotLayout, viaBotApply) = viaBotApply, forwardInfoSizeApply == nil { let viaBotNode = viaBotApply() if strongSelf.viaBotNode == nil { strongSelf.viaBotNode = viaBotNode strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode) } - let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0), size: viaBotLayout.size) + let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0 + 5.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0 - 5.0)), y: headersOffset + 8.0), size: viaBotLayout.size) + viaBotNode.frame = viaBotFrame messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height) + + if let forwardAreaFrameValue = forwardAreaFrame { + forwardAreaFrame = forwardAreaFrameValue.union(viaBotFrame) + } else { + forwardAreaFrame = viaBotFrame + } } else if let viaBotNode = strongSelf.viaBotNode { viaBotNode.removeFromSupernode() strongSelf.viaBotNode = nil @@ -1051,10 +1069,16 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } - let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: forwardInfoSize) + let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0 + 5.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0 - 5.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: forwardInfoSize) forwardInfoNode.frame = forwardInfoFrame - messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0) + messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height + 8.0) + + if let forwardAreaFrameValue = forwardAreaFrame { + forwardAreaFrame = forwardAreaFrameValue.union(forwardInfoFrame) + } else { + forwardAreaFrame = forwardInfoFrame + } } else if let forwardInfoNode = strongSelf.forwardInfoNode { if animation.isAnimated { if let forwardInfoNode = strongSelf.forwardInfoNode { @@ -1069,9 +1093,18 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } } + var forwardBackgroundFrame: CGRect? + if let forwardAreaFrame { + forwardBackgroundFrame = forwardAreaFrame.insetBy(dx: -6.0, dy: -3.0) + } + var replyBackgroundFrame: CGRect? if let (replyInfoSize, replyInfoApply) = replyInfoApply { - let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize) + if headersOffset != 0.0 { + headersOffset += 6.0 + } + + let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - replyInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize) replyBackgroundFrame = replyInfoFrame let replyInfoNode = replyInfoApply(replyInfoFrame.size, synchronousLoads, animation) @@ -1087,24 +1120,25 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { strongSelf.replyInfoNode = nil } - if let replyBackgroundNode = strongSelf.replyBackgroundNode, let replyBackgroundFrame { - replyBackgroundNode.frame = replyBackgroundFrame - - let cornerRadius = 4.0 - replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate) - - if let backgroundContent = strongSelf.replyBackgroundContent { - replyBackgroundNode.isHidden = true - backgroundContent.cornerRadius = cornerRadius - backgroundContent.frame = replyBackgroundNode.frame - if let (rect, containerSize) = strongSelf.absoluteRect { - var backgroundFrame = backgroundContent.frame - backgroundFrame.origin.x += rect.minX - backgroundFrame.origin.y += rect.minY - backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) - } - } else { - replyBackgroundNode.isHidden = false + if let backgroundContent = strongSelf.replyBackgroundContent, let replyBackgroundFrame { + backgroundContent.cornerRadius = 4.0 + backgroundContent.frame = replyBackgroundFrame + if let (rect, containerSize) = strongSelf.absoluteRect { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } + + if let backgroundContent = strongSelf.forwardBackgroundContent, let forwardBackgroundFrame { + backgroundContent.cornerRadius = 4.0 + backgroundContent.frame = forwardBackgroundFrame + if let (rect, containerSize) = strongSelf.absoluteRect { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) } } @@ -1113,7 +1147,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { strongSelf.replyInfoNode?.alpha = panelsAlpha strongSelf.viaBotNode?.alpha = panelsAlpha strongSelf.forwardInfoNode?.alpha = panelsAlpha - strongSelf.replyBackgroundNode?.alpha = panelsAlpha + strongSelf.replyBackgroundContent?.alpha = panelsAlpha + strongSelf.forwardBackgroundContent?.alpha = panelsAlpha if isFailed { let deliveryFailedNode: ChatMessageDeliveryFailedNode @@ -1336,7 +1371,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { for attribute in item.message.attributes { if let attribute = attribute as? ReplyMessageAttribute { return .optionalAction({ - item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text)) + item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil)) }) } else if let attribute = attribute as? ReplyStoryAttribute { return .optionalAction({ @@ -1591,8 +1626,11 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { if let forwardInfoNode = self.forwardInfoNode { transition.updateAlpha(node: forwardInfoNode, alpha: panelsAlpha) } - if let replyBackgroundNode = self.replyBackgroundNode { - transition.updateAlpha(node: replyBackgroundNode, alpha: panelsAlpha) + if let replyBackgroundContent = self.replyBackgroundContent { + transition.updateAlpha(node: replyBackgroundContent, alpha: panelsAlpha) + } + if let forwardBackgroundContent = self.forwardBackgroundContent { + transition.updateAlpha(node: forwardBackgroundContent, alpha: panelsAlpha) } if let selectionState = item.controllerInteraction.selectionState { @@ -1635,10 +1673,10 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { let alpha: CGFloat = 0.0 let previousAlpha = replyInfoNode.alpha replyInfoNode.alpha = alpha - self.replyBackgroundNode?.alpha = alpha + self.replyBackgroundContent?.alpha = alpha if animated { replyInfoNode.layer.animateAlpha(from: previousAlpha, to: alpha, duration: 0.3) - self.replyBackgroundNode?.layer.animateAlpha(from: previousAlpha, to: alpha, duration: 0.3) + self.replyBackgroundContent?.layer.animateAlpha(from: previousAlpha, to: alpha, duration: 0.3) } } } else { @@ -1664,10 +1702,10 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { let alpha: CGFloat = 1.0 let previousAlpha = replyInfoNode.alpha replyInfoNode.alpha = alpha - self.replyBackgroundNode?.alpha = alpha + self.replyBackgroundContent?.alpha = alpha if animated { replyInfoNode.layer.animateAlpha(from: previousAlpha, to: alpha, duration: 0.3) - self.replyBackgroundNode?.layer.animateAlpha(from: previousAlpha, to: alpha, duration: 0.3) + self.replyBackgroundContent?.layer.animateAlpha(from: previousAlpha, to: alpha, duration: 0.3) } } } @@ -1957,9 +1995,13 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { ) let offset = replyInfoNode.animateFromInputPanel(sourceReplyPanel: mappedPanel, localRect: localRect, transition: transition) - if let replyBackgroundNode = self.replyBackgroundNode { - transition.animatePositionAdditive(layer: replyBackgroundNode.layer, offset: offset) - replyBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + if let replyBackgroundContent = self.replyBackgroundContent { + transition.animatePositionAdditive(layer: replyBackgroundContent.layer, offset: offset) + replyBackgroundContent.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + } + if let forwardBackgroundContent = self.forwardBackgroundContent { + transition.animatePositionAdditive(layer: forwardBackgroundContent.layer, offset: offset) + forwardBackgroundContent.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index b8680f093a..05d675a0ae 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -385,7 +385,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { var secondaryColor: UIColor? = nil var tertiaryColor: UIColor? = nil - let nameColors = author?.nameColor.flatMap { item.context.peerNameColors.get($0) } + let nameColors = author?.nameColor.flatMap { item.context.peerNameColors.get($0, dark: item.presentationData.theme.theme.overallDarkAppearance) } if !incoming { mainColor = messageTheme.accentTextColor if let _ = nameColors?.secondary { @@ -766,7 +766,38 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)) } else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { - return ChatMessageBubbleContentTapAction(content: .textMention(peerName)) + var urlRange: NSRange? + if let (_, _, urlRangeValue) = self.textNode.textNode.attributeSubstringWithRange(name: TelegramTextAttributes.PeerTextMention, index: index) { + urlRange = urlRangeValue + } + + return ChatMessageBubbleContentTapAction(content: .textMention(peerName), activate: { [weak self] in + guard let self else { + return nil + } + + let promise = Promise() + + self.linkProgressDisposable?.dispose() + + if self.linkProgressRange != nil { + self.linkProgressRange = nil + self.updateLinkProgressState() + } + + self.linkProgressDisposable = (promise.get() |> deliverOnMainQueue).startStrict(next: { [weak self] value in + guard let self else { + return + } + let updatedRange: NSRange? = value ? urlRange : nil + if self.linkProgressRange != updatedRange { + self.linkProgressRange = updatedRange + self.updateLinkProgressState() + } + }) + + return promise + }) } else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String { return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand)) } else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag { diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 6d2678f8ac..fbeafdfb8e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -264,7 +264,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if peer.id != context.account.peerId { self?.openPeer(peer: peer) } - }, openPeerMention: { [weak self] name in + }, openPeerMention: { [weak self] name, _ in self?.openPeerMention(name) }, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer diff --git a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift index 0f91d1ceb5..8b0ed49bf0 100644 --- a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift +++ b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift @@ -28,7 +28,7 @@ private func addRoundedRectPath(context: CGContext, rect: CGRect, radius: CGFloa context.restoreGState() } -private func generateBackgroundTemplateImage(addStripe: Bool) -> UIImage { +private func generateBackgroundTemplateImage(addStripe: Bool, isTransparent: Bool) -> UIImage { return generateImage(CGSize(width: radius * 2.0 + 4.0, height: radius * 2.0 + 8.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) @@ -36,7 +36,9 @@ private func generateBackgroundTemplateImage(addStripe: Bool) -> UIImage { context.clip() context.setFillColor(UIColor.white.withMultipliedAlpha(0.1).cgColor) - context.fill(CGRect(origin: CGPoint(), size: size)) + if !isTransparent { + context.fill(CGRect(origin: CGPoint(), size: size)) + } if addStripe { context.setFillColor(UIColor.white.withMultipliedAlpha(0.2).cgColor) @@ -69,11 +71,19 @@ private func generateProgressTemplateImage() -> UIImage { } private let backgroundSolidTemplateImage: UIImage = { - return generateBackgroundTemplateImage(addStripe: true) + return generateBackgroundTemplateImage(addStripe: true, isTransparent: false) }() private let backgroundDashTemplateImage: UIImage = { - return generateBackgroundTemplateImage(addStripe: false) + return generateBackgroundTemplateImage(addStripe: false, isTransparent: false) +}() + +private let transparentBackgroundSolidTemplateImage: UIImage = { + return generateBackgroundTemplateImage(addStripe: true, isTransparent: true) +}() + +private let transparentBackgroundDashTemplateImage: UIImage = { + return generateBackgroundTemplateImage(addStripe: false, isTransparent: true) }() private func generateDashBackgroundTemplateImage() -> UIImage { @@ -436,6 +446,7 @@ public final class MessageInlineBlockBackgroundView: UIView { private struct Params: Equatable { var size: CGSize + var isTransparent: Bool var primaryColor: UIColor var secondaryColor: UIColor? var thirdColor: UIColor? @@ -444,6 +455,7 @@ public final class MessageInlineBlockBackgroundView: UIView { init( size: CGSize, + isTransparent: Bool, primaryColor: UIColor, secondaryColor: UIColor?, thirdColor: UIColor?, @@ -451,6 +463,7 @@ public final class MessageInlineBlockBackgroundView: UIView { displayProgress: Bool ) { self.size = size + self.isTransparent = isTransparent self.primaryColor = primaryColor self.secondaryColor = secondaryColor self.thirdColor = thirdColor @@ -467,6 +480,7 @@ public final class MessageInlineBlockBackgroundView: UIView { if let params = self.params { self.update( size: params.size, + isTransparent: params.isTransparent, primaryColor: params.primaryColor, secondaryColor: params.secondaryColor, thirdColor: params.thirdColor, @@ -577,6 +591,7 @@ public final class MessageInlineBlockBackgroundView: UIView { public func update( size: CGSize, + isTransparent: Bool, primaryColor: UIColor, secondaryColor: UIColor?, thirdColor: UIColor?, @@ -585,6 +600,7 @@ public final class MessageInlineBlockBackgroundView: UIView { ) { let params = Params( size: size, + isTransparent: isTransparent, primaryColor: primaryColor, secondaryColor: secondaryColor, thirdColor: thirdColor, @@ -602,10 +618,18 @@ public final class MessageInlineBlockBackgroundView: UIView { patternContentLayer.layerTintColor = primaryColor.cgColor } - if params.secondaryColor != nil { - self.backgroundView.image = backgroundDashTemplateImage + if params.isTransparent { + if params.secondaryColor != nil { + self.backgroundView.image = transparentBackgroundDashTemplateImage + } else { + self.backgroundView.image = transparentBackgroundSolidTemplateImage + } } else { - self.backgroundView.image = backgroundSolidTemplateImage + if params.secondaryColor != nil { + self.backgroundView.image = backgroundDashTemplateImage + } else { + self.backgroundView.image = backgroundSolidTemplateImage + } } self.backgroundView.tintColor = params.primaryColor } @@ -711,7 +735,7 @@ public final class MessageInlineBlockBackgroundView: UIView { let itemSize = CGSize(width: placement.size / 3.0, height: placement.size / 3.0) patternContentLayer.frame = CGRect(origin: CGPoint(x: size.width - placement.position.x / 3.0 - itemSize.width * 0.5, y: placement.position.y / 3.0 - itemSize.height * 0.5), size: itemSize) - var alphaFraction = abs(placement.position.x) / 500.0 + var alphaFraction = abs(placement.position.x) / min(500.0, size.width) alphaFraction = min(1.0, max(0.0, alphaFraction)) patternContentLayer.opacity = 0.3 * Float(1.0 - alphaFraction) diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 26162320b4..fbc222401d 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -112,7 +112,7 @@ public final class ChatControllerInteraction { public let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool public let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void - public let openPeerMention: (String) -> Void + public let openPeerMention: (String, Promise?) -> Void public let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void public let updateMessageReaction: (Message, ChatControllerInteractionReaction) -> Void public let openMessageReactionContextMenu: (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void @@ -230,7 +230,7 @@ public final class ChatControllerInteraction { public init( openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void, - openPeerMention: @escaping (String) -> Void, + openPeerMention: @escaping (String, Promise?) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void, openMessageReactionContextMenu: @escaping (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void, updateMessageReaction: @escaping (Message, ChatControllerInteractionReaction) -> Void, diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift index 8cc1b6b1ff..a2fd9c8995 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift @@ -196,7 +196,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode { media.append(TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "", displayUrl: "", hash: 0, type: nil, websiteName: site, title: title, text: text, embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil)))) } - let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil)] : [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false)] : [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, isCentered: false)) } diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorItem.swift index e573d9f435..edbd18161b 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorItem.swift @@ -138,14 +138,14 @@ private func generateFillImage(nameColor: PeerNameColors.Colors) -> UIImage? { context.setFillColor(secondColor.cgColor) context.fill(circleBounds) - context.move(to: .zero) - context.addLine(to: CGPoint(x: size.width, y: 0.0)) - context.addLine(to: CGPoint(x: 0.0, y: size.height)) - context.closePath() - context.setFillColor(nameColor.main.cgColor) - context.fillPath() - if let thirdColor = nameColor.tertiary { + context.move(to: CGPoint(x: size.width, y: 0.0)) + context.addLine(to: CGPoint(x: size.width, y: size.height)) + context.addLine(to: CGPoint(x: 0.0, y: size.height)) + context.closePath() + context.setFillColor(nameColor.main.cgColor) + context.fillPath() + context.setFillColor(thirdColor.cgColor) context.translateBy(x: size.width / 2.0, y: size.height / 2.0) context.rotate(by: .pi / 4.0) @@ -153,6 +153,13 @@ private func generateFillImage(nameColor: PeerNameColors.Colors) -> UIImage? { let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: -9.0, y: -9.0), size: CGSize(width: 18.0, height: 18.0)), cornerRadius: 4.0) context.addPath(path.cgPath) context.fillPath() + } else { + context.move(to: .zero) + context.addLine(to: CGPoint(x: size.width, y: 0.0)) + context.addLine(to: CGPoint(x: 0.0, y: size.height)) + context.closePath() + context.setFillColor(nameColor.main.cgColor) + context.fillPath() } } else { context.setFillColor(nameColor.main.cgColor) diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift index be6b4473be..46dc8f2cf4 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift @@ -49,7 +49,7 @@ private enum PeerNameColorScreenEntry: ItemListNodeEntry { case colorMessage(wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, items: [PeerNameColorChatPreviewItem.MessageItem]) case colorPicker(colors: PeerNameColors, currentColor: PeerNameColor) case colorDescription(String) - case backgroundEmojiHeader(String) + case backgroundEmojiHeader(String, String?) case backgroundEmoji(EmojiPagerContentComponent, UIColor) var section: ItemListSectionId { @@ -121,8 +121,8 @@ private enum PeerNameColorScreenEntry: ItemListNodeEntry { } else { return false } - case let .backgroundEmojiHeader(text): - if case .backgroundEmojiHeader(text) = rhs { + case let .backgroundEmojiHeader(text, action): + if case .backgroundEmojiHeader(text, action) = rhs { return true } else { return false @@ -170,8 +170,10 @@ private enum PeerNameColorScreenEntry: ItemListNodeEntry { ) case let .colorDescription(text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) - case let .backgroundEmojiHeader(text): - return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) + case let .backgroundEmojiHeader(text, action): + return ItemListSectionHeaderItem(presentationData: presentationData, text: text, actionText: action, action: action != nil ? { + arguments.updateBackgroundEmojiId(0) + } : nil, sectionId: self.section) case let .backgroundEmoji(emojiContent, backgroundIconColor): return EmojiPickerItem(context: arguments.context, theme: presentationData.theme, strings: presentationData.strings, emojiContent: emojiContent, backgroundIconColor: backgroundIconColor, sectionId: self.section) } @@ -204,7 +206,7 @@ private func peerNameColorScreenEntries( nameColor = .blue } - let colors = nameColors.get(nameColor) + let colors = nameColors.get(nameColor, dark: presentationData.theme.overallDarkAppearance) let backgroundEmojiId: Int64? if let updatedBackgroundEmojiId = state.updatedBackgroundEmojiId { @@ -254,7 +256,7 @@ private func peerNameColorScreenEntries( entries.append(.colorDescription(presentationData.strings.NameColor_ChatPreview_Description_Account)) if let emojiContent { - entries.append(.backgroundEmojiHeader(presentationData.strings.NameColor_BackgroundEmoji_Title)) + entries.append(.backgroundEmojiHeader(presentationData.strings.NameColor_BackgroundEmoji_Title, backgroundEmojiId != nil ? presentationData.strings.NameColor_BackgroundEmoji_Remove : nil)) entries.append(.backgroundEmoji(emojiContent, colors.main)) } } @@ -281,8 +283,8 @@ public func PeerNameColorScreen( var presentImpl: ((ViewController) -> Void)? var pushImpl: ((ViewController) -> Void)? var dismissImpl: (() -> Void)? - -// var openQuickReactionImpl: (() -> Void)? + var attemptNavigationImpl: ((@escaping () -> Void) -> Bool)? + var applyChangesImpl: (() -> Void)? let actionsDisposable = DisposableSet() @@ -312,12 +314,12 @@ public func PeerNameColorScreen( peerId = channelId } - let emojiContent = combineLatest( + context.sharedContext.presentationData, statePromise.get(), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) ) - |> mapToSignal { state, peer -> Signal in + |> mapToSignal { presentationData, state, peer -> Signal in var selectedEmojiId: Int64? if let updatedBackgroundEmojiId = state.updatedBackgroundEmojiId { selectedEmojiId = updatedBackgroundEmojiId @@ -330,7 +332,7 @@ public func PeerNameColorScreen( } else { nameColor = (peer?.nameColor ?? .blue) } - let color = context.peerNameColors.get(nameColor) + let color = context.peerNameColors.get(nameColor, dark: presentationData.theme.overallDarkAppearance) let selectedItems: [EngineMedia.Id] if let selectedEmojiId, selectedEmojiId != 0 { @@ -399,59 +401,7 @@ public func PeerNameColorScreen( inProgress: state.inProgress, action: { if !isLocked { - let state = stateValue.with { $0 } - - let nameColor = state.updatedNameColor ?? peer?.nameColor - let backgroundEmojiId = state.updatedBackgroundEmojiId ?? peer?.backgroundEmojiId - - switch subject { - case .account: - let _ = context.engine.accountData.updateNameColorAndEmoji(nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0).startStandalone() - dismissImpl?() - case let .channel(peerId): - updateState { state in - var updatedState = state - updatedState.inProgress = true - return updatedState - } - let _ = (context.engine.peers.updatePeerNameColorAndEmoji(peerId: peerId, nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0) - |> deliverOnMainQueue).startStandalone(next: { - }, error: { error in - if case .channelBoostRequired = error { - let _ = combineLatest( - queue: Queue.mainQueue(), - context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)), - context.engine.peers.getChannelBoostStatus(peerId: peerId) - ).startStandalone(next: { peer, status in - guard let peer, let status else { - return - } - - let link = status.url - let controller = PremiumLimitScreen(context: context, subject: .storiesChannelBoost(peer: peer, boostSubject: .nameColors, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0), count: Int32(status.boosts), action: { - UIPasteboard.general.string = link - presentImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.ChannelBoost_BoostLinkCopied), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false })) - return true - }, openStats: nil, openGift: { - let controller = createGiveawayController(context: context, peerId: peerId, subject: .generic) - pushImpl?(controller) - }) - pushImpl?(controller) - - HapticFeedback().impact(.light) - }) - } else { - - } - updateState { state in - var updatedState = state - updatedState.inProgress = false - return updatedState - } - }, completed: { - dismissImpl?() - }) - } + applyChangesImpl?() } else { HapticFeedback().impact(.light) let controller = UndoOverlayController( @@ -596,7 +546,97 @@ public func PeerNameColorScreen( } controller.dismiss() } - + controller.attemptNavigation = { f in + return attemptNavigationImpl?(f) ?? true + } + attemptNavigationImpl = { f in + if !context.isPremium { + f() + return true + } + let state = stateValue.with({ $0 }) + var hasChanges = false + if state.updatedNameColor != nil || state.updatedBackgroundEmojiId != nil { + hasChanges = true + } + if hasChanges { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.NameColor_UnsavedChanges_Title, text: presentationData.strings.NameColor_UnsavedChanges_Text, actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.NameColor_UnsavedChanges_Discard, action: { + f() + dismissImpl?() + }), + TextAlertAction(type: .defaultAction, title: presentationData.strings.NameColor_UnsavedChanges_Apply, action: { + applyChangesImpl?() + }) + ])) + return false + } else { + f() + return true + } + } + applyChangesImpl = { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).startStandalone(next: { peer in + guard let peer else { + return + } + let state = stateValue.with { $0 } + + let nameColor = state.updatedNameColor ?? peer.nameColor + let backgroundEmojiId = state.updatedBackgroundEmojiId ?? peer.backgroundEmojiId + + switch subject { + case .account: + let _ = context.engine.accountData.updateNameColorAndEmoji(nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0).startStandalone() + dismissImpl?() + case let .channel(peerId): + updateState { state in + var updatedState = state + updatedState.inProgress = true + return updatedState + } + let _ = (context.engine.peers.updatePeerNameColorAndEmoji(peerId: peerId, nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0) + |> deliverOnMainQueue).startStandalone(next: { + }, error: { error in + if case .channelBoostRequired = error { + let _ = combineLatest( + queue: Queue.mainQueue(), + context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)), + context.engine.peers.getChannelBoostStatus(peerId: peerId) + ).startStandalone(next: { peer, status in + guard let peer, let status else { + return + } + + let link = status.url + let controller = PremiumLimitScreen(context: context, subject: .storiesChannelBoost(peer: peer, boostSubject: .nameColors, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0, canBoostAgain: false), count: Int32(status.boosts), action: { + UIPasteboard.general.string = link + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.ChannelBoost_BoostLinkCopied), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false })) + return true + }, openStats: nil, openGift: { + let controller = createGiveawayController(context: context, peerId: peerId, subject: .generic) + pushImpl?(controller) + }) + pushImpl?(controller) + + HapticFeedback().impact(.light) + }) + } else { + + } + updateState { state in + var updatedState = state + updatedState.inProgress = false + return updatedState + } + }, completed: { + dismissImpl?() + }) + } + }) + } return controller } - diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index c30cf65537..a14f0398f9 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -1106,7 +1106,7 @@ final class ShareWithPeersScreenComponent: Component { previousController.dismiss() } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - let controller = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .storiesChannelBoost(peer: peer, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0), count: Int32(status.boosts), forceDark: true, cancel: {}, action: { [weak navigationController] in + let controller = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .storiesChannelBoost(peer: peer, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0, canBoostAgain: false), count: Int32(status.boosts), forceDark: true, cancel: {}, action: { [weak navigationController] in UIPasteboard.general.string = link if let previousController = navigationController?.viewControllers.reversed().first(where: { $0 is ShareWithPeersScreen}) as? ShareWithPeersScreen { diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index ca59798691..c9168b7ba2 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -837,14 +837,14 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD return } - if let (updatedUrlPreviewUrl, signal) = urlPreviewStateForInputText(NSAttributedString(string: url), context: selfController.context, currentQuery: nil), let updatedUrlPreviewUrl { - if let webpage = webpageCache[updatedUrlPreviewUrl] { + if let (updatedUrlPreviewState, signal) = urlPreviewStateForInputText(NSAttributedString(string: url), context: selfController.context, currentQuery: nil), let updatedUrlPreviewState { + if let webpage = webpageCache[updatedUrlPreviewState.detectedUrl] { progress?.set(.single(false)) selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in if state.interfaceState.editMessage != nil { if var urlPreview = state.editingUrlPreview { - urlPreview.url = updatedUrlPreviewUrl + urlPreview.url = updatedUrlPreviewState.detectedUrl urlPreview.webPage = webpage return state.updatedEditingUrlPreview(urlPreview) @@ -853,7 +853,7 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD } } else { if var urlPreview = state.urlPreview { - urlPreview.url = updatedUrlPreviewUrl + urlPreview.url = updatedUrlPreviewState.detectedUrl urlPreview.webPage = webpage return state.updatedUrlPreview(urlPreview) @@ -876,9 +876,9 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in if state.interfaceState.editMessage != nil { if let webpage = result(nil), var urlPreview = state.editingUrlPreview { - urlPreview.url = updatedUrlPreviewUrl + urlPreview.url = updatedUrlPreviewState.detectedUrl urlPreview.webPage = webpage - webpageCache[updatedUrlPreviewUrl] = webpage + webpageCache[updatedUrlPreviewState.detectedUrl] = webpage return state.updatedEditingUrlPreview(urlPreview) } else { @@ -886,9 +886,9 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD } } else { if let webpage = result(nil), var urlPreview = state.urlPreview { - urlPreview.url = updatedUrlPreviewUrl + urlPreview.url = updatedUrlPreviewState.detectedUrl urlPreview.webPage = webpage - webpageCache[updatedUrlPreviewUrl] = webpage + webpageCache[updatedUrlPreviewState.detectedUrl] = webpage return state.updatedUrlPreview(urlPreview) } else { diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift new file mode 100644 index 0000000000..245f78c609 --- /dev/null +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -0,0 +1,516 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import AccountContext +import ChatPresentationInterfaceState +import ChatInterfaceState +import TelegramNotices +import PresentationDataUtils +import TelegramCallsUI +import AttachmentUI + +func updateChatPresentationInterfaceStateImpl( + selfController: ChatControllerImpl, + transition: ContainedViewLayoutTransition, + interactive: Bool, + saveInterfaceState: Bool, + _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, + completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void +) { + var completion = externalCompletion + var temporaryChatPresentationInterfaceState = f(selfController.presentationInterfaceState) + + if selfController.presentationInterfaceState.keyboardButtonsMessage?.visibleButtonKeyboardMarkup != temporaryChatPresentationInterfaceState.keyboardButtonsMessage?.visibleButtonKeyboardMarkup || selfController.presentationInterfaceState.keyboardButtonsMessage?.id != temporaryChatPresentationInterfaceState.keyboardButtonsMessage?.id { + if let keyboardButtonsMessage = temporaryChatPresentationInterfaceState.keyboardButtonsMessage, let keyboardMarkup = keyboardButtonsMessage.visibleButtonKeyboardMarkup { + if selfController.presentationInterfaceState.interfaceState.editMessage == nil && selfController.presentationInterfaceState.interfaceState.composeInputState.inputText.length == 0 && keyboardButtonsMessage.id != temporaryChatPresentationInterfaceState.interfaceState.messageActionsState.closedButtonKeyboardMessageId && keyboardButtonsMessage.id != temporaryChatPresentationInterfaceState.interfaceState.messageActionsState.dismissedButtonKeyboardMessageId && temporaryChatPresentationInterfaceState.botStartPayload == nil { + temporaryChatPresentationInterfaceState = temporaryChatPresentationInterfaceState.updatedInputMode({ _ in + return .inputButtons(persistent: keyboardMarkup.flags.contains(.persistent)) + }) + } + + if case let .peer(peerId) = selfController.chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel || peerId.namespace == Namespaces.Peer.CloudGroup { + if temporaryChatPresentationInterfaceState.interfaceState.replyMessageSubject == nil && temporaryChatPresentationInterfaceState.interfaceState.messageActionsState.processedSetupReplyMessageId != keyboardButtonsMessage.id { + temporaryChatPresentationInterfaceState = temporaryChatPresentationInterfaceState.updatedInterfaceState({ + $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject( + messageId: keyboardButtonsMessage.id, + quote: nil + )).withUpdatedMessageActionsState({ value in + var value = value + value.processedSetupReplyMessageId = keyboardButtonsMessage.id + return value + }) }) + } + } + } else { + temporaryChatPresentationInterfaceState = temporaryChatPresentationInterfaceState.updatedInputMode({ mode in + if case .inputButtons = mode { + return .text + } else { + return mode + } + }) + } + } + + if let keyboardButtonsMessage = temporaryChatPresentationInterfaceState.keyboardButtonsMessage, keyboardButtonsMessage.requestsSetupReply { + if temporaryChatPresentationInterfaceState.interfaceState.replyMessageSubject == nil && temporaryChatPresentationInterfaceState.interfaceState.messageActionsState.processedSetupReplyMessageId != keyboardButtonsMessage.id { + temporaryChatPresentationInterfaceState = temporaryChatPresentationInterfaceState.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject( + messageId: keyboardButtonsMessage.id, + quote: nil + )).withUpdatedMessageActionsState({ value in + var value = value + value.processedSetupReplyMessageId = keyboardButtonsMessage.id + return value + }) }) + } + } + + let inputTextPanelState = inputTextPanelStateForChatPresentationInterfaceState(temporaryChatPresentationInterfaceState, context: selfController.context) + var updatedChatPresentationInterfaceState = temporaryChatPresentationInterfaceState.updatedInputTextPanelState({ _ in return inputTextPanelState }) + + let contextQueryUpdates = contextQueryResultStateForChatInterfacePresentationState(updatedChatPresentationInterfaceState, context: selfController.context, currentQueryStates: &selfController.contextQueryStates, requestBotLocationStatus: { [weak selfController] peerId in + guard let selfController else { + return + } + let _ = (ApplicationSpecificNotice.updateInlineBotLocationRequestState(accountManager: selfController.context.sharedContext.accountManager, peerId: peerId, timestamp: Int32(Date().timeIntervalSince1970 + 10 * 60)) + |> deliverOnMainQueue).startStandalone(next: { [weak selfController] value in + guard let selfController, value else { + return + } + selfController.present(textAlertController(context: selfController.context, updatedPresentationData: selfController.updatedPresentationData, title: nil, text: selfController.presentationData.strings.Conversation_ShareInlineBotLocationConfirmation, actions: [TextAlertAction(type: .defaultAction, title: selfController.presentationData.strings.Common_Cancel, action: { + }), TextAlertAction(type: .defaultAction, title: selfController.presentationData.strings.Common_OK, action: { [weak selfController] in + guard let selfController else { + return + } + let _ = ApplicationSpecificNotice.setInlineBotLocationRequest(accountManager: selfController.context.sharedContext.accountManager, peerId: peerId, value: 0).startStandalone() + })]), in: .window(.root)) + }) + }) + + for (kind, update) in contextQueryUpdates { + switch update { + case .remove: + if let (_, disposable) = selfController.contextQueryStates[kind] { + disposable.dispose() + selfController.contextQueryStates.removeValue(forKey: kind) + + updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedInputQueryResult(queryKind: kind, { _ in + return nil + }) + } + if case .contextRequest = kind { + selfController.performingInlineSearch.set(false) + } + case let .update(query, signal): + let currentQueryAndDisposable = selfController.contextQueryStates[kind] + currentQueryAndDisposable?.1.dispose() + + var inScope = true + var inScopeResult: ((ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?)? + selfController.contextQueryStates[kind] = (query, (signal + |> deliverOnMainQueue).startStrict(next: { [weak selfController] result in + guard let selfController else { + return + } + if Thread.isMainThread && inScope { + inScope = false + inScopeResult = result + } else { + selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInputQueryResult(queryKind: kind, { previousResult in + return result(previousResult) + }) + }) + } + }, error: { [weak selfController] error in + guard let selfController else { + return + } + if case .contextRequest = kind { + selfController.performingInlineSearch.set(false) + } + + switch error { + case .generic: + break + case let .inlineBotLocationRequest(peerId): + selfController.present(textAlertController(context: selfController.context, updatedPresentationData: selfController.updatedPresentationData, title: nil, text: selfController.presentationData.strings.Conversation_ShareInlineBotLocationConfirmation, actions: [TextAlertAction(type: .defaultAction, title: selfController.presentationData.strings.Common_Cancel, action: { [weak selfController] in + guard let selfController else { + return + } + let _ = ApplicationSpecificNotice.setInlineBotLocationRequest(accountManager: selfController.context.sharedContext.accountManager, peerId: peerId, value: Int32(Date().timeIntervalSince1970 + 10 * 60)).startStandalone() + }), TextAlertAction(type: .defaultAction, title: selfController.presentationData.strings.Common_OK, action: { [weak selfController] in + guard let selfController else { + return + } + let _ = ApplicationSpecificNotice.setInlineBotLocationRequest(accountManager: selfController.context.sharedContext.accountManager, peerId: peerId, value: 0).startStandalone() + })]), in: .window(.root)) + } + }, completed: { [weak selfController] in + guard let selfController else { + return + } + if case .contextRequest = kind { + selfController.performingInlineSearch.set(false) + } + })) + inScope = false + if let inScopeResult = inScopeResult { + updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedInputQueryResult(queryKind: kind, { previousResult in + return inScopeResult(previousResult) + }) + } else { + if case .contextRequest = kind { + selfController.performingInlineSearch.set(true) + } + } + + if case let .peer(peerId) = selfController.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { + if case .contextRequest = query { + let _ = (ApplicationSpecificNotice.getSecretChatInlineBotUsage(accountManager: selfController.context.sharedContext.accountManager) + |> deliverOnMainQueue).startStandalone(next: { [weak selfController] value in + guard let selfController, !value else { + return + } + let _ = ApplicationSpecificNotice.setSecretChatInlineBotUsage(accountManager: selfController.context.sharedContext.accountManager).startStandalone() + selfController.present(textAlertController(context: selfController.context, updatedPresentationData: selfController.updatedPresentationData, title: nil, text: selfController.presentationData.strings.Conversation_SecretChatContextBotAlert, actions: [TextAlertAction(type: .defaultAction, title: selfController.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + }) + } + } + } + } + + var isBot = false + if let peer = updatedChatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, peer.botInfo != nil { + isBot = true + } else { + isBot = false + } + selfController.chatDisplayNode.historyNode.chatHasBots = updatedChatPresentationInterfaceState.hasBots || isBot + + if let (updatedSearchQuerySuggestionState, updatedSearchQuerySuggestionSignal) = searchQuerySuggestionResultStateForChatInterfacePresentationState(updatedChatPresentationInterfaceState, context: selfController.context, currentQuery: selfController.searchQuerySuggestionState?.0) { + selfController.searchQuerySuggestionState?.1.dispose() + var inScope = true + var inScopeResult: ((ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?)? + selfController.searchQuerySuggestionState = (updatedSearchQuerySuggestionState, (updatedSearchQuerySuggestionSignal |> deliverOnMainQueue).startStrict(next: { [weak selfController] result in + guard let selfController else { + return + } + if Thread.isMainThread && inScope { + inScope = false + inScopeResult = result + } else { + selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedSearchQuerySuggestionResult { previousResult in + return result(previousResult) + } + }) + } + })) + inScope = false + if let inScopeResult = inScopeResult { + updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedSearchQuerySuggestionResult { previousResult in + return inScopeResult(previousResult) + } + } + } + + if let (updatedUrlPreviewState, updatedUrlPreviewSignal) = urlPreviewStateForInputText(updatedChatPresentationInterfaceState.interfaceState.composeInputState.inputText, context: selfController.context, currentQuery: selfController.urlPreviewQueryState?.0) { + selfController.urlPreviewQueryState?.1.dispose() + var inScope = true + var inScopeResult: ((TelegramMediaWebpage?) -> TelegramMediaWebpage?)? + let linkPreviews: Signal + if case let .peer(peerId) = selfController.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { + linkPreviews = interactiveChatLinkPreviewsEnabled(accountManager: selfController.context.sharedContext.accountManager, displayAlert: { [weak selfController] f in + guard let selfController else { + return + } + selfController.present(textAlertController(context: selfController.context, updatedPresentationData: selfController.updatedPresentationData, title: nil, text: selfController.presentationData.strings.Conversation_SecretLinkPreviewAlert, actions: [ + TextAlertAction(type: .defaultAction, title: selfController.presentationData.strings.Common_Yes, action: { + f.f(true) + }), TextAlertAction(type: .genericAction, title: selfController.presentationData.strings.Common_No, action: { + f.f(false) + })]), in: .window(.root)) + }) + } else { + var bannedEmbedLinks = false + if let channel = selfController.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.hasBannedPermission(.banEmbedLinks) != nil { + bannedEmbedLinks = true + } else if let group = selfController.presentationInterfaceState.renderedPeer?.peer as? TelegramGroup, group.hasBannedPermission(.banEmbedLinks) { + bannedEmbedLinks = true + } + if bannedEmbedLinks { + linkPreviews = .single(false) + } else { + linkPreviews = .single(true) + } + } + let filteredPreviewSignal = linkPreviews + |> take(1) + |> mapToSignal { value -> Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError> in + if value { + return updatedUrlPreviewSignal + } else { + return .single({ _ in return nil }) + } + } + + selfController.urlPreviewQueryState = (updatedUrlPreviewState, (filteredPreviewSignal |> deliverOnMainQueue).startStrict(next: { [weak selfController] (result) in + guard let selfController else { + return + } + if Thread.isMainThread && inScope { + inScope = false + inScopeResult = result + } else { + selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { + if let updatedUrlPreviewState, let webpage = result($0.urlPreview?.webPage) { + let updatedPreview = ChatPresentationInterfaceState.UrlPreview( + url: updatedUrlPreviewState.detectedUrl, + webPage: webpage, + positionBelowText: $0.urlPreview?.positionBelowText ?? true, + largeMedia: $0.urlPreview?.largeMedia + ) + return $0.updatedUrlPreview(updatedPreview) + } else { + return $0.updatedUrlPreview(nil) + } + }) + } + })) + inScope = false + if let inScopeResult = inScopeResult { + if let updatedUrlPreviewState, let webpage = inScopeResult(updatedChatPresentationInterfaceState.urlPreview?.webPage) { + let updatedPreview = ChatPresentationInterfaceState.UrlPreview( + url: updatedUrlPreviewState.detectedUrl, + webPage: webpage, + positionBelowText: updatedChatPresentationInterfaceState.urlPreview?.positionBelowText ?? true, + largeMedia: updatedChatPresentationInterfaceState.urlPreview?.largeMedia + ) + updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedUrlPreview(updatedPreview) + } else { + updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedUrlPreview(nil) + } + } + } + + let isEditingMedia: Bool = updatedChatPresentationInterfaceState.editMessageState?.content != .plaintext + let editingUrlPreviewText: NSAttributedString? = isEditingMedia ? nil : updatedChatPresentationInterfaceState.interfaceState.editMessage?.inputState.inputText + if let (updatedEditingUrlPreviewState, updatedEditingUrlPreviewSignal) = urlPreviewStateForInputText(editingUrlPreviewText, context: selfController.context, currentQuery: selfController.editingUrlPreviewQueryState?.0) { + selfController.editingUrlPreviewQueryState?.1.dispose() + var inScope = true + var inScopeResult: ((TelegramMediaWebpage?) -> TelegramMediaWebpage?)? + selfController.editingUrlPreviewQueryState = (updatedEditingUrlPreviewState, (updatedEditingUrlPreviewSignal |> deliverOnMainQueue).startStrict(next: { [weak selfController] result in + guard let selfController else { + return + } + if Thread.isMainThread && inScope { + inScope = false + inScopeResult = result + } else { + selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { + if let updatedEditingUrlPreviewState, let webpage = result($0.editingUrlPreview?.webPage) { + let updatedPreview = ChatPresentationInterfaceState.UrlPreview( + url: updatedEditingUrlPreviewState.detectedUrl, + webPage: webpage, + positionBelowText: $0.editingUrlPreview?.positionBelowText ?? true, + largeMedia: $0.editingUrlPreview?.largeMedia + ) + return $0.updatedEditingUrlPreview(updatedPreview) + } else { + return $0.updatedEditingUrlPreview(nil) + } + }) + } + })) + inScope = false + if let inScopeResult = inScopeResult { + if let updatedEditingUrlPreviewState, let webpage = inScopeResult(updatedChatPresentationInterfaceState.editingUrlPreview?.webPage) { + let updatedPreview = ChatPresentationInterfaceState.UrlPreview( + url: updatedEditingUrlPreviewState.detectedUrl, + webPage: webpage, + positionBelowText: updatedChatPresentationInterfaceState.editingUrlPreview?.positionBelowText ?? true, + largeMedia: updatedChatPresentationInterfaceState.editingUrlPreview?.largeMedia + ) + updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedEditingUrlPreview(updatedPreview) + } else { + updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedEditingUrlPreview(nil) + } + } + } + + if let replyMessageId = updatedChatPresentationInterfaceState.interfaceState.replyMessageSubject?.messageId { + if selfController.replyMessageState?.0 != replyMessageId { + selfController.replyMessageState?.1.dispose() + updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedReplyMessage(nil) + let disposable = MetaDisposable() + selfController.replyMessageState = (replyMessageId, disposable) + disposable.set((selfController.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.Message(id: replyMessageId)) + |> deliverOnMainQueue).start(next: { [weak selfController] message in + guard let selfController else { + return + } + if message != selfController.presentationInterfaceState.replyMessage.flatMap(EngineMessage.init) { + selfController.updateChatPresentationInterfaceState(interactive: false, { presentationInterfaceState in + return presentationInterfaceState.updatedReplyMessage(message?._asMessage()) + }) + } + })) + } + } else { + if let replyMessageState = selfController.replyMessageState { + selfController.replyMessageState = nil + replyMessageState.1.dispose() + updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedReplyMessage(nil) + } + } + + if let updated = selfController.updateSearch(updatedChatPresentationInterfaceState) { + updatedChatPresentationInterfaceState = updated + } + + let recordingActivityValue: ChatRecordingActivity + if let mediaRecordingState = updatedChatPresentationInterfaceState.inputTextPanelState.mediaRecordingState { + switch mediaRecordingState { + case .audio: + recordingActivityValue = .voice + case .video(ChatVideoRecordingStatus.recording, _): + recordingActivityValue = .instantVideo + default: + recordingActivityValue = .none + } + } else { + recordingActivityValue = .none + } + if recordingActivityValue != selfController.recordingActivityValue { + selfController.recordingActivityValue = recordingActivityValue + selfController.recordingActivityPromise.set(recordingActivityValue) + } + + if (selfController.presentationInterfaceState.interfaceState.selectionState == nil) != (updatedChatPresentationInterfaceState.interfaceState.selectionState == nil) { + selfController.isSelectingMessagesUpdated?(updatedChatPresentationInterfaceState.interfaceState.selectionState != nil) + selfController.updateNextChannelToReadVisibility() + } + + selfController.presentationInterfaceState = updatedChatPresentationInterfaceState + + selfController.updateSlowmodeStatus() + + switch updatedChatPresentationInterfaceState.inputMode { + case .media: + break + default: + selfController.chatDisplayNode.collapseInput() + } + + if selfController.isNodeLoaded { + selfController.chatDisplayNode.updateChatPresentationInterfaceState(updatedChatPresentationInterfaceState, transition: transition, interactive: interactive, completion: completion) + } else { + completion(.immediate) + } + + let updatedServiceTasks = serviceTasksForChatPresentationIntefaceState(context: selfController.context, chatPresentationInterfaceState: updatedChatPresentationInterfaceState, updateState: { [weak selfController] f in + guard let selfController else { + return + } + selfController.updateChatPresentationInterfaceState(animated: false, interactive: false, f) + + //selfController.chatDisplayNode.updateChatPresentationInterfaceState(f(selfController.chatDisplayNode.chatPresentationInterfaceState), transition: transition, interactive: false, completion: { _ in }) + }) + for (id, begin) in updatedServiceTasks { + if selfController.stateServiceTasks[id] == nil { + selfController.stateServiceTasks[id] = begin() + } + } + var removedServiceTaskIds: [AnyHashable] = [] + for (id, _) in selfController.stateServiceTasks { + if updatedServiceTasks[id] == nil { + removedServiceTaskIds.append(id) + } + } + for id in removedServiceTaskIds { + selfController.stateServiceTasks.removeValue(forKey: id)?.dispose() + } + + if let button = leftNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, subject: selfController.subject, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.leftNavigationButton, target: selfController, selector: #selector(selfController.leftNavigationButtonAction)) { + if selfController.leftNavigationButton != button { + var animated = transition.isAnimated + if let currentButton = selfController.leftNavigationButton?.action, currentButton == button.action { + animated = false + } + animated = false + selfController.navigationItem.setLeftBarButton(button.buttonItem, animated: animated) + selfController.leftNavigationButton = button + } + } else if let _ = selfController.leftNavigationButton { + selfController.navigationItem.setLeftBarButton(nil, animated: transition.isAnimated) + selfController.leftNavigationButton = nil + } + + if let button = rightNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.rightNavigationButton, target: selfController, selector: #selector(selfController.rightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) { + if selfController.rightNavigationButton != button { + var animated = transition.isAnimated + if let currentButton = selfController.rightNavigationButton?.action, currentButton == button.action { + animated = false + } + if case .replyThread = selfController.chatLocation { + animated = false + } + selfController.navigationItem.setRightBarButton(button.buttonItem, animated: animated) + selfController.rightNavigationButton = button + } + } else if let _ = selfController.rightNavigationButton { + selfController.navigationItem.setRightBarButton(nil, animated: transition.isAnimated) + selfController.rightNavigationButton = nil + } + + if let controllerInteraction = selfController.controllerInteraction { + if updatedChatPresentationInterfaceState.interfaceState.selectionState != controllerInteraction.selectionState { + controllerInteraction.selectionState = updatedChatPresentationInterfaceState.interfaceState.selectionState + let isBlackout = controllerInteraction.selectionState != nil + let previousCompletion = completion + completion = { [weak selfController] transition in + previousCompletion(transition) + + guard let selfController else { + return + } + (selfController.navigationController as? NavigationController)?.updateMasterDetailsBlackout(isBlackout ? .master : nil, transition: transition) + } + selfController.updateItemNodesSelectionStates(animated: transition.isAnimated) + } + } + + if saveInterfaceState { + selfController.saveInterfaceState(includeScrollState: false) + } + + if let navigationController = selfController.navigationController as? NavigationController, isTopmostChatController(selfController) { + var voiceChatOverlayController: VoiceChatOverlayController? + for controller in navigationController.globalOverlayControllers { + if let controller = controller as? VoiceChatOverlayController { + voiceChatOverlayController = controller + break + } + } + + if let controller = voiceChatOverlayController { + controller.updateVisibility() + } + } + + if let currentMenuWebAppController = selfController.currentMenuWebAppController, !selfController.presentationInterfaceState.showWebView { + selfController.currentMenuWebAppController = nil + if let currentMenuWebAppController = currentMenuWebAppController as? AttachmentController { + currentMenuWebAppController.ensureUnfocused = false + } + currentMenuWebAppController.dismiss(animated: true, completion: nil) + } + + selfController.presentationInterfaceStatePromise.set(selfController.presentationInterfaceState) +} diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 98e76e0d18..4c453a7ef9 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -326,8 +326,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var contextQueryStates: [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)] = [:] var searchQuerySuggestionState: (ChatPresentationInputQuery?, Disposable)? - var urlPreviewQueryState: (String?, Disposable)? - var editingUrlPreviewQueryState: (String?, Disposable)? + var urlPreviewQueryState: (UrlPreviewState?, Disposable)? + var editingUrlPreviewQueryState: (UrlPreviewState?, Disposable)? var replyMessageState: (EngineMessage.Id, Disposable)? var searchState: ChatSearchState? @@ -774,7 +774,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .pinnedMessageUpdated: for attribute in message.attributes { if let attribute = attribute as? ReplyMessageAttribute { - strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text))) + strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil))) break } } @@ -783,7 +783,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .gameScore: for attribute in message.attributes { if let attribute = attribute as? ReplyMessageAttribute { - strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text))) + strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil))) break } } @@ -1113,7 +1113,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, openPeerMention: { [weak self] mention in if let strongSelf = self { - strongSelf.controllerInteraction?.openPeerMention(mention) + strongSelf.controllerInteraction?.openPeerMention(mention, nil) } }, openPeer: { [weak self] peer in if let strongSelf = self { @@ -1205,8 +1205,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G fromReactionMessageId = fromMessage?.id } self?.openPeer(peer: peer, navigation: navigation, fromMessage: fromMessage, fromReactionMessageId: fromReactionMessageId, expandAvatar: expandAvatar) - }, openPeerMention: { [weak self] name in - self?.openPeerMention(name) + }, openPeerMention: { [weak self] name, progress in + self?.openPeerMention(name, progress: progress) }, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in guard let strongSelf = self, strongSelf.isNodeLoaded else { return @@ -12073,477 +12073,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.updateChatPresentationInterfaceState(transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, interactive: interactive, saveInterfaceState: saveInterfaceState, f, completion: completion) } - func updateChatPresentationInterfaceState(transition: ContainedViewLayoutTransition, interactive: Bool, saveInterfaceState: Bool = false, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) { - var completion = externalCompletion - var temporaryChatPresentationInterfaceState = f(self.presentationInterfaceState) - - if self.presentationInterfaceState.keyboardButtonsMessage?.visibleButtonKeyboardMarkup != temporaryChatPresentationInterfaceState.keyboardButtonsMessage?.visibleButtonKeyboardMarkup || self.presentationInterfaceState.keyboardButtonsMessage?.id != temporaryChatPresentationInterfaceState.keyboardButtonsMessage?.id { - if let keyboardButtonsMessage = temporaryChatPresentationInterfaceState.keyboardButtonsMessage, let keyboardMarkup = keyboardButtonsMessage.visibleButtonKeyboardMarkup { - if self.presentationInterfaceState.interfaceState.editMessage == nil && self.presentationInterfaceState.interfaceState.composeInputState.inputText.length == 0 && keyboardButtonsMessage.id != temporaryChatPresentationInterfaceState.interfaceState.messageActionsState.closedButtonKeyboardMessageId && keyboardButtonsMessage.id != temporaryChatPresentationInterfaceState.interfaceState.messageActionsState.dismissedButtonKeyboardMessageId && temporaryChatPresentationInterfaceState.botStartPayload == nil { - temporaryChatPresentationInterfaceState = temporaryChatPresentationInterfaceState.updatedInputMode({ _ in - return .inputButtons(persistent: keyboardMarkup.flags.contains(.persistent)) - }) - } - - if case let .peer(peerId) = self.chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel || peerId.namespace == Namespaces.Peer.CloudGroup { - if temporaryChatPresentationInterfaceState.interfaceState.replyMessageSubject == nil && temporaryChatPresentationInterfaceState.interfaceState.messageActionsState.processedSetupReplyMessageId != keyboardButtonsMessage.id { - temporaryChatPresentationInterfaceState = temporaryChatPresentationInterfaceState.updatedInterfaceState({ - $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject( - messageId: keyboardButtonsMessage.id, - quote: nil - )).withUpdatedMessageActionsState({ value in - var value = value - value.processedSetupReplyMessageId = keyboardButtonsMessage.id - return value - }) }) - } - } - } else { - temporaryChatPresentationInterfaceState = temporaryChatPresentationInterfaceState.updatedInputMode({ mode in - if case .inputButtons = mode { - return .text - } else { - return mode - } - }) - } - } - - if let keyboardButtonsMessage = temporaryChatPresentationInterfaceState.keyboardButtonsMessage, keyboardButtonsMessage.requestsSetupReply { - if temporaryChatPresentationInterfaceState.interfaceState.replyMessageSubject == nil && temporaryChatPresentationInterfaceState.interfaceState.messageActionsState.processedSetupReplyMessageId != keyboardButtonsMessage.id { - temporaryChatPresentationInterfaceState = temporaryChatPresentationInterfaceState.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject( - messageId: keyboardButtonsMessage.id, - quote: nil - )).withUpdatedMessageActionsState({ value in - var value = value - value.processedSetupReplyMessageId = keyboardButtonsMessage.id - return value - }) }) - } - } - - let inputTextPanelState = inputTextPanelStateForChatPresentationInterfaceState(temporaryChatPresentationInterfaceState, context: self.context) - var updatedChatPresentationInterfaceState = temporaryChatPresentationInterfaceState.updatedInputTextPanelState({ _ in return inputTextPanelState }) - - let contextQueryUpdates = contextQueryResultStateForChatInterfacePresentationState(updatedChatPresentationInterfaceState, context: self.context, currentQueryStates: &self.contextQueryStates, requestBotLocationStatus: { [weak self] peerId in - guard let strongSelf = self else { - return - } - let _ = (ApplicationSpecificNotice.updateInlineBotLocationRequestState(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peerId, timestamp: Int32(Date().timeIntervalSince1970 + 10 * 60)) - |> deliverOnMainQueue).startStandalone(next: { value in - guard let strongSelf = self, value else { - return - } - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Conversation_ShareInlineBotLocationConfirmation, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { - }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { - let _ = ApplicationSpecificNotice.setInlineBotLocationRequest(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peerId, value: 0).startStandalone() - })]), in: .window(.root)) - }) - }) - - for (kind, update) in contextQueryUpdates { - switch update { - case .remove: - if let (_, disposable) = self.contextQueryStates[kind] { - disposable.dispose() - self.contextQueryStates.removeValue(forKey: kind) - - updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedInputQueryResult(queryKind: kind, { _ in - return nil - }) - } - if case .contextRequest = kind { - self.performingInlineSearch.set(false) - } - case let .update(query, signal): - let currentQueryAndDisposable = self.contextQueryStates[kind] - currentQueryAndDisposable?.1.dispose() - - var inScope = true - var inScopeResult: ((ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?)? - self.contextQueryStates[kind] = (query, (signal - |> deliverOnMainQueue).startStrict(next: { [weak self] result in - if let strongSelf = self { - if Thread.isMainThread && inScope { - inScope = false - inScopeResult = result - } else { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInputQueryResult(queryKind: kind, { previousResult in - return result(previousResult) - }) - }) - } - } - }, error: { [weak self] error in - if let strongSelf = self { - if case .contextRequest = kind { - strongSelf.performingInlineSearch.set(false) - } - - switch error { - case .generic: - break - case let .inlineBotLocationRequest(peerId): - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Conversation_ShareInlineBotLocationConfirmation, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { - let _ = ApplicationSpecificNotice.setInlineBotLocationRequest(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peerId, value: Int32(Date().timeIntervalSince1970 + 10 * 60)).startStandalone() - }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { - let _ = ApplicationSpecificNotice.setInlineBotLocationRequest(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peerId, value: 0).startStandalone() - })]), in: .window(.root)) - } - } - }, completed: { [weak self] in - if let strongSelf = self { - if case .contextRequest = kind { - strongSelf.performingInlineSearch.set(false) - } - } - })) - inScope = false - if let inScopeResult = inScopeResult { - updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedInputQueryResult(queryKind: kind, { previousResult in - return inScopeResult(previousResult) - }) - } else { - if case .contextRequest = kind { - self.performingInlineSearch.set(true) - } - } - - if case let .peer(peerId) = self.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { - if case .contextRequest = query { - let _ = (ApplicationSpecificNotice.getSecretChatInlineBotUsage(accountManager: self.context.sharedContext.accountManager) - |> deliverOnMainQueue).startStandalone(next: { [weak self] value in - if let strongSelf = self, !value { - let _ = ApplicationSpecificNotice.setSecretChatInlineBotUsage(accountManager: strongSelf.context.sharedContext.accountManager).startStandalone() - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Conversation_SecretChatContextBotAlert, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - } - }) - } - } - } - } - - var isBot = false - if let peer = updatedChatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, peer.botInfo != nil { - isBot = true - } else { - isBot = false - } - self.chatDisplayNode.historyNode.chatHasBots = updatedChatPresentationInterfaceState.hasBots || isBot - - if let (updatedSearchQuerySuggestionState, updatedSearchQuerySuggestionSignal) = searchQuerySuggestionResultStateForChatInterfacePresentationState(updatedChatPresentationInterfaceState, context: context, currentQuery: self.searchQuerySuggestionState?.0) { - self.searchQuerySuggestionState?.1.dispose() - var inScope = true - var inScopeResult: ((ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?)? - self.searchQuerySuggestionState = (updatedSearchQuerySuggestionState, (updatedSearchQuerySuggestionSignal |> deliverOnMainQueue).startStrict(next: { [weak self] result in - if let strongSelf = self { - if Thread.isMainThread && inScope { - inScope = false - inScopeResult = result - } else { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedSearchQuerySuggestionResult { previousResult in - return result(previousResult) - } - }) - } - } - })) - inScope = false - if let inScopeResult = inScopeResult { - updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedSearchQuerySuggestionResult { previousResult in - return inScopeResult(previousResult) - } - } - } - - if let (updatedUrlPreviewUrl, updatedUrlPreviewSignal) = urlPreviewStateForInputText(updatedChatPresentationInterfaceState.interfaceState.composeInputState.inputText, context: self.context, currentQuery: self.urlPreviewQueryState?.0) { - self.urlPreviewQueryState?.1.dispose() - var inScope = true - var inScopeResult: ((TelegramMediaWebpage?) -> TelegramMediaWebpage?)? - let linkPreviews: Signal - if case let .peer(peerId) = self.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { - linkPreviews = interactiveChatLinkPreviewsEnabled(accountManager: self.context.sharedContext.accountManager, displayAlert: { [weak self] f in - if let strongSelf = self { - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Conversation_SecretLinkPreviewAlert, actions: [ - TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Yes, action: { - f.f(true) - }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_No, action: { - f.f(false) - })]), in: .window(.root)) - } - }) - } else { - var bannedEmbedLinks = false - if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.hasBannedPermission(.banEmbedLinks) != nil { - bannedEmbedLinks = true - } else if let group = self.presentationInterfaceState.renderedPeer?.peer as? TelegramGroup, group.hasBannedPermission(.banEmbedLinks) { - bannedEmbedLinks = true - } - if bannedEmbedLinks { - linkPreviews = .single(false) - } else { - linkPreviews = .single(true) - } - } - let filteredPreviewSignal = linkPreviews - |> take(1) - |> mapToSignal { value -> Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError> in - if value { - return updatedUrlPreviewSignal - } else { - return .single({ _ in return nil }) - } - } - - self.urlPreviewQueryState = (updatedUrlPreviewUrl, (filteredPreviewSignal |> deliverOnMainQueue).startStrict(next: { [weak self] (result) in - if let strongSelf = self { - if Thread.isMainThread && inScope { - inScope = false - inScopeResult = result - } else { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - if let updatedUrlPreviewUrl = updatedUrlPreviewUrl, let webpage = result($0.urlPreview?.webPage) { - let updatedPreview = ChatPresentationInterfaceState.UrlPreview( - url: updatedUrlPreviewUrl, - webPage: webpage, - positionBelowText: $0.urlPreview?.positionBelowText ?? true, - largeMedia: $0.urlPreview?.largeMedia - ) - return $0.updatedUrlPreview(updatedPreview) - } else { - return $0.updatedUrlPreview(nil) - } - }) - } - } - })) - inScope = false - if let inScopeResult = inScopeResult { - if let updatedUrlPreviewUrl = updatedUrlPreviewUrl, let webpage = inScopeResult(updatedChatPresentationInterfaceState.urlPreview?.webPage) { - let updatedPreview = ChatPresentationInterfaceState.UrlPreview( - url: updatedUrlPreviewUrl, - webPage: webpage, - positionBelowText: updatedChatPresentationInterfaceState.urlPreview?.positionBelowText ?? true, - largeMedia: updatedChatPresentationInterfaceState.urlPreview?.largeMedia - ) - updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedUrlPreview(updatedPreview) - } else { - updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedUrlPreview(nil) - } - } - } - - let isEditingMedia: Bool = updatedChatPresentationInterfaceState.editMessageState?.content != .plaintext - let editingUrlPreviewText: NSAttributedString? = isEditingMedia ? nil : updatedChatPresentationInterfaceState.interfaceState.editMessage?.inputState.inputText - if let (updatedEditingUrlPreviewUrl, updatedEditingUrlPreviewSignal) = urlPreviewStateForInputText(editingUrlPreviewText, context: self.context, currentQuery: self.editingUrlPreviewQueryState?.0) { - self.editingUrlPreviewQueryState?.1.dispose() - var inScope = true - var inScopeResult: ((TelegramMediaWebpage?) -> TelegramMediaWebpage?)? - self.editingUrlPreviewQueryState = (updatedEditingUrlPreviewUrl, (updatedEditingUrlPreviewSignal |> deliverOnMainQueue).startStrict(next: { [weak self] result in - if let strongSelf = self { - if Thread.isMainThread && inScope { - inScope = false - inScopeResult = result - } else { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - if let updatedEditingUrlPreviewUrl = updatedEditingUrlPreviewUrl, let webpage = result($0.editingUrlPreview?.webPage) { - let updatedPreview = ChatPresentationInterfaceState.UrlPreview( - url: updatedEditingUrlPreviewUrl, - webPage: webpage, - positionBelowText: $0.editingUrlPreview?.positionBelowText ?? true, - largeMedia: $0.editingUrlPreview?.largeMedia - ) - return $0.updatedEditingUrlPreview(updatedPreview) - } else { - return $0.updatedEditingUrlPreview(nil) - } - }) - } - } - })) - inScope = false - if let inScopeResult = inScopeResult { - if let updatedEditingUrlPreviewUrl = updatedEditingUrlPreviewUrl, let webpage = inScopeResult(updatedChatPresentationInterfaceState.editingUrlPreview?.webPage) { - let updatedPreview = ChatPresentationInterfaceState.UrlPreview( - url: updatedEditingUrlPreviewUrl, - webPage: webpage, - positionBelowText: updatedChatPresentationInterfaceState.editingUrlPreview?.positionBelowText ?? true, - largeMedia: updatedChatPresentationInterfaceState.editingUrlPreview?.largeMedia - ) - updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedEditingUrlPreview(updatedPreview) - } else { - updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedEditingUrlPreview(nil) - } - } - } - - if let replyMessageId = updatedChatPresentationInterfaceState.interfaceState.replyMessageSubject?.messageId { - if self.replyMessageState?.0 != replyMessageId { - self.replyMessageState?.1.dispose() - updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedReplyMessage(nil) - let disposable = MetaDisposable() - self.replyMessageState = (replyMessageId, disposable) - disposable.set((self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.Message(id: replyMessageId)) - |> deliverOnMainQueue).start(next: { [weak self] message in - guard let self else { - return - } - if message != self.presentationInterfaceState.replyMessage.flatMap(EngineMessage.init) { - self.updateChatPresentationInterfaceState(interactive: false, { presentationInterfaceState in - return presentationInterfaceState.updatedReplyMessage(message?._asMessage()) - }) - } - })) - } - } else { - if let replyMessageState = self.replyMessageState { - self.replyMessageState = nil - replyMessageState.1.dispose() - updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedReplyMessage(nil) - } - } - - if let updated = self.updateSearch(updatedChatPresentationInterfaceState) { - updatedChatPresentationInterfaceState = updated - } - - let recordingActivityValue: ChatRecordingActivity - if let mediaRecordingState = updatedChatPresentationInterfaceState.inputTextPanelState.mediaRecordingState { - switch mediaRecordingState { - case .audio: - recordingActivityValue = .voice - case .video(ChatVideoRecordingStatus.recording, _): - recordingActivityValue = .instantVideo - default: - recordingActivityValue = .none - } - } else { - recordingActivityValue = .none - } - if recordingActivityValue != self.recordingActivityValue { - self.recordingActivityValue = recordingActivityValue - self.recordingActivityPromise.set(recordingActivityValue) - } - - if (self.presentationInterfaceState.interfaceState.selectionState == nil) != (updatedChatPresentationInterfaceState.interfaceState.selectionState == nil) { - self.isSelectingMessagesUpdated?(updatedChatPresentationInterfaceState.interfaceState.selectionState != nil) - self.updateNextChannelToReadVisibility() - } - - self.presentationInterfaceState = updatedChatPresentationInterfaceState - - self.updateSlowmodeStatus() - - switch updatedChatPresentationInterfaceState.inputMode { - case .media: - break - default: - self.chatDisplayNode.collapseInput() - } - - if self.isNodeLoaded { - self.chatDisplayNode.updateChatPresentationInterfaceState(updatedChatPresentationInterfaceState, transition: transition, interactive: interactive, completion: completion) - } else { - completion(.immediate) - } - - let updatedServiceTasks = serviceTasksForChatPresentationIntefaceState(context: self.context, chatPresentationInterfaceState: updatedChatPresentationInterfaceState, updateState: { [weak self] f in - guard let strongSelf = self else { - return - } - strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, f) - - //strongSelf.chatDisplayNode.updateChatPresentationInterfaceState(f(strongSelf.chatDisplayNode.chatPresentationInterfaceState), transition: transition, interactive: false, completion: { _ in }) - }) - for (id, begin) in updatedServiceTasks { - if self.stateServiceTasks[id] == nil { - self.stateServiceTasks[id] = begin() - } - } - var removedServiceTaskIds: [AnyHashable] = [] - for (id, _) in self.stateServiceTasks { - if updatedServiceTasks[id] == nil { - removedServiceTaskIds.append(id) - } - } - for id in removedServiceTaskIds { - self.stateServiceTasks.removeValue(forKey: id)?.dispose() - } - - if let button = leftNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, subject: self.subject, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.leftNavigationButton, target: self, selector: #selector(self.leftNavigationButtonAction)) { - if self.leftNavigationButton != button { - var animated = transition.isAnimated - if let currentButton = self.leftNavigationButton?.action, currentButton == button.action { - animated = false - } - animated = false - self.navigationItem.setLeftBarButton(button.buttonItem, animated: animated) - self.leftNavigationButton = button - } - } else if let _ = self.leftNavigationButton { - self.navigationItem.setLeftBarButton(nil, animated: transition.isAnimated) - self.leftNavigationButton = nil - } - - if let button = rightNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.rightNavigationButton, target: self, selector: #selector(self.rightNavigationButtonAction), chatInfoNavigationButton: self.chatInfoNavigationButton, moreInfoNavigationButton: self.moreInfoNavigationButton) { - if self.rightNavigationButton != button { - var animated = transition.isAnimated - if let currentButton = self.rightNavigationButton?.action, currentButton == button.action { - animated = false - } - if case .replyThread = self.chatLocation { - animated = false - } - self.navigationItem.setRightBarButton(button.buttonItem, animated: animated) - self.rightNavigationButton = button - } - } else if let _ = self.rightNavigationButton { - self.navigationItem.setRightBarButton(nil, animated: transition.isAnimated) - self.rightNavigationButton = nil - } - - if let controllerInteraction = self.controllerInteraction { - if updatedChatPresentationInterfaceState.interfaceState.selectionState != controllerInteraction.selectionState { - controllerInteraction.selectionState = updatedChatPresentationInterfaceState.interfaceState.selectionState - let isBlackout = controllerInteraction.selectionState != nil - let previousCompletion = completion - completion = { [weak self] transition in - previousCompletion(transition) - (self?.navigationController as? NavigationController)?.updateMasterDetailsBlackout(isBlackout ? .master : nil, transition: transition) - } - self.updateItemNodesSelectionStates(animated: transition.isAnimated) - } - } - - if saveInterfaceState { - self.saveInterfaceState(includeScrollState: false) - } - - if let navigationController = self.navigationController as? NavigationController, isTopmostChatController(self) { - var voiceChatOverlayController: VoiceChatOverlayController? - for controller in navigationController.globalOverlayControllers { - if let controller = controller as? VoiceChatOverlayController { - voiceChatOverlayController = controller - break - } - } - - if let controller = voiceChatOverlayController { - controller.updateVisibility() - } - } - - if let currentMenuWebAppController = self.currentMenuWebAppController, !self.presentationInterfaceState.showWebView { - self.currentMenuWebAppController = nil - if let currentMenuWebAppController = currentMenuWebAppController as? AttachmentController { - currentMenuWebAppController.ensureUnfocused = false - } - currentMenuWebAppController.dismiss(animated: true, completion: nil) - } - - self.presentationInterfaceStatePromise.set(self.presentationInterfaceState) + func updateChatPresentationInterfaceState(transition: ContainedViewLayoutTransition, interactive: Bool, saveInterfaceState: Bool = false, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) { + updateChatPresentationInterfaceStateImpl( + selfController: self, + transition: transition, + interactive: interactive, + saveInterfaceState: saveInterfaceState, + f, + completion: completion + ) } func updateItemNodesSelectionStates(animated: Bool) { @@ -14013,6 +13551,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } completion(controller, controller.mediaPickerContext) strongSelf.controllerNavigationDisposable.set(nil) + + if bot.flags.contains(.notActivated) { + let alertController = webAppTermsAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, bot: bot, completion: { [weak self] allowWrite in + guard let self else { + return + } + if bot.flags.contains(.showInSettingsDisclaimer) { + let _ = self.context.engine.messages.acceptAttachMenuBotDisclaimer(botId: bot.peer.id).startStandalone() + } + let _ = (self.context.engine.messages.addBotToAttachMenu(botId: bot.peer.id, allowWrite: allowWrite) + |> deliverOnMainQueue).startStandalone(error: { _ in + }, completed: { [weak controller] in + controller?.refresh() + }) + }, + dismissed: { + strongSelf.attachmentController?.dismiss(animated: true) + }) + strongSelf.present(alertController, in: .window(.root)) + } default: break } @@ -15011,7 +14569,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case let .textMention(mention): switch action { case .tap: - strongSelf.controllerInteraction?.openPeerMention(mention) + strongSelf.controllerInteraction?.openPeerMention(mention, nil) case .longTap: strongSelf.controllerInteraction?.longTap(.mention(mention), nil) } @@ -15104,7 +14662,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case let .textMention(mention): switch action { case .tap: - strongSelf.controllerInteraction?.openPeerMention(mention) + strongSelf.controllerInteraction?.openPeerMention(mention, nil) case .longTap: strongSelf.controllerInteraction?.longTap(.mention(mention), nil) } @@ -15218,7 +14776,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case let .textMention(mention): switch action { case .tap: - strongSelf.controllerInteraction?.openPeerMention(mention) + strongSelf.controllerInteraction?.openPeerMention(mention, nil) case .longTap: strongSelf.controllerInteraction?.longTap(.mention(mention), nil) } @@ -17034,7 +16592,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let navigationController = strongSelf.effectiveNavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: messageLocation.messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil) })) + var quote: String? + if case let .id(_, params) = messageLocation { + quote = params.quote + } + + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: messageLocation.messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: quote), timecode: nil) })) } }) completion?() @@ -17532,7 +17095,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - func openPeerMention(_ name: String, navigation: ChatControllerInteractionNavigateToPeer = .default, sourceMessageId: MessageId? = nil) { + func openPeerMention(_ name: String, navigation: ChatControllerInteractionNavigateToPeer = .default, sourceMessageId: MessageId? = nil, progress: Promise? = nil) { let _ = self.presentVoiceMessageDiscardAlert(action: { let disposable: MetaDisposable if let resolvePeerByNameDisposable = self.resolvePeerByNameDisposable { @@ -17542,23 +17105,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.resolvePeerByNameDisposable = disposable } var resolveSignal = self.context.engine.peers.resolvePeerByName(name: name, ageLimit: 10) - |> mapToSignal { result -> Signal in - guard case let .result(result) = result else { - return .complete() - } - return .single(result) - } var cancelImpl: (() -> Void)? let presentationData = self.presentationData let progressSignal = Signal { [weak self] subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - self?.present(controller, in: .window(.root)) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() + if progress != nil { + return ActionDisposable { + } + } else { + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + self?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } } } } @@ -17576,22 +17138,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.resolvePeerByNameDisposable?.set(nil) } disposable.set((resolveSignal - |> take(1) - |> mapToSignal { peer -> Signal in - return .single(peer?._asPeer()) - } - |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self { - if let peer = peer { + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let self else { + return + } + switch result { + case .progress: + progress?.set(.single(true)) + case let .result(peer): + progress?.set(.single(false)) + + if let peer { var navigation = navigation if case .default = navigation { - if let peer = peer as? TelegramUser, peer.botInfo != nil { + if case let .user(user) = peer, user.botInfo != nil { navigation = .chat(textInputState: nil, subject: nil, peekData: nil) } } - strongSelf.openResolved(result: .peer(peer, navigation), sourceMessageId: sourceMessageId) + self.openResolved(result: .peer(peer._asPeer(), navigation), sourceMessageId: sourceMessageId) } else { - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } } })) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index dff4f1438f..70225da376 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -566,7 +566,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { mappedQuote = EngineMessageReplyQuote(text: quote, entities: [], media: nil) } - attributes.append(ReplyMessageAttribute(messageId: replyMessage.id, threadMessageId: nil, quote: mappedQuote)) + attributes.append(ReplyMessageAttribute(messageId: replyMessage.id, threadMessageId: nil, quote: mappedQuote, isQuote: mappedQuote != nil)) } let message = Message( diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift index 408ec77ba8..1386845703 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift @@ -506,7 +506,11 @@ func detectUrls(_ inputText: NSAttributedString?) -> [String] { return detectedUrls } -func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: AccountContext, currentQuery: String?) -> (String?, Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError>)? { +struct UrlPreviewState { + var detectedUrl: String +} + +func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: AccountContext, currentQuery: UrlPreviewState?) -> (UrlPreviewState?, Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError>)? { guard let _ = inputText else { if currentQuery != nil { return (nil, .single({ _ in return nil })) @@ -516,9 +520,9 @@ func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: Acco } if let _ = dataDetector { let detectedUrl = detectUrls(inputText).first - if detectedUrl != currentQuery { + if detectedUrl != currentQuery?.detectedUrl { if let detectedUrl = detectedUrl { - return (detectedUrl, webpagePreview(account: context.account, url: detectedUrl) + return (UrlPreviewState(detectedUrl: detectedUrl), webpagePreview(account: context.account, url: detectedUrl) |> mapToSignal { result -> Signal in guard case let .result(result) = result else { return .complete() diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 2ce05e2c0d..82aa0d7393 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -470,6 +470,46 @@ final class ChatTextViewForOverlayContent: UIView, ChatInputPanelViewForOverlayC } } +private func makeTextInputTheme(context: AccountContext, interfaceState: ChatPresentationInterfaceState) -> ChatInputTextView.Theme { + let lineStyle: ChatInputTextView.Theme.Quote.LineStyle + let authorNameColor: UIColor + + if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = peer.info, let nameColor = peer.nameColor { + let colors = context.peerNameColors.get(nameColor) + authorNameColor = colors.main + + if let secondary = colors.secondary, let tertiary = colors.tertiary { + lineStyle = .tripleDashed(mainColor: colors.main, secondaryColor: secondary, tertiaryColor: tertiary) + } else if let secondary = colors.secondary { + lineStyle = .doubleDashed(mainColor: colors.main, secondaryColor: secondary) + } else { + lineStyle = .solid(color: colors.main) + } + } else if let accountPeerColor = interfaceState.accountPeerColor { + authorNameColor = interfaceState.theme.list.itemAccentColor + + switch accountPeerColor.style { + case .solid: + lineStyle = .solid(color: authorNameColor) + case .doubleDashed: + lineStyle = .doubleDashed(mainColor: authorNameColor, secondaryColor: .clear) + case .tripleDashed: + lineStyle = .tripleDashed(mainColor: authorNameColor, secondaryColor: .clear, tertiaryColor: .clear) + } + } else { + lineStyle = .solid(color: interfaceState.theme.list.itemAccentColor) + authorNameColor = interfaceState.theme.list.itemAccentColor + } + + return ChatInputTextView.Theme( + quote: ChatInputTextView.Theme.Quote( + background: authorNameColor.withMultipliedAlpha(interfaceState.theme.overallDarkAppearance ? 0.2 : 0.1), + foreground: authorNameColor, + lineStyle: lineStyle + ) + ) +} + class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, ChatInputTextNodeDelegate { let clippingNode: ASDisplayNode var textPlaceholderNode: ImmediateTextNode @@ -1044,61 +1084,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch var tintColor: UIColor = .blue var baseFontSize: CGFloat = 17.0 var keyboardAppearance: UIKeyboardAppearance = UIKeyboardAppearance.default - if let presentationInterfaceState = self.presentationInterfaceState { - var lineStyle: ChatInputTextView.Theme.Quote.LineStyle = .solid - let authorNameColor: UIColor - let dashSecondaryColor: UIColor? - let dashTertiaryColor: UIColor? - - if let _ = self.context, let peer = presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = peer.info, let nameColor = peer.nameColor { - let _ = nameColor - - lineStyle = .solid - authorNameColor = presentationInterfaceState.theme.list.itemAccentColor - dashSecondaryColor = nil - dashTertiaryColor = nil - - /*let colors = context.peerNameColors.get(nameColor) - - authorNameColor = colors.main - dashSecondaryColor = colors.secondary - dashTertiaryColor = colors.tertiary - - if dashSecondaryColor != nil { - lineStyle = .doubleDashed - } else { - lineStyle = .solid - }*/ - } else if let accountPeerColor = presentationInterfaceState.accountPeerColor { - switch accountPeerColor.style { - case .solid: - lineStyle = .solid - case .doubleDashed: - lineStyle = .doubleDashed - case .tripleDashed: - lineStyle = .tripleDashed - } - - authorNameColor = presentationInterfaceState.theme.list.itemAccentColor - dashSecondaryColor = .clear - dashTertiaryColor = nil - } else { - lineStyle = .solid - authorNameColor = presentationInterfaceState.theme.list.itemAccentColor - dashSecondaryColor = nil - dashTertiaryColor = nil - } - - let _ = dashSecondaryColor - let _ = dashTertiaryColor - - textInputNode.textView.theme = ChatInputTextView.Theme( - quote: ChatInputTextView.Theme.Quote( - background: authorNameColor.withMultipliedAlpha(presentationInterfaceState.theme.overallDarkAppearance ? 0.2 : 0.1), - foreground: authorNameColor, - lineStyle: lineStyle - ) - ) + if let context = self.context, let presentationInterfaceState = self.presentationInterfaceState { + textInputNode.textView.theme = makeTextInputTheme(context: context, interfaceState: presentationInterfaceState) textColor = presentationInterfaceState.theme.chat.inputPanel.inputTextColor tintColor = presentationInterfaceState.theme.list.itemAccentColor @@ -1674,25 +1661,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch textInputNode.tintColorDidChange() } - if let textInputNode = self.textInputNode { - var lineStyle: ChatInputTextView.Theme.Quote.LineStyle = .solid - if let accountPeerColor = interfaceState.accountPeerColor { - switch accountPeerColor.style { - case .solid: - lineStyle = .solid - case .doubleDashed: - lineStyle = .doubleDashed - case .tripleDashed: - lineStyle = .tripleDashed - } - } - textInputNode.textView.theme = ChatInputTextView.Theme( - quote: ChatInputTextView.Theme.Quote( - background: interfaceState.theme.list.itemAccentColor.withMultipliedAlpha(interfaceState.theme.overallDarkAppearance ? 0.2 : 0.1), - foreground: interfaceState.theme.list.itemAccentColor, - lineStyle: lineStyle - ) - ) + if let textInputNode = self.textInputNode, let context = self.context { + textInputNode.textView.theme = makeTextInputTheme(context: context, interfaceState: interfaceState) } let keyboardAppearance = interfaceState.theme.rootController.keyboardColor.keyboardAppearance @@ -2849,8 +2819,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch var hasTracking = false var hasTrackingView = false - if textInputNode.selectedRange.length == 0 && textInputNode.selectedRange.location > 0 { - let selectedSubstring = textInputNode.textView.attributedText.attributedSubstring(from: NSRange(location: 0, length: textInputNode.selectedRange.location)) + if textInputNode.selectedRange.length == 0, textInputNode.selectedRange.location > 0, let attributedText = textInputNode.textView.attributedText { + let selectedSubstring = attributedText.attributedSubstring(from: NSRange(location: 0, length: textInputNode.selectedRange.location)) if let lastCharacter = selectedSubstring.string.last, String(lastCharacter).isSingleEmoji { let queryLength = (String(lastCharacter) as NSString).length if selectedSubstring.attribute(ChatTextInputAttributes.customEmoji, at: selectedSubstring.length - queryLength, effectiveRange: nil) == nil { diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift index 66c579b8d6..888cc24934 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift @@ -459,23 +459,12 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection } chatsNode.updateState { state in var state = state - if "".isEmpty { - if !state.selectedAdditionalCategoryIds.contains(id) { - for id in state.selectedAdditionalCategoryIds { - removedTokenIds.append(id) - state.selectedAdditionalCategoryIds.remove(id) - } - state.selectedAdditionalCategoryIds.insert(id) - addedToken = categoryToken - } + if state.selectedAdditionalCategoryIds.contains(id) { + state.selectedAdditionalCategoryIds.remove(id) + removedTokenIds.append(id) } else { - if state.selectedAdditionalCategoryIds.contains(id) { - state.selectedAdditionalCategoryIds.remove(id) - removedTokenIds.append(id) - } else { - state.selectedAdditionalCategoryIds.insert(id) - addedToken = categoryToken - } + state.selectedAdditionalCategoryIds.insert(id) + addedToken = categoryToken } return state diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 0f00dc700b..6a0ef98092 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -75,7 +75,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu return false } }, openPeer: { _, _, _, _ in - }, openPeerMention: { _ in + }, openPeerMention: { _, _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in }, updateMessageReaction: { _, _ in diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift index 760f6f41d1..59d0c0c0c0 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift @@ -9,12 +9,13 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem { case text(String) case badge(String, UIColor) case semitransparentBadge(String, UIColor) + case titleBadge(String, UIColor) var text: String { switch self { case .none: return "" - case let .text(text), let .badge(text, _), let .semitransparentBadge(text, _): + case let .text(text), let .badge(text, _), let .semitransparentBadge(text, _), let .titleBadge(text, _): return text } } @@ -23,7 +24,7 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem { switch self { case .none, .text: return nil - case let .badge(_, color), let .semitransparentBadge(_, color): + case let .badge(_, color), let .semitransparentBadge(_, color), let .titleBadge(_, color): return color } } @@ -146,6 +147,9 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { } else if case .badge = item.label { labelColorValue = presentationData.theme.list.itemCheckColors.foregroundColor labelFont = Font.regular(15.0) + } else if case .titleBadge = item.label { + labelColorValue = presentationData.theme.list.itemCheckColors.foregroundColor + labelFont = Font.medium(11.0) } else { labelColorValue = presentationData.theme.list.itemSecondaryTextColor labelFont = titleFont @@ -178,7 +182,7 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { if previousItem?.text != item.text { self.iconNode.image = nil self.iconDisposable.set((iconSignal - |> deliverOnMainQueue).startStrict(next: { [weak self] icon in + |> deliverOnMainQueue).startStrict(next: { [weak self] icon in if let self { self.iconNode.image = icon } @@ -217,6 +221,13 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { if self.labelBadgeNode.supernode == nil { self.insertSubnode(self.labelBadgeNode, belowSubnode: self.labelNode) } + } else if case let .titleBadge(text, badgeColor) = item.label, !text.isEmpty { + if previousItem?.label.badgeColor != badgeColor { + self.labelBadgeNode.image = generateFilledRoundedRectImage(size: CGSize(width: 16.0, height: 16.0), cornerRadius: 5.0, color: badgeColor)?.stretchableImage(withLeftCapWidth: 6, topCapHeight: 6) + } + if self.labelBadgeNode.supernode == nil { + self.insertSubnode(self.labelBadgeNode, belowSubnode: self.labelNode) + } } else { self.labelBadgeNode.removeFromSupernode() } @@ -230,11 +241,18 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { labelFrame = CGRect(origin: CGPoint(x: width - rightInset - badgeWidth + (badgeWidth - labelSize.width) / 2.0, y: floor((height - labelSize.height) / 2.0)), size: labelSize) } else if case .badge = item.label { labelFrame = CGRect(origin: CGPoint(x: width - rightInset - badgeWidth + (badgeWidth - labelSize.width) / 2.0, y: floor((height - labelSize.height) / 2.0)), size: labelSize) + } else if case .titleBadge = item.label { + labelFrame = CGRect(origin: CGPoint(x: textFrame.maxX + 10.0, y: floor((height - labelSize.height) / 2.0) + 1.0), size: labelSize) } else { labelFrame = CGRect(origin: CGPoint(x: width - rightInset - labelSize.width, y: 12.0), size: labelSize) } - let labelBadgeNodeFrame = CGRect(origin: CGPoint(x: width - rightInset - badgeWidth, y: floorToScreenPixels(labelFrame.midY - badgeDiameter / 2.0)), size: CGSize(width: badgeWidth, height: badgeDiameter)) + let labelBadgeNodeFrame: CGRect + if case .titleBadge = item.label { + labelBadgeNodeFrame = labelFrame.insetBy(dx: -4.0, dy: -2.0 + UIScreenPixel) + } else { + labelBadgeNodeFrame = CGRect(origin: CGPoint(x: width - rightInset - badgeWidth, y: floorToScreenPixels(labelFrame.midY - badgeDiameter / 2.0)), size: CGSize(width: badgeWidth, height: badgeDiameter)) + } self.activateArea.accessibilityLabel = item.text self.activateArea.accessibilityValue = item.label.text diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index cf8074299d..65d7a6cff0 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -13,6 +13,7 @@ import TelegramNotices import AccountUtils import DeviceAccess import PeerInfoVisualMediaPaneNode +import PhotoResources enum PeerInfoUpdatingAvatar { case none @@ -494,20 +495,56 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, let botsKey = ValueBoxKey(length: 8) botsKey.setInt64(0, value: 0) + + var iconLoaded: [EnginePeer.Id: Bool] = [:] let bots = context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: Namespaces.CachedItemCollection.attachMenuBots, id: botsKey)) |> mapToSignal { entry -> Signal<[AttachMenuBot], NoError> in let bots: [AttachMenuBots.Bot] = entry?.get(AttachMenuBots.self)?.bots ?? [] return context.engine.data.subscribe( EngineDataMap(bots.map(\.peerId).map(TelegramEngine.EngineData.Item.Peer.Peer.init)) ) - |> map { peersMap -> [AttachMenuBot] in - var result: [AttachMenuBot] = [] + |> mapToSignal { peersMap -> Signal<[AttachMenuBot], NoError> in + var result: [Signal] = [] for bot in bots { if let maybePeer = peersMap[bot.peerId], let peer = maybePeer { - result.append(AttachMenuBot(peer: peer, shortName: bot.name, icons: bot.icons, peerTypes: bot.peerTypes, flags: bot.flags)) + let resultBot = AttachMenuBot(peer: peer, shortName: bot.name, icons: bot.icons, peerTypes: bot.peerTypes, flags: bot.flags) + if bot.flags.contains(.showInSettings) { + if let peer = PeerReference(peer._asPeer()), let icon = bot.icons[.iOSSettingsStatic] { + let fileReference: FileMediaReference = .attachBot(peer: peer, media: icon) + let signal: Signal + if let _ = iconLoaded[peer.id] { + signal = .single(resultBot) + } else { + signal = .single(nil) + |> then( + preloadedBotIcon(account: context.account, fileReference: fileReference) + |> filter { $0 } + |> map { _ -> AttachMenuBot? in + return resultBot + } + |> afterNext { _ in + iconLoaded[peer.id] = true + } + ) + } + result.append(signal) + } else { + result.append(.single(resultBot)) + } + } } } - return result + return combineLatest(result) + |> map { bots in + var result: [AttachMenuBot] = [] + for bot in bots { + if let bot { + result.append(bot) + } + } + return result + } + |> distinctUntilChanged } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index bd89764d6b..afcbe8364a 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -818,25 +818,24 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p var appIndex = 1000 if let settings = data.globalSettings { for bot in settings.bots { - if bot.flags.contains(.showInSettings) { - let iconSignal: Signal - if let peer = PeerReference(bot.peer._asPeer()), let icon = bot.icons[.iOSSettingsStatic] { - let fileReference: FileMediaReference = .attachBot(peer: peer, media: icon) - iconSignal = instantPageImageFile(account: context.account, userLocation: .other, fileReference: fileReference, fetched: true) - |> map { generator -> UIImage? in - let size = CGSize(width: 29.0, height: 29.0) - let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: .zero)) - return context?.generateImage() - } - let _ = freeMediaFileInteractiveFetched(account: context.account, userLocation: .other, fileReference: fileReference).startStandalone() - } else { - iconSignal = .single(UIImage(bundleImageName: "Settings/Menu/Websites")!) + let iconSignal: Signal + if let peer = PeerReference(bot.peer._asPeer()), let icon = bot.icons[.iOSSettingsStatic] { + let fileReference: FileMediaReference = .attachBot(peer: peer, media: icon) + iconSignal = instantPageImageFile(account: context.account, userLocation: .other, fileReference: fileReference, fetched: true) + |> map { generator -> UIImage? in + let size = CGSize(width: 29.0, height: 29.0) + let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: .zero)) + return context?.generateImage() } - items[.apps]!.append(PeerInfoScreenDisclosureItem(id: bot.peer.id.id._internalGetInt64Value(), text: bot.shortName, icon: nil, iconSignal: iconSignal, action: { - interaction.openBotApp(bot) - })) - appIndex += 1 + let _ = freeMediaFileInteractiveFetched(account: context.account, userLocation: .other, fileReference: fileReference).startStandalone() + } else { + iconSignal = .single(UIImage(bundleImageName: "Settings/Menu/Websites")!) } + let label: PeerInfoScreenDisclosureItem.Label = bot.flags.contains(.notActivated) || bot.flags.contains(.showInSettingsDisclaimer) ? .titleBadge(presentationData.strings.Settings_New, presentationData.theme.list.itemAccentColor) : .none + items[.apps]!.append(PeerInfoScreenDisclosureItem(id: bot.peer.id.id._internalGetInt64Value(), label: label, text: bot.shortName, icon: nil, iconSignal: iconSignal, action: { + interaction.openBotApp(bot) + })) + appIndex += 1 } } @@ -1621,7 +1620,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL } if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { - let colors = context.peerNameColors.get(data.peer?.nameColor ?? .blue) + let colors = context.peerNameColors.get(data.peer?.nameColor ?? .blue, dark: presentationData.theme.overallDarkAppearance) items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemNameColor, label: .semitransparentBadge(EnginePeer(channel).compactDisplayTitle, colors.main), text: "Channel Color", icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: { interaction.editingOpenNameColorSetup() })) @@ -2413,7 +2412,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return strongSelf.openMessage(id: message.id) }, openPeer: { [weak self] peer, navigation, _, _ in self?.openPeer(peerId: peer.id, navigation: navigation) - }, openPeerMention: { _ in + }, openPeerMention: { _, _ in }, openMessageContextMenu: { [weak self] message, _, node, frame, anyRecognizer, _ in guard let strongSelf = self, let node = node as? ContextExtractedContentContainingNode else { return @@ -4756,6 +4755,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let self else { return } + let showInstalledTooltip = !bot.flags.contains(.showInSettingsDisclaimer) if bot.flags.contains(.showInSettingsDisclaimer) { let _ = self.context.engine.messages.acceptAttachMenuBotDisclaimer(botId: bot.peer.id).startStandalone() } @@ -4763,7 +4763,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let _ = (self.context.engine.messages.addBotToAttachMenu(botId: bot.peer.id, allowWrite: allowWrite) |> deliverOnMainQueue).startStandalone(error: { _ in }, completed: { - proceed(true) + proceed(showInstalledTooltip) }) } else { proceed(false) @@ -8382,7 +8382,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if let previousController = navigationController.viewControllers.last as? ShareWithPeersScreen { previousController.dismiss() } - let controller = PremiumLimitScreen(context: self.context, subject: .storiesChannelBoost(peer: peer, boostSubject: .stories, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0), count: Int32(status.boosts), action: { [weak self] in + let controller = PremiumLimitScreen(context: self.context, subject: .storiesChannelBoost(peer: peer, boostSubject: .stories, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0, canBoostAgain: false), count: Int32(status.boosts), action: { [weak self] in UIPasteboard.general.string = link if let self { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 02a761a096..c76958656d 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1492,7 +1492,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { let controllerInteraction: ChatControllerInteraction controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in - return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in + return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _, _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _, _ in }, navigateToMessageStandalone: { _ in }, navigateToThreadMessage: { _, _, _ in @@ -1812,8 +1812,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSubject = .storiesWeekly case .storiesMonthly: mappedSubject = .storiesMonthly - case let .storiesChannelBoost(peer, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, myBoostCount): - mappedSubject = .storiesChannelBoost(peer: peer, boostSubject: .stories, isCurrent: isCurrent, level: level, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: link, myBoostCount: myBoostCount) + case let .storiesChannelBoost(peer, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, myBoostCount, canBoostAgain): + mappedSubject = .storiesChannelBoost(peer: peer, boostSubject: .stories, isCurrent: isCurrent, level: level, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: link, myBoostCount: myBoostCount, canBoostAgain: canBoostAgain) } return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, forceDark: forceDark, cancel: cancel, action: action) } diff --git a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift index 8b85559944..c55343b4e3 100644 --- a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift +++ b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift @@ -200,17 +200,17 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti } string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand), value: nsString!.substring(with: range), range: range) case .Pre: - string.addAttribute(NSAttributedString.Key.font, value: fixedFont, range: range) + addFontAttributes(range, .monospace) if nsString == nil { nsString = text as NSString } string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Pre), value: nsString!.substring(with: range), range: range) case .Code: - string.addAttribute(NSAttributedString.Key.font, value: fixedFont, range: range) + addFontAttributes(range, .monospace) if nsString == nil { nsString = text as NSString } - string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Pre), value: nsString!.substring(with: range), range: range) + string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Code), value: nsString!.substring(with: range), range: range) case .BlockQuote: addFontAttributes(range, .blockQuote) diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 93500cf246..6bc6826fa0 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -413,6 +413,32 @@ public final class WebAppController: ViewController, AttachmentContainable { }) }) + self.setupWebView() + } + + deinit { + self.placeholderDisposable?.dispose() + self.iconDisposable?.dispose() + self.keepAliveDisposable?.dispose() + self.paymentDisposable?.dispose() + + self.webView?.removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress)) + } + + override func didLoad() { + super.didLoad() + + guard let webView = self.webView else { + return + } + self.view.addSubview(webView) + webView.scrollView.insertSubview(self.topOverscrollNode.view, at: 0) + } + + func setupWebView() { + guard let controller = self.controller else { + return + } if let url = controller.url, controller.source != .menu { self.queryId = controller.queryId if let parsedUrl = URL(string: url) { @@ -433,7 +459,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } } else { if controller.source.isSimple { - let _ = (context.engine.messages.requestSimpleWebView(botId: controller.botId, url: nil, source: .settings, themeParams: generateWebAppThemeParams(presentationData.theme)) + let _ = (self.context.engine.messages.requestSimpleWebView(botId: controller.botId, url: nil, source: .settings, themeParams: generateWebAppThemeParams(presentationData.theme)) |> deliverOnMainQueue).start(next: { [weak self] result in guard let strongSelf = self else { return @@ -443,7 +469,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } }) } else { - let _ = (context.engine.messages.requestWebView(peerId: controller.peerId, botId: controller.botId, url: controller.url, payload: controller.payload, themeParams: generateWebAppThemeParams(presentationData.theme), fromMenu: controller.source == .menu, replyToMessageId: controller.replyToMessageId, threadId: controller.threadId) + let _ = (self.context.engine.messages.requestWebView(peerId: controller.peerId, botId: controller.botId, url: controller.url, payload: controller.payload, themeParams: generateWebAppThemeParams(presentationData.theme), fromMenu: controller.source == .menu, replyToMessageId: controller.replyToMessageId, threadId: controller.threadId) |> deliverOnMainQueue).start(next: { [weak self] result in guard let strongSelf = self else { return @@ -469,25 +495,6 @@ public final class WebAppController: ViewController, AttachmentContainable { } } - deinit { - self.placeholderDisposable?.dispose() - self.iconDisposable?.dispose() - self.keepAliveDisposable?.dispose() - self.paymentDisposable?.dispose() - - self.webView?.removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress)) - } - - override func didLoad() { - super.didLoad() - - guard let webView = self.webView else { - return - } - self.view.addSubview(webView) - webView.scrollView.insertSubview(self.topOverscrollNode.view, at: 0) - } - @objc fileprivate func mainButtonPressed() { if let mainButtonState = self.mainButtonState, !mainButtonState.isVisible || !mainButtonState.isEnabled { return @@ -1624,6 +1631,10 @@ public final class WebAppController: ViewController, AttachmentContainable { self.updateTabBarAlpha(1.0, .immediate) } + public func refresh() { + self.controllerNode.setupWebView() + } + public func requestDismiss(completion: @escaping () -> Void) { if self.controllerNode.needDismissConfirmation { let actionSheet = ActionSheetController(presentationData: self.presentationData) diff --git a/submodules/WebUI/Sources/WebAppTermsAlertController.swift b/submodules/WebUI/Sources/WebAppTermsAlertController.swift index bbd5d81864..2151785cc6 100644 --- a/submodules/WebUI/Sources/WebAppTermsAlertController.swift +++ b/submodules/WebUI/Sources/WebAppTermsAlertController.swift @@ -353,7 +353,8 @@ public func webAppTermsAlertController( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, bot: AttachMenuBot, - completion: @escaping (Bool) -> Void + completion: @escaping (Bool) -> Void, + dismissed: @escaping () -> Void = {} ) -> AlertController { let theme = defaultDarkColorPresentationTheme let presentationData: PresentationData @@ -369,6 +370,7 @@ public func webAppTermsAlertController( completion(true) dismissImpl?(true) }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + dismissed() dismissImpl?(true) })] @@ -382,6 +384,11 @@ public func webAppTermsAlertController( }) } let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) + controller.dismissed = { outside in + if outside { + dismissed() + } + } dismissImpl = { [weak controller] animated in if animated { controller?.dismissAnimated()