Merge commit 'a70c839fb488a4a482d12479eed715aa6bd9a661'

This commit is contained in:
Ali 2023-09-10 15:54:27 +04:00
commit b0894fa9e8
46 changed files with 538 additions and 192 deletions

View File

@ -9914,14 +9914,15 @@ Sorry for the inconvenience.";
"Gallery.ViewOncePhotoTooltip" = "This photo can only be viewed once."; "Gallery.ViewOncePhotoTooltip" = "This photo can only be viewed once.";
"Gallery.ViewOnceVideoTooltip" = "This video 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.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.DisclaimerShortcutsSettingsText" = "**%@** shortcuts will be added in your attachment menu and Settings.";
"WebApp.DisclaimerAgree" = "I agree to the [Terms of Use]()"; "WebApp.DisclaimerAgree" = "I agree to the [Terms of Use]()";
"WebApp.DisclaimerContinue" = "Continue"; "WebApp.DisclaimerContinue" = "Continue";
"WebApp.Disclaimer_URL" = "https://telegram.org/tos/mini-apps"; "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.AllowWriteTitle" = "Allow Sending Messages?";
"WebApp.AllowWriteConfirmation" = "This will allow the bot **%@** to message you on Telegram."; "WebApp.AllowWriteConfirmation" = "This will allow the bot **%@** to message you on Telegram.";
@ -9966,3 +9967,5 @@ Sorry for the inconvenience.";
"SessionReview.Title" = "New Login Prevented"; "SessionReview.Title" = "New Login Prevented";
"SessionReview.Text" = "We have terminated the login attempt from **%1$@**, **%2$@**"; "SessionReview.Text" = "We have terminated the login attempt from **%1$@**, **%2$@**";
"SessionReview.OkAction" = "Got it"; "SessionReview.OkAction" = "Got it";
"WebApp.RemoveAllConfirmationText" = "This will remove **%@** shortcuts from all menus.";

View File

@ -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) { public func animate(_ view: UIView, frame: CGRect) {
@ -476,8 +476,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
public func onAnimateOut() { public func onAnimateOut() {
} }
public func dismissInput() { public func dismissInput() -> Bool {
self.ensureUnfocused() self.ensureUnfocused()
return true
} }
public func baseHeight() -> CGFloat { public func baseHeight() -> CGFloat {
@ -1695,7 +1696,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
} }
} }
if let sendPressed = self.sendPressed, let presentationInterfaceState = self.effectivePresentationInterfaceState?() { if let sendPressed = self.sendPressed, let presentationInterfaceState = self.effectivePresentationInterfaceState?() {
self.dismissInput() let _ = self.dismissInput()
let effectiveInputText = presentationInterfaceState.interfaceState.composeInputState.inputText let effectiveInputText = presentationInterfaceState.interfaceState.composeInputState.inputText
sendPressed(effectiveInputText) sendPressed(effectiveInputText)
return return

View File

@ -19,7 +19,7 @@ public enum AttachmentButtonType: Equatable {
case location case location
case contact case contact
case poll case poll
case app(EnginePeer, String, [AttachMenuBots.Bot.IconName: TelegramMediaFile]) case app(AttachMenuBot)
case gift case gift
case standalone case standalone
@ -55,8 +55,8 @@ public enum AttachmentButtonType: Equatable {
} else { } else {
return false return false
} }
case let .app(lhsPeer, lhsTitle, lhsIcons): case let .app(lhsBot):
if case let .app(rhsPeer, rhsTitle, rhsIcons) = rhs, lhsPeer == rhsPeer, lhsTitle == rhsTitle, lhsIcons == rhsIcons { if case let .app(rhsBot) = rhs, lhsBot.peer.id == rhsBot.peer.id {
return true return true
} else { } else {
return false return false
@ -446,9 +446,9 @@ public class AttachmentController: ViewController {
if let controller = self.controller { if let controller = self.controller {
let _ = self.switchToController(controller.initialButton) 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 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 return true
} else { } else {
return false return false

View File

@ -200,15 +200,15 @@ private final class AttachButtonComponent: CombinedComponent {
case .gift: case .gift:
name = strings.Attachment_Gift name = strings.Attachment_Gift
imageName = "Chat/Attach Menu/Gift" imageName = "Chat/Attach Menu/Gift"
case let .app(peer, appName, appIcons): case let .app(bot):
botPeer = peer botPeer = bot.peer
name = appName name = bot.shortName
imageName = "" imageName = ""
if let file = appIcons[.iOSAnimated] { if let file = bot.icons[.iOSAnimated] {
animationFile = file animationFile = file
} else if let file = appIcons[.iOSStatic] { } else if let file = bot.icons[.iOSStatic] {
imageFile = file imageFile = file
} else if let file = appIcons[.default] { } else if let file = bot.icons[.default] {
imageFile = file imageFile = file
} }
case .standalone: case .standalone:
@ -1142,10 +1142,10 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
} }
let type = self.buttons[i] let type = self.buttons[i]
if case let .app(peer, _, iconFiles) = type { if case let .app(bot) = type {
for (name, file) in iconFiles { for (name, file) in bot.icons {
if [.default, .iOSAnimated, .placeholder].contains(name) { if [.default, .iOSAnimated, .iOSSettingsStatic, .placeholder].contains(name) {
if self.iconDisposables[file.fileId] == nil, let peer = PeerReference(peer._asPeer()) { if self.iconDisposables[file.fileId] == nil, let peer = PeerReference(bot.peer._asPeer()) {
if case .placeholder = name { if case .placeholder = name {
let account = self.context.account let account = self.context.account
let path = account.postbox.mediaBox.cachedRepresentationCompletePath(file.resource.id, representation: CachedPreparedSvgRepresentation()) 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 accessibilityTitle = self.presentationData.strings.Attachment_Poll
case .gift: case .gift:
accessibilityTitle = self.presentationData.strings.Attachment_Gift accessibilityTitle = self.presentationData.strings.Attachment_Gift
case let .app(_, appName, _): case let .app(bot):
accessibilityTitle = appName accessibilityTitle = bot.shortName
case .standalone: case .standalone:
accessibilityTitle = "" accessibilityTitle = ""
} }

View File

@ -631,7 +631,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
func setMessage(_ message: Message, displayInfo: Bool = true, translateToLanguage: String? = nil, peerIsCopyProtected: Bool = false) { func setMessage(_ message: Message, displayInfo: Bool = true, translateToLanguage: String? = nil, peerIsCopyProtected: Bool = false) {
self.currentMessage = message self.currentMessage = message
let canDelete: Bool var canDelete: Bool
var canShare = !message.containsSecretMedia var canShare = !message.containsSecretMedia
var canFullscreen = false var canFullscreen = false
@ -715,6 +715,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
canEdit = false canEdit = false
} }
if message.containsSecretMedia {
canDelete = false
}
var authorNameText: String? var authorNameText: String?
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), let authorSignature = forwardInfo.authorSignature { if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), let authorSignature = forwardInfo.authorSignature {
authorNameText = authorSignature authorNameText = authorSignature

View File

@ -1344,7 +1344,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
strongSelf.isPlayingPromise.set(false) strongSelf.isPlayingPromise.set(false)
strongSelf.isPlaying = false strongSelf.isPlaying = false
if strongSelf.isCentral == true { if strongSelf.isCentral == true {
strongSelf.updateControlsVisibility(true) if !item.isSecret {
strongSelf.updateControlsVisibility(true)
}
} }
} }
} }
@ -1688,6 +1690,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.hideStatusNodeUntilCentrality = false self.hideStatusNodeUntilCentrality = false
self.statusButtonNode.isHidden = self.hideStatusNodeUntilCentrality || self.statusNodeShouldBeHidden self.statusButtonNode.isHidden = self.hideStatusNodeUntilCentrality || self.statusNodeShouldBeHidden
videoNode.playOnceWithSound(playAndRecord: false, seek: seek, actionAtEnd: self.actionAtEnd) 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)
}
})
} }
} }
} }

View File

