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)