diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index 66b4644c74..31a05c84d9 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -378,6 +378,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode { self.iconNode = ASImageNode() self.iconNode.displaysAsynchronously = false self.iconNode.image = generateTintedImage(image: icon, color: self.theme.foregroundColor) + self.iconNode.isUserInteractionEnabled = false super.init() diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/BUILD index b81d8d5223..9f0577a650 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/BUILD @@ -28,6 +28,7 @@ swift_library( "//submodules/Markdown", "//submodules/AvatarNode", "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath", "//submodules/Components/MultilineTextComponent", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/Sources/ChatMessageJoinedChannelBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/Sources/ChatMessageJoinedChannelBubbleContentNode.swift index 5fe4c1e3a3..64c020dda5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/Sources/ChatMessageJoinedChannelBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/Sources/ChatMessageJoinedChannelBubbleContentNode.swift @@ -18,6 +18,7 @@ import ChatControllerInteraction import ShimmerEffect import Markdown import ChatMessageBubbleContentNode +import ChatMessagePollBubbleContentNode import ChatMessageItemCommon import RoundedRectWithTailPath import AvatarNode @@ -343,10 +344,12 @@ public class ChatMessageJoinedChannelBubbleContentNode: ChatMessageBubbleContent presentationData: presentationData, content: .premiumPaywall(title: nil, text: "Subcribe to [Telegram Premium]() to unlock up to **100** channels.", customUndoText: nil, timeout: nil, linkAction: nil), elevatedLayout: false, - action: { [weak self] _ in - if let self, let item = self.item { - let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil) - item.controllerInteraction.navigationController()?.pushViewController(controller) + action: { [weak self] action in + if case .info = action { + if let self, let item = self.item { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil) + item.controllerInteraction.navigationController()?.pushViewController(controller) + } } return true } @@ -536,7 +539,7 @@ private final class ChannelItemComponent: Component { let title: String let subtitle: String let action: (EnginePeer?) -> Void - let contextAction: (EnginePeer, UIView, ContextGesture?) -> Void + let contextAction: ((EnginePeer, UIView, ContextGesture?) -> Void)? init( context: AccountContext, @@ -547,7 +550,7 @@ private final class ChannelItemComponent: Component { title: String, subtitle: String, action: @escaping (EnginePeer?) -> Void, - contextAction: @escaping (EnginePeer, UIView, ContextGesture?) -> Void + contextAction: ((EnginePeer, UIView, ContextGesture?) -> Void)? ) { self.context = context self.theme = theme @@ -593,6 +596,7 @@ private final class ChannelItemComponent: Component { private let circleView2: UIImageView private let avatarNode: AvatarNode + private var mergedAvatarsNode: MergedAvatarsNode? private let avatarBadge: AvatarBadgeView private let subtitleIcon = ComponentView() @@ -608,15 +612,13 @@ private final class ChannelItemComponent: Component { self.circleView.clipsToBounds = true self.circleView.layer.cornerRadius = 30 - let colors: NSArray = [UIColor(rgb: 0x6a2267).cgColor, UIColor(rgb: 0x000000).cgColor] + let colors: NSArray = [UIColor(rgb: 0x000000).cgColor, UIColor(rgb: 0x6a2267).cgColor] self.circleView2 = UIImageView(image: generateGradientFilledCircleImage(diameter: 60, colors: colors)) self.circleView2.clipsToBounds = true self.circleView2.layer.cornerRadius = 30 self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) self.avatarNode.isUserInteractionEnabled = false - self.avatarNode.clipsToBounds = true - self.avatarNode.layer.cornerRadius = 30 self.avatarBadge = AvatarBadgeView(frame: CGRect()) @@ -631,7 +633,7 @@ private final class ChannelItemComponent: Component { self.contextContainer.addSubview(self.circleView2) self.contextContainer.addSubview(self.circleView) self.contextContainer.addSubnode(self.avatarNode) - self.avatarNode.view.addSubview(self.avatarBadge) + self.contextContainer.addSubview(self.avatarBadge) self.avatarNode.badgeView = self.avatarBadge @@ -639,7 +641,7 @@ private final class ChannelItemComponent: Component { self.contextContainer.activated = { [weak self] gesture, point in if let self, let component = self.component, let peer = component.peers.first { - component.contextAction(peer, self.contextContainer, gesture) + component.contextAction?(peer, self.contextContainer, gesture) } } } @@ -663,6 +665,8 @@ private final class ChannelItemComponent: Component { self.component = component self.state = state + self.contextContainer.isGestureEnabled = component.contextAction != nil + let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( @@ -707,7 +711,10 @@ private final class ChannelItemComponent: Component { let subtitleOriginX = floorToScreenPixels((itemSize.width - subtitleTotalWidth) / 2.0) + 1.0 - UIScreenPixel var iconOriginX = subtitleOriginX - var textOriginX = subtitleOriginX + subtitleIconSize.width + subtitleSpacing + var textOriginX = subtitleOriginX + if subtitleIconSize.width > 0.0 { + textOriginX += subtitleIconSize.width + subtitleSpacing + } if component.isLocked { textOriginX = subtitleOriginX iconOriginX = subtitleOriginX + subtitleSize.width + subtitleSpacing @@ -717,7 +724,7 @@ private final class ChannelItemComponent: Component { let subtitleFrame = CGRect(origin: CGPoint(x: textOriginX, y: avatarFrame.maxY - subtitleSize.height - UIScreenPixel), size: subtitleSize) var avatarHorizontalOffset: CGFloat = 0.0 - if component.peers.count > 1 || component.isLocked { + if component.isLocked { avatarHorizontalOffset = -10.0 } avatarFrame = avatarFrame.offsetBy(dx: avatarHorizontalOffset, dy: 0.0) @@ -726,6 +733,27 @@ private final class ChannelItemComponent: Component { self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer) } + if component.peers.count > 1 { + let mergedAvatarsNode: MergedAvatarsNode + if let current = self.mergedAvatarsNode { + mergedAvatarsNode = current + } else { + mergedAvatarsNode = MergedAvatarsNode() + self.contextContainer.insertSubview(mergedAvatarsNode.view, aboveSubview: self.avatarNode.view) + self.mergedAvatarsNode = mergedAvatarsNode + } + + mergedAvatarsNode.update(context: component.context, peers: component.peers.map { $0._asPeer() }, synchronousLoad: false, imageSize: 60.0, imageSpacing: 10.0, borderWidth: 2.0) + let avatarsSize = CGSize(width: avatarSize.width + 20.0, height: avatarSize.height) + mergedAvatarsNode.updateLayout(size: avatarsSize) + mergedAvatarsNode.frame = CGRect(origin: CGPoint(x: avatarFrame.midX - avatarsSize.width / 2.0, y: avatarFrame.minY), size: avatarsSize) + self.avatarNode.isHidden = true + } else { + self.mergedAvatarsNode?.view.removeFromSuperview() + self.mergedAvatarsNode = nil + self.avatarNode.isHidden = false + } + if component.isLocked { self.circleView.isHidden = false self.circleView.frame = avatarFrame.offsetBy(dx: 10.0, dy: 0.0) @@ -760,10 +788,10 @@ private final class ChannelItemComponent: Component { } let strokeWidth: CGFloat = 1.0 + UIScreenPixel - let avatarBadgeSize = CGSize(width: subtitleSize.width + 10.0 + 6.0, height: 15.0) + let avatarBadgeSize = CGSize(width: subtitleSize.width + subtitleIconSize.width + 10.0, height: 15.0) self.avatarBadge.update(size: avatarBadgeSize, text: "", hasTimeoutIcon: false, useSolidColor: true, strokeColor: component.theme.chat.message.incoming.bubble.withoutWallpaper.fill.first!) - let avatarBadgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((avatarFrame.width - avatarBadgeSize.width) / 2.0) - avatarHorizontalOffset, y: avatarFrame.height - avatarBadgeSize.height + 2.0), size: avatarBadgeSize).insetBy(dx: -strokeWidth, dy: -strokeWidth) + let avatarBadgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((itemSize.width - avatarBadgeSize.width) / 2.0), y: avatarFrame.minY + avatarFrame.height - avatarBadgeSize.height + 2.0), size: avatarBadgeSize).insetBy(dx: -strokeWidth, dy: -strokeWidth) self.avatarBadge.frame = avatarBadgeFrame let bounds = CGRect(origin: .zero, size: itemSize) @@ -783,7 +811,7 @@ private final class ChannelItemComponent: Component { } } -private let channelsLimit: Int32 = 8 +private let channelsLimit: Int32 = 10 final class ChannelListPanelComponent: Component { typealias EnvironmentType = Empty @@ -926,7 +954,7 @@ final class ChannelListPanelComponent: Component { let visibleBounds = self.scrollView.bounds.insetBy(dx: -100.0, dy: 0.0) - let hasMore = component.peers.channels.count >= channelsLimit + let hasMore = component.peers.count > channelsLimit var validIds = Set() if let visibleItems = itemLayout.visibleItems(for: visibleBounds) { @@ -953,28 +981,40 @@ final class ChannelListPanelComponent: Component { let title: String let subtitle: String var isLocked = false + var isLast = false if index == upperBound - 1 && hasMore { if !component.context.isPremium { isLocked = true } title = isLocked ? "Unlock More Channels" : "View More Channels" subtitle = "+\(component.peers.count - channelsLimit)" + isLast = true } else { title = item.peer.compactDisplayTitle subtitle = countString(Int64(item.subscribers)) } + + var peers: [EnginePeer] = [item.peer] + if isLast && !isLocked { + for i in index + 1 ..< index + 3 { + if i < component.peers.channels.count { + peers.append(component.peers.channels[i].peer) + } + } + } + let _ = itemView.update( transition: itemTransition, component: AnyComponent(ChannelItemComponent( context: component.context, theme: component.theme, strings: component.strings, - peers: [item.peer], + peers: peers, isLocked: isLocked, title: title, subtitle: subtitle, action: component.action, - contextAction: component.contextAction + contextAction: !isLocked && peers.count == 1 ? component.contextAction : nil )), environment: {}, containerSize: CGSize(width: itemLayout.itemWidth, height: itemLayout.containerHeight) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift index 49ee024edb..b38ab21e95 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift @@ -98,7 +98,7 @@ final class PeerInfoRecommendedChannelsPaneNode: ASDisplayNode, PeerInfoPaneNode private let listNode: ListView private var currentEntries: [RecommendedChannelsListEntry] = [] - private var currentState: RecommendedChannels? + private var currentState: (RecommendedChannels?, Bool)? private var canLoadMore: Bool = false private var enqueuedTransactions: [RecommendedChannelsListTransaction] = [] @@ -106,7 +106,9 @@ final class PeerInfoRecommendedChannelsPaneNode: ASDisplayNode, PeerInfoPaneNode private var unlockText: ComponentView? private var unlockButton: SolidRoundedButtonNode? - private var currentParams: (size: CGSize, isScrollingLockedAtTop: Bool)? + private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, isScrollingLockedAtTop: Bool)? + + private var theme: PresentationTheme? private let presentationDataPromise = Promise() private let ready = Promise() @@ -144,14 +146,18 @@ final class PeerInfoRecommendedChannelsPaneNode: ASDisplayNode, PeerInfoPaneNode self.disposable = (combineLatest(queue: .mainQueue(), self.presentationDataPromise.get(), - context.engine.peers.recommendedChannels(peerId: peerId) + context.engine.peers.recommendedChannels(peerId: peerId), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> map { peer -> Bool in + return peer?.isPremium ?? false + } ) - |> deliverOnMainQueue).startStrict(next: { [weak self] presentationData, recommendedChannels in + |> deliverOnMainQueue).startStrict(next: { [weak self] presentationData, recommendedChannels, isPremium in guard let strongSelf = self else { return } - - strongSelf.updateState(recommendedChannels: recommendedChannels, presentationData: presentationData) + strongSelf.currentState = (recommendedChannels, isPremium) + strongSelf.updateState(recommendedChannels: recommendedChannels, isPremium: isPremium, presentationData: presentationData) }) } @@ -173,7 +179,7 @@ final class PeerInfoRecommendedChannelsPaneNode: ASDisplayNode, PeerInfoPaneNode func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { let isFirstLayout = self.currentParams == nil - self.currentParams = (size, isScrollingLockedAtTop) + self.currentParams = (size, sideInset, bottomInset, isScrollingLockedAtTop) self.presentationDataPromise.set(.single(presentationData)) transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size)) @@ -192,11 +198,41 @@ final class PeerInfoRecommendedChannelsPaneNode: ASDisplayNode, PeerInfoPaneNode self.listNode.scrollEnabled = !isScrollingLockedAtTop - if isFirstLayout, let recommendedChannels = self.currentState { - self.updateState(recommendedChannels: recommendedChannels, presentationData: presentationData) + if isFirstLayout, let (recommendedChannels, isPremium) = self.currentState { + self.updateState(recommendedChannels: recommendedChannels, isPremium: isPremium, presentationData: presentationData) + } + } + + @objc private func unlockPressed() { + let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .ads, forceDark: false, dismissed: nil) + self.chatControllerInteraction.navigationController()?.pushViewController(controller) + } + + private func updateState(recommendedChannels: RecommendedChannels?, isPremium: Bool, presentationData: PresentationData) { + var entries: [RecommendedChannelsListEntry] = [] + + if let channels = recommendedChannels?.channels { + for channel in channels { + entries.append(.peer(theme: presentationData.theme, index: entries.count, peer: channel.peer, subscribers: channel.subscribers)) + } } - if !self.context.isPremium { + let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, action: { [weak self] peer in + self?.chatControllerInteraction.openPeer(peer, .default, nil, .default) + }, openPeerContextAction: { [weak self] peer, node, gesture in + self?.openPeerContextAction(true, peer, node, gesture) + }) + self.currentEntries = entries + self.enqueuedTransactions.append(transaction) + self.dequeueTransaction() + + if !isPremium { + guard let size = self.currentParams?.size, let sideInset = self.currentParams?.sideInset, let bottomInset = self.currentParams?.bottomInset else { + return + } + let themeUpdated = self.theme !== presentationData.theme + self.theme = presentationData.theme + let unlockText: ComponentView let unlockBackground: UIImageView let unlockButton: SolidRoundedButtonNode @@ -210,18 +246,16 @@ final class PeerInfoRecommendedChannelsPaneNode: ASDisplayNode, PeerInfoPaneNode if let current = self.unlockBackground { unlockBackground = current } else { - let topColor = presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.0) - let bottomColor = presentationData.theme.list.plainBackgroundColor - unlockBackground = UIImageView(image: generateGradientImage(size: CGSize(width: 1.0, height: 160.0), colors: [topColor, bottomColor], locations: [0.0, 1.0])) + unlockBackground = UIImageView() unlockBackground.contentMode = .scaleToFill self.view.addSubview(unlockBackground) self.unlockBackground = unlockBackground } - + if let current = self.unlockButton { unlockButton = current } else { - unlockButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: presentationData.theme), cornerRadius: 11.0) + unlockButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: presentationData.theme), height: 50.0, cornerRadius: 10.0) self.view.addSubview(unlockButton.view) self.unlockButton = unlockButton @@ -235,6 +269,13 @@ final class PeerInfoRecommendedChannelsPaneNode: ASDisplayNode, PeerInfoPaneNode } } + if themeUpdated { + let topColor = presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.0) + let bottomColor = presentationData.theme.list.itemBlocksBackgroundColor + unlockBackground.image = generateGradientImage(size: CGSize(width: 1.0, height: 170.0), colors: [topColor, bottomColor, bottomColor], locations: [0.0, 0.3, 1.0]) + unlockButton.updateTheme(SolidRoundedButtonTheme(theme: presentationData.theme)) + } + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let textFont = Font.regular(15.0) let boldTextFont = Font.semibold(15.0) @@ -265,45 +306,24 @@ final class PeerInfoRecommendedChannelsPaneNode: ASDisplayNode, PeerInfoPaneNode view.frame = CGRect(origin: CGPoint(x: floor((size.width - unlockSize.width) / 2.0), y: size.height - bottomInset - unlockSize.height - 13.0), size: unlockSize) } - unlockBackground.frame = CGRect(x: 0.0, y: size.height - bottomInset - 160.0, width: size.width, height: bottomInset + 160.0) + unlockBackground.frame = CGRect(x: 0.0, y: size.height - bottomInset - 170.0, width: size.width, height: bottomInset + 170.0) let buttonSideInset = sideInset + 16.0 let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0) unlockButton.frame = CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - bottomInset - unlockSize.height - buttonSize.height - 26.0), size: buttonSize) - let _ = unlockButton.updateLayout(width: buttonSize.width, transition: transition) - } else { + let _ = unlockButton.updateLayout(width: buttonSize.width, transition: .immediate) + } else { self.unlockBackground?.removeFromSuperview() self.unlockBackground = nil + self.unlockButton?.view.removeFromSuperview() + self.unlockButton = nil + self.unlockText?.view?.removeFromSuperview() self.unlockText = nil } } - @objc private func unlockPressed() { - let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .ads, forceDark: false, dismissed: nil) - self.chatControllerInteraction.navigationController()?.pushViewController(controller) - } - - private func updateState(recommendedChannels: RecommendedChannels?, presentationData: PresentationData) { - var entries: [RecommendedChannelsListEntry] = [] - - if let channels = recommendedChannels?.channels { - for channel in channels { - entries.append(.peer(theme: presentationData.theme, index: entries.count, peer: channel.peer, subscribers: channel.subscribers)) - } - } - - let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, action: { [weak self] peer in - self?.chatControllerInteraction.openPeer(peer, .default, nil, .default) - }, openPeerContextAction: { [weak self] peer, node, gesture in - self?.openPeerContextAction(true, peer, node, gesture) - }) - self.currentEntries = entries - self.enqueuedTransactions.append(transaction) - self.dequeueTransaction() - } - private func dequeueTransaction() { guard let _ = self.currentParams, let transaction = self.enqueuedTransactions.first else { return