@ -60,16 +60,20 @@ private final class SecretMediaPreviewControllerNode: GalleryControllerNode {
private var validLayout: (ContainerViewLayout, CGFloat)? private var validLayout: (ContainerViewLayout, CGFloat)?
var beginTimeAndTimeout: (Double, Double)? { var beginTimeAndTimeout: (Double, Double, Bool)? {
didSet { didSet {
if let (beginTime, timeout) = self.beginTimeAndTimeout { if let (beginTime, timeout, isOutgoing) = self.beginTimeAndTimeout {
var beginTime = beginTime var beginTime = beginTime
if self.timeoutNode == nil { if self.timeoutNode == nil {
let timeoutNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5)) let timeoutNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
self.timeoutNode = timeoutNode self.timeoutNode = timeoutNode
let icon: RadialStatusNodeState.SecretTimeoutIcon let icon: RadialStatusNodeState.SecretTimeoutIcon
let timeoutValue = Int32(timeout) let timeoutValue = Int32(timeout)
if timeoutValue == viewOnceTimeout {
let state: RadialStatusNodeState
if timeoutValue == 0 && isOutgoing {
state = .staticTimeout
} else if timeoutValue == viewOnceTimeout {
beginTime = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 beginTime = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
if let image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/ViewOnce"), color: .white) { if let image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/ViewOnce"), color: .white) {
@ -77,10 +81,11 @@ private final class SecretMediaPreviewControllerNode: GalleryControllerNode {
} else { } else {
icon = .flame icon = .flame
} }
state = .secretTimeout(color: .white, icon: icon, beginTime: beginTime, timeout: timeout, sparks: isOutgoing ? false : true)
} else { } 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) self.addSubnode(timeoutNode)
timeoutNode.addTarget(self, action: #selector(self.statusTapGesture), forControlEvents: .touchUpInside) timeoutNode.addTarget(self, action: #selector(self.statusTapGesture), forControlEvents: .touchUpInside)
@ -308,7 +313,7 @@ public final class SecretMediaPreviewController: ViewController {
var hiddenItem: (MessageId, Media)? var hiddenItem: (MessageId, Media)?
if let _ = index { if let _ = index {
if let message = strongSelf.messageView?.message, let media = mediaForMessage(message: message) { if let message = strongSelf.messageView?.message, let media = mediaForMessage(message: message) {
var beginTimeAndTimeout: (Double, Double)? var beginTimeAndTimeout: (Double, Double, Bool)?
var videoDuration: Double? var videoDuration: Double?
for media in message.media { for media in message.media {
if let file = media as? TelegramMediaFile { if let file = media as? TelegramMediaFile {
@ -316,26 +321,34 @@ public final class SecretMediaPreviewController: ViewController {
} }
} }
var timerStarted = false
let isOutgoing = !message.flags.contains(.Incoming)
if let attribute = message.autoclearAttribute { if let attribute = message.autoclearAttribute {
strongSelf.currentNodeMessageIsViewOnce = attribute.timeout == viewOnceTimeout strongSelf.currentNodeMessageIsViewOnce = attribute.timeout == viewOnceTimeout
if let countdownBeginTime = attribute.countdownBeginTime { if let countdownBeginTime = attribute.countdownBeginTime {
timerStarted = true
if let videoDuration = videoDuration, attribute.timeout != viewOnceTimeout { 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 { } 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 { } else if let attribute = message.autoremoveAttribute {
strongSelf.currentNodeMessageIsViewOnce = attribute.timeout == viewOnceTimeout strongSelf.currentNodeMessageIsViewOnce = attribute.timeout == viewOnceTimeout
if let countdownBeginTime = attribute.countdownBeginTime { if let countdownBeginTime = attribute.countdownBeginTime {
timerStarted = true
if let videoDuration = videoDuration, attribute.timeout != viewOnceTimeout { 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 { } 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 { if let file = media as? TelegramMediaFile {
@ -360,12 +373,12 @@ public final class SecretMediaPreviewController: ViewController {
strongSelf.controllerNode.beginTimeAndTimeout = beginTimeAndTimeout strongSelf.controllerNode.beginTimeAndTimeout = beginTimeAndTimeout
} }
if strongSelf.currentNodeMessageIsVideo { if message.flags.contains(.Incoming) || strongSelf.currentNodeMessageIsVideo {
if let node = strongSelf.controllerNode.pager.centralItemNode() { if let node = strongSelf.controllerNode.pager.centralItemNode() {
strongSelf.footerContentNode.set(node.footerContent()) strongSelf.footerContentNode.set(node.footerContent())
} }
} else if !message.flags.contains(.Incoming) { } else {
if let _ = beginTimeAndTimeout { if timerStarted {
strongSelf.controllerNode.updatePresentationState({ strongSelf.controllerNode.updatePresentationState({
$0.withUpdatedFooterContentNode(nil) $0.withUpdatedFooterContentNode(nil)
}, transition: .immediate) }, transition: .immediate)
@ -537,28 +550,34 @@ public final class SecretMediaPreviewController: ViewController {
self._ready.set(ready |> map { true }) self._ready.set(ready |> map { true })
self.markMessageAsConsumedDisposable.set(self.context.engine.messages.markMessageContentAsConsumedInteractively(messageId: message.id).start()) self.markMessageAsConsumedDisposable.set(self.context.engine.messages.markMessageContentAsConsumedInteractively(messageId: message.id).start())
} else { } else {
var beginTimeAndTimeout: (Double, Double)? var beginTimeAndTimeout: (Double, Double, Bool)?
var videoDuration: Double? var videoDuration: Double?
for media in message.media { for media in message.media {
if let file = media as? TelegramMediaFile { if let file = media as? TelegramMediaFile {
videoDuration = file.duration videoDuration = file.duration
} }
} }
let isOutgoing = !message.flags.contains(.Incoming)
if let attribute = message.autoclearAttribute { if let attribute = message.autoclearAttribute {
if let countdownBeginTime = attribute.countdownBeginTime { if let countdownBeginTime = attribute.countdownBeginTime {
if let videoDuration = videoDuration, attribute.timeout != viewOnceTimeout { 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 { } 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 { } else if let attribute = message.autoremoveAttribute {
if let countdownBeginTime = attribute.countdownBeginTime { if let countdownBeginTime = attribute.countdownBeginTime {
if let videoDuration = videoDuration, attribute.timeout != viewOnceTimeout { 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 { } 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 +636,7 @@ public final class SecretMediaPreviewController: ViewController {
location: .point(location, .top), location: .point(location, .top),
displayDuration: .default, displayDuration: .default,
inset: 8.0, inset: 8.0,
cornerRadius: 8.0,
shouldDismissOnTouch: { _, _ in shouldDismissOnTouch: { _, _ in
return .ignore return .ignore
} }

View File

@ -54,6 +54,8 @@
- (void)editorTransitionIn; - (void)editorTransitionIn;
- (void)editorTransitionOut; - (void)editorTransitionOut;
- (void)onDismiss;
- (void)setTabBarUserInteractionEnabled:(bool)enabled; - (void)setTabBarUserInteractionEnabled:(bool)enabled;
@end @end

View File

@ -36,7 +36,7 @@
- (void)setCaption:(NSAttributedString *)caption animated:(bool)animated; - (void)setCaption:(NSAttributedString *)caption animated:(bool)animated;
- (void)setCaptionPanelHidden:(bool)hidden 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; - (void)updateLayoutWithFrame:(CGRect)frame edgeInsets:(UIEdgeInsets)edgeInsets animated:(bool)animated;

View File

@ -22,11 +22,11 @@
@property (nonatomic, readonly) UIView * _Nonnull view; @property (nonatomic, readonly) UIView * _Nonnull view;
- (void)setTimeout:(int32_t)timeout; - (void)setTimeout:(int32_t)timeout isVideo:(bool)isVideo;
- (NSAttributedString * _Nonnull)caption; - (NSAttributedString * _Nonnull)caption;
- (void)setCaption:(NSAttributedString * _Nullable)caption; - (void)setCaption:(NSAttributedString * _Nullable)caption;
- (void)dismissInput; - (bool)dismissInput;
- (void)animateView:(UIView * _Nonnull)view frame:(CGRect)frame; - (void)animateView:(UIView * _Nonnull)view frame:(CGRect)frame;

View File

@ -838,7 +838,7 @@
id<TGMediaEditAdjustments> adjustments = dict[@"adjustments"]; id<TGMediaEditAdjustments> adjustments = dict[@"adjustments"];
NSNumber *timer = dict[@"timer"]; NSNumber *timer = dict[@"timer"];
[strongSelf->_captionMixin setTimeout:[timer intValue]]; [strongSelf->_captionMixin setTimeout:[timer intValue] isVideo:editableMediaItem.isVideo];
if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]]) if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]])
{ {
@ -1328,6 +1328,10 @@
[_captionMixin onAnimateOut]; [_captionMixin onAnimateOut];
} }
- (void)onDismiss {
[_captionMixin onAnimateOut];
}
- (void)setTransitionOutProgress:(CGFloat)transitionOutProgress manual:(bool)manual - (void)setTransitionOutProgress:(CGFloat)transitionOutProgress manual:(bool)manual
{ {
[_captionMixin onAnimateOut]; [_captionMixin onAnimateOut];

View File

@ -141,8 +141,8 @@
[_inputPanel setCaption:caption]; [_inputPanel setCaption:caption];
} }
- (void)setTimeout:(int32_t)timeout { - (void)setTimeout:(int32_t)timeout isVideo:(bool)isVideo {
[_inputPanel setTimeout:timeout]; [_inputPanel setTimeout:timeout isVideo:isVideo];
} }
- (void)setCaptionPanelHidden:(bool)hidden animated:(bool)__unused animated - (void)setCaptionPanelHidden:(bool)hidden animated:(bool)__unused animated
@ -170,13 +170,14 @@
if (gestureRecognizer.state != UIGestureRecognizerStateRecognized) if (gestureRecognizer.state != UIGestureRecognizerStateRecognized)
return; return;
_editing = false; if ([self.inputPanel dismissInput]) {
_editing = false;
[self.inputPanel dismissInput];
[_dismissView removeFromSuperview]; [_dismissView removeFromSuperview];
if (self.finishedWithCaption != nil) if (self.finishedWithCaption != nil)
self.finishedWithCaption([_inputPanel caption]); self.finishedWithCaption([_inputPanel caption]);
}
} }
#pragma mark - Input Panel Delegate #pragma mark - Input Panel Delegate

View File

@ -282,7 +282,9 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?,
dismissImpl() dismissImpl()
}) })
} }
sheetController.sendSilently = { sheetController.sendSilently = { [weak model] in
model?.interfaceView.onDismiss()
completed(item.asset, true, nil, { completed(item.asset, true, nil, {
dismissImpl() dismissImpl()
}) })

View File

@ -50,6 +50,7 @@ final class MediaPickerTitleView: UIView {
transition.updateAlpha(node: self.arrowNode, alpha: self.segmentsHidden ? 1.0 : 0.0) transition.updateAlpha(node: self.arrowNode, alpha: self.segmentsHidden ? 1.0 : 0.0)
transition.updateAlpha(node: self.segmentedControlNode, alpha: self.segmentsHidden ? 0.0 : 1.0) transition.updateAlpha(node: self.segmentedControlNode, alpha: self.segmentsHidden ? 0.0 : 1.0)
self.segmentedControlNode.isUserInteractionEnabled = !self.segmentsHidden self.segmentedControlNode.isUserInteractionEnabled = !self.segmentsHidden
self.buttonNode.isUserInteractionEnabled = self.isEnabled && self.segmentsHidden
} }
} }
} }

View File

@ -581,7 +581,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
guard let peer = peer, let user = user else { guard let peer = peer, let user = user else {
return 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)
}) })
} }

View File

@ -21,7 +21,7 @@ private final class RadialStatusIconContentNodeParameters: NSObject {
} }
final class RadialStatusIconContentNode: RadialStatusContentNode { final class RadialStatusIconContentNode: RadialStatusContentNode {
private let icon: RadialStatusIcon let icon: RadialStatusIcon
private var animationNode: FireIconNode? private var animationNode: FireIconNode?
@ -35,7 +35,7 @@ final class RadialStatusIconContentNode: RadialStatusContentNode {
self.isOpaque = false self.isOpaque = false
if case .timeout = icon { if case .timeout = icon {
let animationNode = FireIconNode() let animationNode = FireIconNode(animate: true)
self.animationNode = animationNode self.animationNode = animationNode
self.addSubnode(animationNode) self.addSubnode(animationNode)
} }
@ -44,7 +44,14 @@ final class RadialStatusIconContentNode: RadialStatusContentNode {
override func layout() { override func layout() {
super.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? { override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {

View File

@ -224,7 +224,11 @@ public enum RadialStatusNodeState: Equatable {
case .staticTimeout: case .staticTimeout:
return RadialStatusIconContentNode(icon: .timeout, synchronous: synchronous) return RadialStatusIconContentNode(icon: .timeout, synchronous: synchronous)
case let .secretTimeout(color, icon, beginTime, timeout, sparks): 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)
} }
} }
} }

View File

@ -29,13 +29,15 @@ private final class RadialStatusSecretTimeoutContentNodeParameters: NSObject {
let progress: CGFloat let progress: CGFloat
let sparks: Bool let sparks: Bool
let particles: [ContentParticle] 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.color = color
self.icon = icon self.icon = icon
self.progress = progress self.progress = progress
self.sparks = sparks self.sparks = sparks
self.particles = particles self.particles = particles
self.alphaProgress = alphaProgress
} }
} }
@ -51,14 +53,17 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
private let icon: RadialStatusNodeState.SecretTimeoutIcon private let icon: RadialStatusNodeState.SecretTimeoutIcon
private let sparks: Bool private let sparks: Bool
private var animationBeginTime: Double?
private var progress: CGFloat = 0.0 private var progress: CGFloat = 0.0
private var alphaProgress: CGFloat = 0.0
private var particles: [ContentParticle] = [] private var particles: [ContentParticle] = []
private let animationNode = FireIconNode() private var animationNode: FireIconNode?
private var displayLink: CADisplayLink? 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.color = color
self.beginTime = beginTime self.beginTime = beginTime
self.timeout = timeout self.timeout = timeout
@ -85,7 +90,13 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
self.displayLink?.add(to: RunLoop.main, forMode: .common) self.displayLink?.add(to: RunLoop.main, forMode: .common)
if case .flame = icon { 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 offset = 0.08
} }
let size = floorToScreenPixels(self.bounds.width * factor) 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) { override func animateOut(to: RadialStatusNodeState, completion: @escaping () -> Void) {
@ -132,12 +143,23 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
return return
} }
let absoluteTimestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 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)) var progress = min(1.0, CGFloat((absoluteTimestamp - self.beginTime) / self.timeout))
if self.timeout == 0x7fffffff { if self.timeout == 0x7fffffff {
progress = 0.0 progress = 0.0
} }
self.progress = progress self.progress = progress
self.alphaProgress = alphaProgress
if self.sparks { if self.sparks {
let lineWidth: CGFloat = 1.75 let lineWidth: CGFloat = 1.75
@ -193,7 +215,7 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
} }
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { 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) { @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 let endAngle: CGFloat = -CGFloat.pi / 2.0 + 2.0 * CGFloat.pi * parameters.progress
if drawArc { if drawArc {
context.setAlpha(parameters.alphaProgress)
let path = CGMutablePath() let path = CGMutablePath()
path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true) path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
context.addPath(path) context.addPath(path)
@ -248,7 +272,7 @@ final class RadialStatusSecretTimeoutContentNode: RadialStatusContentNode {
for particle in parameters.particles { for particle in parameters.particles {
let size: CGFloat = 1.3 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))) 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 { final class FireIconNode: ManagedAnimationNode {
init() { init(animate: Bool) {
super.init(size: CGSize(width: 100.0, height: 100.0)) 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))
}
} }
} }

View File

@ -377,7 +377,7 @@ public func dataPrivacyController(context: AccountContext) -> ViewController {
text = nil text = nil
} }
if let text = text { 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 return state
} }
let presentationData = context.sharedContext.currentPresentationData.with { $0 } 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: {})])) }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})]))
} }
@ -476,7 +476,7 @@ public func dataPrivacyController(context: AccountContext) -> ViewController {
return state return state
} }
let presentationData = context.sharedContext.currentPresentationData.with { $0 } 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() dismissAction()

