diff --git a/submodules/AccountContext/Sources/AttachmentMainButtonState.swift b/submodules/AccountContext/Sources/AttachmentMainButtonState.swift index 026706daf6..49650fca00 100644 --- a/submodules/AccountContext/Sources/AttachmentMainButtonState.swift +++ b/submodules/AccountContext/Sources/AttachmentMainButtonState.swift @@ -33,6 +33,7 @@ public struct AttachmentMainButtonState { } public let text: String? + public let badge: String? public let font: Font public let background: Background public let textColor: UIColor @@ -44,6 +45,7 @@ public struct AttachmentMainButtonState { public init( text: String?, + badge: String? = nil, font: Font, background: Background, textColor: UIColor, @@ -54,6 +56,7 @@ public struct AttachmentMainButtonState { position: Position? = nil ) { self.text = text + self.badge = badge self.font = font self.background = background self.textColor = textColor diff --git a/submodules/AccountContext/Sources/ContactMultiselectionController.swift b/submodules/AccountContext/Sources/ContactMultiselectionController.swift index 88192e710e..792a53a30e 100644 --- a/submodules/AccountContext/Sources/ContactMultiselectionController.swift +++ b/submodules/AccountContext/Sources/ContactMultiselectionController.swift @@ -79,7 +79,7 @@ public enum ContactMultiselectionControllerMode { case channelCreation case chatSelection(ChatSelection) case premiumGifting(birthdays: [EnginePeer.Id: TelegramBirthday]?, selectToday: Bool, hasActions: Bool) - case requestedUsersSelection + case requestedUsersSelection(isBot: Bool?, isPremium: Bool?) } public enum ContactListFilter { diff --git a/submodules/AccountContext/Sources/PeerSelectionController.swift b/submodules/AccountContext/Sources/PeerSelectionController.swift index b978e362f5..8fb3a006aa 100644 --- a/submodules/AccountContext/Sources/PeerSelectionController.swift +++ b/submodules/AccountContext/Sources/PeerSelectionController.swift @@ -59,6 +59,7 @@ public final class PeerSelectionControllerParams { public let createNewGroup: (() -> Void)? public let pretendPresentedInModal: Bool public let multipleSelection: Bool + public let multipleSelectionLimit: Int32? public let forwardedMessageIds: [EngineMessage.Id] public let hasTypeHeaders: Bool public let selectForumThreads: Bool @@ -80,6 +81,7 @@ public final class PeerSelectionControllerParams { createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false, + multipleSelectionLimit: Int32? = nil, forwardedMessageIds: [EngineMessage.Id] = [], hasTypeHeaders: Bool = false, selectForumThreads: Bool = false, @@ -100,6 +102,7 @@ public final class PeerSelectionControllerParams { self.createNewGroup = createNewGroup self.pretendPresentedInModal = pretendPresentedInModal self.multipleSelection = multipleSelection + self.multipleSelectionLimit = multipleSelectionLimit self.forwardedMessageIds = forwardedMessageIds self.hasTypeHeaders = hasTypeHeaders self.selectForumThreads = selectForumThreads diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 5344b52b73..aa0382e630 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -382,12 +382,102 @@ private final class LoadingProgressNode: ASDisplayNode { } } +private final class BadgeNode: ASDisplayNode { + private var fillColor: UIColor + private var strokeColor: UIColor + private var textColor: UIColor + + private let textNode: ImmediateTextNode + private let backgroundNode: ASImageNode + + private let font: UIFont = Font.with(size: 15.0, design: .round, weight: .bold) + + var text: String = "" { + didSet { + self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor) + self.invalidateCalculatedLayout() + } + } + + init(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) { + self.fillColor = fillColor + self.strokeColor = strokeColor + self.textColor = textColor + + self.textNode = ImmediateTextNode() + self.textNode.isUserInteractionEnabled = false + self.textNode.displaysAsynchronously = false + + self.backgroundNode = ASImageNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.displayWithoutProcessing = true + self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: nil, strokeWidth: 1.0) + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.textNode) + + self.isUserInteractionEnabled = false + } + + func updateTheme(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) { + self.fillColor = fillColor + self.strokeColor = strokeColor + self.textColor = textColor + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: strokeColor, strokeWidth: 1.0) + self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor) + } + + func animateBump(incremented: Bool) { + if incremented { + let firstTransition = ContainedViewLayoutTransition.animated(duration: 0.1, curve: .easeInOut) + firstTransition.updateTransformScale(layer: self.backgroundNode.layer, scale: 1.2) + firstTransition.updateTransformScale(layer: self.textNode.layer, scale: 1.2, completion: { finished in + if finished { + let secondTransition = ContainedViewLayoutTransition.animated(duration: 0.1, curve: .easeInOut) + secondTransition.updateTransformScale(layer: self.backgroundNode.layer, scale: 1.0) + secondTransition.updateTransformScale(layer: self.textNode.layer, scale: 1.0) + } + }) + } else { + let firstTransition = ContainedViewLayoutTransition.animated(duration: 0.1, curve: .easeInOut) + firstTransition.updateTransformScale(layer: self.backgroundNode.layer, scale: 0.8) + firstTransition.updateTransformScale(layer: self.textNode.layer, scale: 0.8, completion: { finished in + if finished { + let secondTransition = ContainedViewLayoutTransition.animated(duration: 0.1, curve: .easeInOut) + secondTransition.updateTransformScale(layer: self.backgroundNode.layer, scale: 1.0) + secondTransition.updateTransformScale(layer: self.textNode.layer, scale: 1.0) + } + }) + } + } + + func animateOut() { + let timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue + self.backgroundNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, delay: 0.0, timingFunction: timingFunction, removeOnCompletion: true, completion: nil) + self.textNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, delay: 0.0, timingFunction: timingFunction, removeOnCompletion: true, completion: nil) + } + + func update(_ constrainedSize: CGSize) -> CGSize { + let badgeSize = self.textNode.updateLayout(constrainedSize) + let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 8.0), height: 18.0) + let backgroundFrame = CGRect(origin: CGPoint(), size: backgroundSize) + self.backgroundNode.frame = backgroundFrame + self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: floorToScreenPixels((backgroundFrame.size.height - badgeSize.height) / 2.0) - UIScreenPixel), size: badgeSize) + + return backgroundSize + } +} + private final class MainButtonNode: HighlightTrackingButtonNode { private var state: AttachmentMainButtonState private var size: CGSize? private let backgroundAnimationNode: ASImageNode fileprivate let textNode: ImmediateTextNode + private var badgeNode: BadgeNode? private let statusNode: SemanticStatusNode private var progressNode: ASImageNode? @@ -616,6 +706,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode { progressNode.image = generateIndefiniteActivityIndicatorImage(color: state.textColor, diameter: diameter, lineWidth: 3.0) } + var textFrame: CGRect = .zero if let text = state.text { let font: UIFont switch state.font { @@ -627,13 +718,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode { self.textNode.attributedText = NSAttributedString(string: text, font: font, textColor: state.textColor) let textSize = self.textNode.updateLayout(size) - let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) - if self.textNode.frame.width.isZero { - self.textNode.frame = textFrame - } else { - self.textNode.bounds = CGRect(origin: .zero, size: textSize) - transition.updatePosition(node: self.textNode, position: textFrame.center) - } + textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) switch state.background { case let .color(backgroundColor): @@ -669,6 +754,40 @@ private final class MainButtonNode: HighlightTrackingButtonNode { } } + if let badge = state.badge { + let badgeNode: BadgeNode + var badgeTransition = transition + if let current = self.badgeNode { + badgeNode = current + } else { + badgeTransition = .immediate + var textColor: UIColor + switch state.background { + case let .color(backgroundColor): + textColor = backgroundColor + case .premium: + textColor = UIColor(rgb: 0x0077ff) + } + badgeNode = BadgeNode(fillColor: state.textColor, strokeColor: .clear, textColor: textColor) + self.badgeNode = badgeNode + self.addSubnode(badgeNode) + } + badgeNode.text = badge + let badgeSize = badgeNode.update(CGSize(width: 100.0, height: 100.0)) + textFrame.origin.x -= badgeSize.width / 2.0 + badgeTransition.updateFrame(node: badgeNode, frame: CGRect(origin: CGPoint(x: textFrame.maxX + 6.0, y: textFrame.minY + floorToScreenPixels((textFrame.height - badgeSize.height) * 0.5)), size: badgeSize)) + } else if let badgeNode = self.badgeNode { + self.badgeNode = nil + badgeNode.removeFromSupernode() + } + + if self.textNode.frame.width.isZero { + self.textNode.frame = textFrame + } else { + self.textNode.bounds = CGRect(origin: .zero, size: textFrame.size) + transition.updatePosition(node: self.textNode, position: textFrame.center) + } + if previousState.progress != state.progress { if state.progress == .center { self.transitionToProgress() diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 1f81618ce4..ff66eefd09 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -2491,6 +2491,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { case let .user(userType): if case let .user(user) = peer { match = true + if user.id.isVerificationCodes { + match = false + } if let isBot = userType.isBot { if isBot != (user.botInfo != nil) { match = false diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 10b8e0ee48..42595394ed 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -878,14 +878,10 @@ private final class ChatListMediaPreviewNode: ASDisplayNode { if file.isInstantVideo { isRound = true } - if file.isSticker || file.isAnimatedSticker { - self.playIcon.isHidden = true + if file.isVideo && !file.isAnimated { + self.playIcon.isHidden = false } else { - if file.isAnimated { - self.playIcon.isHidden = true - } else { - self.playIcon.isHidden = false - } + self.playIcon.isHidden = true } if let mediaDimensions = file.dimensions { dimensions = mediaDimensions.cgSize @@ -2646,6 +2642,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } else if contentImageIsDisplayedAsAvatar && (file.isSticker || file.isVideoSticker) { let fitSize = contentImageSize contentImageSpecs.append(ContentImageSpec(message: message, media: .file(file), size: fitSize)) + } else if !file.previewRepresentations.isEmpty, let _ = file.dimensions { + let fitSize = contentImageSize + contentImageSpecs.append(ContentImageSpec(message: message, media: .file(file), size: fitSize)) } break inner } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 9d33d16d27..93d21da646 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -2464,6 +2464,9 @@ public final class ChatListNode: ListView { case let .user(userType): if case let .user(user) = peer { match = true + if user.id.isVerificationCodes { + match = false + } if let isBot = userType.isBot { if isBot != (user.botInfo != nil) { match = false diff --git a/submodules/CounterControllerTitleView/Sources/CounterControllerTitleView.swift b/submodules/CounterControllerTitleView/Sources/CounterControllerTitleView.swift index 42d3b93faa..117417dc7e 100644 --- a/submodules/CounterControllerTitleView/Sources/CounterControllerTitleView.swift +++ b/submodules/CounterControllerTitleView/Sources/CounterControllerTitleView.swift @@ -59,7 +59,7 @@ public final class CounterControllerTitleView: UIView { let primaryTextColor = self.primaryTextColor ?? self.theme.rootController.navigationBar.primaryTextColor let secondaryTextColor = self.secondaryTextColor ?? self.theme.rootController.navigationBar.secondaryTextColor self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.semibold(17.0), textColor: primaryTextColor) - self.subtitleNode.attributedText = NSAttributedString(string: self.title.counter, font: Font.regular(13.0), textColor: secondaryTextColor) + self.subtitleNode.attributedText = NSAttributedString(string: self.title.counter, font: Font.with(size: 13.0, traits: .monospacedNumbers), textColor: secondaryTextColor) self.accessibilityLabel = self.title.title self.accessibilityValue = self.title.counter diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorSliderView.m b/submodules/LegacyComponents/Sources/TGPhotoEditorSliderView.m index b7c8e59536..e32c11cdb5 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorSliderView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorSliderView.m @@ -381,7 +381,11 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f; - (void)setValue:(CGFloat)value animated:(BOOL)__unused animated { - _value = MIN(MAX(_lowerBoundValue, MAX(value, _minimumValue)), _maximumValue); + if (_lowerBoundValue > FLT_EPSILON) { + _value = MIN(MAX(_lowerBoundValue, MAX(value, _minimumValue)), _maximumValue); + } else { + _value = MIN(MAX(value, _minimumValue), _maximumValue); + } [self setNeedsLayout]; } diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift index f47734863d..1d50c9f753 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift @@ -297,9 +297,24 @@ final class StickerPackEmojisItemNode: GridItemNode { self.setNeedsLayout() } + + private var visibleRect: CGRect? + override func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) { + var y: CGFloat + if absoluteRect.minY > 0.0 { + y = 0.0 + } else { + y = absoluteRect.minY * -1.0 + } + var rect = CGRect(origin: CGPoint(x: 0.0, y: y), size: CGSize(width: containerSize.width, height: containerSize.height)) + rect.size.height += 96.0 + self.visibleRect = rect + + self.updateVisibleItems(attemptSynchronousLoads: false, transition: .immediate) + } func updateVisibleItems(attemptSynchronousLoads: Bool, transition: ContainedViewLayoutTransition) { - guard let item = self.item, !self.size.width.isZero else { + guard let item = self.item, !self.size.width.isZero, let visibleRect = self.visibleRect else { return } @@ -321,17 +336,26 @@ final class StickerPackEmojisItemNode: GridItemNode { self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: item.title != nil ? 61.0 : 0.0), size: CGSize(width: itemLayout.width, height: itemLayout.height)) for index in 0 ..< items.count { + var itemFrame = itemLayout.frame(itemIndex: index) + if !visibleRect.intersects(itemFrame) { + continue + } let item = items[index] let itemId = EmojiKeyboardItemLayer.Key( groupId: 0, itemId: .animation(.file(item.file.fileId)) ) - validIds.insert(itemId) let itemDimensions = item.file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) let itemNativeFitSize = itemDimensions.fitted(CGSize(width: nativeItemSize, height: nativeItemSize)) let itemVisibleFitSize = itemDimensions.fitted(CGSize(width: itemLayout.visibleItemSize, height: itemLayout.visibleItemSize)) + validIds.insert(itemId) + + itemFrame.origin.x += floor((itemFrame.width - itemVisibleFitSize.width) / 2.0) + itemFrame.origin.y += floor((itemFrame.height - itemVisibleFitSize.height) / 2.0) + itemFrame.size = itemVisibleFitSize + var updateItemLayerPlaceholder = false var itemTransition = transition let itemLayer: EmojiKeyboardItemLayer @@ -426,12 +450,6 @@ final class StickerPackEmojisItemNode: GridItemNode { itemLayer.layerTintColor = color.cgColor } - var itemFrame = itemLayout.frame(itemIndex: index) - - itemFrame.origin.x += floor((itemFrame.width - itemVisibleFitSize.width) / 2.0) - itemFrame.origin.y += floor((itemFrame.height - itemVisibleFitSize.height) / 2.0) - itemFrame.size = itemVisibleFitSize - let itemPosition = CGPoint(x: itemFrame.midX, y: itemFrame.midY) let itemBounds = CGRect(origin: CGPoint(), size: itemFrame.size) itemTransition.updatePosition(layer: itemLayer, position: itemPosition) diff --git a/submodules/TelegramUI/Components/PeerSelectionController/BUILD b/submodules/TelegramUI/Components/PeerSelectionController/BUILD index 8cce950b84..230b96365d 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/BUILD +++ b/submodules/TelegramUI/Components/PeerSelectionController/BUILD @@ -34,6 +34,7 @@ swift_library( "//submodules/ContextUI", "//submodules/TextFormat", "//submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode", + "//submodules/CounterControllerTitleView", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift index 6aa6fe8309..ea74588420 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift @@ -9,6 +9,7 @@ import ProgressNavigationButtonNode import AccountContext import SearchUI import ChatListUI +import CounterControllerTitleView public final class PeerSelectionControllerImpl: ViewController, PeerSelectionController { private let context: AccountContext @@ -64,6 +65,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon private let forwardedMessageIds: [EngineMessage.Id] private let hasTypeHeaders: Bool private let requestPeerType: [ReplyMarkupButtonRequestPeerType]? + let multipleSelectionLimit: Int32? private let hasCreation: Bool let immediatelyActivateMultipleSelection: Bool @@ -81,6 +83,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon } } + private(set) var titleView: CounterControllerTitleView? private var searchContentNode: NavigationBarSearchContentNode? var tabContainerNode: ChatListFilterTabContainerNode? private var tabContainerData: ([ChatListFilterTabEntry], Bool, Int32?)? @@ -107,6 +110,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon self.requestPeerType = params.requestPeerType self.hasCreation = params.hasCreation self.immediatelyActivateMultipleSelection = params.immediatelyActivateMultipleSelection + self.multipleSelectionLimit = params.multipleSelectionLimit super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) @@ -133,7 +137,13 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon } } - self.title = self.customTitle ?? self.presentationData.strings.Conversation_ForwardTitle + if let maxCount = params.multipleSelectionLimit { + self.titleView = CounterControllerTitleView(theme: self.presentationData.theme) + self.titleView?.title = CounterControllerTitle(title: self.customTitle ?? self.presentationData.strings.Conversation_ForwardTitle, counter: "0/\(maxCount)") + self.navigationItem.titleView = self.titleView + } else { + self.title = self.customTitle ?? self.presentationData.strings.Conversation_ForwardTitle + } if params.forumPeerId == nil { self.navigationPresentation = .modal diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 7b32c110a3..6f189e3089 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -24,6 +24,7 @@ import SolidRoundedButtonNode import ContextUI import TextFormat import ForwardAccessoryPanelNode +import CounterControllerTitleView final class PeerSelectionControllerNode: ASDisplayNode { private let context: AccountContext @@ -108,7 +109,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { return (self.presentationData, self.presentationDataPromise.get()) } - init(context: AccountContext, controller: PeerSelectionControllerImpl, presentationData: PresentationData, filter: ChatListNodePeersFilter, forumPeerId: EnginePeer.Id?, hasFilters: Bool, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessageIds: [EngineMessage.Id], hasTypeHeaders: Bool, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, hasCreation: Bool, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) { + init(context: AccountContext, controller: PeerSelectionControllerImpl, presentationData: PresentationData, filter: ChatListNodePeersFilter, forumPeerId: EnginePeer.Id?, hasFilters: Bool, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessageIds: [EngineMessage.Id], hasTypeHeaders: Bool, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, hasCreation: Bool, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) { self.context = context self.controller = controller self.present = present @@ -215,6 +216,9 @@ final class PeerSelectionControllerNode: ASDisplayNode { } else { self.mainContainerNode = nil self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: chatListMode, theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false, autoSetReady: true, isMainTab: false) + if let multipleSelectionLimit = controller.multipleSelectionLimit { + self.chatListNode?.selectionLimit = multipleSelectionLimit + } } super.init() @@ -926,7 +930,17 @@ final class PeerSelectionControllerNode: ASDisplayNode { } else if let chatListNode = self.chatListNode { chatListNode.selectionCountChanged = { [weak self] count in if let self { + if let _ = self.controller?.multipleSelectionLimit { + self.countPanelNode?.buttonTitle = self.presentationData.strings.Premium_Gift_ContactSelection_Proceed + } else { + self.countPanelNode?.buttonTitle = self.presentationData.strings.ShareMenu_Send + } self.countPanelNode?.count = count + + if let titleView = self.controller?.titleView, let maxCount = self.controller?.multipleSelectionLimit { + titleView.title = CounterControllerTitle(title: titleView.title.title, counter: "\(count)/\(maxCount)") + } + if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .animated(duration: 0.3, curve: .spring)) } @@ -1789,10 +1803,11 @@ private final class PeersCountPanelNode: ASDisplayNode { private var validLayout: (CGFloat, CGFloat, CGFloat)? + var buttonTitle: String = "" var count: Int = 0 { didSet { if self.count != oldValue && self.count > 0 { - self.button.title = self.strings.ShareMenu_Send + self.button.title = self.buttonTitle self.button.badge = "\(self.count)" if let (width, sideInset, bottomInset) = self.validLayout { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 881792d520..0f0ad3d6a1 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4060,10 +4060,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.present(controller, in: .window(.root)) } - if case .user = peerType, maxQuantity > 1 { + if case let .user(requestUser) = peerType, maxQuantity > 1, requestUser.isBot == nil && requestUser.isPremium == nil { let presentationData = self.presentationData var reachedLimitImpl: ((Int32) -> Void)? - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .requestedUsersSelection, isPeerEnabled: { peer in + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .requestedUsersSelection(isBot: requestUser.isBot, isPremium: requestUser.isPremium), isPeerEnabled: { peer in if case let .user(user) = peer, user.botInfo == nil { return true } else { @@ -4105,7 +4105,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var createNewGroupImpl: (() -> Void)? let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: [peerType], hasContactSelector: false, createNewGroup: { createNewGroupImpl?() - }, hasCreation: true)) + }, multipleSelection: maxQuantity > 1, multipleSelectionLimit: maxQuantity > 1 ? maxQuantity : nil, hasCreation: true, immediatelyActivateMultipleSelection: maxQuantity > 1)) controller.peerSelected = { [weak self, weak controller] peer, _ in guard let strongSelf = self else { @@ -4126,6 +4126,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } } + controller.multiplePeersSelected = { [weak controller] peers, _, _, _, _, _ in + let peerIds = peers.map { $0.id } + let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerIds: peerIds).startStandalone() + controller?.dismiss() + } createNewGroupImpl = { [weak controller] in switch peerType { case .user: