diff --git a/submodules/AccountContext/Sources/PeerSelectionController.swift b/submodules/AccountContext/Sources/PeerSelectionController.swift index 7230f902ce..25656685b2 100644 --- a/submodules/AccountContext/Sources/PeerSelectionController.swift +++ b/submodules/AccountContext/Sources/PeerSelectionController.swift @@ -63,6 +63,7 @@ public final class PeerSelectionControllerParams { public let hasTypeHeaders: Bool public let selectForumThreads: Bool public let hasCreation: Bool + public let immediatelyActivateMultipleSelection: Bool public init( context: AccountContext, @@ -82,7 +83,8 @@ public final class PeerSelectionControllerParams { forwardedMessageIds: [EngineMessage.Id] = [], hasTypeHeaders: Bool = false, selectForumThreads: Bool = false, - hasCreation: Bool = false + hasCreation: Bool = false, + immediatelyActivateMultipleSelection: Bool = false ) { self.context = context self.updatedPresentationData = updatedPresentationData @@ -102,6 +104,7 @@ public final class PeerSelectionControllerParams { self.hasTypeHeaders = hasTypeHeaders self.selectForumThreads = selectForumThreads self.hasCreation = hasCreation + self.immediatelyActivateMultipleSelection = immediatelyActivateMultipleSelection } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 696b5a791f..e6d75143ab 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -644,13 +644,17 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL status: status, requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging, enabled: true, - selection: .none, + selection: editing ? .selectable(selected: selected) : .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in if let chatPeer = chatPeer { - nodeInteraction.peerSelected(chatPeer, nil, nil, nil) + if editing { + nodeInteraction.togglePeerSelected(chatPeer, nil) + } else { + nodeInteraction.peerSelected(chatPeer, nil, nil, nil) + } } }, disabledAction: peerEntry.requiresPremiumForMessaging ? { _ in if let chatPeer { @@ -984,13 +988,17 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL status: status, requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging, enabled: true, - selection: .none, + selection: editing ? .selectable(selected: selected) : .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in if let chatPeer = chatPeer { - nodeInteraction.peerSelected(chatPeer, nil, nil, nil) + if editing { + nodeInteraction.togglePeerSelected(chatPeer, nil) + } else { + nodeInteraction.peerSelected(chatPeer, nil, nil, nil) + } } }, disabledAction: peerEntry.requiresPremiumForMessaging ? { _ in if let chatPeer { diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index 7074cd2454..5574b7f9f1 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -737,7 +737,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode { var nextContentOrigin = floor((buttonFrame.width - contentWidth) / 2.0) let iconFrame: CGRect - let titleFrame: CGRect + var titleFrame: CGRect switch self.iconPosition { case .left: iconFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize) @@ -757,7 +757,6 @@ public final class SolidRoundedButtonNode: ASDisplayNode { if let animationNode = self.animationNode { transition.updateFrame(node: animationNode, frame: iconFrame) } - transition.updateFrame(node: self.titleNode, frame: titleFrame) if let badge = self.badge { let badgeNode: BadgeNode @@ -770,12 +769,15 @@ public final class SolidRoundedButtonNode: ASDisplayNode { } badgeNode.text = badge let badgeSize = badgeNode.update(CGSize(width: 100.0, height: 100.0)) - transition.updateFrame(node: badgeNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: titleFrame.minY + floor((titleFrame.height - badgeSize.height) * 0.5)), size: badgeSize)) + titleFrame.origin.x -= badgeSize.width / 2.0 + transition.updateFrame(node: badgeNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 6.0, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - badgeSize.height) * 0.5)), size: badgeSize)) } else if let badgeNode = self.badgeNode { self.badgeNode = nil badgeNode.removeFromSupernode() } + transition.updateFrame(node: self.titleNode, frame: titleFrame) + if self.subtitle != self.subtitleNode.attributedText?.string { self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: self.theme.foregroundColor) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 8ba02042c6..6123e14b94 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -83,7 +83,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1262359766] = { return Api.Boost.parse_boost($0) } dict[-1778593322] = { return Api.BotApp.parse_botApp($0) } dict[1571189943] = { return Api.BotApp.parse_botAppNotModified($0) } - dict[-2103898979] = { return Api.BotAppSettings.parse_botAppSettings($0) } + dict[-912582320] = { return Api.BotAppSettings.parse_botAppSettings($0) } dict[-1989921868] = { return Api.BotBusinessConnection.parse_botBusinessConnection($0) } dict[-1032140601] = { return Api.BotCommand.parse_botCommand($0) } dict[-1180016534] = { return Api.BotCommandScope.parse_botCommandScopeChatAdmins($0) } diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index 3c674d8c8b..04b97a79c0 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -1114,16 +1114,16 @@ public extension Api { } public extension Api { enum BotAppSettings: TypeConstructorDescription { - case botAppSettings(flags: Int32, placeholderDocument: Api.Document?, backgroundColor: Int32?, backgroundDarkColor: Int32?, headerColor: Int32?, headerDarkColor: Int32?) + case botAppSettings(flags: Int32, placeholderPath: Buffer?, backgroundColor: Int32?, backgroundDarkColor: Int32?, headerColor: Int32?, headerDarkColor: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .botAppSettings(let flags, let placeholderDocument, let backgroundColor, let backgroundDarkColor, let headerColor, let headerDarkColor): + case .botAppSettings(let flags, let placeholderPath, let backgroundColor, let backgroundDarkColor, let headerColor, let headerDarkColor): if boxed { - buffer.appendInt32(-2103898979) + buffer.appendInt32(-912582320) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {placeholderDocument!.serialize(buffer, true)} + if Int(flags) & Int(1 << 0) != 0 {serializeBytes(placeholderPath!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(backgroundColor!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeInt32(backgroundDarkColor!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeInt32(headerColor!, buffer: buffer, boxed: false)} @@ -1134,18 +1134,16 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .botAppSettings(let flags, let placeholderDocument, let backgroundColor, let backgroundDarkColor, let headerColor, let headerDarkColor): - return ("botAppSettings", [("flags", flags as Any), ("placeholderDocument", placeholderDocument as Any), ("backgroundColor", backgroundColor as Any), ("backgroundDarkColor", backgroundDarkColor as Any), ("headerColor", headerColor as Any), ("headerDarkColor", headerDarkColor as Any)]) + case .botAppSettings(let flags, let placeholderPath, let backgroundColor, let backgroundDarkColor, let headerColor, let headerDarkColor): + return ("botAppSettings", [("flags", flags as Any), ("placeholderPath", placeholderPath as Any), ("backgroundColor", backgroundColor as Any), ("backgroundDarkColor", backgroundDarkColor as Any), ("headerColor", headerColor as Any), ("headerDarkColor", headerDarkColor as Any)]) } } public static func parse_botAppSettings(_ reader: BufferReader) -> BotAppSettings? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.Document? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Document - } } + var _2: Buffer? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseBytes(reader) } var _3: Int32? if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } var _4: Int32? @@ -1161,7 +1159,7 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.BotAppSettings.botAppSettings(flags: _1!, placeholderDocument: _2, backgroundColor: _3, backgroundDarkColor: _4, headerColor: _5, headerDarkColor: _6) + return Api.BotAppSettings.botAppSettings(flags: _1!, placeholderPath: _2, backgroundColor: _3, backgroundDarkColor: _4, headerColor: _5, headerDarkColor: _6) } else { return nil diff --git a/submodules/TelegramCore/Sources/ApiUtils/BotInfo.swift b/submodules/TelegramCore/Sources/ApiUtils/BotInfo.swift index 213f9d8441..8ab1969f24 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/BotInfo.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/BotInfo.swift @@ -16,9 +16,9 @@ extension BotMenuButton { extension BotAppSettings { init(apiBotAppSettings: Api.BotAppSettings) { switch apiBotAppSettings { - case let .botAppSettings(_, placeholderDocument, backgroundColor, backgroundDarkColor, headerColor, headerDarkColor): + case let .botAppSettings(_, placeholder, backgroundColor, backgroundDarkColor, headerColor, headerDarkColor): self.init( - placeholder: placeholderDocument.flatMap { telegramMediaFileFromApiDocument($0, altDocuments: []) }, + placeholderData: placeholder.flatMap { $0.makeData() }, backgroundColor: backgroundColor, backgroundDarkColor: backgroundDarkColor, headerColor: headerColor, diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_BotInfo.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_BotInfo.swift index 632f67a86b..99e2146a39 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_BotInfo.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_BotInfo.swift @@ -1,3 +1,4 @@ +import Foundation import Postbox public struct BotCommand: PostboxCoding, Hashable { @@ -46,14 +47,14 @@ public enum BotMenuButton: PostboxCoding, Hashable { } public struct BotAppSettings: PostboxCoding, Equatable { - public let placeholder: TelegramMediaFile? + public let placeholderData: Data? public let backgroundColor: Int32? public let backgroundDarkColor: Int32? public let headerColor: Int32? public let headerDarkColor: Int32? - public init(placeholder: TelegramMediaFile?, backgroundColor: Int32?, backgroundDarkColor: Int32?, headerColor: Int32?, headerDarkColor: Int32?) { - self.placeholder = placeholder + public init(placeholderData: Data?, backgroundColor: Int32?, backgroundDarkColor: Int32?, headerColor: Int32?, headerDarkColor: Int32?) { + self.placeholderData = placeholderData self.backgroundColor = backgroundColor self.backgroundDarkColor = backgroundDarkColor self.headerColor = headerColor @@ -61,11 +62,7 @@ public struct BotAppSettings: PostboxCoding, Equatable { } public init(decoder: PostboxDecoder) { - if let placeholder = decoder.decodeObjectForKey("p", decoder: { TelegramMediaFile(decoder: $0) }) as? TelegramMediaFile { - self.placeholder = placeholder - } else { - self.placeholder = nil - } + self.placeholderData = decoder.decodeDataForKey("pd") self.backgroundColor = decoder.decodeOptionalInt32ForKey("b") self.backgroundDarkColor = decoder.decodeOptionalInt32ForKey("bd") self.headerColor = decoder.decodeOptionalInt32ForKey("h") @@ -73,10 +70,10 @@ public struct BotAppSettings: PostboxCoding, Equatable { } public func encode(_ encoder: PostboxEncoder) { - if let placeholder = self.placeholder { - encoder.encodeObject(placeholder, forKey: "p") + if let placeholderData = self.placeholderData { + encoder.encodeData(placeholderData, forKey: "pd") } else { - encoder.encodeNil(forKey: "p") + encoder.encodeNil(forKey: "pd") } if let backgroundColor = self.backgroundColor { encoder.encodeInt32(backgroundColor, forKey: "b") diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift index 8bbd8de7b4..632ab70017 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift @@ -244,7 +244,7 @@ func _internal_requestWebView(postbox: Postbox, network: Network, stateManager: if (webViewFlags & (1 << 1)) != 0 { resultFlags.insert(.fullSize) } - if (flags & (1 << 2)) != 0 { + if (webViewFlags & (1 << 2)) != 0 { resultFlags.insert(.fullScreen) } let keepAlive: Signal? diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift index 2c448b1054..6aa6fe8309 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift @@ -65,6 +65,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon private let hasTypeHeaders: Bool private let requestPeerType: [ReplyMarkupButtonRequestPeerType]? private let hasCreation: Bool + let immediatelyActivateMultipleSelection: Bool override public var _presentedInModal: Bool { get { @@ -105,6 +106,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon self.selectForumThreads = params.selectForumThreads self.requestPeerType = params.requestPeerType self.hasCreation = params.hasCreation + self.immediatelyActivateMultipleSelection = params.immediatelyActivateMultipleSelection super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) @@ -167,7 +169,11 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon }) self.navigationBar?.setContentNode(self.searchContentNode, animated: false) - if params.multipleSelection { + if params.immediatelyActivateMultipleSelection { + Queue.mainQueue().after(0.1) { + self.beginSelection() + } + } else if params.multipleSelection { self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Select, style: .plain, target: self, action: #selector(self.beginSelection)) } diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 934bc52777..94e30c8f79 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -95,6 +95,8 @@ final class PeerSelectionControllerNode: ASDisplayNode { private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer + private var countPanelNode: PeersCountPanelNode? + private var readyValue = Promise() var ready: Signal { return self.readyValue.get() @@ -853,35 +855,56 @@ final class PeerSelectionControllerNode: ASDisplayNode { } func beginSelection() { - if let _ = self.textInputPanelNode { - } else { - let forwardAccessoryPanelNode = ForwardAccessoryPanelNode(context: self.context, messageIds: self.forwardedMessageIds, theme: self.presentationData.theme, strings: self.presentationData.strings, fontSize: self.presentationData.chatFontSize, nameDisplayOrder: self.presentationData.nameDisplayOrder, forwardOptionsState: self.presentationInterfaceState.interfaceState.forwardOptionsState, animationCache: nil, animationRenderer: nil) - forwardAccessoryPanelNode.interfaceInteraction = self.interfaceInteraction - self.addSubnode(forwardAccessoryPanelNode) - self.forwardAccessoryPanelNode = forwardAccessoryPanelNode - - let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, presentController: { [weak self] c in self?.present(c, nil) }, makeEntityInputView: { - return nil - }) - textInputPanelNode.interfaceInteraction = self.interfaceInteraction - textInputPanelNode.sendMessage = { [weak self] mode, messageEffect in - guard let strongSelf = self else { + guard let controller = self.controller else { + return + } + if controller.immediatelyActivateMultipleSelection { + let countPanelNode = PeersCountPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: { [weak self] in + guard let self else { return } - - let effectiveInputText = strongSelf.presentationInterfaceState.interfaceState.composeInputState.inputText - let forwardOptionsState = strongSelf.presentationInterfaceState.interfaceState.forwardOptionsState - - let (selectedPeers, selectedPeerMap) = strongSelf.selectedPeers - if !selectedPeers.isEmpty { - strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, forwardOptionsState, messageEffect) + let (selectedPeers, selectedPeerMap) = self.selectedPeers + if !self.isEmpty { + self.requestSend?(selectedPeers, selectedPeerMap, NSAttributedString(), .generic, nil, nil) } - } - self.addSubnode(textInputPanelNode) - self.textInputPanelNode = textInputPanelNode + }) + self.addSubnode(countPanelNode) + self.countPanelNode = countPanelNode if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .animated(duration: 0.3, curve: .spring)) + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .immediate) + } + } else { + if let _ = self.textInputPanelNode { + } else { + let forwardAccessoryPanelNode = ForwardAccessoryPanelNode(context: self.context, messageIds: self.forwardedMessageIds, theme: self.presentationData.theme, strings: self.presentationData.strings, fontSize: self.presentationData.chatFontSize, nameDisplayOrder: self.presentationData.nameDisplayOrder, forwardOptionsState: self.presentationInterfaceState.interfaceState.forwardOptionsState, animationCache: nil, animationRenderer: nil) + forwardAccessoryPanelNode.interfaceInteraction = self.interfaceInteraction + self.addSubnode(forwardAccessoryPanelNode) + self.forwardAccessoryPanelNode = forwardAccessoryPanelNode + + let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, presentController: { [weak self] c in self?.present(c, nil) }, makeEntityInputView: { + return nil + }) + textInputPanelNode.interfaceInteraction = self.interfaceInteraction + textInputPanelNode.sendMessage = { [weak self] mode, messageEffect in + guard let strongSelf = self else { + return + } + + let effectiveInputText = strongSelf.presentationInterfaceState.interfaceState.composeInputState.inputText + let forwardOptionsState = strongSelf.presentationInterfaceState.interfaceState.forwardOptionsState + + let (selectedPeers, selectedPeerMap) = strongSelf.selectedPeers + if !selectedPeers.isEmpty { + strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, forwardOptionsState, messageEffect) + } + } + self.addSubnode(textInputPanelNode) + self.textInputPanelNode = textInputPanelNode + + if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .animated(duration: 0.3, curve: .spring)) + } } } @@ -901,6 +924,14 @@ final class PeerSelectionControllerNode: ASDisplayNode { return state }) } else if let chatListNode = self.chatListNode { + chatListNode.selectionCountChanged = { [weak self] count in + if let self { + self.countPanelNode?.count = count + if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .animated(duration: 0.3, curve: .spring)) + } + } + } chatListNode.updateState { state in var state = state state.editing = true @@ -979,7 +1010,15 @@ final class PeerSelectionControllerNode: ASDisplayNode { transition.updateFrame(node: forwardAccessoryPanelNode, frame: panelFrame) } - if let segmentedControlNode = self.segmentedControlNode, let toolbarBackgroundNode = self.toolbarBackgroundNode, let toolbarSeparatorNode = self.toolbarSeparatorNode { + if let countPanelNode = self.countPanelNode { + let countPanelHeight = countPanelNode.updateLayout(width: layout.size.width, sideInset: layout.safeInsets.left, bottomInset: layout.intrinsicInsets.bottom, transition: transition) + if countPanelNode.count == 0 { + transition.updateFrame(node: countPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: countPanelHeight))) + } else { + toolbarHeight = countPanelHeight + transition.updateFrame(node: countPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - countPanelHeight), size: CGSize(width: layout.size.width, height: countPanelHeight))) + } + } else if let segmentedControlNode = self.segmentedControlNode, let toolbarBackgroundNode = self.toolbarBackgroundNode, let toolbarSeparatorNode = self.toolbarSeparatorNode { if let textPanelHeight = textPanelHeight { toolbarHeight = textPanelHeight + accessoryHeight } else { @@ -1740,3 +1779,64 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent func animatedIn() { } } + +private final class PeersCountPanelNode: ASDisplayNode { + private let theme: PresentationTheme + private let strings: PresentationStrings + + private let separatorNode: ASDisplayNode + private let button: SolidRoundedButtonNode + + private var validLayout: (CGFloat, CGFloat, CGFloat)? + + var count: Int = 0 { + didSet { + if self.count != oldValue && self.count > 0 { + //TODO:localize + self.button.title = "Send" + self.button.badge = "\(self.count)" + + if let (width, sideInset, bottomInset) = self.validLayout { + let _ = self.updateLayout(width: width, sideInset: sideInset, bottomInset: bottomInset, transition: .immediate) + } + } + } + } + + init(theme: PresentationTheme, strings: PresentationStrings, action: @escaping () -> Void) { + self.theme = theme + self.strings = strings + + self.separatorNode = ASDisplayNode() + self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor + + self.button = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 48.0, cornerRadius: 10.0) + + super.init() + + self.backgroundColor = theme.rootController.navigationBar.opaqueBackgroundColor + + self.addSubnode(self.button) + self.addSubnode(self.separatorNode) + + self.button.pressed = { + action() + } + } + + func updateLayout(width: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + self.validLayout = (width, sideInset, bottomInset) + let topInset: CGFloat = 9.0 + var bottomInset = bottomInset + bottomInset += topInset - (bottomInset.isZero ? 0.0 : 4.0) + + let buttonInset: CGFloat = 16.0 + sideInset + let buttonWidth = width - buttonInset * 2.0 + let buttonHeight = self.button.updateLayout(width: buttonWidth, transition: transition) + transition.updateFrame(node: self.button, frame: CGRect(x: buttonInset, y: topInset, width: buttonWidth, height: buttonHeight)) + + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel))) + + return topInset + buttonHeight + bottomInset + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddSquare.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddSquare.imageset/Contents.json new file mode 100644 index 0000000000..7a01fb1680 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddSquare.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "sqadd.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddSquare.imageset/sqadd.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddSquare.imageset/sqadd.pdf new file mode 100644 index 0000000000..8076304138 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddSquare.imageset/sqadd.pdf differ diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index 8f63fbc7f0..3aff2f15bd 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -601,9 +601,5 @@ func updateChatPresentationInterfaceStateImpl( } } - if let cachedData = selfController.peerView?.cachedData as? CachedUserData, let appSettings = cachedData.botInfo?.appSettings { - let _ = WebAppController.preloadAppPlaceholder(context: selfController.context, appSettings: appSettings).startStandalone() - } - selfController.stateUpdated?(transition) } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index e8b43c3088..2c38b5eb9e 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -1462,6 +1462,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } let height = adPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition, interfaceState: self.chatPresentationInterfaceState) + if let adMessage = self.chatPresentationInterfaceState.adMessage, let opaqueId = adMessage.adAttribute?.opaqueId { + self.historyNode.markAdAsSeen(opaqueId: opaqueId) + } + adPanelHeight = height if transition.isAnimated && animateAppearance { adPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) diff --git a/submodules/WebUI/BUILD b/submodules/WebUI/BUILD index 8d92f68ccd..182a8df853 100644 --- a/submodules/WebUI/BUILD +++ b/submodules/WebUI/BUILD @@ -49,6 +49,7 @@ swift_library( "//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/TelegramUI/Components/Chat/ChatMessageItemImpl", "//submodules/DeviceLocationManager", + "//submodules/TelegramUI/Components/Utils/GenerateStickerPlaceholderImage", ], visibility = [ "//visibility:public", diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 2cda37f8b7..aa5718dde3 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -35,6 +35,7 @@ import TelegramUIPreferences import CoreMotion import DeviceLocationManager import LegacyMediaPickerUI +import GenerateStickerPlaceholderImage private let durgerKingBotIds: [Int64] = [5104055776, 2200339955] @@ -259,8 +260,8 @@ public final class WebAppController: ViewController, AttachmentContainable { } } } - if let placeholderFile = controller.botAppSettings?.placeholder { - placeholder = .single((.standalone(media: placeholderFile), false)) + if let _ = controller.botAppSettings?.placeholderData { + placeholder = .single(nil) } else if durgerKingBotIds.contains(controller.botId.id._internalGetInt64Value()) { placeholder = .single(nil) |> delay(0.05, queue: Queue.mainQueue()) @@ -293,46 +294,58 @@ public final class WebAppController: ViewController, AttachmentContainable { } } - self.placeholderDisposable.set((placeholder - |> deliverOnMainQueue).start(next: { [weak self] fileReferenceAndIsPlaceholder in - guard let strongSelf = self else { - return - } - let fileReference: FileMediaReference? - let isPlaceholder: Bool - if let (maybeFileReference, maybeIsPlaceholder) = fileReferenceAndIsPlaceholder { - fileReference = maybeFileReference - isPlaceholder = maybeIsPlaceholder - } else { - fileReference = nil - isPlaceholder = true - } - - if let fileReference = fileReference { - let _ = freeMediaFileInteractiveFetched(account: strongSelf.context.account, userLocation: .other, fileReference: fileReference).start() - } - let _ = (svgIconImageFile(account: strongSelf.context.account, fileReference: fileReference, stickToTop: isPlaceholder) - |> deliverOnMainQueue).start(next: { [weak self] transform in - if let strongSelf = self { - let imageSize: CGSize - if isPlaceholder, let (layout, _) = strongSelf.validLayout { - let minSize = min(layout.size.width, layout.size.height) - imageSize = CGSize(width: minSize, height: minSize * 2.0) - } else { - imageSize = CGSize(width: 75.0, height: 75.0) + if let placeholderData = controller.botAppSettings?.placeholderData { + Queue.mainQueue().justDispatch { + let size = CGSize(width: 75.0, height: 75.0) + if let image = generateStickerPlaceholderImage(data: placeholderData, size: size, scale: min(2.0, UIScreenScale), imageSize: CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: .white) { + self.placeholderIcon = (image.withRenderingMode(.alwaysTemplate), false) + if let (layout, navigationBarHeight) = self.validLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) } - let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()) - let drawingContext = transform(arguments) - if let image = drawingContext?.generateImage()?.withRenderingMode(.alwaysTemplate) { - strongSelf.placeholderIcon = (image, isPlaceholder) - if let (layout, navigationBarHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - } - strongSelf.placeholderNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - }) - })) + } + } else { + self.placeholderDisposable.set((placeholder + |> deliverOnMainQueue).start(next: { [weak self] fileReferenceAndIsPlaceholder in + guard let strongSelf = self else { + return + } + let fileReference: FileMediaReference? + let isPlaceholder: Bool + if let (maybeFileReference, maybeIsPlaceholder) = fileReferenceAndIsPlaceholder { + fileReference = maybeFileReference + isPlaceholder = maybeIsPlaceholder + } else { + fileReference = nil + isPlaceholder = true + } + + if let fileReference = fileReference { + let _ = freeMediaFileInteractiveFetched(account: strongSelf.context.account, userLocation: .other, fileReference: fileReference).start() + } + let _ = (svgIconImageFile(account: strongSelf.context.account, fileReference: fileReference, stickToTop: isPlaceholder) + |> deliverOnMainQueue).start(next: { [weak self] transform in + if let strongSelf = self { + let imageSize: CGSize + if isPlaceholder, let (layout, _) = strongSelf.validLayout { + let minSize = min(layout.size.width, layout.size.height) + imageSize = CGSize(width: minSize, height: minSize * 2.0) + } else { + imageSize = CGSize(width: 75.0, height: 75.0) + } + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()) + let drawingContext = transform(arguments) + if let image = drawingContext?.generateImage()?.withRenderingMode(.alwaysTemplate) { + strongSelf.placeholderIcon = (image, isPlaceholder) + if let (layout, navigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } + } + strongSelf.placeholderNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + }) + })) + } self.iconDisposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: controller.botId)) |> mapToSignal { peer -> Signal in @@ -768,6 +781,16 @@ public final class WebAppController: ViewController, AttachmentContainable { transition.updateFrame(view: webView, frame: webViewFrame) } + if let snapshotView = self.fullscreenSwitchSnapshotView { + self.fullscreenSwitchSnapshotView = nil + + transition.updatePosition(layer: snapshotView.layer, position: webViewFrame.center) + transition.updateTransform(layer: snapshotView.layer, transform: CATransform3DMakeScale(webViewFrame.width / snapshotView.frame.width, webViewFrame.height / snapshotView.frame.height, 1.0)) + transition.updateAlpha(layer: snapshotView.layer, alpha: 0.0, completion: { _ in + snapshotView.removeFromSuperview() + }) + } + var customInsets: UIEdgeInsets = .zero if controller.isFullscreen { customInsets.top = layout.statusBarHeight ?? 0.0 @@ -1410,8 +1433,8 @@ public final class WebAppController: ViewController, AttachmentContainable { self.setIsGyroscopeActive(false) case "web_app_set_emoji_status": if let json = json, let emojiIdString = json["custom_emoji_id"] as? String, let emojiId = Int64(emojiIdString) { - let expirationDate = json["expiration_date"] as? Double - self.setEmojiStatus(emojiId, expirationDate: expirationDate.flatMap { Int32($0) }) + let duration = json["duration"] as? Double + self.setEmojiStatus(emojiId, duration: duration.flatMap { Int32($0) }) } case "web_app_add_to_home_screen": self.addToHomeScreen() @@ -2066,6 +2089,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } } + private var fullscreenSwitchSnapshotView: UIView? fileprivate func setIsFullscreen(_ isFullscreen: Bool) { guard let controller = self.controller else { return @@ -2079,6 +2103,14 @@ public final class WebAppController: ViewController, AttachmentContainable { self.webView?.sendEvent(name: "fullscreen_changed", data: paramsString) controller.isFullscreen = isFullscreen + + if let (layout, _) = self.validLayout, case .regular = layout.metrics.widthClass { + if let snapshotView = self.webView?.snapshotView(afterScreenUpdates: false) { + self.webView?.superview?.addSubview(snapshotView) + self.fullscreenSwitchSnapshotView = snapshotView + } + } + (controller.parentController() as? AttachmentController)?.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) } @@ -2467,7 +2499,7 @@ public final class WebAppController: ViewController, AttachmentContainable { }) } - fileprivate func setEmojiStatus(_ fileId: Int64, expirationDate: Int32? = nil) { + fileprivate func setEmojiStatus(_ fileId: Int64, duration: Int32? = nil) { guard let controller = self.controller else { return } @@ -2489,6 +2521,7 @@ public final class WebAppController: ViewController, AttachmentContainable { botName: controller.botName, accountPeer: accountPeer, file: file, + duration: duration, completion: { [weak self, weak controller] result in guard let self else { return @@ -2509,6 +2542,10 @@ public final class WebAppController: ViewController, AttachmentContainable { return } + var expirationDate: Int32? + if let duration { + expirationDate = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + duration + } let _ = (self.context.engine.accountData.setEmojiStatus(file: file, expirationDate: expirationDate) |> deliverOnMainQueue).start(completed: { [weak self] in self?.webView?.sendEvent(name: "emoji_status_set", data: nil) @@ -2550,7 +2587,13 @@ public final class WebAppController: ViewController, AttachmentContainable { if let name = controller.appName { appName = "/\(name)" } - let url = URL(string: "x-safari-https://t.me/\(addressName)\(appName)?startapp&addToHomeScreen")! + let scheme: String + if #available(iOS 18.0, *) { + scheme = "x-safari-https" + } else { + scheme = "https" + } + let url = URL(string: "\(scheme)://t.me/\(addressName)\(appName)?startapp&addToHomeScreen")! UIApplication.shared.open(url) controller.dismiss() @@ -2986,7 +3029,7 @@ public final class WebAppController: ViewController, AttachmentContainable { //TODO:localize if let _ = self?.appName { items.append(.action(ContextMenuActionItem(text: "Add to Home Screen", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.contextMenu.primaryColor) + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddSquare"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in c?.dismiss(completion: nil) @@ -3141,7 +3184,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self.controllerNode.webView?.setNeedsLayout() } - self.controllerNode.webView?.sendEvent(name: "visibility_changed", data: "{is_visible: \"\(self.isMinimized ? "false" : "true")\"}") + self.controllerNode.webView?.sendEvent(name: "visibility_changed", data: "{is_visible: \(self.isMinimized ? "false" : "true")}") } } } @@ -3195,30 +3238,6 @@ public final class WebAppController: ViewController, AttachmentContainable { }) return imageView } - - public static func preloadAppPlaceholder(context: AccountContext, appSettings: BotAppSettings) -> Signal { - guard let file = appSettings.placeholder else { - return .complete() - } - let path = context.account.postbox.mediaBox.cachedRepresentationCompletePath(file.resource.id, representation: CachedPreparedSvgRepresentation()) - if !FileManager.default.fileExists(atPath: path) { - let accountFullSizeData = Signal<(Data?, Bool), NoError> { subscriber in - let accountResource = context.account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedPreparedSvgRepresentation(), complete: false, fetch: true) - - let fetchedFullSize = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: MediaResourceUserContentType(file: file), reference: .standalone(resource: file.resource)) - let fetchedFullSizeDisposable = fetchedFullSize.start() - let fullSizeDisposable = accountResource.start() - - return ActionDisposable { - fetchedFullSizeDisposable.dispose() - fullSizeDisposable.dispose() - } - } - return accountFullSizeData - |> ignoreValues - } - return .complete() - } } final class WebAppPickerContext: AttachmentMediaPickerContext { diff --git a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift index a5289644f1..3230a718bf 100644 --- a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift +++ b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift @@ -21,6 +21,7 @@ import PresentationDataUtils import ListSectionComponent import ListItemComponentAdaptor import TelegramStringFormatting +import UndoUI private final class SheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -408,16 +409,45 @@ public final class WebAppMessagePreviewScreen: ViewControllerComponentContainer fatalError("init(coder:) has not been implemented") } - fileprivate func complete(peer: EnginePeer) { + fileprivate func complete(peers: [EnginePeer]) { + for peer in peers { + let _ = self.context.engine.messages.enqueueOutgoingMessage( + to: peer.id, + replyTo: nil, + storyId: nil, + content: .preparedInlineMessage(self.preparedMessage) + ).start() + } + + let text: String + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + if peers.count == 1, let peer = peers.first { + let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string + } else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last { + let firstPeerName = firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let secondPeerName = secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string + } else if let peer = peers.first { + let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string + } else { + text = "" + } + + if let navigationController = self.navigationController as? NavigationController { + Queue.mainQueue().after(1.0) { + guard let lastController = navigationController.viewControllers.last as? ViewController else { + return + } + lastController.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, position: .top, animateInAsReplacement: true, action: { action in + return false + }), in: .window(.root)) + } + } + self.completeWithResult(true) self.dismiss() - - let _ = self.context.engine.messages.enqueueOutgoingMessage( - to: peer.id, - replyTo: nil, - storyId: nil, - content: .preparedInlineMessage(preparedMessage) - ).start() } private var completed = false @@ -430,15 +460,17 @@ public final class WebAppMessagePreviewScreen: ViewControllerComponentContainer fileprivate func proceed() { let requestPeerType = self.preparedMessage.peerTypes.requestPeerTypes - let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: requestPeerType, hasContactSelector: false)) - - controller.peerSelected = { [weak self, weak controller] peer, _ in + + let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: requestPeerType, hasContactSelector: false, multipleSelection: true, immediatelyActivateMultipleSelection: true)) + + controller.multiplePeersSelected = { [weak self, weak controller] peers, _, _, _, _, _ in guard let self else { return } - self.complete(peer: peer) + self.complete(peers: peers) controller?.dismiss() } + self.push(controller) } diff --git a/submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift b/submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift index 182b5156f7..7dfbf453f2 100644 --- a/submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift +++ b/submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift @@ -25,6 +25,7 @@ private final class SheetContent: CombinedComponent { let botName: String let accountPeer: EnginePeer let file: TelegramMediaFile + let duration: Int32? let dismiss: () -> Void init( @@ -32,12 +33,14 @@ private final class SheetContent: CombinedComponent { botName: String, accountPeer: EnginePeer, file: TelegramMediaFile, + duration: Int32?, dismiss: @escaping () -> Void ) { self.context = context self.botName = botName self.accountPeer = accountPeer self.file = file + self.duration = duration self.dismiss = dismiss } @@ -148,11 +151,19 @@ private final class SheetContent: CombinedComponent { let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in return (TelegramTextAttributes.URL, contents) }) + + var textString: String + if let _ = component.duration { + //TODO:localize + textString = "Do you want to set this emoji status suggested by **\(component.botName)** for **5 minutes**?" + } else { + textString = "Do you want to set this emoji status suggested by **\(component.botName)**?" + } let text = text.update( component: BalancedTextComponent( text: .markdown( - text: "Do you want to set this emoji status suggested by **\(component.botName)**?", + text: textString, attributes: markdownAttributes ), horizontalAlignment: .center, @@ -229,17 +240,20 @@ private final class WebAppSetEmojiStatusSheetComponent: CombinedComponent { private let botName: String private let accountPeer: EnginePeer private let file: TelegramMediaFile + private let duration: Int32? init( context: AccountContext, botName: String, accountPeer: EnginePeer, - file: TelegramMediaFile + file: TelegramMediaFile, + duration: Int32? ) { self.context = context self.botName = botName self.accountPeer = accountPeer self.file = file + self.duration = duration } static func ==(lhs: WebAppSetEmojiStatusSheetComponent, rhs: WebAppSetEmojiStatusSheetComponent) -> Bool { @@ -252,6 +266,9 @@ private final class WebAppSetEmojiStatusSheetComponent: CombinedComponent { if lhs.accountPeer != rhs.accountPeer { return false } + if lhs.duration != rhs.duration { + return false + } return true } @@ -271,6 +288,7 @@ private final class WebAppSetEmojiStatusSheetComponent: CombinedComponent { botName: context.component.botName, accountPeer: context.component.accountPeer, file: context.component.file, + duration: context.component.duration, dismiss: { animateOut.invoke(Action { _ in if let controller = controller() as? WebAppSetEmojiStatusScreen { @@ -331,6 +349,7 @@ public final class WebAppSetEmojiStatusScreen: ViewControllerComponentContainer botName: String, accountPeer: EnginePeer, file: TelegramMediaFile, + duration: Int32?, completion: @escaping (Bool) -> Void ) { self.context = context @@ -342,7 +361,8 @@ public final class WebAppSetEmojiStatusScreen: ViewControllerComponentContainer context: context, botName: botName, accountPeer: accountPeer, - file: file + file: file, + duration: duration ), navigationBarAppearance: .none, statusBarStyle: .ignore,