diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index 24c45642b9..aa0c9bd066 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -90,6 +90,7 @@ public protocol AttachmentContainable: ViewController { var requestAttachmentMenuExpansion: () -> Void { get set } var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void { get set } var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void { get set } + var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void { get set } var cancelPanGesture: () -> Void { get set } var isContainerPanning: () -> Bool { get set } var isContainerExpanded: () -> Bool { get set } @@ -564,6 +565,11 @@ public class AttachmentController: ViewController { strongSelf.panel.updateBackgroundAlpha(alpha, transition: transition) } } + controller.updateTabBarVisibility = { [weak self, weak controller] isVisible, transition in + if let strongSelf = self, strongSelf.currentControllers.contains(where: { $0 === controller }) { + strongSelf.updateIsPanelVisible(isVisible, transition: transition) + } + } controller.cancelPanGesture = { [weak self] in if let strongSelf = self { @@ -748,6 +754,18 @@ public class AttachmentController: ViewController { private var hasButton = false + private var isPanelVisible: Bool = true + + private func updateIsPanelVisible(_ isVisible: Bool, transition: ContainedViewLayoutTransition) { + if self.isPanelVisible == isVisible { + return + } + self.isPanelVisible = isVisible + if let layout = self.validLayout { + self.containerLayoutUpdated(layout, transition: transition) + } + } + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { self.validLayout = layout @@ -837,6 +855,9 @@ public class AttachmentController: ViewController { if let controller = self.controller, controller.buttons.count > 1 || controller.hasTextInput { hasPanel = true } + if !self.isPanelVisible { + hasPanel = false + } let isEffecitvelyCollapsedUpdated = (self.selectionCount > 0) != (self.panel.isSelecting) var panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, elevateProgress: !hasPanel && !hasButton, transition: transition) diff --git a/submodules/ComposePollUI/Sources/ComposePollScreen.swift b/submodules/ComposePollUI/Sources/ComposePollScreen.swift index a3f6abfcc0..1525320faf 100644 --- a/submodules/ComposePollUI/Sources/ComposePollScreen.swift +++ b/submodules/ComposePollUI/Sources/ComposePollScreen.swift @@ -33,17 +33,20 @@ final class ComposePollScreenComponent: Component { let context: AccountContext let peer: EnginePeer let isQuiz: Bool? + let initialData: ComposePollScreen.InitialData let completion: (ComposedPoll) -> Void init( context: AccountContext, peer: EnginePeer, isQuiz: Bool?, + initialData: ComposePollScreen.InitialData, completion: @escaping (ComposedPoll) -> Void ) { self.context = context self.peer = peer self.isQuiz = isQuiz + self.initialData = initialData self.completion = completion } @@ -69,7 +72,8 @@ final class ComposePollScreenComponent: Component { private let quizAnswerSection = ComponentView() private let pollOptionsSectionHeader = ComponentView() - private let pollOptionsSectionFooter = ComponentView() + private let pollOptionsSectionFooterContainer = UIView() + private var pollOptionsSectionFooter = ComponentView() private var pollOptionsSectionContainer: ListSectionContentView private let pollSettingsSection = ComponentView() @@ -78,6 +82,8 @@ final class ComposePollScreenComponent: Component { private var reactionSelectionControl: ComponentView? private var isUpdating: Bool = false + private var ignoreScrolling: Bool = false + private var previousHadInputHeight: Bool = false private var component: ComposePollScreenComponent? private(set) weak var state: EmptyComponentState? @@ -92,6 +98,7 @@ final class ComposePollScreenComponent: Component { private var nextPollOptionId: Int = 0 private var pollOptions: [PollOption] = [] + private var currentPollOptionsLimitReached: Bool = false private var isAnonymous: Bool = true private var isMultiAnswer: Bool = false @@ -106,6 +113,7 @@ final class ComposePollScreenComponent: Component { private var inputMediaInteraction: ChatEntityKeyboardInputNode.Interaction? private var inputMediaNode: ChatEntityKeyboardInputNode? private var inputMediaNodeBackground = SimpleLayer() + private var inputMediaNodeTargetTag: AnyObject? private let inputMediaNodeDataPromise = Promise() @@ -208,6 +216,8 @@ final class ComposePollScreenComponent: Component { } } + let usedCustomEmojiFiles: [Int64: TelegramMediaFile] = [:] + return ComposedPoll( publicity: self.isAnonymous ? .anonymous : .public, kind: mappedKind, @@ -222,7 +232,8 @@ final class ComposePollScreenComponent: Component { return TelegramMediaPollResults.Solution(text: mappedSolution, entities: []) } ), - deadlineTimeout: nil + deadlineTimeout: nil, + usedCustomEmojiFiles: usedCustomEmojiFiles ) } @@ -237,7 +248,9 @@ final class ComposePollScreenComponent: Component { } func scrollViewDidScroll(_ scrollView: UIScrollView) { - self.updateScrolling(transition: .immediate) + if !self.ignoreScrolling { + self.updateScrolling(transition: .immediate) + } } private func updateScrolling(transition: Transition) { @@ -279,6 +292,10 @@ final class ComposePollScreenComponent: Component { var height: CGFloat = 0.0 if case .emoji = self.currentInputMode, let inputData = self.inputMediaNodeData { + if let updatedTag = self.collectTextInputStates().first(where: { $1.isEditing })?.view.currentTag { + self.inputMediaNodeTargetTag = updatedTag + } + let inputMediaNode: ChatEntityKeyboardInputNode var inputMediaNodeTransition = transition var animateIn = false @@ -293,6 +310,7 @@ final class ComposePollScreenComponent: Component { updatedInputData: self.inputMediaNodeDataPromise.get(), defaultToEmojiTab: true, opaqueTopPanelBackground: false, + useOpaqueTheme: true, interaction: self.inputMediaInteraction, chatPeerId: nil, stateContext: self.inputMediaNodeStateContext @@ -351,7 +369,7 @@ final class ComposePollScreenComponent: Component { } if animateIn { - var targetFrame = inputMediaNode.frame + var targetFrame = inputNodeFrame targetFrame.origin.y = availableSize.height inputMediaNodeTransition.setFrame(layer: inputMediaNode.layer, frame: targetFrame) @@ -367,39 +385,42 @@ final class ComposePollScreenComponent: Component { } height = heightAndOverflow.0 - } else if let inputMediaNode = self.inputMediaNode { - self.inputMediaNode = nil + } else { + self.inputMediaNodeTargetTag = nil - var targetFrame = inputMediaNode.frame - targetFrame.origin.y = availableSize.height - transition.setFrame(view: inputMediaNode.view, frame: targetFrame, completion: { [weak inputMediaNode] _ in - if let inputMediaNode { + if let inputMediaNode = self.inputMediaNode { + self.inputMediaNode = nil + var targetFrame = inputMediaNode.frame + targetFrame.origin.y = availableSize.height + transition.setFrame(view: inputMediaNode.view, frame: targetFrame, completion: { [weak inputMediaNode] _ in + if let inputMediaNode { + Queue.mainQueue().after(0.3) { + inputMediaNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { [weak inputMediaNode] _ in + inputMediaNode?.view.removeFromSuperview() + }) + } + } + }) + transition.setFrame(layer: self.inputMediaNodeBackground, frame: targetFrame, completion: { [weak self] _ in Queue.mainQueue().after(0.3) { - inputMediaNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { [weak inputMediaNode] _ in - inputMediaNode?.view.removeFromSuperview() - }) + guard let self else { + return + } + if self.currentInputMode == .keyboard { + self.inputMediaNodeBackground.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { [weak self] finished in + guard let self else { + return + } + + if finished { + self.inputMediaNodeBackground.removeFromSuperlayer() + } + self.inputMediaNodeBackground.removeAllAnimations() + }) + } } - } - }) - transition.setFrame(layer: self.inputMediaNodeBackground, frame: targetFrame, completion: { [weak self] _ in - Queue.mainQueue().after(0.3) { - guard let self else { - return - } - if self.currentInputMode == .keyboard { - self.inputMediaNodeBackground.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { [weak self] finished in - guard let self else { - return - } - - if finished { - self.inputMediaNodeBackground.removeFromSuperlayer() - } - self.inputMediaNodeBackground.removeAllAnimations() - }) - } - } - }) + }) + } } /*if needsInputActivation { @@ -409,9 +430,12 @@ final class ComposePollScreenComponent: Component { } }*/ - /*if let controller = self.environment?.controller() as? ComposePollScreen { - controller.updateTabBarAlpha(self.inputMediaNode == nil ? 1.0 : 0.0, transition.containedViewLayoutTransition) - }*/ + if let controller = self.environment?.controller() as? ComposePollScreen { + let isTabBarVisible = self.inputMediaNode == nil + DispatchQueue.main.async { [weak controller] in + controller?.updateTabBarVisibility(isTabBarVisible, transition.containedViewLayoutTransition) + } + } return height } @@ -436,6 +460,11 @@ final class ComposePollScreenComponent: Component { self.isUpdating = false } + var alphaTransition = transition + if !transition.animation.isImmediate { + alphaTransition = alphaTransition.withAnimation(.curve(duration: 0.25, curve: .easeInOut)) + } + let environment = environment[EnvironmentType.self].value let themeUpdated = self.environment?.theme !== environment.theme self.environment = environment @@ -491,7 +520,7 @@ final class ComposePollScreenComponent: Component { return } self.currentInputMode = .keyboard - self.state?.updated(transition: .immediate) + self.state?.updated(transition: .spring(duration: 0.4)) }, dismissTextInput: { }, @@ -500,23 +529,45 @@ final class ComposePollScreenComponent: Component { return } + var found = false for (textInputView, externalState) in self.collectTextInputStates() { if externalState.isEditing { textInputView.insertText(text: text) + found = true break } } + if !found, let inputMediaNodeTargetTag = self.inputMediaNodeTargetTag { + for (textInputView, _) in self.collectTextInputStates() { + if textInputView.currentTag === inputMediaNodeTargetTag { + textInputView.insertText(text: text) + found = true + break + } + } + } }, backwardsDeleteText: { [weak self] in guard let self else { return } + var found = false for (textInputView, externalState) in self.collectTextInputStates() { if externalState.isEditing { textInputView.backwardsDeleteText() + found = true break } } + if !found, let inputMediaNodeTargetTag = self.inputMediaNodeTargetTag { + for (textInputView, _) in self.collectTextInputStates() { + if textInputView.currentTag === inputMediaNodeTargetTag { + textInputView.backwardsDeleteText() + found = true + break + } + } + } }, openStickerEditor: { }, @@ -576,7 +627,8 @@ final class ComposePollScreenComponent: Component { resetText: self.resetPollText.flatMap { resetText in return ListComposePollOptionComponent.ResetText(value: resetText) }, - characterLimit: 256, + assumeIsEditing: self.inputMediaNodeTargetTag === self.pollTextFieldTag, + characterLimit: component.initialData.maxPollTextLength, returnKeyAction: { [weak self] in guard let self else { return @@ -670,7 +722,8 @@ final class ComposePollScreenComponent: Component { resetText: pollOption.resetText.flatMap { resetText in return ListComposePollOptionComponent.ResetText(value: resetText) }, - characterLimit: 256, + assumeIsEditing: self.inputMediaNodeTargetTag === pollOption.textFieldTag, + characterLimit: component.initialData.maxPollOptionLength, returnKeyAction: { [weak self] in guard let self else { return @@ -692,7 +745,11 @@ final class ComposePollScreenComponent: Component { return } if let index = self.pollOptions.firstIndex(where: { $0.id == optionId }) { - if index != 0 { + if index == 0 { + if let textInputView = self.pollTextSection.findTaggedView(tag: self.pollTextFieldTag) as? ListComposePollOptionComponent.View { + textInputView.activateInput() + } + } else { if let pollOptionView = self.pollOptionsSectionContainer.itemViews[self.pollOptions[index - 1].id] { if let pollOptionComponentView = pollOptionView.contents.view as? ListComposePollOptionComponent.View { pollOptionComponentView.activateInput() @@ -832,14 +889,31 @@ final class ComposePollScreenComponent: Component { contentHeight += pollOptionsSectionUpdateResult.size.height contentHeight += 7.0 - var pollOptionsFooterItems: [AnimatedTextComponent.Item] = [] - if self.pollOptions.count >= 10, !"".isEmpty { - pollOptionsFooterItems.append(AnimatedTextComponent.Item( - id: 3, - isUnbreakable: true, - content: .text("You have added the maximum number of options.") + + let pollOptionsLimitReached = self.pollOptions.count >= 10 + var animatePollOptionsFooterIn = false + var pollOptionsFooterTransition = transition + if self.currentPollOptionsLimitReached != pollOptionsLimitReached { + self.currentPollOptionsLimitReached = pollOptionsLimitReached + if let pollOptionsSectionFooterView = self.pollOptionsSectionFooter.view { + animatePollOptionsFooterIn = true + pollOptionsFooterTransition = pollOptionsFooterTransition.withAnimation(.none) + alphaTransition.setAlpha(view: pollOptionsSectionFooterView, alpha: 0.0, completion: { [weak pollOptionsSectionFooterView] _ in + pollOptionsSectionFooterView?.removeFromSuperview() + }) + self.pollOptionsSectionFooter = ComponentView() + } + } + + let pollOptionsComponent: AnyComponent + if pollOptionsLimitReached { + pollOptionsFooterTransition = pollOptionsFooterTransition.withAnimation(.none) + pollOptionsComponent = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "You have added the maximum number of options.", font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor)), + maximumNumberOfLines: 0 )) } else { + var pollOptionsFooterItems: [AnimatedTextComponent.Item] = [] pollOptionsFooterItems.append(AnimatedTextComponent.Item( id: 0, isUnbreakable: true, @@ -855,25 +929,36 @@ final class ComposePollScreenComponent: Component { isUnbreakable: true, content: .text(" more options.") )) - } - let pollOptionsSectionFooterSize = self.pollOptionsSectionFooter.update( - transition: transition, - component: AnyComponent(AnimatedTextComponent( + pollOptionsComponent = AnyComponent(AnimatedTextComponent( font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), color: environment.theme.list.freeTextColor, items: pollOptionsFooterItems - )), + )) + } + + let pollOptionsSectionFooterSize = self.pollOptionsSectionFooter.update( + transition: pollOptionsFooterTransition, + component: pollOptionsComponent, environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - sectionHeaderSideInset * 2.0, height: 1000.0) ) let pollOptionsSectionFooterFrame = CGRect(origin: CGPoint(x: sideInset + sectionHeaderSideInset, y: contentHeight), size: pollOptionsSectionFooterSize) + + if self.pollOptionsSectionFooterContainer.superview == nil { + self.scrollView.addSubview(self.pollOptionsSectionFooterContainer) + } + transition.setFrame(view: self.pollOptionsSectionFooterContainer, frame: pollOptionsSectionFooterFrame) + if let pollOptionsSectionFooterView = self.pollOptionsSectionFooter.view { if pollOptionsSectionFooterView.superview == nil { pollOptionsSectionFooterView.layer.anchorPoint = CGPoint() - self.scrollView.addSubview(pollOptionsSectionFooterView) + self.pollOptionsSectionFooterContainer.addSubview(pollOptionsSectionFooterView) } - transition.setPosition(view: pollOptionsSectionFooterView, position: pollOptionsSectionFooterFrame.origin) + pollOptionsFooterTransition.setPosition(view: pollOptionsSectionFooterView, position: CGPoint()) pollOptionsSectionFooterView.bounds = CGRect(origin: CGPoint(), size: pollOptionsSectionFooterFrame.size) + if animatePollOptionsFooterIn && !transition.animation.isImmediate { + alphaTransition.animateAlpha(view: pollOptionsSectionFooterView, from: 0.0, to: 1.0) + } } contentHeight += pollOptionsSectionFooterSize.height contentHeight += sectionSpacing @@ -1049,6 +1134,9 @@ final class ComposePollScreenComponent: Component { deviceMetrics: environment.deviceMetrics, transition: transition ) + if self.inputMediaNode == nil { + inputHeight = environment.inputHeight + } let textInputStates = self.collectTextInputStates() @@ -1188,14 +1276,47 @@ final class ComposePollScreenComponent: Component { } } + let combinedBottomInset: CGFloat if isEditing { - contentHeight += bottomInset + 8.0 - contentHeight += inputHeight + combinedBottomInset = bottomInset + 8.0 + inputHeight } else { - contentHeight += bottomInset - contentHeight += environment.safeInsets.bottom + combinedBottomInset = bottomInset + environment.safeInsets.bottom } + contentHeight += combinedBottomInset + var recenterOnTag: AnyObject? + if let hint = transition.userData(TextFieldComponent.AnimationHint.self), let targetView = hint.view { + var matches = false + switch hint.kind { + case .textChanged: + matches = true + case let .textFocusChanged(isFocused): + if isFocused { + matches = true + } + } + + if matches { + for (textView, _) in self.collectTextInputStates() { + if targetView.isDescendant(of: textView) { + recenterOnTag = textView.currentTag + break + } + } + } + } + if recenterOnTag == nil && self.previousHadInputHeight != (inputHeight > 0.0) { + for (textView, state) in self.collectTextInputStates() { + if state.isEditing { + recenterOnTag = textView.currentTag + break + } + } + } + self.previousHadInputHeight = (inputHeight > 0.0) + + self.ignoreScrolling = true + let previousBounds = self.scrollView.bounds let contentSize = CGSize(width: availableSize.width, height: contentHeight) if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) { self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize) @@ -1208,6 +1329,31 @@ final class ComposePollScreenComponent: Component { self.scrollView.scrollIndicatorInsets = scrollInsets } + if let recenterOnTag { + if let targetView = self.collectTextInputStates().first(where: { $0.view.currentTag === recenterOnTag })?.view { + let caretRect = targetView.convert(targetView.bounds, to: self.scrollView) + var scrollViewBounds = self.scrollView.bounds + let minButtonDistance: CGFloat = 16.0 + if -scrollViewBounds.minY + caretRect.maxY > availableSize.height - combinedBottomInset - minButtonDistance { + scrollViewBounds.origin.y = -(availableSize.height - combinedBottomInset - minButtonDistance - caretRect.maxY) + if scrollViewBounds.origin.y < 0.0 { + scrollViewBounds.origin.y = 0.0 + } + } + if self.scrollView.bounds != scrollViewBounds { + self.scrollView.bounds = scrollViewBounds + } + } + } + if !previousBounds.isEmpty, !transition.animation.isImmediate { + let bounds = self.scrollView.bounds + if bounds.maxY != previousBounds.maxY { + let offsetY = previousBounds.maxY - bounds.maxY + transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true) + } + } + self.ignoreScrolling = false + self.updateScrolling(transition: transition) if isEditing { @@ -1239,6 +1385,19 @@ final class ComposePollScreenComponent: Component { } public class ComposePollScreen: ViewControllerComponentContainer, AttachmentContainable { + public final class InitialData { + fileprivate let maxPollTextLength: Int + fileprivate let maxPollOptionLength: Int + + fileprivate init( + maxPollTextLength: Int, + maxPollOptionLength: Int + ) { + self.maxPollTextLength = maxPollTextLength + self.maxPollOptionLength = maxPollOptionLength + } + } + private let context: AccountContext private let completion: (ComposedPoll) -> Void private var isDismissed: Bool = false @@ -1251,6 +1410,8 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in + } public var cancelPanGesture: () -> Void = { } public var isContainerPanning: () -> Bool = { @@ -1272,6 +1433,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont public init( context: AccountContext, + initialData: InitialData, peer: EnginePeer, isQuiz: Bool?, completion: @escaping (ComposedPoll) -> Void @@ -1283,6 +1445,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont context: context, peer: peer, isQuiz: isQuiz, + initialData: initialData, completion: completion ), navigationBarAppearance: .default, theme: .default) @@ -1319,6 +1482,13 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont deinit { } + public static func initialData(context: AccountContext) -> InitialData { + return InitialData( + maxPollTextLength: Int(context.userLimits.maxCaptionLength), + maxPollOptionLength: 100 + ) + } + @objc private func cancelPressed() { self.dismiss() } diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index d64ad833f8..e6b6a83b86 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -506,6 +506,7 @@ public final class ComposedPoll { public let correctAnswers: [Data]? public let results: TelegramMediaPollResults public let deadlineTimeout: Int32? + public let usedCustomEmojiFiles: [Int64: TelegramMediaFile] public init( publicity: TelegramMediaPollPublicity, @@ -514,7 +515,8 @@ public final class ComposedPoll { options: [TelegramMediaPollOption], correctAnswers: [Data]?, results: TelegramMediaPollResults, - deadlineTimeout: Int32? + deadlineTimeout: Int32?, + usedCustomEmojiFiles: [Int64: TelegramMediaFile] ) { self.publicity = publicity self.kind = kind @@ -523,6 +525,7 @@ public final class ComposedPoll { self.correctAnswers = correctAnswers self.results = results self.deadlineTimeout = deadlineTimeout + self.usedCustomEmojiFiles = usedCustomEmojiFiles } } @@ -561,6 +564,7 @@ public class CreatePollControllerImpl: ItemListController, AttachmentContainable public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } public var isContainerPanning: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false } @@ -609,15 +613,16 @@ public class CreatePollControllerImpl: ItemListController, AttachmentContainable } public func createPollController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, isQuiz: Bool? = nil, completion: @escaping (ComposedPoll) -> Void) -> ViewController { - if "".isEmpty { - return ComposePollScreen( - context: context, - peer: peer, - isQuiz: isQuiz, - completion: completion - ) - } + return ComposePollScreen( + context: context, + initialData: ComposePollScreen.initialData(context: context), + peer: peer, + isQuiz: isQuiz, + completion: completion + ) +} +private func legacyCreatePollController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, isQuiz: Bool?, completion: @escaping (ComposedPoll) -> Void) -> ViewController { var initialState = CreatePollControllerState() if let isQuiz = isQuiz { initialState.isQuiz = isQuiz @@ -973,7 +978,8 @@ public func createPollController(context: AccountContext, updatedPresentationDat options: options, correctAnswers: correctAnswers, results: TelegramMediaPollResults(voters: nil, totalVoters: nil, recentVoters: [], solution: resolvedSolution), - deadlineTimeout: deadlineTimeout + deadlineTimeout: deadlineTimeout, + usedCustomEmojiFiles: [:] )) }) diff --git a/submodules/ComposePollUI/Sources/ListComposePollOptionComponent.swift b/submodules/ComposePollUI/Sources/ListComposePollOptionComponent.swift index 09482df1a1..cc4a16853f 100644 --- a/submodules/ComposePollUI/Sources/ListComposePollOptionComponent.swift +++ b/submodules/ComposePollUI/Sources/ListComposePollOptionComponent.swift @@ -73,6 +73,7 @@ public final class ListComposePollOptionComponent: Component { public let theme: PresentationTheme public let strings: PresentationStrings public let resetText: ResetText? + public let assumeIsEditing: Bool public let characterLimit: Int? public let returnKeyAction: (() -> Void)? public let backspaceKeyAction: (() -> Void)? @@ -87,6 +88,7 @@ public final class ListComposePollOptionComponent: Component { theme: PresentationTheme, strings: PresentationStrings, resetText: ResetText? = nil, + assumeIsEditing: Bool = false, characterLimit: Int, returnKeyAction: (() -> Void)?, backspaceKeyAction: (() -> Void)?, @@ -100,6 +102,7 @@ public final class ListComposePollOptionComponent: Component { self.theme = theme self.strings = strings self.resetText = resetText + self.assumeIsEditing = assumeIsEditing self.characterLimit = characterLimit self.returnKeyAction = returnKeyAction self.backspaceKeyAction = backspaceKeyAction @@ -125,6 +128,9 @@ public final class ListComposePollOptionComponent: Component { if lhs.resetText != rhs.resetText { return false } + if lhs.assumeIsEditing != rhs.assumeIsEditing { + return false + } if lhs.characterLimit != rhs.characterLimit { return false } @@ -236,6 +242,10 @@ public final class ListComposePollOptionComponent: Component { return self.textField.view as? TextFieldComponent.View } + public var currentTag: AnyObject? { + return self.component?.tag + } + public var customUpdateIsHighlighted: ((Bool) -> Void)? public private(set) var separatorInset: CGFloat = 0.0 diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index 9e2ee47237..63272c8937 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -81,6 +81,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } public var isContainerPanning: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false } diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift index a1a18dd424..ebb6d36a09 100644 --- a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift @@ -153,6 +153,7 @@ public final class MediaGroupsScreen: ViewController, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } public var isContainerPanning: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index c36a897a91..b0abae0272 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -203,6 +203,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = { } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } public var isContainerPanning: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false } diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index 2922f6bb25..eddb892c99 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -1297,6 +1297,7 @@ open class PremiumGiftScreen: ViewControllerComponentContainer { public var animationColor: UIColor? public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public let mainButtonStatePromise = Promise(nil) private let mainButtonActionSlot = ActionSlot() diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 9e0c9f35d8..8dfc7b8813 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -710,8 +710,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[236446268] = { return Api.PhotoSize.parse_photoSizeEmpty($0) } dict[-96535659] = { return Api.PhotoSize.parse_photoSizeProgressive($0) } dict[-525288402] = { return Api.PhotoSize.parse_photoStrippedSize($0) } - dict[-2032041631] = { return Api.Poll.parse_poll($0) } - dict[1823064809] = { return Api.PollAnswer.parse_pollAnswer($0) } + dict[1484026161] = { return Api.Poll.parse_poll($0) } + dict[-15277366] = { return Api.PollAnswer.parse_pollAnswer($0) } dict[997055186] = { return Api.PollAnswerVoters.parse_pollAnswerVoters($0) } dict[2061444128] = { return Api.PollResults.parse_pollResults($0) } dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } @@ -1358,7 +1358,7 @@ public extension Api { return parser(reader) } else { - telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + telegramApiLog("Type constructor \(String(UInt32(bitPattern: signature), radix: 16, uppercase: false)) not found") return nil } } diff --git a/submodules/TelegramApi/Sources/Api18.swift b/submodules/TelegramApi/Sources/Api18.swift index a040d11388..86d042f173 100644 --- a/submodules/TelegramApi/Sources/Api18.swift +++ b/submodules/TelegramApi/Sources/Api18.swift @@ -436,17 +436,17 @@ public extension Api { } public extension Api { enum Poll: TypeConstructorDescription { - case poll(id: Int64, flags: Int32, question: String, answers: [Api.PollAnswer], closePeriod: Int32?, closeDate: Int32?) + case poll(id: Int64, flags: Int32, question: Api.TextWithEntities, answers: [Api.PollAnswer], closePeriod: Int32?, closeDate: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .poll(let id, let flags, let question, let answers, let closePeriod, let closeDate): if boxed { - buffer.appendInt32(-2032041631) + buffer.appendInt32(1484026161) } serializeInt64(id, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(question, buffer: buffer, boxed: false) + question.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(answers.count)) for item in answers { @@ -470,8 +470,10 @@ public extension Api { _1 = reader.readInt64() var _2: Int32? _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) + var _3: Api.TextWithEntities? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } var _4: [Api.PollAnswer]? if let _ = reader.readInt32() { _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PollAnswer.self) @@ -498,15 +500,15 @@ public extension Api { } public extension Api { enum PollAnswer: TypeConstructorDescription { - case pollAnswer(text: String, option: Buffer) + case pollAnswer(text: Api.TextWithEntities, option: Buffer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .pollAnswer(let text, let option): if boxed { - buffer.appendInt32(1823064809) + buffer.appendInt32(-15277366) } - serializeString(text, buffer: buffer, boxed: false) + text.serialize(buffer, true) serializeBytes(option, buffer: buffer, boxed: false) break } @@ -520,8 +522,10 @@ public extension Api { } public static func parse_pollAnswer(_ reader: BufferReader) -> PollAnswer? { - var _1: String? - _1 = parseString(reader) + var _1: Api.TextWithEntities? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } var _2: Buffer? _2 = parseBytes(reader) let _c1 = _1 != nil diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 42798b6167..d6b2e230e1 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -433,7 +433,16 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI } else { kind = .poll(multipleAnswers: (flags & (1 << 2)) != 0) } - return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: question, textEntities: [], options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod), nil, nil, nil, nil) + + let questionText: String + let questionEntities: [MessageTextEntity] + switch question { + case let .textWithEntities(text, entities): + questionText = text + questionEntities = messageTextEntitiesFromApiEntities(entities) + } + + return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod), nil, nil, nil, nil) } case let .messageMediaDice(value, emoticon): return (TelegramMediaDice(emoji: emoticon, value: value), nil, nil, nil, nil) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift index b22e227098..6fb5933183 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift @@ -6,13 +6,21 @@ import TelegramApi extension TelegramMediaPollOption { init(apiOption: Api.PollAnswer) { switch apiOption { - case let .pollAnswer(text, option): - self.init(text: text, entities: [], opaqueIdentifier: option.makeData()) + case let .pollAnswer(text, option): + let answerText: String + let answerEntities: [MessageTextEntity] + switch text { + case let .textWithEntities(text, entities): + answerText = text + answerEntities = messageTextEntitiesFromApiEntities(entities) + } + + self.init(text: answerText, entities: answerEntities, opaqueIdentifier: option.makeData()) } } var apiOption: Api.PollAnswer { - return .pollAnswer(text: self.text, option: Buffer(data: self.opaqueIdentifier)) + return .pollAnswer(text: .textWithEntities(text: self.text, entities: apiEntitiesFromMessageTextEntities(self.entities, associatedPeers: SimpleDictionary())), option: Buffer(data: self.opaqueIdentifier)) } } diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index 895c8fb945..b2fa27871d 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -251,7 +251,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post mappedSolutionEntities = apiTextAttributeEntities(TextEntitiesMessageAttribute(entities: solution.entities), associatedPeers: SimpleDictionary()) pollMediaFlags |= 1 << 1 } - let inputPoll = Api.InputMedia.inputMediaPoll(flags: pollMediaFlags, poll: Api.Poll.poll(id: 0, flags: pollFlags, question: poll.text, answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities) + let inputPoll = Api.InputMedia.inputMediaPoll(flags: pollMediaFlags, poll: Api.Poll.poll(id: 0, flags: pollFlags, question: .textWithEntities(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary())), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities) return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil, cacheReferenceKey: nil))) } else if let media = media as? TelegramMediaDice { let inputDice = Api.InputMedia.inputMediaDice(emoticon: media.emoji) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 18f849d883..b1bf536936 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -3897,7 +3897,16 @@ func replayFinalState( } else { kind = .poll(multipleAnswers: (flags & (1 << 2)) != 0) } - updatedPoll = TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: question, textEntities: [], options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: poll.results, isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod) + + let questionText: String + let questionEntities: [MessageTextEntity] + switch question { + case let .textWithEntities(text, entities): + questionText = text + questionEntities = messageTextEntitiesFromApiEntities(entities) + } + + updatedPoll = TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: poll.results, isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod) } } updatedPoll = updatedPoll.withUpdatedResults(TelegramMediaPollResults(apiResults: results), min: resultsMin) diff --git a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift index 186090cc6c..66f0bd7d58 100644 --- a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift @@ -93,6 +93,8 @@ final class AccountTaskManager { tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .emoji).start()) tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .status).start()) tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .avatar).start()) + tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .chatStickers).start()) + tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .greetingStickers).start()) tasks.add(managedSynchronizeAttachMenuBots(accountPeerId: self.accountPeerId, postbox: self.stateManager.postbox, network: self.stateManager.network, force: true).start()) tasks.add(managedSynchronizeNotificationSoundList(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedChatListFilters(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start()) diff --git a/submodules/TelegramCore/Sources/State/EmojiSearchCategories.swift b/submodules/TelegramCore/Sources/State/EmojiSearchCategories.swift index 67c481223b..b70a36c9d4 100644 --- a/submodules/TelegramCore/Sources/State/EmojiSearchCategories.swift +++ b/submodules/TelegramCore/Sources/State/EmojiSearchCategories.swift @@ -8,6 +8,8 @@ public final class EmojiSearchCategories: Equatable, Codable { case emoji = 0 case status = 1 case avatar = 2 + case chatStickers = 3 + case greetingStickers = 4 } public struct Group: Codable, Equatable { @@ -128,6 +130,12 @@ func managedSynchronizeEmojiSearchCategories(postbox: Postbox, network: Network, |> `catch` { _ -> Signal in return .single(.emojiGroupsNotModified) } + //TODO:localize + case .chatStickers, .greetingStickers: + signal = network.request(Api.functions.messages.getEmojiGroups(hash: current?.hash ?? 0)) + |> `catch` { _ -> Signal in + return .single(.emojiGroupsNotModified) + } } return signal diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift index f9880bef27..259a7ccf17 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift @@ -44,7 +44,14 @@ func _internal_requestMessageSelectPollOption(account: Account, messageId: Messa } else { kind = .poll(multipleAnswers: (flags & (1 << 2)) != 0) } - resultPoll = TelegramMediaPoll(pollId: pollId, publicity: publicity, kind: kind, text: question, textEntities: [], options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod) + let questionText: String + let questionEntities: [MessageTextEntity] + switch question { + case let .textWithEntities(text, entities): + questionText = text + questionEntities = messageTextEntitiesFromApiEntities(entities) + } + resultPoll = TelegramMediaPoll(pollId: pollId, publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod) } } @@ -135,7 +142,7 @@ func _internal_requestClosePoll(postbox: Postbox, network: Network, stateManager pollMediaFlags |= 1 << 1 } - return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(flags: pollMediaFlags, poll: .poll(id: poll.pollId.id, flags: pollFlags, question: poll.text, answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities), replyMarkup: nil, entities: nil, scheduleDate: nil, quickReplyShortcutId: nil)) + return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(flags: pollMediaFlags, poll: .poll(id: poll.pollId.id, flags: pollFlags, question: .textWithEntities(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary())), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities), replyMarkup: nil, entities: nil, scheduleDate: nil, quickReplyShortcutId: nil)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift index 671d8394d8..049fd4aa3f 100644 --- a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift +++ b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift @@ -966,7 +966,7 @@ private final class AdminUserActionsSheetComponent: Component { component: AnyComponent(PlainButtonComponent( content: AnyComponent(OptionsSectionFooterComponent( theme: environment.theme, - text: self.isConfigurationExpanded ? partiallyRestrictTitle : fullyBanTitle, + text: self.isConfigurationExpanded ? fullyBanTitle : partiallyRestrictTitle, fontSize: presentationData.listsFontSize.itemListBaseHeaderFontSize, isExpanded: self.isConfigurationExpanded )), diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index 4b545754f9..123dfeb747 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -1542,8 +1542,7 @@ public final class AvatarEditorScreen: ViewControllerComponentContainer { hasTrending: false, forceHasPremium: true, searchIsPlaceholderOnly: false, - isProfilePhotoEmojiSelection: !isGroup, - isGroupPhotoEmojiSelection: isGroup + subject: isGroup ? .groupPhotoEmojiSelection : .profilePhotoEmojiSelection ) let signal = combineLatest(queue: .mainQueue(), diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index b5cc2f4ae1..69ed4bd50d 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -197,7 +197,20 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { let stickerItems: Signal if hasStickers { - stickerItems = EmojiPagerContentComponent.stickerInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, stickerNamespaces: stickerNamespaces, stickerOrderedItemListCollectionIds: stickerOrderedItemListCollectionIds, chatPeerId: chatPeerId, hasSearch: hasSearch, hasTrending: hasTrending, forceHasPremium: false, hasEdit: hasEdit, hideBackground: hideBackground) + stickerItems = EmojiPagerContentComponent.stickerInputData( + context: context, + animationCache: animationCache, + animationRenderer: animationRenderer, + stickerNamespaces: stickerNamespaces, + stickerOrderedItemListCollectionIds: stickerOrderedItemListCollectionIds, + chatPeerId: chatPeerId, + hasSearch: hasSearch, + hasTrending: hasTrending, + forceHasPremium: false, + hasEdit: hasEdit, + subject: .chatStickers, + hideBackground: hideBackground + ) |> map(Optional.init) } else { stickerItems = .single(nil) @@ -345,6 +358,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { private var inputDataDisposable: Disposable? private var hasRecentGifsDisposable: Disposable? private let opaqueTopPanelBackground: Bool + private let useOpaqueTheme: Bool private struct EmojiSearchResult { var groups: [EmojiPagerContentComponent.ItemGroup] @@ -451,11 +465,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { |> distinctUntilChanged } - public init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal, defaultToEmojiTab: Bool, opaqueTopPanelBackground: Bool = false, interaction: ChatEntityKeyboardInputNode.Interaction?, chatPeerId: PeerId?, stateContext: StateContext?) { + public init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal, defaultToEmojiTab: Bool, opaqueTopPanelBackground: Bool = false, useOpaqueTheme: Bool = false, interaction: ChatEntityKeyboardInputNode.Interaction?, chatPeerId: PeerId?, stateContext: StateContext?) { self.context = context self.currentInputData = currentInputData self.defaultToEmojiTab = defaultToEmojiTab self.opaqueTopPanelBackground = opaqueTopPanelBackground + self.useOpaqueTheme = useOpaqueTheme self.stateContext = stateContext self.interaction = interaction @@ -1164,7 +1179,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { externalBackground: nil, externalExpansionView: nil, customContentView: nil, - useOpaqueTheme: false, + useOpaqueTheme: self.useOpaqueTheme, hideBackground: false, stateContext: self.stateContext?.emojiState, addImage: nil @@ -1508,7 +1523,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { externalBackground: nil, externalExpansionView: nil, customContentView: nil, - useOpaqueTheme: false, + useOpaqueTheme: self.useOpaqueTheme, hideBackground: false, stateContext: nil, addImage: nil diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index e7208d01ca..b5b8755e77 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -1589,6 +1589,13 @@ public extension EmojiPagerContentComponent { return emojiItems } + enum StickersSubject { + case profilePhotoEmojiSelection + case groupPhotoEmojiSelection + case chatStickers + case greetingStickers + } + static func stickerInputData( context: AccountContext, animationCache: AnimationCache, @@ -1601,8 +1608,7 @@ public extension EmojiPagerContentComponent { forceHasPremium: Bool, hasEdit: Bool = false, searchIsPlaceholderOnly: Bool = true, - isProfilePhotoEmojiSelection: Bool = false, - isGroupPhotoEmojiSelection: Bool = false, + subject: StickersSubject = .chatStickers, hideBackground: Bool = false ) -> Signal { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) @@ -1652,12 +1658,17 @@ public extension EmojiPagerContentComponent { let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings + //TODO:localize let searchCategories: Signal - if isProfilePhotoEmojiSelection || isGroupPhotoEmojiSelection { + switch subject { + case .groupPhotoEmojiSelection, .profilePhotoEmojiSelection: searchCategories = context.engine.stickers.emojiSearchCategories(kind: .avatar) - } else { - searchCategories = context.engine.stickers.emojiSearchCategories(kind: .emoji) + case .chatStickers: + searchCategories = context.engine.stickers.emojiSearchCategories(kind: .chatStickers) + case .greetingStickers: + searchCategories = context.engine.stickers.emojiSearchCategories(kind: .greetingStickers) } + return combineLatest( context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: stickerOrderedItemListCollectionIds, namespaces: stickerNamespaces, aroundIndex: nil, count: 10000000), hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: false), @@ -2091,6 +2102,14 @@ public extension EmojiPagerContentComponent { ) } + let warpContentsOnEdges: Bool + switch subject { + case .profilePhotoEmojiSelection, .groupPhotoEmojiSelection: + warpContentsOnEdges = true + default: + warpContentsOnEdges = false + } + return EmojiPagerContentComponent( id: isMasks ? "masks" : "stickers", context: context, @@ -2103,7 +2122,7 @@ public extension EmojiPagerContentComponent { itemLayoutType: .detailed, itemContentUniqueId: nil, searchState: .empty(hasResults: false), - warpContentsOnEdges: isProfilePhotoEmojiSelection || isGroupPhotoEmojiSelection, + warpContentsOnEdges: warpContentsOnEdges, hideBackground: hideBackground, displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil, searchCategories: searchCategories, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index f01e8633c7..712bd98407 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -468,7 +468,7 @@ final class MediaEditorScreenComponent: Component { self.currentInputMode = .text if hasFirstResponder(self) { if let view = self.inputPanel.view as? MessageInputPanelComponent.View { - self.nextTransitionUserData = TextFieldComponent.AnimationHint(view: nil, kind: .textFocusChanged) + self.nextTransitionUserData = TextFieldComponent.AnimationHint(view: nil, kind: .textFocusChanged(isFocused: false)) if view.isActive { view.deactivateInput(force: true) } else { @@ -476,7 +476,7 @@ final class MediaEditorScreenComponent: Component { } } } else { - self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(view: nil, kind: .textFocusChanged))) + self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(view: nil, kind: .textFocusChanged(isFocused: false)))) } } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index 27beabbb90..81fb4a55ef 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -1305,6 +1305,8 @@ public final class QuickReplySetupScreen: ViewControllerComponentContainer, Atta } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in + } public var cancelPanGesture: () -> Void = { } public var isContainerPanning: () -> Bool = { diff --git a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift index 8f173579b0..047ec57524 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift @@ -229,7 +229,8 @@ final class BusinessIntroSetupScreenComponent: Component { hasSearch: true, hasTrending: false, forceHasPremium: true, - searchIsPlaceholderOnly: false + searchIsPlaceholderOnly: false, + subject: .greetingStickers ) self.stickerContentDisposable = (stickerContent |> deliverOnMainQueue).start(next: { [weak self] stickerContent in diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift index d7a193bade..69902150f8 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift @@ -338,6 +338,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } public var isContainerPanning: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 1a59d41eb5..d4970771d5 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -905,7 +905,7 @@ public final class StoryItemSetContainerComponent: Component { if hasFirstResponder(self) { view.deactivateInput() } else { - self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(view: nil, kind: .textFocusChanged))) + self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(view: nil, kind: .textFocusChanged(isFocused: false)))) } } } diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index b242d121f6..f4143dc31a 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -69,9 +69,9 @@ public final class TextFieldComponent: Component { public final class AnimationHint { - public enum Kind { + public enum Kind: Equatable { case textChanged - case textFocusChanged + case textFocusChanged(isFocused: Bool) } public weak var view: View? @@ -105,6 +105,7 @@ public final class TextFieldComponent: Component { public let hideKeyboard: Bool public let customInputView: UIView? public let resetText: NSAttributedString? + public let assumeIsEditing: Bool public let isOneLineWhenUnfocused: Bool public let characterLimit: Int? public let emptyLineHandling: EmptyLineHandling @@ -127,6 +128,7 @@ public final class TextFieldComponent: Component { hideKeyboard: Bool, customInputView: UIView?, resetText: NSAttributedString?, + assumeIsEditing: Bool = false, isOneLineWhenUnfocused: Bool, characterLimit: Int? = nil, emptyLineHandling: EmptyLineHandling = .allowed, @@ -148,6 +150,7 @@ public final class TextFieldComponent: Component { self.hideKeyboard = hideKeyboard self.customInputView = customInputView self.resetText = resetText + self.assumeIsEditing = assumeIsEditing self.isOneLineWhenUnfocused = isOneLineWhenUnfocused self.characterLimit = characterLimit self.emptyLineHandling = emptyLineHandling @@ -191,6 +194,9 @@ public final class TextFieldComponent: Component { if lhs.resetText != rhs.resetText { return false } + if lhs.assumeIsEditing != rhs.assumeIsEditing { + return false + } if lhs.isOneLineWhenUnfocused != rhs.isOneLineWhenUnfocused { return false } @@ -450,7 +456,7 @@ public final class TextFieldComponent: Component { return } if !self.isUpdating { - self.state?.updated(transition: Transition(animation: .curve(duration: 0.5, curve: .spring)).withUserData(AnimationHint(view: self, kind: .textFocusChanged))) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.5, curve: .spring)).withUserData(AnimationHint(view: self, kind: .textFocusChanged(isFocused: true)))) } if component.isOneLineWhenUnfocused { Queue.mainQueue().justDispatch { @@ -461,7 +467,7 @@ public final class TextFieldComponent: Component { public func chatInputTextNodeDidFinishEditing() { if !self.isUpdating { - self.state?.updated(transition: Transition(animation: .curve(duration: 0.5, curve: .spring)).withUserData(AnimationHint(view: self, kind: .textFocusChanged))) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.5, curve: .spring)).withUserData(AnimationHint(view: self, kind: .textFocusChanged(isFocused: false)))) } } @@ -1141,7 +1147,7 @@ public final class TextFieldComponent: Component { } let wasEditing = component.externalState.isEditing - let isEditing = self.textView.isFirstResponder + let isEditing = self.textView.isFirstResponder || component.assumeIsEditing var innerTextInsets = component.insets innerTextInsets.left = 0.0 diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Sources/AttachmentFileController.swift index 456dde1f03..8c56d16af5 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -197,6 +197,7 @@ class AttachmentFileControllerImpl: ItemListController, AttachmentFileController public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } public var isContainerPanning: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false } diff --git a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift index c07cb0e544..74bda81035 100644 --- a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift +++ b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift @@ -14,6 +14,7 @@ import TelegramStringFormatting import StorageUsageScreen import SettingsUI import DeleteChatPeerActionSheetItem +import OverlayStatusController fileprivate struct InitialBannedRights { var value: TelegramChatBannedRights? @@ -64,7 +65,7 @@ extension ChatControllerImpl { } else { text.append("**\(result.updateBannedRights.count)** users restricted.") } - for id in result.banPeers { + for (id, _) in result.updateBannedRights { if let value = initialUserBannedRights[id] { undoRights[id] = value } @@ -131,14 +132,40 @@ extension ChatControllerImpl { return } - /*if "".isEmpty { - self.push(RecentActionsSettingsSheet(context: self.context, adminPeers: authors.map(EnginePeer.init), completion: { _ in })) - return - }*/ - - self.navigationActionDisposable.set((combineLatest(authors.map { author in + var signal = combineLatest(authors.map { author in self.context.engine.peers.fetchChannelParticipant(peerId: peerId, participantId: author.id) }) + let disposables = MetaDisposable() + self.navigationActionDisposable.set(disposables) + + var cancelImpl: (() -> Void)? + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let progressSignal = Signal { [weak self] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + self?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.3, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.startStrict() + + signal = signal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + cancelImpl = { + disposables.set(nil) + } + + disposables.set((signal |> deliverOnMainQueue).startStrict(next: { [weak self] participants in guard let self else { return @@ -195,52 +222,88 @@ extension ChatControllerImpl { return } - do { - self.navigationActionDisposable.set((self.context.engine.peers.fetchChannelParticipant(peerId: peerId, participantId: author.id) - |> deliverOnMainQueue).startStrict(next: { [weak self] participant in - if let strongSelf = self { - if "".isEmpty { - guard let participant else { + var signal = self.context.engine.peers.fetchChannelParticipant(peerId: peerId, participantId: author.id) + let disposables = MetaDisposable() + self.navigationActionDisposable.set(disposables) + + var cancelImpl: (() -> Void)? + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let progressSignal = Signal { [weak self] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + self?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.3, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.startStrict() + + signal = signal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + cancelImpl = { + disposables.set(nil) + } + + disposables.set((signal + |> deliverOnMainQueue).startStrict(next: { [weak self] participant in + guard let self, let participant else { + return + } + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), + TelegramEngine.EngineData.Item.Peer.Peer(id: author.id) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] chatPeer, authorPeer in + guard let self, let chatPeer else { + return + } + guard let authorPeer else { + return + } + var initialUserBannedRights: [EnginePeer.Id: InitialBannedRights] = [:] + switch participant { + case .creator: + break + case let .member(_, _, _, banInfo, _): + if let banInfo { + initialUserBannedRights[participant.peerId] = InitialBannedRights(value: banInfo.rights) + } else { + initialUserBannedRights[participant.peerId] = InitialBannedRights(value: nil) + } + } + self.push(AdminUserActionsSheet( + context: self.context, + chatPeer: chatPeer, + peers: [RenderedChannelParticipant( + participant: participant, + peer: authorPeer._asPeer() + )], + messageCount: messageIds.count, + completion: { [weak self] result in + guard let self else { return } - let _ = (strongSelf.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), - TelegramEngine.EngineData.Item.Peer.Peer(id: author.id) - ) - |> deliverOnMainQueue).startStandalone(next: { chatPeer, authorPeer in - guard let self, let chatPeer else { - return - } - guard let authorPeer else { - return - } - var initialUserBannedRights: [EnginePeer.Id: InitialBannedRights] = [:] - switch participant { - case .creator: - break - case let .member(_, _, _, banInfo, _): - if let banInfo { - initialUserBannedRights[participant.peerId] = InitialBannedRights(value: banInfo.rights) - } else { - initialUserBannedRights[participant.peerId] = InitialBannedRights(value: nil) - } - } - self.push(AdminUserActionsSheet( - context: self.context, - chatPeer: chatPeer, - peers: [RenderedChannelParticipant( - participant: participant, - peer: authorPeer._asPeer() - )], - messageCount: messageIds.count, - completion: { [weak self] result in - guard let self else { - return - } - self.applyAdminUserActionsResult(messageIds: messageIds, result: result, initialUserBannedRights: initialUserBannedRights) - } - )) - }) + self.applyAdminUserActionsResult(messageIds: messageIds, result: result, initialUserBannedRights: initialUserBannedRights) + } + )) + }) + })) + + /*do { + self.navigationActionDisposable.set((self.context.engine.peers.fetchChannelParticipant(peerId: peerId, participantId: author.id) + |> deliverOnMainQueue).startStrict(next: { + if let strongSelf = self { + if "".isEmpty { + return } @@ -315,7 +378,7 @@ extension ChatControllerImpl { strongSelf.present(actionSheet, in: .window(.root)) } })) - } + }*/ } func presentDeleteMessageOptions(messageIds: Set, options: ChatAvailableMessageActionOptions, contextController: ContextControllerProtocol?, completion: @escaping (ContextMenuActionResult) -> Void) { diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index 4cae4dae53..a4e96a6a95 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -79,6 +79,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController var requestAttachmentMenuExpansion: () -> Void = {} var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } var cancelPanGesture: () -> Void = { } var isContainerPanning: () -> Bool = { return false } var isContainerExpanded: () -> Bool = { return false } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index a5a6f19f4d..6b481b5d66 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -259,6 +259,7 @@ public final class WebAppController: ViewController, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = { } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } public var isContainerPanning: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false }