From 2364234f040f5412cb9c21dde4d0c35d020ae2bb Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 8 Sep 2023 21:58:28 +0400 Subject: [PATCH 01/11] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 7 +- .../Sources/AttachmentController.swift | 10 +-- .../Sources/AttachmentPanel.swift | 24 +++---- .../SecretMediaPreviewController.swift | 49 ++++++++----- .../TGMediaPickerGalleryInterfaceView.h | 2 + .../TGMediaPickerGalleryInterfaceView.m | 4 ++ .../Sources/LegacyMediaPickerGallery.swift | 4 +- .../Sources/MediaPickerTitleView.swift | 1 + .../Sources/RadialStatusIconContentNode.swift | 13 +++- .../Sources/RadialStatusNode.swift | 6 +- ...RadialStatusSecretTimeoutContentNode.swift | 46 +++++++++--- .../PendingMessageUploadedContent.swift | 16 +++-- .../Messages/AttachMenuBots.swift | 6 ++ .../ChatEntityKeyboardInputNode/BUILD | 2 +- .../Sources/ChatEntityKeyboardInputNode.swift | 3 +- .../Components/LegacyMessageInputPanel/BUILD | 2 + .../Sources/LegacyMessageInputPanel.swift | 70 +++++++++++++++++-- .../LegacyMessageInputPanelInputView/BUILD | 19 +++++ .../LegacyMessageInputPanelInputView.swift | 11 +++ .../Sources/MediaEditorScreen.swift | 1 + .../Sources/StoryPreviewComponent.swift | 1 + .../Sources/MessageInputPanelComponent.swift | 7 ++ .../StoryItemSetContainerComponent.swift | 1 + ...StoryItemSetContainerViewSendMessage.swift | 6 +- .../Sources/TextFieldComponent.swift | 24 ++++++- .../TelegramUI/Sources/ChatController.swift | 44 ++++++++++-- .../ChatInterfaceStateContextMenus.swift | 2 +- .../Sources/ChatMessageBubbleItemNode.swift | 5 +- .../PeerInfoScreenDisclosureItem.swift | 37 ++++++++-- .../Sources/PeerInfo/PeerInfoScreen.swift | 40 +++++++++-- .../Sources/SharedAccountContext.swift | 5 +- .../Sources/WebAppTermsAlertController.swift | 12 ++-- 32 files changed, 385 insertions(+), 95 deletions(-) create mode 100644 submodules/TelegramUI/Components/LegacyMessageInputPanelInputView/BUILD create mode 100644 submodules/TelegramUI/Components/LegacyMessageInputPanelInputView/Sources/LegacyMessageInputPanelInputView.swift diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index bcdd769bda..0f6e1d3e4f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9914,14 +9914,17 @@ Sorry for the inconvenience."; "Gallery.ViewOncePhotoTooltip" = "This photo can only be viewed once."; "Gallery.ViewOnceVideoTooltip" = "This video can only be viewed once."; -"WebApp.DisclaimerTitle" = "Warning"; +"WebApp.DisclaimerTitle" = "Terms of Use"; "WebApp.DisclaimerText" = "You are about to use a mini app operated by an independent party not affiliated with Telegram. You must agree to the Terms of Use of mini apps to continue."; -"WebApp.DisclaimerShortcutsText" = "**%@** shortcuts will be added in your attachment menu."; +"WebApp.DisclaimerShortcutsText" = "**%@** shortcut will be added in your attachment menu."; "WebApp.DisclaimerShortcutsSettingsText" = "**%@** shortcuts will be added in your attachment menu and Settings."; "WebApp.DisclaimerAgree" = "I agree to the [Terms of Use]()"; "WebApp.DisclaimerContinue" = "Continue"; "WebApp.Disclaimer_URL" = "https://telegram.org/tos/mini-apps"; +"WebApp.ShortcutsAdded" = "**%@** shortcut added in attachment menu."; +"WebApp.ShortcutsSettingsAdded" = "**%@** shortcut added in attachment menu and Settings."; + "WebApp.AllowWriteTitle" = "Allow Sending Messages?"; "WebApp.AllowWriteConfirmation" = "This will allow the bot **%@** to message you on Telegram."; diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index 04983504d4..eea8063153 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -19,7 +19,7 @@ public enum AttachmentButtonType: Equatable { case location case contact case poll - case app(EnginePeer, String, [AttachMenuBots.Bot.IconName: TelegramMediaFile]) + case app(AttachMenuBot) case gift case standalone @@ -55,8 +55,8 @@ public enum AttachmentButtonType: Equatable { } else { return false } - case let .app(lhsPeer, lhsTitle, lhsIcons): - if case let .app(rhsPeer, rhsTitle, rhsIcons) = rhs, lhsPeer == rhsPeer, lhsTitle == rhsTitle, lhsIcons == rhsIcons { + case let .app(lhsBot): + if case let .app(rhsBot) = rhs, lhsBot.peer.id == rhsBot.peer.id { return true } else { return false @@ -446,9 +446,9 @@ public class AttachmentController: ViewController { if let controller = self.controller { let _ = self.switchToController(controller.initialButton) - if case let .app(bot, _, _) = controller.initialButton { + if case let .app(bot) = controller.initialButton { if let index = controller.buttons.firstIndex(where: { - if case let .app(otherBot, _, _) = $0, otherBot.id == bot.id { + if case let .app(otherBot) = $0, otherBot.peer.id == bot.peer.id { return true } else { return false diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 8f55a0d317..766a54d4de 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -200,15 +200,15 @@ private final class AttachButtonComponent: CombinedComponent { case .gift: name = strings.Attachment_Gift imageName = "Chat/Attach Menu/Gift" - case let .app(peer, appName, appIcons): - botPeer = peer - name = appName + case let .app(bot): + botPeer = bot.peer + name = bot.shortName imageName = "" - if let file = appIcons[.iOSAnimated] { + if let file = bot.icons[.iOSAnimated] { animationFile = file - } else if let file = appIcons[.iOSStatic] { + } else if let file = bot.icons[.iOSStatic] { imageFile = file - } else if let file = appIcons[.default] { + } else if let file = bot.icons[.default] { imageFile = file } case .standalone: @@ -1142,10 +1142,10 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { } let type = self.buttons[i] - if case let .app(peer, _, iconFiles) = type { - for (name, file) in iconFiles { - if [.default, .iOSAnimated, .placeholder].contains(name) { - if self.iconDisposables[file.fileId] == nil, let peer = PeerReference(peer._asPeer()) { + if case let .app(bot) = type { + for (name, file) in bot.icons { + if [.default, .iOSAnimated, .iOSSettingsStatic, .placeholder].contains(name) { + if self.iconDisposables[file.fileId] == nil, let peer = PeerReference(bot.peer._asPeer()) { if case .placeholder = name { let account = self.context.account let path = account.postbox.mediaBox.cachedRepresentationCompletePath(file.resource.id, representation: CachedPreparedSvgRepresentation()) @@ -1214,8 +1214,8 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { accessibilityTitle = self.presentationData.strings.Attachment_Poll case .gift: accessibilityTitle = self.presentationData.strings.Attachment_Gift - case let .app(_, appName, _): - accessibilityTitle = appName + case let .app(bot): + accessibilityTitle = bot.shortName case .standalone: accessibilityTitle = "" } diff --git a/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift b/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift index cf96f5b519..b930b63cac 100644 --- a/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift +++ b/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift @@ -60,16 +60,20 @@ private final class SecretMediaPreviewControllerNode: GalleryControllerNode { private var validLayout: (ContainerViewLayout, CGFloat)? - var beginTimeAndTimeout: (Double, Double)? { + var beginTimeAndTimeout: (Double, Double, Bool)? { didSet { - if let (beginTime, timeout) = self.beginTimeAndTimeout { + if let (beginTime, timeout, isOutgoing) = self.beginTimeAndTimeout { var beginTime = beginTime if self.timeoutNode == nil { let timeoutNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5)) self.timeoutNode = timeoutNode let icon: RadialStatusNodeState.SecretTimeoutIcon let timeoutValue = Int32(timeout) - if timeoutValue == viewOnceTimeout { + + let state: RadialStatusNodeState + if timeoutValue == 0 && isOutgoing { + state = .staticTimeout + } else if timeoutValue == viewOnceTimeout { beginTime = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 if let image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/ViewOnce"), color: .white) { @@ -77,10 +81,11 @@ private final class SecretMediaPreviewControllerNode: GalleryControllerNode { } else { icon = .flame } + state = .secretTimeout(color: .white, icon: icon, beginTime: beginTime, timeout: timeout, sparks: isOutgoing ? false : true) } else { - icon = .flame + state = .secretTimeout(color: .white, icon: .flame, beginTime: beginTime, timeout: timeout, sparks: true) } - timeoutNode.transitionToState(.secretTimeout(color: .white, icon: icon, beginTime: beginTime, timeout: timeout, sparks: true), completion: {}) + timeoutNode.transitionToState(state, completion: {}) self.addSubnode(timeoutNode) timeoutNode.addTarget(self, action: #selector(self.statusTapGesture), forControlEvents: .touchUpInside) @@ -308,7 +313,7 @@ public final class SecretMediaPreviewController: ViewController { var hiddenItem: (MessageId, Media)? if let _ = index { if let message = strongSelf.messageView?.message, let media = mediaForMessage(message: message) { - var beginTimeAndTimeout: (Double, Double)? + var beginTimeAndTimeout: (Double, Double, Bool)? var videoDuration: Double? for media in message.media { if let file = media as? TelegramMediaFile { @@ -316,26 +321,31 @@ public final class SecretMediaPreviewController: ViewController { } } + let isOutgoing = !message.flags.contains(.Incoming) if let attribute = message.autoclearAttribute { strongSelf.currentNodeMessageIsViewOnce = attribute.timeout == viewOnceTimeout if let countdownBeginTime = attribute.countdownBeginTime { if let videoDuration = videoDuration, attribute.timeout != viewOnceTimeout { - beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, max(videoDuration, Double(attribute.timeout))) + beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, max(videoDuration, Double(attribute.timeout)), isOutgoing) } else { - beginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout)) + beginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout), isOutgoing) } + } else if isOutgoing { + beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, attribute.timeout != viewOnceTimeout ? 0.0 : Double(viewOnceTimeout), isOutgoing) } } else if let attribute = message.autoremoveAttribute { strongSelf.currentNodeMessageIsViewOnce = attribute.timeout == viewOnceTimeout if let countdownBeginTime = attribute.countdownBeginTime { if let videoDuration = videoDuration, attribute.timeout != viewOnceTimeout { - beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, max(videoDuration, Double(attribute.timeout))) + beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, max(videoDuration, Double(attribute.timeout)), isOutgoing) } else { - beginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout)) + beginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout), isOutgoing) } - } + } else if isOutgoing { + beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, attribute.timeout != viewOnceTimeout ? 0.0 : Double(viewOnceTimeout), isOutgoing) + } } if let file = media as? TelegramMediaFile { @@ -537,28 +547,34 @@ public final class SecretMediaPreviewController: ViewController { self._ready.set(ready |> map { true }) self.markMessageAsConsumedDisposable.set(self.context.engine.messages.markMessageContentAsConsumedInteractively(messageId: message.id).start()) } else { - var beginTimeAndTimeout: (Double, Double)? + var beginTimeAndTimeout: (Double, Double, Bool)? var videoDuration: Double? for media in message.media { if let file = media as? TelegramMediaFile { videoDuration = file.duration } } + + let isOutgoing = !message.flags.contains(.Incoming) if let attribute = message.autoclearAttribute { if let countdownBeginTime = attribute.countdownBeginTime { if let videoDuration = videoDuration, attribute.timeout != viewOnceTimeout { - beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, max(videoDuration, Double(attribute.timeout))) + beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, max(videoDuration, Double(attribute.timeout)), isOutgoing) } else { - beginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout)) + beginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout), isOutgoing) } + } else if isOutgoing { + beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, attribute.timeout != viewOnceTimeout ? 0.0 : Double(viewOnceTimeout), isOutgoing) } } else if let attribute = message.autoremoveAttribute { if let countdownBeginTime = attribute.countdownBeginTime { if let videoDuration = videoDuration, attribute.timeout != viewOnceTimeout { - beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, max(videoDuration, Double(attribute.timeout))) + beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, max(videoDuration, Double(attribute.timeout)), isOutgoing) } else { - beginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout)) + beginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout), isOutgoing) } + } else if isOutgoing { + beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, attribute.timeout != viewOnceTimeout ? 0.0 : Double(viewOnceTimeout), isOutgoing) } } @@ -617,6 +633,7 @@ public final class SecretMediaPreviewController: ViewController { location: .point(location, .top), displayDuration: .default, inset: 8.0, + cornerRadius: 8.0, shouldDismissOnTouch: { _, _ in return .ignore } diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h index 696f9c9d92..ba7f57225c 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h @@ -54,6 +54,8 @@ - (void)editorTransitionIn; - (void)editorTransitionOut; +- (void)onDismiss; + - (void)setTabBarUserInteractionEnabled:(bool)enabled; @end diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index 22c215165c..774db532df 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -1328,6 +1328,10 @@ [_captionMixin onAnimateOut]; } +- (void)onDismiss { + [_captionMixin onAnimateOut]; +} + - (void)setTransitionOutProgress:(CGFloat)transitionOutProgress manual:(bool)manual { [_captionMixin onAnimateOut]; diff --git a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift index 5856601071..b19e46b3ac 100644 --- a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift +++ b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift @@ -282,7 +282,9 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, dismissImpl() }) } - sheetController.sendSilently = { + sheetController.sendSilently = { [weak model] in + model?.interfaceView.onDismiss() + completed(item.asset, true, nil, { dismissImpl() }) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift b/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift index eb42c6da28..2cae7588f2 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift @@ -50,6 +50,7 @@ final class MediaPickerTitleView: UIView { transition.updateAlpha(node: self.arrowNode, alpha: self.segmentsHidden ? 1.0 : 0.0) transition.updateAlpha(node: self.segmentedControlNode, alpha: self.segmentsHidden ? 0.0 : 1.0) self.segmentedControlNode.isUserInteractionEnabled = !self.segmentsHidden + self.buttonNode.isUserInteractionEnabled = self.isEnabled && self.segmentsHidden } } } diff --git a/submodules/RadialStatusNode/Sources/RadialStatusIconContentNode.swift b/submodules/RadialStatusNode/Sources/RadialStatusIconContentNode.swift index 0d1510195a..794d1f3af1 100644 --- a/submodules/RadialStatusNode/Sources/RadialStatusIconContentNode.swift +++ b/submodules/RadialStatusNode/Sources/RadialStatusIconContentNode.swift @@ -21,7 +21,7 @@ private final class RadialStatusIconContentNodeParameters: NSObject { } final class RadialStatusIconContentNode: RadialStatusContentNode { - private let icon: RadialStatusIcon + let icon: RadialStatusIcon private var animationNode: FireIconNode? @@ -35,7 +35,7 @@ final class RadialStatusIconContentNode: RadialStatusContentNode { self.isOpaque = false if case .timeout = icon { - let animationNode = FireIconNode() + let animationNode = FireIconNode(animate: true) self.animationNode = animationNode self.addSubnode(animationNode) } @@ -44,7 +44,14 @@ final class RadialStatusIconContentNode: RadialStatusContentNode { override func layout() { super.layout() - self.animationNode?.frame = CGRect(x: 6.0, y: 2.0, width: 36.0, height: 36.0) + var factor: CGFloat = 0.75 + var offset: CGFloat = 0.0415 + if self.bounds.width < 30.0 { + factor = 1.0 + offset = 0.0 + } + let size = floorToScreenPixels(self.bounds.width * factor) + self.animationNode?.frame = CGRect(x: floorToScreenPixels((self.bounds.width - size) / 2.0), y: ceil(self.bounds.height * offset), width: size, height: size) } override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { diff --git a/submodules/RadialStatusNode/Sources/RadialStatusNode.swift b/submodules/RadialStatusNode/Sources/RadialStatusNode.swift index 1b7d5e95dd..2aa3acba77 100644 --- a/submodules/RadialStatusNode/Sources/RadialStatusNode.swift +++ b/submodules/RadialStatusNode/Sources/RadialStatusNode.swift @@ -224,7 +224,11 @@ public enum RadialStatusNodeState: Equatable { case .staticTimeout: return RadialStatusIconContentNode(icon: .timeout, synchronous: synchronous) case let .secretTimeout(color, icon, beginTime, timeout, sparks): - return RadialStatusSecretTimeoutContentNode(color: color, beginTime: beginTime, timeout: timeout, icon: icon, sparks: sparks) + var animate = true + if let current = current as? RadialStatusIconContentNode, case .timeout = current.icon { + animate = false + } + return RadialStatusSecretTimeoutContentNode(color: color, beginTime: beginTime, timeout: timeout, icon: icon, sparks: sparks, animate: animate) } } } diff --git a/submodules/RadialStatusNode/Sources/RadialStatusSecretTimeoutContentNode.swift b/submodules/RadialStatusNode/Sources/RadialStatusSecretTimeoutContentNode.swift index bd2ec3b6ff..03133297ea 100644 --- a/submodules/RadialStatusNode/Sources/RadialStatusSecretTimeoutContentNode.swift +++ b/submodules/RadialStatusNode/Sources/RadialStatusSecretTimeoutContentNode.swift @@ -29,13 +29,15 @@ private final class RadialStatusSecretTimeoutContentNodeParameters: NSObject { let progress: CGFloat let sparks: Bool let particles: [ContentParticle] + let alphaProgress: CGFloat - init(color: UIColor, icon: RadialStatusNodeState.SecretTimeoutIcon, progress: CGFloat, sparks: Bool, particles: [ContentParticle]) { + init(color: UIColor, icon: RadialStatusNodeState.SecretTimeoutIcon, progress: CGFloat, sparks: Bool, particles: [ContentParticle], alphaProgress: CGFloat) { self.color = color self.icon = icon self.progress = progress self.sparks = sparks self.particles = particles + self.alphaProgress = alphaProgress } } @@ -51,14 +53,17 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode { private let icon: RadialStatusNodeState.SecretTimeoutIcon private let sparks: Bool + private var animationBeginTime: Double? + private var progress: CGFloat = 0.0 + private var alphaProgress: CGFloat = 0.0 private var particles: [ContentParticle] = [] - private let animationNode = FireIconNode() + private var animationNode: FireIconNode? private var displayLink: CADisplayLink? - init(color: UIColor, beginTime: Double, timeout: Double, icon: RadialStatusNodeState.SecretTimeoutIcon, sparks: Bool) { + init(color: UIColor, beginTime: Double, timeout: Double, icon: RadialStatusNodeState.SecretTimeoutIcon, sparks: Bool, animate: Bool = true) { self.color = color self.beginTime = beginTime self.timeout = timeout @@ -85,7 +90,13 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode { self.displayLink?.add(to: RunLoop.main, forMode: .common) if case .flame = icon { - self.addSubnode(self.animationNode) + if !animate { + self.animationBeginTime = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + } + + let animationNode = FireIconNode(animate: animate) + self.animationNode = animationNode + self.addSubnode(animationNode) } } @@ -103,7 +114,7 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode { offset = 0.08 } let size = floorToScreenPixels(self.bounds.width * factor) - self.animationNode.frame = CGRect(x: floorToScreenPixels((self.bounds.width - size) / 2.0), y: ceil(self.bounds.height * offset), width: size, height: size) + self.animationNode?.frame = CGRect(x: floorToScreenPixels((self.bounds.width - size) / 2.0), y: ceil(self.bounds.height * offset), width: size, height: size) } override func animateOut(to: RadialStatusNodeState, completion: @escaping () -> Void) { @@ -132,12 +143,23 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode { return } + let absoluteTimestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + + let alphaProgress: CGFloat + if let animationBeginTime = self.animationBeginTime { + let fadeInDuration: Double = 0.4 + alphaProgress = max(0.0, min(1.0, (absoluteTimestamp - animationBeginTime) / fadeInDuration)) + } else { + alphaProgress = 1.0 + } + var progress = min(1.0, CGFloat((absoluteTimestamp - self.beginTime) / self.timeout)) if self.timeout == 0x7fffffff { progress = 0.0 } self.progress = progress + self.alphaProgress = alphaProgress if self.sparks { let lineWidth: CGFloat = 1.75 @@ -193,7 +215,7 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode { } override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { - return RadialStatusSecretTimeoutContentNodeParameters(color: self.color, icon: self.icon, progress: self.progress, sparks: self.sparks, particles: self.particles) + return RadialStatusSecretTimeoutContentNodeParameters(color: self.color, icon: self.icon, progress: self.progress, sparks: self.sparks, particles: self.particles, alphaProgress: self.alphaProgress) } @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { @@ -240,6 +262,8 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode { let endAngle: CGFloat = -CGFloat.pi / 2.0 + 2.0 * CGFloat.pi * parameters.progress if drawArc { + context.setAlpha(parameters.alphaProgress) + let path = CGMutablePath() path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true) context.addPath(path) @@ -248,7 +272,7 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode { for particle in parameters.particles { let size: CGFloat = 1.3 - context.setAlpha(particle.alpha) + context.setAlpha(particle.alpha * parameters.alphaProgress) context.fillEllipse(in: CGRect(origin: CGPoint(x: particle.position.x - size / 2.0, y: particle.position.y - size / 2.0), size: CGSize(width: size, height: size))) } } @@ -256,9 +280,13 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode { } final class FireIconNode: ManagedAnimationNode { - init() { + init(animate: Bool) { super.init(size: CGSize(width: 100.0, height: 100.0)) - self.trackTo(item: ManagedAnimationItem(source: .local("anim_autoremove_on"), frames: .range(startFrame: 0, endFrame: 120), duration: 2.0)) + if animate { + self.trackTo(item: ManagedAnimationItem(source: .local("anim_autoremove_on"), frames: .range(startFrame: 0, endFrame: 120), duration: 2.0)) + } else { + self.trackTo(item: ManagedAnimationItem(source: .local("anim_autoremove_on"), frames: .range(startFrame: 120, endFrame: 120), duration: 0.001)) + } } } diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index a53fd3bac3..8afb29f4c1 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -122,7 +122,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post } } } - return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: true, isGrouped: isGrouped, passFetchProgress: false, forceNoBigParts: false, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file) + return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: true, isGrouped: isGrouped, passFetchProgress: false, forceNoBigParts: false, peerId: peerId, messageId: messageId, text: text, attributes: attributes, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, file: file) } else { if forceReupload { let mediaReference: AnyMediaReference @@ -156,7 +156,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil, query: emojiSearchQuery), text), reuploadInfo: nil, cacheReferenceKey: nil))) } } else { - return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: passFetchProgress, forceNoBigParts: forceNoBigParts, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file) + return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: passFetchProgress, forceNoBigParts: forceNoBigParts, peerId: peerId, messageId: messageId, text: text, attributes: attributes, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, file: file) } } else if let contact = media as? TelegramMediaContact { let input = Api.InputMedia.inputMediaContact(phoneNumber: contact.phoneNumber, firstName: contact.firstName, lastName: contact.lastName, vcard: contact.vCardData ?? "") @@ -296,7 +296,7 @@ private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods: } #if DEBUG - if "".isEmpty { + if !"".isEmpty { return .single(.none) } #endif @@ -655,7 +655,7 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA return .file } -private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, forceNoBigParts: Bool, peerId: PeerId, messageId: MessageId?, text: String, attributes: [MessageAttribute], file: TelegramMediaFile) -> Signal { +private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, forceNoBigParts: Bool, peerId: PeerId, messageId: MessageId?, text: String, attributes: [MessageAttribute], autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?, autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?, file: TelegramMediaFile) -> Signal { return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, peerId: peerId, resource: file.resource, forceRefresh: forceReupload) |> mapToSignal { result -> Signal in var referenceKey: CachedSentMediaReferenceKey? @@ -663,6 +663,12 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili case let .media(media, key): if !forceReupload, let file = media as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference { var flags: Int32 = 0 + var ttlSeconds: Int32? + if let autoclearMessageAttribute = autoclearMessageAttribute { + flags |= 1 << 0 + ttlSeconds = autoclearMessageAttribute.timeout + } + for attribute in attributes { if let _ = attribute as? MediaSpoilerMessageAttribute { flags |= 1 << 2 @@ -671,7 +677,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili return .single(.progress(1.0)) |> then( - .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil))) + .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil))) ) } referenceKey = key diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift index 68a86ee647..ea393f6d7b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift @@ -18,7 +18,9 @@ public final class AttachMenuBots: Equatable, Codable { case `default` = 0 case iOSStatic case iOSAnimated + case iOSSettingsStatic case macOSAnimated + case macOSSettingsStatic case placeholder init?(string: String) { @@ -29,6 +31,10 @@ public final class AttachMenuBots: Equatable, Codable { self = .iOSStatic case "ios_animated": self = .iOSAnimated + case "ios_side_menu_static": + self = .iOSSettingsStatic + case "macos_side_menu_static": + self = .macOSSettingsStatic case "macos_animated": self = .macOSAnimated case "placeholder_static": diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD index 24e6e395b8..fa0c0f90a3 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD @@ -30,7 +30,6 @@ swift_library( "//submodules/UndoUI:UndoUI", "//submodules/ContextUI:ContextUI", "//submodules/GalleryUI:GalleryUI", - "//submodules/AttachmentTextInputPanelNode:AttachmentTextInputPanelNode", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramNotices:TelegramNotices", "//submodules/StickerPeekUI:StickerPeekUI", @@ -40,6 +39,7 @@ swift_library( "//submodules/FeaturedStickersScreen:FeaturedStickersScreen", "//submodules/StickerPackPreviewUI", "//submodules/TelegramUI/Components/EntityKeyboardGifContent:EntityKeyboardGifContent", + "//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView:LegacyMessageInputPanelInputView", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 720a374434..92cab8ba35 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -32,6 +32,7 @@ import FeaturedStickersScreen import Pasteboard import StickerPackPreviewUI import EntityKeyboardGifContent +import LegacyMessageInputPanelInputView public final class EmptyInputView: UIView, UIInputViewAudioFeedback { public var enableInputClicksWhenVisible: Bool { @@ -2080,7 +2081,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent } } -public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputView, UIInputViewAudioFeedback { +public final class EntityInputView: UIInputView, LegacyMessageInputPanelInputView, AttachmentTextInputPanelInputView, UIInputViewAudioFeedback { private let context: AccountContext public var insertText: ((NSAttributedString) -> Void)? diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD b/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD index 9ffc71d6c7..ce295914a0 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD @@ -21,7 +21,9 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/ContextUI", "//submodules/TooltipUI", + "//submodules/UndoUI", "//submodules/TelegramUI/Components/MessageInputPanelComponent", + "//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift index 8492d951f9..4728568f63 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift @@ -13,6 +13,8 @@ import MessageInputPanelComponent import TelegramPresentationData import ContextUI import TooltipUI +import LegacyMessageInputPanelInputView +import UndoUI public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { private let context: AccountContext @@ -20,7 +22,8 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { private let isScheduledMessages: Bool private let present: (ViewController) -> Void private let presentInGlobalOverlay: (ViewController) -> Void - + private let makeEntityInputView: () -> LegacyMessageInputPanelInputView? + private let state = ComponentState() private let inputPanelExternalState = MessageInputPanelComponent.ExternalState() private let inputPanel = ComponentView() @@ -31,6 +34,9 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { private let hapticFeedback = HapticFeedback() + private var inputView: LegacyMessageInputPanelInputView? + private var isEmojiKeyboardActive = false + private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, metrics: LayoutMetrics)? public init( @@ -38,13 +44,15 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { chatLocation: ChatLocation, isScheduledMessages: Bool, present: @escaping (ViewController) -> Void, - presentInGlobalOverlay: @escaping (ViewController) -> Void + presentInGlobalOverlay: @escaping (ViewController) -> Void, + makeEntityInputView: @escaping () -> LegacyMessageInputPanelInputView? ) { self.context = context self.chatLocation = chatLocation self.isScheduledMessages = isScheduledMessages self.present = present self.presentInGlobalOverlay = presentInGlobalOverlay + self.makeEntityInputView = makeEntityInputView super.init() @@ -98,6 +106,8 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { public func dismissInput() { if let view = self.inputPanel.view as? MessageInputPanelComponent.View { + self.isEmojiKeyboardActive = false + self.inputView = nil view.deactivateInput() } } @@ -171,8 +181,12 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { queryTypes: [.mention], alwaysDarkWhenHasText: false, resetInputContents: resetInputContents, - nextInputMode: { _ in - return .emoji + nextInputMode: { [weak self] _ in + if self?.isEmojiKeyboardActive == true { + return .text + } else { + return .emoji + } }, areVoiceMessagesAvailable: false, presentController: self.present, @@ -193,7 +207,11 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { myReaction: nil, likeAction: nil, likeOptionsAction: nil, - inputModeAction: nil, + inputModeAction: { [weak self] in + if let self { + self.toggleInputMode() + } + }, timeoutAction: self.chatLocation.peerId?.namespace == Namespaces.Peer.CloudUser && !self.isScheduledMessages ? { [weak self] sourceView, gesture in if let self { self.presentTimeoutSetup(sourceView: sourceView, gesture: gesture) @@ -217,6 +235,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { bottomInset: 0.0, isFormattingLocked: false, hideKeyboard: false, + customInputView: self.inputView, forceIsEditing: false, disabledPlaceholder: nil, isChannel: false, @@ -248,6 +267,47 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { return inputPanelSize.height - 8.0 } + private func toggleInputMode() { + self.isEmojiKeyboardActive = !self.isEmojiKeyboardActive + + if self.isEmojiKeyboardActive { + let inputView = self.makeEntityInputView() + inputView?.insertText = { [weak self] text in + if let self { + self.inputPanelExternalState.insertText(text) + } + } + inputView?.deleteBackwards = { [weak self] in + if let self { + self.inputPanelExternalState.deleteBackward() + } + } + inputView?.switchToKeyboard = { [weak self] in + if let self { + self.isEmojiKeyboardActive = false + self.inputView = nil + self.update(transition: .immediate) + } + } + inputView?.presentController = { [weak self] c in + if let self { + if !(c is UndoOverlayController) { + self.isEmojiKeyboardActive = false + if let view = self.inputPanel.view as? MessageInputPanelComponent.View { + view.deactivateInput(force: true) + } + } + self.present(c) + } + } + self.inputView = inputView + self.update(transition: .immediate) + } else { + self.inputView = nil + self.update(transition: .immediate) + } + } + private func presentTimeoutSetup(sourceView: UIView, gesture: ContextGesture?) { self.hapticFeedback.impact(.light) diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanelInputView/BUILD b/submodules/TelegramUI/Components/LegacyMessageInputPanelInputView/BUILD new file mode 100644 index 0000000000..e3708e9d4f --- /dev/null +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanelInputView/BUILD @@ -0,0 +1,19 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "LegacyMessageInputPanelInputView", + module_name = "LegacyMessageInputPanelInputView", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanelInputView/Sources/LegacyMessageInputPanelInputView.swift b/submodules/TelegramUI/Components/LegacyMessageInputPanelInputView/Sources/LegacyMessageInputPanelInputView.swift new file mode 100644 index 0000000000..a5e095dd9c --- /dev/null +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanelInputView/Sources/LegacyMessageInputPanelInputView.swift @@ -0,0 +1,11 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display + +public protocol LegacyMessageInputPanelInputView: UIView { + var insertText: ((NSAttributedString) -> Void)? { get set } + var deleteBackwards: (() -> Void)? { get set } + var switchToKeyboard: (() -> Void)? { get set } + var presentController: ((ViewController) -> Void)? { get set } +} diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 3bd5173492..9e937e4080 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -1188,6 +1188,7 @@ final class MediaEditorScreenComponent: Component { bottomInset: 0.0, isFormattingLocked: !state.isPremium, hideKeyboard: self.currentInputMode == .emoji, + customInputView: nil, forceIsEditing: self.currentInputMode == .emoji, disabledPlaceholder: nil, isChannel: false, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift index 1cd86d4e2d..266eeee692 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift @@ -290,6 +290,7 @@ final class StoryPreviewComponent: Component { bottomInset: 0.0, isFormattingLocked: false, hideKeyboard: false, + customInputView: nil, forceIsEditing: false, disabledPlaceholder: nil, isChannel: false, diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index a4ed47280f..b98b96a6c3 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -142,6 +142,7 @@ public final class MessageInputPanelComponent: Component { public let bottomInset: CGFloat public let isFormattingLocked: Bool public let hideKeyboard: Bool + public let customInputView: UIView? public let forceIsEditing: Bool public let disabledPlaceholder: String? public let isChannel: Bool @@ -194,6 +195,7 @@ public final class MessageInputPanelComponent: Component { bottomInset: CGFloat, isFormattingLocked: Bool, hideKeyboard: Bool, + customInputView: UIView?, forceIsEditing: Bool, disabledPlaceholder: String?, isChannel: Bool, @@ -245,6 +247,7 @@ public final class MessageInputPanelComponent: Component { self.bottomInset = bottomInset self.isFormattingLocked = isFormattingLocked self.hideKeyboard = hideKeyboard + self.customInputView = customInputView self.forceIsEditing = forceIsEditing self.disabledPlaceholder = disabledPlaceholder self.isChannel = isChannel @@ -328,6 +331,9 @@ public final class MessageInputPanelComponent: Component { if lhs.hideKeyboard != rhs.hideKeyboard { return false } + if lhs.customInputView !== rhs.customInputView { + return false + } if lhs.forceIsEditing != rhs.forceIsEditing { return false } @@ -713,6 +719,7 @@ public final class MessageInputPanelComponent: Component { textColor: UIColor(rgb: 0xffffff), insets: UIEdgeInsets(top: 9.0, left: 8.0, bottom: 10.0, right: 48.0), hideKeyboard: component.hideKeyboard, + customInputView: component.customInputView, resetText: component.resetInputContents.flatMap { resetInputContents in switch resetInputContents { case let .text(value): diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index d7c10d0cdc..c162ebb255 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -2893,6 +2893,7 @@ public final class StoryItemSetContainerComponent: Component { bottomInset: component.inputHeight != 0.0 || inputNodeVisible ? 0.0 : bottomContentInset, isFormattingLocked: false, hideKeyboard: self.sendMessageContext.currentInputMode == .media, + customInputView: nil, forceIsEditing: self.sendMessageContext.currentInputMode == .media, disabledPlaceholder: disabledPlaceholder, isChannel: isChannel, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 55585862f8..3b1151680d 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -1412,7 +1412,7 @@ final class StoryItemSetContainerSendMessage { peerType.insert(.sameBot) peerType.remove(.bot) } - let button: AttachmentButtonType = .app(bot.peer, bot.shortName, bot.icons) + let button: AttachmentButtonType = .app(bot) if !bot.peerTypes.intersection(peerType).isEmpty { buttons.insert(button, at: 1) @@ -1784,7 +1784,7 @@ final class StoryItemSetContainerSendMessage { }*/ //TODO:gift controller break - case let .app(bot, botName, _): + case let .app(bot): var payload: String? var fromAttachMenu = true /*if case let .bot(_, botPayload, _) = subject { @@ -1793,7 +1793,7 @@ final class StoryItemSetContainerSendMessage { }*/ payload = nil fromAttachMenu = true - let params = WebAppParameters(peerId: peer.id, botId: bot.id, botName: botName, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, fromMenu: false, fromAttachMenu: fromAttachMenu, isInline: false, isSimple: false, forceHasSettings: false) + let params = WebAppParameters(peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, fromMenu: false, fromAttachMenu: fromAttachMenu, isInline: false, isSimple: false, forceHasSettings: false) let theme = component.theme let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) let controller = WebAppController(context: component.context, updatedPresentationData: updatedPresentationData, params: params, replyToMessageId: nil, threadId: nil) diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index beb222bc18..ad7753684b 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -90,6 +90,7 @@ public final class TextFieldComponent: Component { public let textColor: UIColor public let insets: UIEdgeInsets public let hideKeyboard: Bool + public let customInputView: UIView? public let resetText: NSAttributedString? public let isOneLineWhenUnfocused: Bool public let formatMenuAvailability: FormatMenuAvailability @@ -105,6 +106,7 @@ public final class TextFieldComponent: Component { textColor: UIColor, insets: UIEdgeInsets, hideKeyboard: Bool, + customInputView: UIView?, resetText: NSAttributedString?, isOneLineWhenUnfocused: Bool, formatMenuAvailability: FormatMenuAvailability, @@ -119,6 +121,7 @@ public final class TextFieldComponent: Component { self.textColor = textColor self.insets = insets self.hideKeyboard = hideKeyboard + self.customInputView = customInputView self.resetText = resetText self.isOneLineWhenUnfocused = isOneLineWhenUnfocused self.formatMenuAvailability = formatMenuAvailability @@ -146,6 +149,9 @@ public final class TextFieldComponent: Component { if lhs.hideKeyboard != rhs.hideKeyboard { return false } + if lhs.customInputView !== rhs.customInputView { + return false + } if lhs.resetText != rhs.resetText { return false } @@ -816,9 +822,14 @@ public final class TextFieldComponent: Component { } while glyphIndexForStringStart < NSMaxRange(glyphRange) && !NSLocationInRange(glyphRange.location, lineRange) let padding = self.textView.textContainerInset.left - let rightmostX = lineRect.maxX + padding + var rightmostX = lineRect.maxX + padding let rightmostY = lineRect.minY + self.textView.textContainerInset.top + let stringIndex = self.textView.text.index(self.textView.text.startIndex, offsetBy: lineRange.location + lineRange.length - 1) + if self.textView.text[stringIndex] == " " { + rightmostX -= 3.0 + } + return CGPoint(x: rightmostX, y: rightmostY) } @@ -882,7 +893,14 @@ public final class TextFieldComponent: Component { component.externalState.isEditing = isEditing component.externalState.textLength = self.textStorage.string.count - if component.hideKeyboard { + if let inputView = component.customInputView { + if self.textView.inputView == nil { + self.textView.inputView = inputView + if self.textView.isFirstResponder { + self.textView.reloadInputViews() + } + } + } else if component.hideKeyboard { if self.textView.inputView == nil { self.textView.inputView = EmptyInputView() if self.textView.isFirstResponder { @@ -916,7 +934,7 @@ public final class TextFieldComponent: Component { view.alpha = 0.0 self.textView.addSubview(view) } - let ellipsisFrame = CGRect(origin: CGPoint(x: position.x - 11.0, y: position.y), size: ellipsisSize) + let ellipsisFrame = CGRect(origin: CGPoint(x: position.x - 8.0, y: position.y), size: ellipsisSize) transition.setFrame(view: view, frame: ellipsisFrame) let hasMoreThanOneLine = ellipsisFrame.maxY < self.textView.contentSize.height - 12.0 diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 43e01dc52e..32ae407809 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -13367,7 +13367,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G peerType.insert(.sameBot) peerType.remove(.bot) } - let button: AttachmentButtonType = .app(bot.peer, bot.shortName, bot.icons) + let button: AttachmentButtonType = .app(bot) if !bot.peerTypes.intersection(peerType).isEmpty { buttons.insert(button, at: 1) @@ -13408,17 +13408,27 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if !premiumGiftOptions.isEmpty { buttons.insert(.gift, at: 1) } - + guard let initialButton = initialButton else { if case let .bot(botId, botPayload, botJustInstalled) = subject { if let button = allButtons.first(where: { button in - if case let .app(botPeer, _, _) = button, botPeer.id == botId { + if case let .app(bot) = button, bot.peer.id == botId { return true } else { return false } - }), case let .app(_, botName, _) = button { - strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: botJustInstalled ? strongSelf.presentationData.strings.WebApp_AddToAttachmentSucceeded(botName).string : strongSelf.presentationData.strings.WebApp_AddToAttachmentAlreadyAddedError, timeout: nil), elevatedLayout: false, action: { _ in return false }), in: .current) + }), case let .app(bot) = button { + let content: UndoOverlayContent + if botJustInstalled { + if bot.flags.contains(.showInSettings) { + content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(bot.shortName).string) + } else { + content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsAdded(bot.shortName).string) + } + } else { + content = .info(title: nil, text: strongSelf.presentationData.strings.WebApp_AddToAttachmentAlreadyAddedError, timeout: nil) + } + strongSelf.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, action: { _ in return false }), in: .current) } else { let _ = (context.engine.messages.getAttachMenuBot(botId: botId) |> deliverOnMainQueue).start(next: { bot in @@ -13746,14 +13756,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = ApplicationSpecificNotice.incrementDismissedPremiumGiftSuggestion(accountManager: context.sharedContext.accountManager, peerId: peer.id).start() } - case let .app(bot, botName, _): + case let .app(bot): var payload: String? var fromAttachMenu = true if case let .bot(_, botPayload, _) = subject { payload = botPayload fromAttachMenu = false } - let params = WebAppParameters(peerId: peer.id, botId: bot.id, botName: botName, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, fromMenu: false, fromAttachMenu: fromAttachMenu, isInline: false, isSimple: false, forceHasSettings: false) + let params = WebAppParameters(peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, fromMenu: false, fromAttachMenu: fromAttachMenu, isInline: false, isSimple: false, forceHasSettings: false) let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, replyToMessageId: replyMessageId, threadId: strongSelf.chatLocation.threadId) controller.openUrl = { [weak self] url, concealed, commit in @@ -13780,6 +13790,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G attachmentController.navigationPresentation = .flatModal strongSelf.push(attachmentController) strongSelf.attachmentController = attachmentController + + if case let .bot(botId, _, botJustInstalled) = subject, botJustInstalled { + if let button = allButtons.first(where: { button in + if case let .app(bot) = button, bot.peer.id == botId { + return true + } else { + return false + } + }), case let .app(bot) = button { + Queue.mainQueue().after(0.3) { + let content: UndoOverlayContent + if bot.flags.contains(.showInSettings) { + content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(bot.shortName).string) + } else { + content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsAdded(bot.shortName).string) + } + attachmentController.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: true, action: { _ in return false }), in: .current) + } + } + } } if inputIsActive { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 0deaf5af56..d022d526b9 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1483,7 +1483,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuForward, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in - interfaceInteraction.forwardMessages(selectAll ? messages : [message]) + interfaceInteraction.forwardMessages(selectAll || isImage ? messages : [message]) f(.dismissWithoutContent) }))) } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 28a40df703..9bac377525 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -4192,10 +4192,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode case let .message(message, _, _, _, _): for media in message.media { if let action = media as? TelegramMediaAction { - if case .phoneCall = action.action { } else { + if case .phoneCall = action.action { + } else { canHaveSelection = false break } + } else if media is TelegramMediaExpiredContent { + canHaveSelection = false } } if message.adAttribute != nil { diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift index b42992ff22..a2af49ad6d 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift @@ -1,5 +1,6 @@ import AsyncDisplayKit import Display +import SwiftSignalKit import TelegramPresentationData final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem { @@ -31,13 +32,15 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem { let label: Label let text: String let icon: UIImage? + let iconSignal: Signal? let action: (() -> Void)? - init(id: AnyHashable, label: Label = .none, text: String, icon: UIImage? = nil, action: (() -> Void)?) { + init(id: AnyHashable, label: Label = .none, text: String, icon: UIImage? = nil, iconSignal: Signal? = nil, action: (() -> Void)?) { self.id = id self.label = label self.text = text self.icon = icon + self.iconSignal = iconSignal self.action = action } @@ -57,6 +60,8 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { private let bottomSeparatorNode: ASDisplayNode private let activateArea: AccessibilityAreaNode + private var iconDisposable: Disposable? + private var item: PeerInfoScreenDisclosureItem? override init() { @@ -109,6 +114,10 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { self.addSubnode(self.activateArea) } + deinit { + self.iconDisposable?.dispose() + } + override func update(width: CGFloat, safeInsets: UIEdgeInsets, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, hasCorners: Bool, transition: ContainedViewLayoutTransition) -> CGFloat { guard let item = item as? PeerInfoScreenDisclosureItem else { return 10.0 @@ -120,9 +129,9 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { self.selectionNode.pressed = item.action let sideInset: CGFloat = 16.0 + safeInsets.left - let leftInset = (item.icon == nil ? sideInset : sideInset + 29.0 + 16.0) + let leftInset = (item.icon == nil && item.iconSignal == nil ? sideInset : sideInset + 29.0 + 16.0) let rightInset = sideInset + 18.0 - let separatorInset = item.icon == nil ? sideInset : leftInset - 1.0 + let separatorInset = item.icon == nil && item.iconSignal == nil ? sideInset : leftInset - 1.0 let titleFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor @@ -149,12 +158,28 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { let height = textSize.height + 24.0 - if let icon = item.icon { + if item.icon != nil || item.iconSignal != nil { if self.iconNode.supernode == nil { self.addSubnode(self.iconNode) } - self.iconNode.image = icon - let iconFrame = CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((height - icon.size.height) / 2.0)), size: icon.size) + let iconSize: CGSize + if let icon = item.icon { + self.iconNode.image = icon + iconSize = icon.size + } else if let iconSignal = item.iconSignal { + if previousItem == nil { + self.iconDisposable = (iconSignal + |> deliverOnMainQueue).start(next: { [weak self] icon in + if let self { + self.iconNode.image = icon + } + }) + } + iconSize = CGSize(width: 29.0, height: 29.0) + } else { + iconSize = CGSize(width: 29.0, height: 29.0) + } + let iconFrame = CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize) transition.updateFrame(node: self.iconNode, frame: iconFrame) } else if self.iconNode.supernode != nil { self.iconNode.image = nil diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 4874ea7a02..233d88a12b 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -800,7 +800,20 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p if let settings = data.globalSettings { for bot in settings.bots { if bot.flags.contains(.showInSettings) { - items[.apps]!.append(PeerInfoScreenDisclosureItem(id: appIndex, text: bot.peer.compactDisplayTitle, icon: PresentationResourcesSettings.passport, action: { + 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).start() + } else { + iconSignal = .single(UIImage(bundleImageName: "Settings/Menu/Websites")!) + } + items[.apps]!.append(PeerInfoScreenDisclosureItem(id: appIndex, text: bot.peer.compactDisplayTitle, icon: nil, iconSignal: iconSignal, action: { interaction.openBotApp(bot) })) appIndex += 1 @@ -4636,12 +4649,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let controller = self.controller else { return } - let proceed = { [weak self] in + let presentationData = self.presentationData + let proceed: (Bool) -> Void = { [weak self] installed in guard let self else { return } - let presentationData = self.presentationData let progressSignal = Signal { [weak self] subscriber in let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) self?.controller?.present(controller, in: .window(.root)) @@ -4676,6 +4689,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }) controller.navigationPresentation = .flatModal self.controller?.push(controller) + + if installed { + Queue.mainQueue().after(0.3, { + let text: String + if bot.flags.contains(.showInSettings) { + text = presentationData.strings.WebApp_ShortcutsSettingsAdded(bot.peer.compactDisplayTitle).string + } else { + text = presentationData.strings.WebApp_ShortcutsAdded(bot.peer.compactDisplayTitle).string + } + controller.present( + UndoOverlayController(presentationData: presentationData, content: .succeed(text: text), elevatedLayout: false, action: { _ in return false }), + in: .current + ) + }) + } }, error: { [weak self] error in if let self { self.controller?.present(textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: self.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { @@ -4697,15 +4725,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro |> deliverOnMainQueue).start(error: { _ in }, completed: { - proceed() + proceed(true) }) } else { - proceed() + proceed(false) } }) controller.present(alertController, in: .window(.root)) } else { - proceed() + proceed(false) } } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 361167958b..e5714d833f 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1633,7 +1633,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { chatLocation: chatLocation, isScheduledMessages: isScheduledMessages, present: present, - presentInGlobalOverlay: presentInGlobalOverlay + presentInGlobalOverlay: presentInGlobalOverlay, + makeEntityInputView: { + return EntityInputView(context: context, isDark: true, areCustomEmojiEnabled: customEmojiAvailable) + } ) return inputPanelNode } diff --git a/submodules/WebUI/Sources/WebAppTermsAlertController.swift b/submodules/WebUI/Sources/WebAppTermsAlertController.swift index 565a1e3b7e..766ab47053 100644 --- a/submodules/WebUI/Sources/WebAppTermsAlertController.swift +++ b/submodules/WebUI/Sources/WebAppTermsAlertController.swift @@ -117,11 +117,7 @@ private final class WebAppTermsAlertContentNode: AlertContentNode, UIGestureReco for separatorNode in self.actionVerticalSeparators { self.addSubnode(separatorNode) } - - if let firstAction = self.actionNodes.first { - firstAction.actionEnabled = false - } - + self.acceptTermsCheckNode.valueChanged = { [weak self] value in if let strongSelf = self { strongSelf.acceptedTerms = !strongSelf.acceptedTerms @@ -150,6 +146,10 @@ private final class WebAppTermsAlertContentNode: AlertContentNode, UIGestureReco let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.acceptTap(_:))) tapGesture.delegate = self self.view.addGestureRecognizer(tapGesture) + + if let firstAction = self.actionNodes.first { + firstAction.actionEnabled = false + } } override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { @@ -366,7 +366,7 @@ public func webAppTermsAlertController( var dismissImpl: ((Bool) -> Void)? let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_DisclaimerContinue, action: { - completion(false) + completion(true) dismissImpl?(true) }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { dismissImpl?(true) From 87c2c67c093a8967acab190ce392fc2d15c41eb1 Mon Sep 17 00:00:00 2001 From: Mike Renoir <> Date: Sat, 9 Sep 2023 11:20:24 +0400 Subject: [PATCH 02/11] macos static icon --- .../Sources/TelegramEngine/Messages/AttachMenuBots.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift index 68a86ee647..7dae8f1b9c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift @@ -18,6 +18,7 @@ public final class AttachMenuBots: Equatable, Codable { case `default` = 0 case iOSStatic case iOSAnimated + case macOSStaticSettings case macOSAnimated case placeholder @@ -31,6 +32,8 @@ public final class AttachMenuBots: Equatable, Codable { self = .iOSAnimated case "macos_animated": self = .macOSAnimated + case "macos_side_menu_static": + self = .macOSStaticSettings case "placeholder_static": self = .placeholder default: From a47e6380f72c573d73b7b3bbf86cc39f5330a9ce Mon Sep 17 00:00:00 2001 From: Mike Renoir <> Date: Sat, 9 Sep 2023 13:04:44 +0400 Subject: [PATCH 03/11] bug fixes --- .../Sources/TelegramEngine/Peers/AddressNames.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift index 75b15431e4..1fcdf35648 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift @@ -617,7 +617,7 @@ func _internal_channelsForStories(account: Account) -> Signal<[Peer], NoError> { } } - if let cachedPeers { + if let cachedPeers = cachedPeers { return .single(cachedPeers) |> then(remote) } else { return remote From f13b1da0bb46e67bb24a7153a5bffff1dd44e090 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 9 Sep 2023 17:34:49 +0400 Subject: [PATCH 04/11] Web app improvements --- .../Sources/ChannelAdminsController.swift | 2 +- .../DataPrivacySettingsController.swift | 6 +- .../Messages/AttachMenuBots.swift | 4 +- .../Sources/LegacyMessageInputPanel.swift | 4 +- .../Sources/StorageUsageScreen.swift | 2 +- ...StoryItemSetContainerViewSendMessage.swift | 10 +--- .../TelegramUI/Sources/ChatController.swift | 28 ++++----- .../PeerInfoScreenDisclosureItem.swift | 3 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 11 ++-- .../Sources/UndoOverlayController.swift | 2 +- .../Sources/UndoOverlayControllerNode.swift | 4 +- .../WebUI/Sources/WebAppController.swift | 58 ++++++++++--------- 12 files changed, 69 insertions(+), 65 deletions(-) diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index 50dc14e4e8..afc2bb66c6 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -581,7 +581,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation guard let peer = peer, let user = user else { return } - presentControllerImpl?(UndoOverlayController(presentationData: context.sharedContext.currentPresentationData.with { $0 }, content: .succeed(text: presentationData.strings.Channel_OwnershipTransfer_TransferCompleted(user.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string), elevatedLayout: false, action: { _ in return false }), nil) + presentControllerImpl?(UndoOverlayController(presentationData: context.sharedContext.currentPresentationData.with { $0 }, content: .succeed(text: presentationData.strings.Channel_OwnershipTransfer_TransferCompleted(user.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, timeout: nil), elevatedLayout: false, action: { _ in return false }), nil) }) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift index 5227e16baa..855d3bfc45 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift @@ -377,7 +377,7 @@ public func dataPrivacyController(context: AccountContext) -> ViewController { text = nil } if let text = text { - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: text), elevatedLayout: false, action: { _ in return false })) + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: text, timeout: nil), elevatedLayout: false, action: { _ in return false })) } })) } @@ -426,7 +426,7 @@ public func dataPrivacyController(context: AccountContext) -> ViewController { return state } let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.Privacy_ContactsReset_ContactsDeleted), elevatedLayout: false, action: { _ in return false })) + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.Privacy_ContactsReset_ContactsDeleted, timeout: nil), elevatedLayout: false, action: { _ in return false })) })) }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})])) } @@ -476,7 +476,7 @@ public func dataPrivacyController(context: AccountContext) -> ViewController { return state } let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.Privacy_DeleteDrafts_DraftsDeleted), elevatedLayout: false, action: { _ in return false })) + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.Privacy_DeleteDrafts_DraftsDeleted, timeout: nil), elevatedLayout: false, action: { _ in return false })) })) } dismissAction() diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift index ea393f6d7b..33861f0caa 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift @@ -426,6 +426,8 @@ func _internal_addBotToAttachMenu(accountPeerId: PeerId, postbox: Postbox, netwo } func _internal_removeBotFromAttachMenu(accountPeerId: PeerId, postbox: Postbox, network: Network, botId: PeerId) -> Signal { + let _ = removeCachedAttachMenuBot(postbox: postbox, botId: botId).start() + return postbox.transaction { transaction -> Signal in guard let peer = transaction.getPeer(botId), let inputUser = apiInputUser(peer) else { return .complete() @@ -445,7 +447,7 @@ func _internal_removeBotFromAttachMenu(accountPeerId: PeerId, postbox: Postbox, |> afterCompleted { let _ = (managedSynchronizeAttachMenuBots(accountPeerId: accountPeerId, postbox: postbox, network: network, force: true) |> take(1)).start(completed: { - let _ = removeCachedAttachMenuBot(postbox: postbox, botId: botId) + let _ = removeCachedAttachMenuBot(postbox: postbox, botId: botId).start() }) } } diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift index 4728568f63..6b4d2d8383 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift @@ -108,7 +108,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { if let view = self.inputPanel.view as? MessageInputPanelComponent.View { self.isEmojiKeyboardActive = false self.inputView = nil - view.deactivateInput() + view.deactivateInput(force: true) } } @@ -158,6 +158,8 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { var maxInputPanelHeight = maxHeight if keyboardHeight.isZero { maxInputPanelHeight = 60.0 + } else { + maxInputPanelHeight = maxHeight - keyboardHeight - 100.0 } var resetInputContents: MessageInputPanelComponent.SendMessageInput? diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift index 546aa5725a..e9a3bba3f9 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift @@ -2276,7 +2276,7 @@ final class StorageUsageScreenComponent: Component { } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - controller.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(size, formatting: DataSizeStringFormatting(presentationData: presentationData)))", stringForDeviceType()).string), elevatedLayout: false, action: { _ in return false }), in: .current) + controller.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(size, formatting: DataSizeStringFormatting(presentationData: presentationData)))", stringForDeviceType()).string, timeout: nil), elevatedLayout: false, action: { _ in return false }), in: .current) } private func reloadStats(firstTime: Bool, completion: @escaping () -> Void) { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 3b1151680d..62c567c9d6 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -1785,15 +1785,7 @@ final class StoryItemSetContainerSendMessage { //TODO:gift controller break case let .app(bot): - var payload: String? - var fromAttachMenu = true - /*if case let .bot(_, botPayload, _) = subject { - payload = botPayload - fromAttachMenu = false - }*/ - payload = nil - fromAttachMenu = true - let params = WebAppParameters(peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, fromMenu: false, fromAttachMenu: fromAttachMenu, isInline: false, isSimple: false, forceHasSettings: false) + let params = WebAppParameters(source: .attachMenu, peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, url: nil, queryId: nil, payload: nil, buttonText: nil, keepAliveSignal: nil, forceHasSettings: false) let theme = component.theme let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) let controller = WebAppController(context: component.context, updatedPresentationData: updatedPresentationData, params: params, replyToMessageId: nil, threadId: nil) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 32ae407809..d2d2428450 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4220,7 +4220,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return state.updatedShowWebView(true).updatedForceInputCommandsHidden(true) } - let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, fromMenu: true, fromAttachMenu: false, isInline: false, isSimple: false, forceHasSettings: false) + let params = WebAppParameters(source: .menu, peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit) }, requestSwitchInline: { [weak self] query, chatTypes, completion in @@ -4291,7 +4291,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - let params = WebAppParameters(peerId: peerId, botId: botId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, fromMenu: false, fromAttachMenu: false, isInline: isInline, isSimple: true, forceHasSettings: false) + let params = WebAppParameters(source: isInline ? .inline : .simple, peerId: peerId, botId: botId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit) }, requestSwitchInline: { [weak self] query, chatTypes, completion in @@ -4331,7 +4331,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, fromMenu: false, fromAttachMenu: false, isInline: false, isSimple: false, forceHasSettings: false) + let params = WebAppParameters(source: .generic, peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit) }, completion: { [weak self] in @@ -10453,7 +10453,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } unarchiveAutomaticallyArchivedPeer(account: strongSelf.context.account, peerId: peerId) - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: strongSelf.presentationData.strings.Conversation_UnarchiveDone), elevatedLayout: false, action: { _ in return false }), in: .current) + strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: strongSelf.presentationData.strings.Conversation_UnarchiveDone, timeout: nil), elevatedLayout: false, action: { _ in return false }), in: .current) }, scrollToTop: { [weak self] in guard let strongSelf = self else { return @@ -12937,7 +12937,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G disposable.set((signal |> deliverOnMainQueue).start(completed: { [weak self] in if let strongSelf = self, let _ = strongSelf.validLayout { - strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))", stringForDeviceType()).string), elevatedLayout: false, action: { _ in return false }), in: .current) + strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))", stringForDeviceType()).string, timeout: nil), elevatedLayout: false, action: { _ in return false }), in: .current) } })) @@ -13185,7 +13185,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botApp.title, url: url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, fromMenu: false, fromAttachMenu: false, isInline: false, isSimple: false, forceHasSettings: false) + let params = WebAppParameters(source: .generic, peerId: peerId, botId: peerId, botName: botApp.title, url: url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: false) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit) }, requestSwitchInline: { [weak self] query, chatTypes, completion in @@ -13421,14 +13421,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let content: UndoOverlayContent if botJustInstalled { if bot.flags.contains(.showInSettings) { - content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(bot.shortName).string) + content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(bot.shortName).string, timeout: 5.0) } else { - content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsAdded(bot.shortName).string) + content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsAdded(bot.shortName).string, timeout: 5.0) } } else { content = .info(title: nil, text: strongSelf.presentationData.strings.WebApp_AddToAttachmentAlreadyAddedError, timeout: nil) } - strongSelf.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, action: { _ in return false }), in: .current) + strongSelf.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) } else { let _ = (context.engine.messages.getAttachMenuBot(botId: botId) |> deliverOnMainQueue).start(next: { bot in @@ -13584,7 +13584,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G contactsController.navigationPresentation = .modal completion(contactsController, contactsController.mediaPickerContext) strongSelf.controllerNavigationDisposable.set((contactsController.result - |> deliverOnMainQueue).start(next: { [weak self] peers in + |> deliverOnMainQueue).start(next: { [weak self] peers in if let strongSelf = self, let (peers, _, silent, scheduleTime, text) = peers { var textEnqueueMessage: EnqueueMessage? if let text = text, text.length > 0 { @@ -13763,7 +13763,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G payload = botPayload fromAttachMenu = false } - let params = WebAppParameters(peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, fromMenu: false, fromAttachMenu: fromAttachMenu, isInline: false, isSimple: false, forceHasSettings: false) + let params = WebAppParameters(source: fromAttachMenu ? .attachMenu : .generic, peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, forceHasSettings: false) let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, replyToMessageId: replyMessageId, threadId: strongSelf.chatLocation.threadId) controller.openUrl = { [weak self] url, concealed, commit in @@ -13802,11 +13802,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G Queue.mainQueue().after(0.3) { let content: UndoOverlayContent if bot.flags.contains(.showInSettings) { - content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(bot.shortName).string) + content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(bot.shortName).string, timeout: 5.0) } else { - content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsAdded(bot.shortName).string) + content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsAdded(bot.shortName).string, timeout: 5.0) } - attachmentController.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: true, action: { _ in return false }), in: .current) + attachmentController.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) } } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift index a2af49ad6d..0b4a4de9c4 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift @@ -167,7 +167,8 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { self.iconNode.image = icon iconSize = icon.size } else if let iconSignal = item.iconSignal { - if previousItem == nil { + if previousItem?.text != item.text { + self.iconNode.image = nil self.iconDisposable = (iconSignal |> deliverOnMainQueue).start(next: { [weak self] icon in if let self { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 233d88a12b..c1a8e30957 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -813,7 +813,7 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p } else { iconSignal = .single(UIImage(bundleImageName: "Settings/Menu/Websites")!) } - items[.apps]!.append(PeerInfoScreenDisclosureItem(id: appIndex, text: bot.peer.compactDisplayTitle, icon: nil, iconSignal: iconSignal, action: { + items[.apps]!.append(PeerInfoScreenDisclosureItem(id: bot.peer.id.id._internalGetInt64Value(), text: bot.peer.compactDisplayTitle, icon: nil, iconSignal: iconSignal, action: { interaction.openBotApp(bot) })) appIndex += 1 @@ -821,7 +821,7 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p } } - items[.apps]!.append(PeerInfoScreenDisclosureItem(id: appIndex, text: presentationData.strings.Settings_MyStories, icon: PresentationResourcesSettings.stories, action: { + items[.apps]!.append(PeerInfoScreenDisclosureItem(id: 0, text: presentationData.strings.Settings_MyStories, icon: PresentationResourcesSettings.stories, action: { interaction.openSettings(.stories) })) @@ -4115,6 +4115,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if let previousSuggestPasswordConfirmation = previousData?.globalSettings?.suggestPasswordConfirmation, previousSuggestPasswordConfirmation != data.globalSettings?.suggestPasswordConfirmation { infoUpdated = true } + if let previousBots = previousData?.globalSettings?.bots, previousBots.count != (data.globalSettings?.bots ?? []).count { + infoUpdated = true + } } if previousCallsPrivate != currentCallsPrivate || (previousVideoCallsAvailable != currentVideoCallsAvailable && currentVideoCallsAvailable != nil) { infoUpdated = true @@ -4680,7 +4683,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let self else { return } - let params = WebAppParameters(peerId: self.context.account.peerId, botId: bot.peer.id, botName: bot.peer.compactDisplayTitle, url: url, queryId: nil, payload: nil, buttonText: nil, keepAliveSignal: nil, fromMenu: false, fromAttachMenu: false, isInline: false, isSimple: true, forceHasSettings: bot.flags.contains(.hasSettings)) + let params = WebAppParameters(source: .settings, peerId: self.context.account.peerId, botId: bot.peer.id, botName: bot.peer.compactDisplayTitle, url: url, queryId: nil, payload: nil, buttonText: nil, keepAliveSignal: nil, forceHasSettings: bot.flags.contains(.hasSettings)) let controller = standaloneWebAppController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, params: params, threadId: nil, openUrl: { [weak self] url, concealed, commit in self?.openUrl(url: url, concealed: concealed, external: false, forceExternal: true, commit: commit) }, requestSwitchInline: { _, _, _ in @@ -4699,7 +4702,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro text = presentationData.strings.WebApp_ShortcutsAdded(bot.peer.compactDisplayTitle).string } controller.present( - UndoOverlayController(presentationData: presentationData, content: .succeed(text: text), elevatedLayout: false, action: { _ in return false }), + UndoOverlayController(presentationData: presentationData, content: .succeed(text: text, timeout: 5.0), elevatedLayout: false, position: .top, action: { _ in return false }), in: .current ) }) diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index 11f68ec1fb..5be099fc8e 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -11,7 +11,7 @@ public enum UndoOverlayContent { case archivedChat(peerId: Int64, title: String, text: String, undo: Bool) case hidArchive(title: String, text: String, undo: Bool) case revealedArchive(title: String, text: String, undo: Bool) - case succeed(text: String) + case succeed(text: String, timeout: Double?) case info(title: String?, text: String, timeout: Double?) case emoji(name: String, text: String) case swipeToReply(title: String, text: String) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 594da666cc..d81161b01b 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -191,7 +191,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { if text.contains("](") { isUserInteractionEnabled = true } - case let .succeed(text): + case let .succeed(text, timeout): self.avatarNode = nil self.iconNode = nil self.iconCheckNode = nil @@ -204,7 +204,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.textNode.attributedText = attributedText self.textNode.maximumNumberOfLines = 5 displayUndo = false - self.originalRemainingSeconds = 3 + self.originalRemainingSeconds = timeout ?? 3 case let .info(title, text, timeout): self.avatarNode = nil self.iconNode = nil diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index d886f5b6d0..dd083c0925 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -170,6 +170,24 @@ public class WebAppCancelButtonNode: ASDisplayNode { } public struct WebAppParameters { + public enum Source { + case generic + case menu + case attachMenu + case inline + case simple + case settings + + var isSimple: Bool { + if [.simple, .inline, .settings].contains(self) { + return true + } else { + return false + } + } + } + + let source: Source let peerId: PeerId let botId: PeerId let botName: String @@ -178,13 +196,10 @@ public struct WebAppParameters { let payload: String? let buttonText: String? let keepAliveSignal: Signal? - let fromMenu: Bool - let fromAttachMenu: Bool - let isInline: Bool - let isSimple: Bool let forceHasSettings: Bool public init( + source: Source, peerId: PeerId, botId: PeerId, botName: String, @@ -193,12 +208,9 @@ public struct WebAppParameters { payload: String?, buttonText: String?, keepAliveSignal: Signal?, - fromMenu: Bool, - fromAttachMenu: Bool, - isInline: Bool, - isSimple: Bool, forceHasSettings: Bool ) { + self.source = source self.peerId = peerId self.botId = botId self.botName = botName @@ -207,10 +219,6 @@ public struct WebAppParameters { self.payload = payload self.buttonText = buttonText self.keepAliveSignal = keepAliveSignal - self.fromMenu = fromMenu - self.fromAttachMenu = fromAttachMenu - self.isInline = isInline - self.isSimple = isSimple self.forceHasSettings = forceHasSettings } } @@ -393,7 +401,7 @@ public final class WebAppController: ViewController, AttachmentContainable { }) }) - if let url = controller.url, !controller.fromMenu { + if let url = controller.url, controller.source != .menu { self.queryId = controller.queryId if let parsedUrl = URL(string: url) { self.webView?.load(URLRequest(url: parsedUrl)) @@ -412,7 +420,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.fromMenu, replyToMessageId: controller.replyToMessageId, threadId: controller.threadId) + 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) |> deliverOnMainQueue).start(next: { [weak self] result in guard let strongSelf = self else { return @@ -707,11 +715,11 @@ public final class WebAppController: ViewController, AttachmentContainable { } } case "web_app_data_send": - if controller.isSimple, let eventData = body["eventData"] as? String { + if controller.source.isSimple, let eventData = body["eventData"] as? String { self.handleSendData(data: eventData) } case "web_app_setup_main_button": - if let webView = self.webView, !webView.didTouchOnce && controller.url == nil && controller.fromAttachMenu { + if let webView = self.webView, !webView.didTouchOnce && controller.url == nil && controller.source == .attachMenu { self.delayedScriptMessage = message } else if let json = json { if var isVisible = json["is_visible"] as? Bool { @@ -1301,6 +1309,7 @@ public final class WebAppController: ViewController, AttachmentContainable { fileprivate let moreButtonNode: MoreButtonNode private let context: AccountContext + private let source: WebAppParameters.Source private let peerId: PeerId private let botId: PeerId private let botName: String @@ -1308,10 +1317,6 @@ public final class WebAppController: ViewController, AttachmentContainable { private let queryId: Int64? private let payload: String? private let buttonText: String? - private let fromMenu: Bool - private let fromAttachMenu: Bool - private let isInline: Bool - private let isSimple: Bool private let forceHasSettings: Bool private let keepAliveSignal: Signal? private let replyToMessageId: MessageId? @@ -1328,6 +1333,7 @@ public final class WebAppController: ViewController, AttachmentContainable { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, params: WebAppParameters, replyToMessageId: MessageId?, threadId: Int64?) { self.context = context + self.source = params.source self.peerId = params.peerId self.botId = params.botId self.botName = params.botName @@ -1335,10 +1341,6 @@ public final class WebAppController: ViewController, AttachmentContainable { self.queryId = params.queryId self.payload = params.payload self.buttonText = params.buttonText - self.fromMenu = params.fromMenu - self.fromAttachMenu = params.fromAttachMenu - self.isInline = params.isInline - self.isSimple = params.isSimple self.forceHasSettings = params.forceHasSettings self.keepAliveSignal = params.keepAliveSignal self.replyToMessageId = replyToMessageId @@ -1454,12 +1456,14 @@ public final class WebAppController: ViewController, AttachmentContainable { let url = self.url let forceHasSettings = self.forceHasSettings + let source = self.source + let items = context.engine.messages.attachMenuBots() |> take(1) |> map { [weak self] attachMenuBots -> ContextController.Items in var items: [ContextMenuItem] = [] - let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == botId}) + let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == botId && !$0.flags.contains(.notActivated) }) let hasSettings: Bool if url == nil { @@ -1517,7 +1521,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self?.controllerNode.webView?.reload() }))) - if let _ = attachMenuBot, self?.url == nil { + if let _ = attachMenuBot, [.attachMenu, .settings].contains(source) { items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_RemoveBot, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in @@ -1672,7 +1676,7 @@ public func standaloneWebAppController( didDismiss: @escaping () -> Void = {}, getNavigationController: @escaping () -> NavigationController? = { return nil }, getSourceRect: (() -> CGRect?)? = nil) -> ViewController { - let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: params.peerId), buttons: [.standalone], initialButton: .standalone, fromMenu: params.fromMenu, hasTextInput: false, makeEntityInputView: { + let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: params.peerId), buttons: [.standalone], initialButton: .standalone, fromMenu: params.source == .menu, hasTextInput: false, makeEntityInputView: { return nil }) controller.getInputContainerNode = getInputContainerNode From d6095101a62789c7ed26e3f419934f6f27cb73d0 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 9 Sep 2023 18:16:10 +0400 Subject: [PATCH 05/11] Web app improvements --- Telegram/Telegram-iOS/en.lproj/Localizable.strings | 2 -- .../WebUI/Sources/WebAppTermsAlertController.swift | 9 +-------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 0f6e1d3e4f..bfd9d0aceb 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9916,8 +9916,6 @@ Sorry for the inconvenience."; "WebApp.DisclaimerTitle" = "Terms of Use"; "WebApp.DisclaimerText" = "You are about to use a mini app operated by an independent party not affiliated with Telegram. You must agree to the Terms of Use of mini apps to continue."; -"WebApp.DisclaimerShortcutsText" = "**%@** shortcut will be added in your attachment menu."; -"WebApp.DisclaimerShortcutsSettingsText" = "**%@** shortcuts will be added in your attachment menu and Settings."; "WebApp.DisclaimerAgree" = "I agree to the [Terms of Use]()"; "WebApp.DisclaimerContinue" = "Continue"; "WebApp.Disclaimer_URL" = "https://telegram.org/tos/mini-apps"; diff --git a/submodules/WebUI/Sources/WebAppTermsAlertController.swift b/submodules/WebUI/Sources/WebAppTermsAlertController.swift index 766ab47053..bbd5d81864 100644 --- a/submodules/WebUI/Sources/WebAppTermsAlertController.swift +++ b/submodules/WebUI/Sources/WebAppTermsAlertController.swift @@ -374,14 +374,7 @@ public func webAppTermsAlertController( let title = presentationData.strings.WebApp_DisclaimerTitle let text = presentationData.strings.WebApp_DisclaimerText - let additionalText: String? - if bot.flags.contains(.showInSettings) { - additionalText = presentationData.strings.WebApp_DisclaimerShortcutsSettingsText(bot.peer.compactDisplayTitle).string - } else if bot.flags.contains(.showInAttachMenu) { - additionalText = presentationData.strings.WebApp_DisclaimerShortcutsText(bot.peer.compactDisplayTitle).string - } else { - additionalText = nil - } + let additionalText: String? = nil let contentNode = WebAppTermsAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, title: title, text: text, additionalText: additionalText, actions: actions) contentNode.openTerms = { From 28a5ce4e1fd62afb49c603e91636720ec27c3351 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 9 Sep 2023 18:37:45 +0400 Subject: [PATCH 06/11] Web app improvements --- submodules/TelegramUI/Sources/ChatController.swift | 2 +- submodules/WebUI/Sources/WebAppController.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index d2d2428450..22b5b41351 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -13185,7 +13185,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - let params = WebAppParameters(source: .generic, peerId: peerId, botId: peerId, botName: botApp.title, url: url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: false) + let params = WebAppParameters(source: .generic, peerId: peerId, botId: peerId, botName: botApp.title, url: url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings)) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit) }, requestSwitchInline: { [weak self] query, chatTypes, completion in diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index dd083c0925..ac7d8f9b5a 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -1473,7 +1473,7 @@ public final class WebAppController: ViewController, AttachmentContainable { hasSettings = attachMenuBot?.flags.contains(.hasSettings) == true } } else { - hasSettings = false + hasSettings = forceHasSettings } if hasSettings { From 28dd60b88789c32986924e6509e36dedb831aac3 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 9 Sep 2023 19:38:57 +0400 Subject: [PATCH 07/11] Secret media improvements --- .../AttachmentTextInputPanelNode.swift | 2 +- .../ChatItemGalleryFooterContentNode.swift | 6 +++- .../Items/UniversalVideoGalleryItem.swift | 4 ++- .../SecretMediaPreviewController.swift | 9 ++++-- .../TGPhotoCaptionInputMixin.h | 2 +- .../TGPhotoPaintStickersContext.h | 2 +- .../TGMediaPickerGalleryInterfaceView.m | 2 +- .../Sources/TGPhotoCaptionInputMixin.m | 4 +-- submodules/StatisticsUI/BUILD | 1 + .../PendingMessageUploadedContent.swift | 29 ++++++++++++++++--- .../Sources/LegacyMessageInputPanel.swift | 11 +++++-- 11 files changed, 54 insertions(+), 18 deletions(-) diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index 8c29e54cc2..744a83953f 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -466,7 +466,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } - public func setTimeout(_ timeout: Int32) { + public func setTimeout(_ timeout: Int32, isVideo: Bool) { } public func animate(_ view: UIView, frame: CGRect) { diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 675f3aecf5..0d4b651228 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -631,7 +631,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll func setMessage(_ message: Message, displayInfo: Bool = true, translateToLanguage: String? = nil, peerIsCopyProtected: Bool = false) { self.currentMessage = message - let canDelete: Bool + var canDelete: Bool var canShare = !message.containsSecretMedia var canFullscreen = false @@ -715,6 +715,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll canEdit = false } + if message.containsSecretMedia { + canDelete = false + } + var authorNameText: String? if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), let authorSignature = forwardInfo.authorSignature { authorNameText = authorSignature diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index d273b63e9d..3ea942acae 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1344,7 +1344,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { strongSelf.isPlayingPromise.set(false) strongSelf.isPlaying = false if strongSelf.isCentral == true { - strongSelf.updateControlsVisibility(true) + if !item.isSecret { + strongSelf.updateControlsVisibility(true) + } } } } diff --git a/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift b/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift index b930b63cac..040e84b471 100644 --- a/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift +++ b/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift @@ -321,11 +321,13 @@ public final class SecretMediaPreviewController: ViewController { } } + var timerStarted = false let isOutgoing = !message.flags.contains(.Incoming) if let attribute = message.autoclearAttribute { strongSelf.currentNodeMessageIsViewOnce = attribute.timeout == viewOnceTimeout if let countdownBeginTime = attribute.countdownBeginTime { + timerStarted = true if let videoDuration = videoDuration, attribute.timeout != viewOnceTimeout { beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, max(videoDuration, Double(attribute.timeout)), isOutgoing) } else { @@ -338,6 +340,7 @@ public final class SecretMediaPreviewController: ViewController { strongSelf.currentNodeMessageIsViewOnce = attribute.timeout == viewOnceTimeout if let countdownBeginTime = attribute.countdownBeginTime { + timerStarted = true if let videoDuration = videoDuration, attribute.timeout != viewOnceTimeout { beginTimeAndTimeout = (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, max(videoDuration, Double(attribute.timeout)), isOutgoing) } else { @@ -370,12 +373,12 @@ public final class SecretMediaPreviewController: ViewController { strongSelf.controllerNode.beginTimeAndTimeout = beginTimeAndTimeout } - if strongSelf.currentNodeMessageIsVideo { + if message.flags.contains(.Incoming) || strongSelf.currentNodeMessageIsVideo { if let node = strongSelf.controllerNode.pager.centralItemNode() { strongSelf.footerContentNode.set(node.footerContent()) } - } else if !message.flags.contains(.Incoming) { - if let _ = beginTimeAndTimeout { + } else { + if timerStarted { strongSelf.controllerNode.updatePresentationState({ $0.withUpdatedFooterContentNode(nil) }, transition: .immediate) diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h index e420bc3634..fed6a04d4c 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h @@ -36,7 +36,7 @@ - (void)setCaption:(NSAttributedString *)caption animated:(bool)animated; - (void)setCaptionPanelHidden:(bool)hidden animated:(bool)animated; -- (void)setTimeout:(int32_t)timeout; +- (void)setTimeout:(int32_t)timeout isVideo:(bool)isVideo; - (void)updateLayoutWithFrame:(CGRect)frame edgeInsets:(UIEdgeInsets)edgeInsets animated:(bool)animated; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h index eaf71260ae..edfd7b7a28 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h @@ -22,7 +22,7 @@ @property (nonatomic, readonly) UIView * _Nonnull view; -- (void)setTimeout:(int32_t)timeout; +- (void)setTimeout:(int32_t)timeout isVideo:(bool)isVideo; - (NSAttributedString * _Nonnull)caption; - (void)setCaption:(NSAttributedString * _Nullable)caption; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index 774db532df..177ec3ddbe 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -838,7 +838,7 @@ id adjustments = dict[@"adjustments"]; NSNumber *timer = dict[@"timer"]; - [strongSelf->_captionMixin setTimeout:[timer intValue]]; + [strongSelf->_captionMixin setTimeout:[timer intValue] isVideo:editableMediaItem.isVideo]; if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]]) { diff --git a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m index 08d8017a9f..7393ad99e0 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m +++ b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m @@ -141,8 +141,8 @@ [_inputPanel setCaption:caption]; } -- (void)setTimeout:(int32_t)timeout { - [_inputPanel setTimeout:timeout]; +- (void)setTimeout:(int32_t)timeout isVideo:(bool)isVideo { + [_inputPanel setTimeout:timeout isVideo:isVideo]; } - (void)setCaptionPanelHidden:(bool)hidden animated:(bool)__unused animated diff --git a/submodules/StatisticsUI/BUILD b/submodules/StatisticsUI/BUILD index 299a082dee..3a79d9bcd3 100644 --- a/submodules/StatisticsUI/BUILD +++ b/submodules/StatisticsUI/BUILD @@ -32,6 +32,7 @@ swift_library( "//submodules/ItemListPeerItem:ItemListPeerItem", "//submodules/ItemListPeerActionItem:ItemListPeerActionItem", "//submodules/ContextUI:ContextUI", + "//submodules/PremiumUI:PremiumUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index 8afb29f4c1..0f3cf05f66 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -290,11 +290,15 @@ private func maybePredownloadedImageResource(postbox: Postbox, peerId: PeerId, r |> switchToLatest } -private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, peerId: PeerId, resource: MediaResource, forceRefresh: Bool) -> Signal { +private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, peerId: PeerId, resource: MediaResource, autoRemove: Bool, forceRefresh: Bool) -> Signal { if peerId.namespace == Namespaces.Peer.SecretChat { return .single(.none) } + if autoRemove { + return .single(.none) + } + #if DEBUG if !"".isEmpty { return .single(.none) @@ -506,7 +510,13 @@ if "".isEmpty { if hasSpoiler { flags |= 1 << 1 } - return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(flags: flags, id: .inputPhoto(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: nil)), media: mediaImage) + + let result: PendingMessageUploadedContentResult = .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(flags: flags, id: .inputPhoto(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: nil)) + if let _ = ttlSeconds { + return .single(result) + } else { + return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: result, media: mediaImage) + } } default: break @@ -656,7 +666,7 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA } private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, forceNoBigParts: Bool, peerId: PeerId, messageId: MessageId?, text: String, attributes: [MessageAttribute], autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?, autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?, file: TelegramMediaFile) -> Signal { - return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, peerId: peerId, resource: file.resource, forceRefresh: forceReupload) + return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, peerId: peerId, resource: file.resource, autoRemove: autoremoveMessageAttribute != nil || autoclearMessageAttribute != nil, forceRefresh: forceReupload) |> mapToSignal { result -> Signal in var referenceKey: CachedSentMediaReferenceKey? switch result { @@ -890,10 +900,21 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili case let .messageMediaDocument(_, document, _, _): if let document = document, let mediaFile = telegramMediaFileFromApiDocument(document), let resource = mediaFile.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference { var flags: Int32 = 0 + var ttlSeconds: Int32? + if let autoclearMessageAttribute = autoclearMessageAttribute { + flags |= 1 << 0 + ttlSeconds = autoclearMessageAttribute.timeout + } if hasSpoiler { flags |= (1 << 2) } - return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaDocument(flags: flags, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil)), media: mediaFile) + + let result: PendingMessageUploadedContentResult = .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaDocument(flags: flags, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil)) + if let _ = ttlSeconds { + return .single(result) + } else { + return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: result, media: mediaFile) + } } default: break diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift index 6b4d2d8383..5745644894 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift @@ -31,6 +31,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { private var currentTimeout: Int32? private var currentIsEditing = false private var currentHeight: CGFloat? + private var currentIsVideo = false private let hapticFeedback = HapticFeedback() @@ -95,13 +96,14 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { transition.setFrame(view: view, frame: frame) } - public func setTimeout(_ timeout: Int32) { + public func setTimeout(_ timeout: Int32, isVideo: Bool) { self.dismissTimeoutTooltip() var timeout: Int32? = timeout if timeout == 0 { timeout = nil } self.currentTimeout = timeout + self.currentIsVideo = isVideo } public func dismissInput() { @@ -317,10 +319,13 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { let updateTimeout: (Int32?) -> Void = { [weak self] timeout in if let self { + let previousTimeout = self.currentTimeout self.currentTimeout = timeout self.timerUpdated?(timeout as? NSNumber) self.update(transition: .immediate) - self.presentTimeoutTooltip(sourceView: sourceView, timeout: timeout) + if previousTimeout != timeout { + self.presentTimeoutTooltip(sourceView: sourceView, timeout: timeout) + } } } @@ -382,7 +387,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0) let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 2.0), size: CGSize()) - let isVideo = !"".isEmpty + let isVideo = self.currentIsVideo let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let text: String let iconName: String From fa57a05bcbfb6dabc3c280c1634efddb470fddbb Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 9 Sep 2023 20:49:46 +0400 Subject: [PATCH 08/11] Media editor improvements --- .../Sources/AttachmentTextInputPanelNode.swift | 5 +++-- .../TGPhotoPaintStickersContext.h | 2 +- .../Sources/TGPhotoCaptionInputMixin.m | 15 ++++++++------- .../Sources/LegacyMessageInputPanel.swift | 18 +++++++++++++----- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index 744a83953f..dc68e2f27f 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -476,8 +476,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS public func onAnimateOut() { } - public func dismissInput() { + public func dismissInput() -> Bool { self.ensureUnfocused() + return true } public func baseHeight() -> CGFloat { @@ -1695,7 +1696,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } if let sendPressed = self.sendPressed, let presentationInterfaceState = self.effectivePresentationInterfaceState?() { - self.dismissInput() + let _ = self.dismissInput() let effectiveInputText = presentationInterfaceState.interfaceState.composeInputState.inputText sendPressed(effectiveInputText) return diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h index edfd7b7a28..61ae441820 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h @@ -26,7 +26,7 @@ - (NSAttributedString * _Nonnull)caption; - (void)setCaption:(NSAttributedString * _Nullable)caption; -- (void)dismissInput; +- (bool)dismissInput; - (void)animateView:(UIView * _Nonnull)view frame:(CGRect)frame; diff --git a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m index 7393ad99e0..1a0bc8ce62 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m +++ b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m @@ -170,13 +170,14 @@ if (gestureRecognizer.state != UIGestureRecognizerStateRecognized) return; - _editing = false; - - [self.inputPanel dismissInput]; - [_dismissView removeFromSuperview]; - - if (self.finishedWithCaption != nil) - self.finishedWithCaption([_inputPanel caption]); + if ([self.inputPanel dismissInput]) { + _editing = false; + + [_dismissView removeFromSuperview]; + + if (self.finishedWithCaption != nil) + self.finishedWithCaption([_inputPanel caption]); + } } #pragma mark - Input Panel Delegate diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift index 5745644894..15312be8a7 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift @@ -106,11 +106,19 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { self.currentIsVideo = isVideo } - public func dismissInput() { + public func dismissInput() -> Bool { if let view = self.inputPanel.view as? MessageInputPanelComponent.View { - self.isEmojiKeyboardActive = false - self.inputView = nil - view.deactivateInput(force: true) + if view.canDeactivateInput() { + self.isEmojiKeyboardActive = false + self.inputView = nil + view.deactivateInput(force: true) + return true + } else { + view.animateError() + return false + } + } else { + return true } } @@ -198,7 +206,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { sendMessageAction: { [weak self] in if let self { self.sendPressed?(self.caption()) - self.dismissInput() + let _ = self.dismissInput() } }, sendMessageOptionsAction: nil, From b8aa47ec81c4431785f0e0a6362d146a3605a0cf Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 9 Sep 2023 21:49:09 +0400 Subject: [PATCH 09/11] Web app improvements --- submodules/WebUI/Sources/WebAppController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index ac7d8f9b5a..4319ea2e02 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -53,9 +53,10 @@ public class WebAppCancelButtonNode: ASDisplayNode { private let strings: PresentationStrings public func updateColor(_ color: UIColor?, transition: ContainedViewLayoutTransition) { + let previousColor = self.color self.color = color - if case let .animated(duration, curve) = transition { + if case let .animated(duration, curve) = transition, previousColor != color { if let snapshotView = self.view.snapshotContentTree() { snapshotView.frame = self.bounds self.view.addSubview(snapshotView) From edbdcc2f0d7792d2fd080c71ce6aeb6bfdfca942 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 10 Sep 2023 02:25:57 +0400 Subject: [PATCH 10/11] Various fixes --- .../Sources/TextFieldComponent.swift | 10 +++++++--- submodules/TelegramUI/Sources/ChatController.swift | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index ad7753684b..24f6b9349c 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -825,9 +825,13 @@ public final class TextFieldComponent: Component { var rightmostX = lineRect.maxX + padding let rightmostY = lineRect.minY + self.textView.textContainerInset.top - let stringIndex = self.textView.text.index(self.textView.text.startIndex, offsetBy: lineRange.location + lineRange.length - 1) - if self.textView.text[stringIndex] == " " { - rightmostX -= 3.0 + let nsString = (self.textView.text as NSString) + let firstLineEndRange = NSMakeRange(lineRange.location + lineRange.length - 1, 1) + if nsString.length > firstLineEndRange.location + firstLineEndRange.length { + let lastChar = nsString.substring(with: firstLineEndRange) + if lastChar == " " { + rightmostX -= 2.0 + } } return CGPoint(x: rightmostX, y: rightmostY) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 22b5b41351..8ec6cdceca 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -19381,6 +19381,8 @@ func canAddMessageReactions(message: Message) -> Bool { if story.isMention { return false } + } else if let _ = media as? TelegramMediaExpiredContent { + return false } } return true From a70c839fb488a4a482d12479eed715aa6bd9a661 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 10 Sep 2023 15:38:18 +0400 Subject: [PATCH 11/11] Web app improvements --- Telegram/Telegram-iOS/en.lproj/Localizable.strings | 2 ++ .../GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift | 6 ++++++ submodules/WebUI/Sources/WebAppController.swift | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index bfd9d0aceb..95647dc58e 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9967,3 +9967,5 @@ Sorry for the inconvenience."; "SessionReview.Title" = "New Login Prevented"; "SessionReview.Text" = "We have terminated the login attempt from **%1$@**, **%2$@**"; "SessionReview.OkAction" = "Got it"; + +"WebApp.RemoveAllConfirmationText" = "This will remove **%@** shortcuts from all menus."; diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 3ea942acae..411c2b64a3 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1690,6 +1690,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.hideStatusNodeUntilCentrality = false self.statusButtonNode.isHidden = self.hideStatusNodeUntilCentrality || self.statusNodeShouldBeHidden videoNode.playOnceWithSound(playAndRecord: false, seek: seek, actionAtEnd: self.actionAtEnd) + + Queue.mainQueue().after(1.0, { + if let item = self.item, item.isSecret, !self.isPlaying { + videoNode.playOnceWithSound(playAndRecord: false, seek: .start, actionAtEnd: self.actionAtEnd) + } + }) } } } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 4319ea2e02..82f5f4f6f7 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -1530,7 +1530,7 @@ public final class WebAppController: ViewController, AttachmentContainable { if let strongSelf = self { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - strongSelf.present(textAlertController(context: context, title: presentationData.strings.WebApp_RemoveConfirmationTitle, text: presentationData.strings.WebApp_RemoveConfirmationText(strongSelf.botName).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { [weak self] in + strongSelf.present(textAlertController(context: context, title: presentationData.strings.WebApp_RemoveConfirmationTitle, text: presentationData.strings.WebApp_RemoveAllConfirmationText(strongSelf.botName).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { [weak self] in if let strongSelf = self { let _ = context.engine.messages.removeBotFromAttachMenu(botId: strongSelf.botId).start() strongSelf.dismiss()