View File

@ -32,6 +32,7 @@ swift_library(
"//submodules/ItemListPeerItem:ItemListPeerItem", "//submodules/ItemListPeerItem:ItemListPeerItem",
"//submodules/ItemListPeerActionItem:ItemListPeerActionItem", "//submodules/ItemListPeerActionItem:ItemListPeerActionItem",
"//submodules/ContextUI:ContextUI", "//submodules/ContextUI:ContextUI",
"//submodules/PremiumUI:PremiumUI",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -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 { } else {
if forceReupload { if forceReupload {
let mediaReference: AnyMediaReference 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))) 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 { } 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 { } else if let contact = media as? TelegramMediaContact {
let input = Api.InputMedia.inputMediaContact(phoneNumber: contact.phoneNumber, firstName: contact.firstName, lastName: contact.lastName, vcard: contact.vCardData ?? "") let input = Api.InputMedia.inputMediaContact(phoneNumber: contact.phoneNumber, firstName: contact.firstName, lastName: contact.lastName, vcard: contact.vCardData ?? "")
@ -290,13 +290,17 @@ private func maybePredownloadedImageResource(postbox: Postbox, peerId: PeerId, r
|> switchToLatest |> switchToLatest
} }
private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, peerId: PeerId, resource: MediaResource, forceRefresh: Bool) -> Signal<PredownloadedResource, PendingMessageUploadError> { private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, peerId: PeerId, resource: MediaResource, autoRemove: Bool, forceRefresh: Bool) -> Signal<PredownloadedResource, PendingMessageUploadError> {
if peerId.namespace == Namespaces.Peer.SecretChat { if peerId.namespace == Namespaces.Peer.SecretChat {
return .single(.none) return .single(.none)
} }
if autoRemove {
return .single(.none)
}
#if DEBUG #if DEBUG
if "".isEmpty { if !"".isEmpty {
return .single(.none) return .single(.none)
} }
#endif #endif
@ -506,7 +510,13 @@ if "".isEmpty {
if hasSpoiler { if hasSpoiler {
flags |= 1 << 1 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: default:
break break
@ -655,14 +665,20 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA
return .file 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<PendingMessageUploadedContentResult, PendingMessageUploadError> { 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<PendingMessageUploadedContentResult, PendingMessageUploadError> {
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<PendingMessageUploadedContentResult, PendingMessageUploadError> in |> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
var referenceKey: CachedSentMediaReferenceKey? var referenceKey: CachedSentMediaReferenceKey?
switch result { switch result {
case let .media(media, key): case let .media(media, key):
if !forceReupload, let file = media as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference { if !forceReupload, let file = media as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference {
var flags: Int32 = 0 var flags: Int32 = 0
var ttlSeconds: Int32?
if let autoclearMessageAttribute = autoclearMessageAttribute {
flags |= 1 << 0
ttlSeconds = autoclearMessageAttribute.timeout
}
for attribute in attributes { for attribute in attributes {
if let _ = attribute as? MediaSpoilerMessageAttribute { if let _ = attribute as? MediaSpoilerMessageAttribute {
flags |= 1 << 2 flags |= 1 << 2
@ -671,7 +687,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
return .single(.progress(1.0)) return .single(.progress(1.0))
|> then( |> 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 referenceKey = key
@ -884,10 +900,21 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
case let .messageMediaDocument(_, document, _, _): case let .messageMediaDocument(_, document, _, _):
if let document = document, let mediaFile = telegramMediaFileFromApiDocument(document), let resource = mediaFile.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference { if let document = document, let mediaFile = telegramMediaFileFromApiDocument(document), let resource = mediaFile.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference {
var flags: Int32 = 0 var flags: Int32 = 0
var ttlSeconds: Int32?
if let autoclearMessageAttribute = autoclearMessageAttribute {
flags |= 1 << 0
ttlSeconds = autoclearMessageAttribute.timeout
}
if hasSpoiler { if hasSpoiler {
flags |= (1 << 2) 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: default:
break break

View File

@ -18,7 +18,9 @@ public final class AttachMenuBots: Equatable, Codable {
case `default` = 0 case `default` = 0
case iOSStatic case iOSStatic
case iOSAnimated case iOSAnimated
case iOSSettingsStatic
case macOSAnimated case macOSAnimated
case macOSSettingsStatic
case placeholder case placeholder
init?(string: String) { init?(string: String) {
@ -29,6 +31,10 @@ public final class AttachMenuBots: Equatable, Codable {
self = .iOSStatic self = .iOSStatic
case "ios_animated": case "ios_animated":
self = .iOSAnimated self = .iOSAnimated
case "ios_side_menu_static":
self = .iOSSettingsStatic
case "macos_side_menu_static":
self = .macOSSettingsStatic
case "macos_animated": case "macos_animated":
self = .macOSAnimated self = .macOSAnimated
case "placeholder_static": case "placeholder_static":
@ -420,6 +426,8 @@ func _internal_addBotToAttachMenu(accountPeerId: PeerId, postbox: Postbox, netwo
} }
func _internal_removeBotFromAttachMenu(accountPeerId: PeerId, postbox: Postbox, network: Network, botId: PeerId) -> Signal<Bool, NoError> { func _internal_removeBotFromAttachMenu(accountPeerId: PeerId, postbox: Postbox, network: Network, botId: PeerId) -> Signal<Bool, NoError> {
let _ = removeCachedAttachMenuBot(postbox: postbox, botId: botId).start()
return postbox.transaction { transaction -> Signal<Bool, NoError> in return postbox.transaction { transaction -> Signal<Bool, NoError> in
guard let peer = transaction.getPeer(botId), let inputUser = apiInputUser(peer) else { guard let peer = transaction.getPeer(botId), let inputUser = apiInputUser(peer) else {
return .complete() return .complete()
@ -439,7 +447,7 @@ func _internal_removeBotFromAttachMenu(accountPeerId: PeerId, postbox: Postbox,
|> afterCompleted { |> afterCompleted {
let _ = (managedSynchronizeAttachMenuBots(accountPeerId: accountPeerId, postbox: postbox, network: network, force: true) let _ = (managedSynchronizeAttachMenuBots(accountPeerId: accountPeerId, postbox: postbox, network: network, force: true)
|> take(1)).start(completed: { |> take(1)).start(completed: {
let _ = removeCachedAttachMenuBot(postbox: postbox, botId: botId) let _ = removeCachedAttachMenuBot(postbox: postbox, botId: botId).start()
}) })
} }
} }

View File

@ -617,7 +617,7 @@ func _internal_channelsForStories(account: Account) -> Signal<[Peer], NoError> {
} }
} }
if let cachedPeers { if let cachedPeers = cachedPeers {
return .single(cachedPeers) |> then(remote) return .single(cachedPeers) |> then(remote)
} else { } else {
return remote return remote

View File

@ -30,7 +30,6 @@ swift_library(
"//submodules/UndoUI:UndoUI", "//submodules/UndoUI:UndoUI",
"//submodules/ContextUI:ContextUI", "//submodules/ContextUI:ContextUI",
"//submodules/GalleryUI:GalleryUI", "//submodules/GalleryUI:GalleryUI",
"//submodules/AttachmentTextInputPanelNode:AttachmentTextInputPanelNode",
"//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/TelegramNotices:TelegramNotices", "//submodules/TelegramNotices:TelegramNotices",
"//submodules/StickerPeekUI:StickerPeekUI", "//submodules/StickerPeekUI:StickerPeekUI",
@ -40,6 +39,7 @@ swift_library(
"//submodules/FeaturedStickersScreen:FeaturedStickersScreen", "//submodules/FeaturedStickersScreen:FeaturedStickersScreen",
"//submodules/StickerPackPreviewUI", "//submodules/StickerPackPreviewUI",
"//submodules/TelegramUI/Components/EntityKeyboardGifContent:EntityKeyboardGifContent", "//submodules/TelegramUI/Components/EntityKeyboardGifContent:EntityKeyboardGifContent",
"//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView:LegacyMessageInputPanelInputView",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -32,6 +32,7 @@ import FeaturedStickersScreen
import Pasteboard import Pasteboard
import StickerPackPreviewUI import StickerPackPreviewUI
import EntityKeyboardGifContent import EntityKeyboardGifContent
import LegacyMessageInputPanelInputView
public final class EmptyInputView: UIView, UIInputViewAudioFeedback { public final class EmptyInputView: UIView, UIInputViewAudioFeedback {
public var enableInputClicksWhenVisible: Bool { 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 private let context: AccountContext
public var insertText: ((NSAttributedString) -> Void)? public var insertText: ((NSAttributedString) -> Void)?

View File

@ -21,7 +21,9 @@ swift_library(
"//submodules/TelegramPresentationData", "//submodules/TelegramPresentationData",
"//submodules/ContextUI", "//submodules/ContextUI",
"//submodules/TooltipUI", "//submodules/TooltipUI",
"//submodules/UndoUI",
"//submodules/TelegramUI/Components/MessageInputPanelComponent", "//submodules/TelegramUI/Components/MessageInputPanelComponent",
"//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -13,6 +13,8 @@ import MessageInputPanelComponent
import TelegramPresentationData import TelegramPresentationData
import ContextUI import ContextUI
import TooltipUI import TooltipUI
import LegacyMessageInputPanelInputView
import UndoUI
public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
private let context: AccountContext private let context: AccountContext
@ -20,7 +22,8 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
private let isScheduledMessages: Bool private let isScheduledMessages: Bool
private let present: (ViewController) -> Void private let present: (ViewController) -> Void
private let presentInGlobalOverlay: (ViewController) -> Void private let presentInGlobalOverlay: (ViewController) -> Void
private let makeEntityInputView: () -> LegacyMessageInputPanelInputView?
private let state = ComponentState() private let state = ComponentState()
private let inputPanelExternalState = MessageInputPanelComponent.ExternalState() private let inputPanelExternalState = MessageInputPanelComponent.ExternalState()
private let inputPanel = ComponentView<Empty>() private let inputPanel = ComponentView<Empty>()
@ -28,9 +31,13 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
private var currentTimeout: Int32? private var currentTimeout: Int32?
private var currentIsEditing = false private var currentIsEditing = false
private var currentHeight: CGFloat? private var currentHeight: CGFloat?
private var currentIsVideo = false
private let hapticFeedback = HapticFeedback() 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)? private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, metrics: LayoutMetrics)?
public init( public init(
@ -38,13 +45,15 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
chatLocation: ChatLocation, chatLocation: ChatLocation,
isScheduledMessages: Bool, isScheduledMessages: Bool,
present: @escaping (ViewController) -> Void, present: @escaping (ViewController) -> Void,
presentInGlobalOverlay: @escaping (ViewController) -> Void presentInGlobalOverlay: @escaping (ViewController) -> Void,
makeEntityInputView: @escaping () -> LegacyMessageInputPanelInputView?
) { ) {
self.context = context self.context = context
self.chatLocation = chatLocation self.chatLocation = chatLocation
self.isScheduledMessages = isScheduledMessages self.isScheduledMessages = isScheduledMessages
self.present = present self.present = present
self.presentInGlobalOverlay = presentInGlobalOverlay self.presentInGlobalOverlay = presentInGlobalOverlay
self.makeEntityInputView = makeEntityInputView
super.init() super.init()
@ -87,18 +96,29 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
transition.setFrame(view: view, frame: frame) transition.setFrame(view: view, frame: frame)
} }
public func setTimeout(_ timeout: Int32) { public func setTimeout(_ timeout: Int32, isVideo: Bool) {
self.dismissTimeoutTooltip() self.dismissTimeoutTooltip()
var timeout: Int32? = timeout var timeout: Int32? = timeout
if timeout == 0 { if timeout == 0 {
timeout = nil timeout = nil
} }
self.currentTimeout = timeout self.currentTimeout = timeout
self.currentIsVideo = isVideo
} }
public func dismissInput() { public func dismissInput() -> Bool {
if let view = self.inputPanel.view as? MessageInputPanelComponent.View { if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
view.deactivateInput() if view.canDeactivateInput() {
self.isEmojiKeyboardActive = false
self.inputView = nil
view.deactivateInput(force: true)
return true
} else {
view.animateError()
return false
}
} else {
return true
} }
} }
@ -148,6 +168,8 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
var maxInputPanelHeight = maxHeight var maxInputPanelHeight = maxHeight
if keyboardHeight.isZero { if keyboardHeight.isZero {
maxInputPanelHeight = 60.0 maxInputPanelHeight = 60.0
} else {
maxInputPanelHeight = maxHeight - keyboardHeight - 100.0
} }
var resetInputContents: MessageInputPanelComponent.SendMessageInput? var resetInputContents: MessageInputPanelComponent.SendMessageInput?
@ -171,8 +193,12 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
queryTypes: [.mention], queryTypes: [.mention],
alwaysDarkWhenHasText: false, alwaysDarkWhenHasText: false,
resetInputContents: resetInputContents, resetInputContents: resetInputContents,
nextInputMode: { _ in nextInputMode: { [weak self] _ in
return .emoji if self?.isEmojiKeyboardActive == true {
return .text
} else {
return .emoji
}
}, },
areVoiceMessagesAvailable: false, areVoiceMessagesAvailable: false,
presentController: self.present, presentController: self.present,
@ -180,7 +206,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
sendMessageAction: { [weak self] in sendMessageAction: { [weak self] in
if let self { if let self {
self.sendPressed?(self.caption()) self.sendPressed?(self.caption())
self.dismissInput() let _ = self.dismissInput()
} }
}, },
sendMessageOptionsAction: nil, sendMessageOptionsAction: nil,
@ -193,7 +219,11 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
myReaction: nil, myReaction: nil,
likeAction: nil, likeAction: nil,
likeOptionsAction: 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 timeoutAction: self.chatLocation.peerId?.namespace == Namespaces.Peer.CloudUser && !self.isScheduledMessages ? { [weak self] sourceView, gesture in
if let self { if let self {
self.presentTimeoutSetup(sourceView: sourceView, gesture: gesture) self.presentTimeoutSetup(sourceView: sourceView, gesture: gesture)
@ -217,6 +247,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
bottomInset: 0.0, bottomInset: 0.0,
isFormattingLocked: false, isFormattingLocked: false,
hideKeyboard: false, hideKeyboard: false,
customInputView: self.inputView,
forceIsEditing: false, forceIsEditing: false,
disabledPlaceholder: nil, disabledPlaceholder: nil,
isChannel: false, isChannel: false,
@ -248,6 +279,47 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
return inputPanelSize.height - 8.0 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?) { private func presentTimeoutSetup(sourceView: UIView, gesture: ContextGesture?) {
self.hapticFeedback.impact(.light) self.hapticFeedback.impact(.light)
@ -255,10 +327,13 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
let updateTimeout: (Int32?) -> Void = { [weak self] timeout in let updateTimeout: (Int32?) -> Void = { [weak self] timeout in
if let self { if let self {
let previousTimeout = self.currentTimeout
self.currentTimeout = timeout self.currentTimeout = timeout
self.timerUpdated?(timeout as? NSNumber) self.timerUpdated?(timeout as? NSNumber)
self.update(transition: .immediate) self.update(transition: .immediate)
self.presentTimeoutTooltip(sourceView: sourceView, timeout: timeout) if previousTimeout != timeout {
self.presentTimeoutTooltip(sourceView: sourceView, timeout: timeout)
}
} }
} }
@ -320,7 +395,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0) 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 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 presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let text: String let text: String
let iconName: String let iconName: String

View File

@ -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",
],
)

View File

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

View File

@ -1188,6 +1188,7 @@ final class MediaEditorScreenComponent: Component {
bottomInset: 0.0, bottomInset: 0.0,
isFormattingLocked: !state.isPremium, isFormattingLocked: !state.isPremium,
hideKeyboard: self.currentInputMode == .emoji, hideKeyboard: self.currentInputMode == .emoji,
customInputView: nil,
forceIsEditing: self.currentInputMode == .emoji, forceIsEditing: self.currentInputMode == .emoji,
disabledPlaceholder: nil, disabledPlaceholder: nil,
isChannel: false, isChannel: false,

View File

@ -290,6 +290,7 @@ final class StoryPreviewComponent: Component {
bottomInset: 0.0, bottomInset: 0.0,
isFormattingLocked: false, isFormattingLocked: false,
hideKeyboard: false, hideKeyboard: false,
customInputView: nil,
forceIsEditing: false, forceIsEditing: false,
disabledPlaceholder: nil, disabledPlaceholder: nil,
isChannel: false, isChannel: false,

View File

@ -142,6 +142,7 @@ public final class MessageInputPanelComponent: Component {
public let bottomInset: CGFloat public let bottomInset: CGFloat
public let isFormattingLocked: Bool public let isFormattingLocked: Bool
public let hideKeyboard: Bool public let hideKeyboard: Bool
public let customInputView: UIView?
public let forceIsEditing: Bool public let forceIsEditing: Bool
public let disabledPlaceholder: String? public let disabledPlaceholder: String?
public let isChannel: Bool public let isChannel: Bool
@ -194,6 +195,7 @@ public final class MessageInputPanelComponent: Component {
bottomInset: CGFloat, bottomInset: CGFloat,
isFormattingLocked: Bool, isFormattingLocked: Bool,
hideKeyboard: Bool, hideKeyboard: Bool,
customInputView: UIView?,
forceIsEditing: Bool, forceIsEditing: Bool,
disabledPlaceholder: String?, disabledPlaceholder: String?,
isChannel: Bool, isChannel: Bool,
@ -245,6 +247,7 @@ public final class MessageInputPanelComponent: Component {
self.bottomInset = bottomInset self.bottomInset = bottomInset
self.isFormattingLocked = isFormattingLocked self.isFormattingLocked = isFormattingLocked
self.hideKeyboard = hideKeyboard self.hideKeyboard = hideKeyboard
self.customInputView = customInputView
self.forceIsEditing = forceIsEditing self.forceIsEditing = forceIsEditing
self.disabledPlaceholder = disabledPlaceholder self.disabledPlaceholder = disabledPlaceholder
self.isChannel = isChannel self.isChannel = isChannel
@ -328,6 +331,9 @@ public final class MessageInputPanelComponent: Component {
if lhs.hideKeyboard != rhs.hideKeyboard { if lhs.hideKeyboard != rhs.hideKeyboard {
return false return false
} }
if lhs.customInputView !== rhs.customInputView {
return false
}
if lhs.forceIsEditing != rhs.forceIsEditing { if lhs.forceIsEditing != rhs.forceIsEditing {
return false return false
} }
@ -713,6 +719,7 @@ public final class MessageInputPanelComponent: Component {
textColor: UIColor(rgb: 0xffffff), textColor: UIColor(rgb: 0xffffff),
insets: UIEdgeInsets(top: 9.0, left: 8.0, bottom: 10.0, right: 48.0), insets: UIEdgeInsets(top: 9.0, left: 8.0, bottom: 10.0, right: 48.0),
hideKeyboard: component.hideKeyboard, hideKeyboard: component.hideKeyboard,
customInputView: component.customInputView,
resetText: component.resetInputContents.flatMap { resetInputContents in resetText: component.resetInputContents.flatMap { resetInputContents in
switch resetInputContents { switch resetInputContents {
case let .text(value): case let .text(value):

View File

@ -2276,7 +2276,7 @@ final class StorageUsageScreenComponent: Component {
} }
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } 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) { private func reloadStats(firstTime: Bool, completion: @escaping () -> Void) {

View File

@ -2928,6 +2928,7 @@ public final class StoryItemSetContainerComponent: Component {
bottomInset: component.inputHeight != 0.0 || inputNodeVisible ? 0.0 : bottomContentInset, bottomInset: component.inputHeight != 0.0 || inputNodeVisible ? 0.0 : bottomContentInset,
isFormattingLocked: false, isFormattingLocked: false,
hideKeyboard: self.sendMessageContext.currentInputMode == .media, hideKeyboard: self.sendMessageContext.currentInputMode == .media,
customInputView: nil,
forceIsEditing: self.sendMessageContext.currentInputMode == .media, forceIsEditing: self.sendMessageContext.currentInputMode == .media,
disabledPlaceholder: disabledPlaceholder, disabledPlaceholder: disabledPlaceholder,
isChannel: isChannel, isChannel: isChannel,

View File

@ -1412,7 +1412,7 @@ final class StoryItemSetContainerSendMessage {
peerType.insert(.sameBot) peerType.insert(.sameBot)
peerType.remove(.bot) peerType.remove(.bot)
} }
let button: AttachmentButtonType = .app(bot.peer, bot.shortName, bot.icons) let button: AttachmentButtonType = .app(bot)
if !bot.peerTypes.intersection(peerType).isEmpty { if !bot.peerTypes.intersection(peerType).isEmpty {
buttons.insert(button, at: 1) buttons.insert(button, at: 1)
@ -1784,16 +1784,8 @@ final class StoryItemSetContainerSendMessage {
}*/ }*/
//TODO:gift controller //TODO:gift controller
break break
case let .app(bot, botName, _): case let .app(bot):
var payload: String? 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)
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.id, botName: botName, 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 theme = component.theme
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (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) let controller = WebAppController(context: component.context, updatedPresentationData: updatedPresentationData, params: params, replyToMessageId: nil, threadId: nil)

View File

@ -90,6 +90,7 @@ public final class TextFieldComponent: Component {
public let textColor: UIColor public let textColor: UIColor
public let insets: UIEdgeInsets public let insets: UIEdgeInsets
public let hideKeyboard: Bool public let hideKeyboard: Bool
public let customInputView: UIView?
public let resetText: NSAttributedString? public let resetText: NSAttributedString?
public let isOneLineWhenUnfocused: Bool public let isOneLineWhenUnfocused: Bool
public let formatMenuAvailability: FormatMenuAvailability public let formatMenuAvailability: FormatMenuAvailability
@ -105,6 +106,7 @@ public final class TextFieldComponent: Component {
textColor: UIColor, textColor: UIColor,
insets: UIEdgeInsets, insets: UIEdgeInsets,
hideKeyboard: Bool, hideKeyboard: Bool,
customInputView: UIView?,
resetText: NSAttributedString?, resetText: NSAttributedString?,
isOneLineWhenUnfocused: Bool, isOneLineWhenUnfocused: Bool,
formatMenuAvailability: FormatMenuAvailability, formatMenuAvailability: FormatMenuAvailability,
@ -119,6 +121,7 @@ public final class TextFieldComponent: Component {
self.textColor = textColor self.textColor = textColor
self.insets = insets self.insets = insets
self.hideKeyboard = hideKeyboard self.hideKeyboard = hideKeyboard
self.customInputView = customInputView
self.resetText = resetText self.resetText = resetText
self.isOneLineWhenUnfocused = isOneLineWhenUnfocused self.isOneLineWhenUnfocused = isOneLineWhenUnfocused
self.formatMenuAvailability = formatMenuAvailability self.formatMenuAvailability = formatMenuAvailability
@ -146,6 +149,9 @@ public final class TextFieldComponent: Component {
if lhs.hideKeyboard != rhs.hideKeyboard { if lhs.hideKeyboard != rhs.hideKeyboard {
return false return false
} }
if lhs.customInputView !== rhs.customInputView {
return false
}
if lhs.resetText != rhs.resetText { if lhs.resetText != rhs.resetText {
return false return false
} }
@ -816,9 +822,18 @@ public final class TextFieldComponent: Component {
} while glyphIndexForStringStart < NSMaxRange(glyphRange) && !NSLocationInRange(glyphRange.location, lineRange) } while glyphIndexForStringStart < NSMaxRange(glyphRange) && !NSLocationInRange(glyphRange.location, lineRange)
let padding = self.textView.textContainerInset.left 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 rightmostY = lineRect.minY + self.textView.textContainerInset.top
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) return CGPoint(x: rightmostX, y: rightmostY)
} }
@ -882,7 +897,14 @@ public final class TextFieldComponent: Component {
component.externalState.isEditing = isEditing component.externalState.isEditing = isEditing
component.externalState.textLength = self.textStorage.string.count 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 { if self.textView.inputView == nil {
self.textView.inputView = EmptyInputView() self.textView.inputView = EmptyInputView()
if self.textView.isFirstResponder { if self.textView.isFirstResponder {
@ -916,7 +938,7 @@ public final class TextFieldComponent: Component {
view.alpha = 0.0 view.alpha = 0.0
self.textView.addSubview(view) 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) transition.setFrame(view: view, frame: ellipsisFrame)
let hasMoreThanOneLine = ellipsisFrame.maxY < self.textView.contentSize.height - 12.0 let hasMoreThanOneLine = ellipsisFrame.maxY < self.textView.contentSize.height - 12.0

View File

@ -4220,7 +4220,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return state.updatedShowWebView(true).updatedForceInputCommandsHidden(true) 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 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) self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit)
}, requestSwitchInline: { [weak self] query, chatTypes, completion in }, requestSwitchInline: { [weak self] query, chatTypes, completion in
@ -4291,7 +4291,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self else { guard let strongSelf = self else {
return 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 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) self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit)
}, requestSwitchInline: { [weak self] query, chatTypes, completion in }, requestSwitchInline: { [weak self] query, chatTypes, completion in
@ -4331,7 +4331,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self else { guard let strongSelf = self else {
return 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 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) self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit)
}, completion: { [weak self] in }, completion: { [weak self] in
@ -10453,7 +10453,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
unarchiveAutomaticallyArchivedPeer(account: strongSelf.context.account, peerId: peerId) 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 }, scrollToTop: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -12937,7 +12937,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
disposable.set((signal disposable.set((signal
|> deliverOnMainQueue).start(completed: { [weak self] in |> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self, let _ = strongSelf.validLayout { 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 { guard let strongSelf = self else {
return 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: 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 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) self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit)
}, requestSwitchInline: { [weak self] query, chatTypes, completion in }, requestSwitchInline: { [weak self] query, chatTypes, completion in
@ -13367,7 +13367,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
peerType.insert(.sameBot) peerType.insert(.sameBot)
peerType.remove(.bot) peerType.remove(.bot)
} }
let button: AttachmentButtonType = .app(bot.peer, bot.shortName, bot.icons) let button: AttachmentButtonType = .app(bot)
if !bot.peerTypes.intersection(peerType).isEmpty { if !bot.peerTypes.intersection(peerType).isEmpty {
buttons.insert(button, at: 1) buttons.insert(button, at: 1)
@ -13408,17 +13408,27 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if !premiumGiftOptions.isEmpty { if !premiumGiftOptions.isEmpty {
buttons.insert(.gift, at: 1) buttons.insert(.gift, at: 1)
} }
guard let initialButton = initialButton else { guard let initialButton = initialButton else {
if case let .bot(botId, botPayload, botJustInstalled) = subject { if case let .bot(botId, botPayload, botJustInstalled) = subject {
if let button = allButtons.first(where: { button in 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 return true
} else { } else {
return false return false
} }
}), case let .app(_, botName, _) = button { }), case let .app(bot) = 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) let content: UndoOverlayContent
if botJustInstalled {
if bot.flags.contains(.showInSettings) {
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, 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, position: .top, action: { _ in return false }), in: .current)
} else { } else {
let _ = (context.engine.messages.getAttachMenuBot(botId: botId) let _ = (context.engine.messages.getAttachMenuBot(botId: botId)
|> deliverOnMainQueue).start(next: { bot in |> deliverOnMainQueue).start(next: { bot in
@ -13574,7 +13584,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
contactsController.navigationPresentation = .modal contactsController.navigationPresentation = .modal
completion(contactsController, contactsController.mediaPickerContext) completion(contactsController, contactsController.mediaPickerContext)
strongSelf.controllerNavigationDisposable.set((contactsController.result 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 { if let strongSelf = self, let (peers, _, silent, scheduleTime, text) = peers {
var textEnqueueMessage: EnqueueMessage? var textEnqueueMessage: EnqueueMessage?
if let text = text, text.length > 0 { if let text = text, text.length > 0 {
@ -13746,14 +13756,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = ApplicationSpecificNotice.incrementDismissedPremiumGiftSuggestion(accountManager: context.sharedContext.accountManager, peerId: peer.id).start() let _ = ApplicationSpecificNotice.incrementDismissedPremiumGiftSuggestion(accountManager: context.sharedContext.accountManager, peerId: peer.id).start()
} }
case let .app(bot, botName, _): case let .app(bot):
var payload: String? var payload: String?
var fromAttachMenu = true var fromAttachMenu = true
if case let .bot(_, botPayload, _) = subject { if case let .bot(_, botPayload, _) = subject {
payload = botPayload payload = botPayload
fromAttachMenu = false 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(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 replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, replyToMessageId: replyMessageId, threadId: strongSelf.chatLocation.threadId) 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 controller.openUrl = { [weak self] url, concealed, commit in
@ -13780,6 +13790,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
attachmentController.navigationPresentation = .flatModal attachmentController.navigationPresentation = .flatModal
strongSelf.push(attachmentController) strongSelf.push(attachmentController)
strongSelf.attachmentController = 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, timeout: 5.0)
} else {
content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsAdded(bot.shortName).string, timeout: 5.0)
}
attachmentController.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current)
}
}
}
} }
if inputIsActive { if inputIsActive {
@ -19351,6 +19381,8 @@ func canAddMessageReactions(message: Message) -> Bool {
if story.isMention { if story.isMention {
return false return false
} }
} else if let _ = media as? TelegramMediaExpiredContent {
return false
} }
} }
return true return true

View File

@ -1483,7 +1483,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuForward, icon: { theme in 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) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, action: { _, f in
interfaceInteraction.forwardMessages(selectAll ? messages : [message]) interfaceInteraction.forwardMessages(selectAll || isImage ? messages : [message])
f(.dismissWithoutContent) f(.dismissWithoutContent)
}))) })))
} }

View File

@ -4192,10 +4192,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
case let .message(message, _, _, _, _): case let .message(message, _, _, _, _):
for media in message.media { for media in message.media {
if let action = media as? TelegramMediaAction { if let action = media as? TelegramMediaAction {
if case .phoneCall = action.action { } else { if case .phoneCall = action.action {
} else {
canHaveSelection = false canHaveSelection = false
break break
} }
} else if media is TelegramMediaExpiredContent {
canHaveSelection = false
} }
} }
if message.adAttribute != nil { if message.adAttribute != nil {

View File

@ -1,5 +1,6 @@
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
import SwiftSignalKit
import TelegramPresentationData import TelegramPresentationData
final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem { final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
@ -31,13 +32,15 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
let label: Label let label: Label
let text: String let text: String
let icon: UIImage? let icon: UIImage?
let iconSignal: Signal<UIImage?, NoError>?
let action: (() -> Void)? 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<UIImage?, NoError>? = nil, action: (() -> Void)?) {
self.id = id self.id = id
self.label = label self.label = label
self.text = text self.text = text
self.icon = icon self.icon = icon
self.iconSignal = iconSignal
self.action = action self.action = action
} }
@ -57,6 +60,8 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
private let bottomSeparatorNode: ASDisplayNode private let bottomSeparatorNode: ASDisplayNode
private let activateArea: AccessibilityAreaNode private let activateArea: AccessibilityAreaNode
private var iconDisposable: Disposable?
private var item: PeerInfoScreenDisclosureItem? private var item: PeerInfoScreenDisclosureItem?
override init() { override init() {
@ -109,6 +114,10 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
self.addSubnode(self.activateArea) 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 { 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 { guard let item = item as? PeerInfoScreenDisclosureItem else {
return 10.0 return 10.0
@ -120,9 +129,9 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
self.selectionNode.pressed = item.action self.selectionNode.pressed = item.action
let sideInset: CGFloat = 16.0 + safeInsets.left 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 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) let titleFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
@ -149,12 +158,29 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
let height = textSize.height + 24.0 let height = textSize.height + 24.0
if let icon = item.icon { if item.icon != nil || item.iconSignal != nil {
if self.iconNode.supernode == nil { if self.iconNode.supernode == nil {
self.addSubnode(self.iconNode) self.addSubnode(self.iconNode)
} }
self.iconNode.image = icon let iconSize: CGSize
let iconFrame = CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((height - icon.size.height) / 2.0)), size: icon.size) if let icon = item.icon {
self.iconNode.image = icon
iconSize = icon.size
} else if let iconSignal = item.iconSignal {
if previousItem?.text != item.text {
self.iconNode.image = 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) transition.updateFrame(node: self.iconNode, frame: iconFrame)
} else if self.iconNode.supernode != nil { } else if self.iconNode.supernode != nil {
self.iconNode.image = nil self.iconNode.image = nil

View File

@ -800,7 +800,20 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
if let settings = data.globalSettings { if let settings = data.globalSettings {
for bot in settings.bots { for bot in settings.bots {
if bot.flags.contains(.showInSettings) { if bot.flags.contains(.showInSettings) {
items[.apps]!.append(PeerInfoScreenDisclosureItem(id: appIndex, text: bot.peer.compactDisplayTitle, icon: PresentationResourcesSettings.passport, action: { let iconSignal: Signal<UIImage?, NoError>
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: bot.peer.id.id._internalGetInt64Value(), text: bot.peer.compactDisplayTitle, icon: nil, iconSignal: iconSignal, action: {
interaction.openBotApp(bot) interaction.openBotApp(bot)
})) }))
appIndex += 1 appIndex += 1
@ -808,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) interaction.openSettings(.stories)
})) }))
@ -4102,6 +4115,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
if let previousSuggestPasswordConfirmation = previousData?.globalSettings?.suggestPasswordConfirmation, previousSuggestPasswordConfirmation != data.globalSettings?.suggestPasswordConfirmation { if let previousSuggestPasswordConfirmation = previousData?.globalSettings?.suggestPasswordConfirmation, previousSuggestPasswordConfirmation != data.globalSettings?.suggestPasswordConfirmation {
infoUpdated = true infoUpdated = true
} }
if let previousBots = previousData?.globalSettings?.bots, previousBots.count != (data.globalSettings?.bots ?? []).count {
infoUpdated = true
}
} }
if previousCallsPrivate != currentCallsPrivate || (previousVideoCallsAvailable != currentVideoCallsAvailable && currentVideoCallsAvailable != nil) { if previousCallsPrivate != currentCallsPrivate || (previousVideoCallsAvailable != currentVideoCallsAvailable && currentVideoCallsAvailable != nil) {
infoUpdated = true infoUpdated = true
@ -4636,12 +4652,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
guard let controller = self.controller else { guard let controller = self.controller else {
return return
} }
let proceed = { [weak self] in let presentationData = self.presentationData
let proceed: (Bool) -> Void = { [weak self] installed in
guard let self else { guard let self else {
return return
} }
let presentationData = self.presentationData
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
self?.controller?.present(controller, in: .window(.root)) self?.controller?.present(controller, in: .window(.root))
@ -4667,7 +4683,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
guard let self else { guard let self else {
return 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 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) self?.openUrl(url: url, concealed: concealed, external: false, forceExternal: true, commit: commit)
}, requestSwitchInline: { _, _, _ in }, requestSwitchInline: { _, _, _ in
@ -4676,6 +4692,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}) })
controller.navigationPresentation = .flatModal controller.navigationPresentation = .flatModal
self.controller?.push(controller) 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, timeout: 5.0), elevatedLayout: false, position: .top, action: { _ in return false }),
in: .current
)
})
}
}, error: { [weak self] error in }, error: { [weak self] error in
if let self { 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: { 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 +4728,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|> deliverOnMainQueue).start(error: { _ in |> deliverOnMainQueue).start(error: { _ in
}, completed: { }, completed: {
proceed() proceed(true)
}) })
} else { } else {
proceed() proceed(false)
} }
}) })
controller.present(alertController, in: .window(.root)) controller.present(alertController, in: .window(.root))
} else { } else {
proceed() proceed(false)
} }
} }

View File

@ -1633,7 +1633,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
chatLocation: chatLocation, chatLocation: chatLocation,
isScheduledMessages: isScheduledMessages, isScheduledMessages: isScheduledMessages,
present: present, present: present,
presentInGlobalOverlay: presentInGlobalOverlay presentInGlobalOverlay: presentInGlobalOverlay,
makeEntityInputView: {
return EntityInputView(context: context, isDark: true, areCustomEmojiEnabled: customEmojiAvailable)
}
) )
return inputPanelNode return inputPanelNode
} }

View File

@ -11,7 +11,7 @@ public enum UndoOverlayContent {
case archivedChat(peerId: Int64, title: String, text: String, undo: Bool) case archivedChat(peerId: Int64, title: String, text: String, undo: Bool)
case hidArchive(title: String, text: String, undo: Bool) case hidArchive(title: String, text: String, undo: Bool)
case revealedArchive(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 info(title: String?, text: String, timeout: Double?)
case emoji(name: String, text: String) case emoji(name: String, text: String)
case swipeToReply(title: String, text: String) case swipeToReply(title: String, text: String)

View File

@ -191,7 +191,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
if text.contains("](") { if text.contains("](") {
isUserInteractionEnabled = true isUserInteractionEnabled = true
} }
case let .succeed(text): case let .succeed(text, timeout):
self.avatarNode = nil self.avatarNode = nil
self.iconNode = nil self.iconNode = nil
self.iconCheckNode = nil self.iconCheckNode = nil
@ -204,7 +204,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.attributedText = attributedText self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 5 self.textNode.maximumNumberOfLines = 5
displayUndo = false displayUndo = false
self.originalRemainingSeconds = 3 self.originalRemainingSeconds = timeout ?? 3
case let .info(title, text, timeout): case let .info(title, text, timeout):
self.avatarNode = nil self.avatarNode = nil
self.iconNode = nil self.iconNode = nil

View File

@ -53,9 +53,10 @@ public class WebAppCancelButtonNode: ASDisplayNode {
private let strings: PresentationStrings private let strings: PresentationStrings
public func updateColor(_ color: UIColor?, transition: ContainedViewLayoutTransition) { public func updateColor(_ color: UIColor?, transition: ContainedViewLayoutTransition) {
let previousColor = self.color
self.color = 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() { if let snapshotView = self.view.snapshotContentTree() {
snapshotView.frame = self.bounds snapshotView.frame = self.bounds
self.view.addSubview(snapshotView) self.view.addSubview(snapshotView)
@ -170,6 +171,24 @@ public class WebAppCancelButtonNode: ASDisplayNode {
} }
public struct WebAppParameters { 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 peerId: PeerId
let botId: PeerId let botId: PeerId
let botName: String let botName: String
@ -178,13 +197,10 @@ public struct WebAppParameters {
let payload: String? let payload: String?
let buttonText: String? let buttonText: String?
let keepAliveSignal: Signal<Never, KeepWebViewError>? let keepAliveSignal: Signal<Never, KeepWebViewError>?
let fromMenu: Bool
let fromAttachMenu: Bool
let isInline: Bool
let isSimple: Bool
let forceHasSettings: Bool let forceHasSettings: Bool
public init( public init(
source: Source,
peerId: PeerId, peerId: PeerId,
botId: PeerId, botId: PeerId,
botName: String, botName: String,
@ -193,12 +209,9 @@ public struct WebAppParameters {
payload: String?, payload: String?,
buttonText: String?, buttonText: String?,
keepAliveSignal: Signal<Never, KeepWebViewError>?, keepAliveSignal: Signal<Never, KeepWebViewError>?,
fromMenu: Bool,
fromAttachMenu: Bool,
isInline: Bool,
isSimple: Bool,
forceHasSettings: Bool forceHasSettings: Bool
) { ) {
self.source = source
self.peerId = peerId self.peerId = peerId
self.botId = botId self.botId = botId
self.botName = botName self.botName = botName
@ -207,10 +220,6 @@ public struct WebAppParameters {
self.payload = payload self.payload = payload
self.buttonText = buttonText self.buttonText = buttonText
self.keepAliveSignal = keepAliveSignal self.keepAliveSignal = keepAliveSignal
self.fromMenu = fromMenu
self.fromAttachMenu = fromAttachMenu
self.isInline = isInline
self.isSimple = isSimple
self.forceHasSettings = forceHasSettings self.forceHasSettings = forceHasSettings
} }
} }
@ -393,7 +402,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 self.queryId = controller.queryId
if let parsedUrl = URL(string: url) { if let parsedUrl = URL(string: url) {
self.webView?.load(URLRequest(url: parsedUrl)) self.webView?.load(URLRequest(url: parsedUrl))
@ -412,7 +421,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
}) })
} }
} else { } 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 |> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -707,11 +716,11 @@ public final class WebAppController: ViewController, AttachmentContainable {
} }
} }
case "web_app_data_send": 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) self.handleSendData(data: eventData)
} }
case "web_app_setup_main_button": 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 self.delayedScriptMessage = message
} else if let json = json { } else if let json = json {
if var isVisible = json["is_visible"] as? Bool { if var isVisible = json["is_visible"] as? Bool {
@ -1301,6 +1310,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
fileprivate let moreButtonNode: MoreButtonNode fileprivate let moreButtonNode: MoreButtonNode
private let context: AccountContext private let context: AccountContext
private let source: WebAppParameters.Source
private let peerId: PeerId private let peerId: PeerId
private let botId: PeerId private let botId: PeerId
private let botName: String private let botName: String
@ -1308,10 +1318,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
private let queryId: Int64? private let queryId: Int64?
private let payload: String? private let payload: String?
private let buttonText: 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 forceHasSettings: Bool
private let keepAliveSignal: Signal<Never, KeepWebViewError>? private let keepAliveSignal: Signal<Never, KeepWebViewError>?
private let replyToMessageId: MessageId? private let replyToMessageId: MessageId?
@ -1328,6 +1334,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, params: WebAppParameters, replyToMessageId: MessageId?, threadId: Int64?) { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, params: WebAppParameters, replyToMessageId: MessageId?, threadId: Int64?) {
self.context = context self.context = context
self.source = params.source
self.peerId = params.peerId self.peerId = params.peerId
self.botId = params.botId self.botId = params.botId
self.botName = params.botName self.botName = params.botName
@ -1335,10 +1342,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
self.queryId = params.queryId self.queryId = params.queryId
self.payload = params.payload self.payload = params.payload
self.buttonText = params.buttonText 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.forceHasSettings = params.forceHasSettings
self.keepAliveSignal = params.keepAliveSignal self.keepAliveSignal = params.keepAliveSignal
self.replyToMessageId = replyToMessageId self.replyToMessageId = replyToMessageId
@ -1454,12 +1457,14 @@ public final class WebAppController: ViewController, AttachmentContainable {
let url = self.url let url = self.url
let forceHasSettings = self.forceHasSettings let forceHasSettings = self.forceHasSettings
let source = self.source
let items = context.engine.messages.attachMenuBots() let items = context.engine.messages.attachMenuBots()
|> take(1) |> take(1)
|> map { [weak self] attachMenuBots -> ContextController.Items in |> map { [weak self] attachMenuBots -> ContextController.Items in
var items: [ContextMenuItem] = [] 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 let hasSettings: Bool
if url == nil { if url == nil {
@ -1469,7 +1474,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
hasSettings = attachMenuBot?.flags.contains(.hasSettings) == true hasSettings = attachMenuBot?.flags.contains(.hasSettings) == true
} }
} else { } else {
hasSettings = false hasSettings = forceHasSettings
} }
if hasSettings { if hasSettings {
@ -1517,7 +1522,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
self?.controllerNode.webView?.reload() 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 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) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, action: { [weak self] c, _ in }, action: { [weak self] c, _ in
@ -1525,7 +1530,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
if let strongSelf = self { if let strongSelf = self {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } 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 { if let strongSelf = self {
let _ = context.engine.messages.removeBotFromAttachMenu(botId: strongSelf.botId).start() let _ = context.engine.messages.removeBotFromAttachMenu(botId: strongSelf.botId).start()
strongSelf.dismiss() strongSelf.dismiss()
@ -1672,7 +1677,7 @@ public func standaloneWebAppController(
didDismiss: @escaping () -> Void = {}, didDismiss: @escaping () -> Void = {},
getNavigationController: @escaping () -> NavigationController? = { return nil }, getNavigationController: @escaping () -> NavigationController? = { return nil },
getSourceRect: (() -> CGRect?)? = nil) -> ViewController { 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 return nil
}) })
controller.getInputContainerNode = getInputContainerNode controller.getInputContainerNode = getInputContainerNode

View File

@ -117,11 +117,7 @@ private final class WebAppTermsAlertContentNode: AlertContentNode, UIGestureReco
for separatorNode in self.actionVerticalSeparators { for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode) self.addSubnode(separatorNode)
} }
if let firstAction = self.actionNodes.first {
firstAction.actionEnabled = false
}
self.acceptTermsCheckNode.valueChanged = { [weak self] value in self.acceptTermsCheckNode.valueChanged = { [weak self] value in
if let strongSelf = self { if let strongSelf = self {
strongSelf.acceptedTerms = !strongSelf.acceptedTerms strongSelf.acceptedTerms = !strongSelf.acceptedTerms
@ -150,6 +146,10 @@ private final class WebAppTermsAlertContentNode: AlertContentNode, UIGestureReco
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.acceptTap(_:))) let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.acceptTap(_:)))
tapGesture.delegate = self tapGesture.delegate = self
self.view.addGestureRecognizer(tapGesture) self.view.addGestureRecognizer(tapGesture)
if let firstAction = self.actionNodes.first {
firstAction.actionEnabled = false
}
} }
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
@ -366,7 +366,7 @@ public func webAppTermsAlertController(
var dismissImpl: ((Bool) -> Void)? var dismissImpl: ((Bool) -> Void)?
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_DisclaimerContinue, action: { let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_DisclaimerContinue, action: {
completion(false) completion(true)
dismissImpl?(true) dismissImpl?(true)
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?(true) dismissImpl?(true)
@ -374,14 +374,7 @@ public func webAppTermsAlertController(
let title = presentationData.strings.WebApp_DisclaimerTitle let title = presentationData.strings.WebApp_DisclaimerTitle
let text = presentationData.strings.WebApp_DisclaimerText let text = presentationData.strings.WebApp_DisclaimerText
let additionalText: String? let additionalText: String? = nil
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 contentNode = WebAppTermsAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, title: title, text: text, additionalText: additionalText, actions: actions) let contentNode = WebAppTermsAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, title: title, text: text, additionalText: additionalText, actions: actions)
contentNode.openTerms = { contentNode.openTerms = {